koishi-plugin-chat-patch 3.0.2 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/client/vue/index.vue +60 -634
  2. package/client/web/env.d.ts +56 -0
  3. package/client/web/index.html +21 -0
  4. package/client/web/package-lock.json +3419 -0
  5. package/client/web/package.json +62 -0
  6. package/client/web/public/bcui/css/color-dark.css +35 -0
  7. package/client/web/public/bcui/css/color-light.css +35 -0
  8. package/client/web/public/bcui/css/style.css +468 -0
  9. package/client/web/public/bcui/js/main.js +193 -0
  10. package/client/web/public/css/append-dark.css +13 -0
  11. package/client/web/public/css/append-light.css +13 -0
  12. package/client/web/public/css/append-napcat-dark.css +14 -0
  13. package/client/web/public/css/append-napcat-light.css +14 -0
  14. package/client/web/public/favicon.ico +0 -0
  15. package/client/web/public/img/icons/icon-for-fuck-apple.png +0 -0
  16. package/client/web/public/img/icons/icon-maskable.png +0 -0
  17. package/client/web/public/img/icons/icon.png +0 -0
  18. package/client/web/public/img/icons/icon.svg +10 -0
  19. package/client/web/public/robots.txt +2 -0
  20. package/client/web/public/sw.js +58 -0
  21. package/client/web/scripts/copy-bcui.mjs +17 -0
  22. package/client/web/src/App.vue +1127 -0
  23. package/client/web/src/assets/css/append/append_darwin.css +68 -0
  24. package/client/web/src/assets/css/append/append_full_vibrancy.css +290 -0
  25. package/client/web/src/assets/css/append/append_linux.css +125 -0
  26. package/client/web/src/assets/css/append/append_linux_vibrancy.css +22 -0
  27. package/client/web/src/assets/css/append/append_napcat.css +35 -0
  28. package/client/web/src/assets/css/append/append_new.css +843 -0
  29. package/client/web/src/assets/css/append/append_vibrancy.css +113 -0
  30. package/client/web/src/assets/css/append/append_win32.css +109 -0
  31. package/client/web/src/assets/css/append/mobile/append_mobile_horizontal.css +349 -0
  32. package/client/web/src/assets/css/append/mobile/append_mobile_vertical.css +96 -0
  33. package/client/web/src/assets/css/chat.css +2104 -0
  34. package/client/web/src/assets/css/msg.css +941 -0
  35. package/client/web/src/assets/css/options.css +811 -0
  36. package/client/web/src/assets/css/sys_notice.css +62 -0
  37. package/client/web/src/assets/css/view.css +2105 -0
  38. package/client/web/src/assets/font//346/226/271/346/255/243/345/226/265/345/221/234/344/275/223.ttf +0 -0
  39. package/client/web/src/assets/img/Vue.png +0 -0
  40. package/client/web/src/assets/img/cakes.svg +189 -0
  41. package/client/web/src/assets/img/icons/AppIcon.png +0 -0
  42. package/client/web/src/assets/img/icons/AppIconDark.png +0 -0
  43. package/client/web/src/assets/img/icons/CapacitorAppIcon.png +0 -0
  44. package/client/web/src/assets/img/icons/CapacitorAppIconDark.png +0 -0
  45. package/client/web/src/assets/img/icons/NapcatAppIcon.png +0 -0
  46. package/client/web/src/assets/img/icons/NapcatAppIconDark.png +0 -0
  47. package/client/web/src/assets/img/icons/exit.png +0 -0
  48. package/client/web/src/assets/img/icons/pen.png +0 -0
  49. package/client/web/src/assets/img/stars.svg +89 -0
  50. package/client/web/src/assets/img//346/234/237/345/276/205.png +0 -0
  51. package/client/web/src/assets/l10n/_About I10n.md +35 -0
  52. package/client/web/src/assets/l10n/_l10nconfig.json +7 -0
  53. package/client/web/src/assets/l10n/zh-CN.po +1941 -0
  54. package/client/web/src/assets/logo.png +0 -0
  55. package/client/web/src/assets/pathMap/LLOneBot.yaml +2 -0
  56. package/client/web/src/assets/pathMap/Lagrange.OneBot.yaml +180 -0
  57. package/client/web/src/assets/pathMap/NapCat.Onebot.yaml +224 -0
  58. package/client/web/src/assets/pathMap/SnowLuma.yaml +80 -0
  59. package/client/web/src/assets/qed.txt +1 -0
  60. package/client/web/src/components/AboutPan.vue +197 -0
  61. package/client/web/src/components/BulletinBody.vue +129 -0
  62. package/client/web/src/components/DepPan.vue +310 -0
  63. package/client/web/src/components/EmojiFace.vue +32 -0
  64. package/client/web/src/components/FacePan.vue +461 -0
  65. package/client/web/src/components/FileBody.vue +129 -0
  66. package/client/web/src/components/FileManager.vue +532 -0
  67. package/client/web/src/components/FriendBody.vue +54 -0
  68. package/client/web/src/components/GlobalSessionSearchBar.vue +301 -0
  69. package/client/web/src/components/History.vue +49 -0
  70. package/client/web/src/components/LazyLottie.vue +82 -0
  71. package/client/web/src/components/MergePan.vue +127 -0
  72. package/client/web/src/components/MsgBody.vue +1478 -0
  73. package/client/web/src/components/MusicPlayer.vue +549 -0
  74. package/client/web/src/components/NoticeBody.vue +168 -0
  75. package/client/web/src/components/QzonePermissionPan.vue +150 -0
  76. package/client/web/src/components/RawMsgRenderPreviewPan.vue +190 -0
  77. package/client/web/src/components/ThemeColorPickerPan.vue +420 -0
  78. package/client/web/src/components/TinySessionBody.vue +51 -0
  79. package/client/web/src/components/UmamiInfoPan.vue +1549 -0
  80. package/client/web/src/components/UpdatePan.vue +263 -0
  81. package/client/web/src/components/ViewerCom.vue +1360 -0
  82. package/client/web/src/components/VoiceMsg.vue +626 -0
  83. package/client/web/src/components/WelPan.vue +894 -0
  84. package/client/web/src/components/msg-body/.gitkeep +0 -0
  85. package/client/web/src/components/msg-component/JsonSegComp.vue +138 -0
  86. package/client/web/src/components/msg-component/XmlSegComp.vue +132 -0
  87. package/client/web/src/components/msg-component/jsonComp/AutoReply.vue +97 -0
  88. package/client/web/src/components/msg-component/jsonComp/Contact.lua.vue +129 -0
  89. package/client/web/src/components/msg-component/jsonComp/Feed.lua.vue +94 -0
  90. package/client/web/src/components/msg-component/jsonComp/Forum.vue +84 -0
  91. package/client/web/src/components/msg-component/jsonComp/Mannounce.vue +111 -0
  92. package/client/web/src/components/msg-component/jsonComp/Map.vue +52 -0
  93. package/client/web/src/components/msg-component/jsonComp/Miniapp.lua.vue +53 -0
  94. package/client/web/src/components/msg-component/jsonComp/Miniapp.vue +69 -0
  95. package/client/web/src/components/msg-component/jsonComp/Music.lua.vue +135 -0
  96. package/client/web/src/components/msg-component/jsonComp/Tuwen.lua.vue +56 -0
  97. package/client/web/src/components/notice-component/.gitkeep +0 -0
  98. package/client/web/src/components/notice-component/MealHungryPan.vue +119 -0
  99. package/client/web/src/components/tooltip/CustomFaceTooltip.vue +22 -0
  100. package/client/web/src/components/tooltip/Tooltip.vue +50 -0
  101. package/client/web/src/components/tooltip/Tooltips.vue +14 -0
  102. package/client/web/src/components/tooltip/UserInfoTooltip.vue +69 -0
  103. package/client/web/src/function/base.ts +238 -0
  104. package/client/web/src/function/connect.ts +2042 -0
  105. package/client/web/src/function/debug.ts +10 -0
  106. package/client/web/src/function/elements/information.ts +141 -0
  107. package/client/web/src/function/elements/system.ts +101 -0
  108. package/client/web/src/function/elements/vueComp.ts +62 -0
  109. package/client/web/src/function/favicon.ts +76 -0
  110. package/client/web/src/function/input.ts +69 -0
  111. package/client/web/src/function/model/emoji.ts +127 -0
  112. package/client/web/src/function/model/img.ts +150 -0
  113. package/client/web/src/function/model/msg-body.ts +51 -0
  114. package/client/web/src/function/msg.ts +2674 -0
  115. package/client/web/src/function/notify.ts +225 -0
  116. package/client/web/src/function/option.ts +749 -0
  117. package/client/web/src/function/satori-model.ts +818 -0
  118. package/client/web/src/function/satori.ts +325 -0
  119. package/client/web/src/function/sender.ts +178 -0
  120. package/client/web/src/function/tooltip.ts +49 -0
  121. package/client/web/src/function/utils/appUtil.ts +2288 -0
  122. package/client/web/src/function/utils/avatarUtil.ts +31 -0
  123. package/client/web/src/function/utils/backgroundUtil.ts +110 -0
  124. package/client/web/src/function/utils/linkViewUtil.ts +101 -0
  125. package/client/web/src/function/utils/localHistoryUtil.ts +517 -0
  126. package/client/web/src/function/utils/msgUtil.ts +1242 -0
  127. package/client/web/src/function/utils/pinyin.ts +141 -0
  128. package/client/web/src/function/utils/recordUtil.ts +187 -0
  129. package/client/web/src/function/utils/sessionUtil.ts +114 -0
  130. package/client/web/src/function/utils/systemUtil.ts +664 -0
  131. package/client/web/src/main.ts +91 -0
  132. package/client/web/src/pages/Chat.vue +3453 -0
  133. package/client/web/src/pages/Friends.vue +636 -0
  134. package/client/web/src/pages/Info.vue +717 -0
  135. package/client/web/src/pages/Messages.vue +875 -0
  136. package/client/web/src/pages/Options.vue +82 -0
  137. package/client/web/src/pages/Qzone.vue +1178 -0
  138. package/client/web/src/pages/chat-view/ChatDanmaku.vue +759 -0
  139. package/client/web/src/pages/chat-view/ChatGlagame.vue +642 -0
  140. package/client/web/src/pages/chat-view/ChatTerminal.vue +1044 -0
  141. package/client/web/src/pages/chat-view/SystemNotice.vue +171 -0
  142. package/client/web/src/pages/options/OptAccount.vue +107 -0
  143. package/client/web/src/pages/options/OptAddon.vue +185 -0
  144. package/client/web/src/pages/options/OptDev.vue +704 -0
  145. package/client/web/src/pages/options/OptFunction.vue +403 -0
  146. package/client/web/src/pages/options/OptInfo.vue +165 -0
  147. package/client/web/src/pages/options/OptView.vue +846 -0
  148. package/client/web/src/runtime/backend.ts +310 -0
  149. package/client/web/src/state/auth.ts +14 -0
  150. package/client/web/src/state/chat.ts +36 -0
  151. package/client/web/src/state/connection.ts +20 -0
  152. package/client/web/src/state/contact.ts +37 -0
  153. package/client/web/src/state/qzone.ts +18 -0
  154. package/client/web/src/state/sessionHistory.ts +25 -0
  155. package/client/web/src/state/settings.ts +18 -0
  156. package/client/web/src/state/sticker.ts +10 -0
  157. package/client/web/src/state/ui.ts +56 -0
  158. package/client/web/tsconfig.json +27 -0
  159. package/client/web/tsconfig.tsbuildinfo +1 -0
  160. package/client/web/vite.config.ts +33 -0
  161. package/dist/index.js +1 -12
  162. package/dist/style.css +1 -1
  163. package/lib/index.js +2070 -2077
  164. package/package.json +15 -2
  165. package/readme.md +0 -22
  166. package/src/bootstrap.ts +71 -0
  167. package/src/cache.ts +319 -0
  168. package/src/config.ts +23 -35
  169. package/src/database.ts +650 -0
  170. package/src/index.ts +27 -59
  171. package/src/media.ts +302 -0
  172. package/src/recorder.ts +175 -0
  173. package/src/self-message.ts +456 -0
  174. package/src/server.d.ts +26 -0
  175. package/src/types.ts +108 -48
  176. package/src/web.ts +712 -0
  177. package/client/vue/chat-logic.ts +0 -683
  178. package/client/vue/composables/useChatActions.ts +0 -61
  179. package/client/vue/composables/useChatData.ts +0 -185
  180. package/client/vue/composables/useImageCache.ts +0 -281
  181. package/client/vue/composables/useVideoCache.ts +0 -125
  182. package/client/vue/types.ts +0 -72
  183. package/lib/api-handlers.d.ts +0 -25
  184. package/lib/config.d.ts +0 -14
  185. package/lib/file-manager.d.ts +0 -124
  186. package/lib/index.d.ts +0 -17
  187. package/lib/logger.d.ts +0 -9
  188. package/lib/message-handler.d.ts +0 -35
  189. package/lib/types.d.ts +0 -57
  190. package/lib/utils.d.ts +0 -18
  191. package/src/api-handlers.ts +0 -646
  192. package/src/file-manager.ts +0 -1436
  193. package/src/message-handler.ts +0 -507
  194. package/src/utils.ts +0 -189
@@ -0,0 +1,549 @@
1
+ <template>
2
+ <div class="music-controller">
3
+ <div>
4
+ <img :src="currentMusic?.cover">
5
+ <div>
6
+ <a>{{ currentMusic?.title }}
7
+ <a v-if="currentMusic?.free != null">{{ $t('(试听)') }}</a>
8
+ <span> / {{ currentMusic?.author?.join('/') }}</span>
9
+ </a>
10
+ <audio :src="backend.proxyUrl(currentMusic?.url ?? '')"
11
+ @loadedmetadata="audioLoaded"
12
+ @error="audioLoadFail"
13
+ @timeupdate="audioUpdate()" />
14
+ <div>
15
+ <label for="music-controller-bar" class="sr-only">{{ $t('音乐播放进度') }}</label>
16
+ <input id="music-controller-bar" value="0" min="0"
17
+ :max="sizeMax" step="0.1" type="range"
18
+ @input="audioChange">
19
+ <div>
20
+ <div :style="{
21
+ width: sizeDur / sizeMax * 100 > 100 ? 'calc(100% + 9px)' : ('calc(' + (sizeDur / sizeMax * 100) + '% + 9px)'),
22
+ }" />
23
+ <div :style="{
24
+ width: sizeReal > 0 ? ('calc(' + ((1 - (sizeReal / sizeMax)) * 100) + '% - 9px)') : '0%',
25
+ marginLeft: sizeReal > 0 ? ('calc(' + ((sizeReal / sizeMax * 100) + '% + 9px)')) : '0%',
26
+ }" />
27
+ </div>
28
+ <font-awesome-icon v-if="!isLoaded" :icon="['fas', 'spinner']" spin />
29
+ <template v-else>
30
+ <font-awesome-icon v-if="audio?.paused" :icon="['fas', 'play']" @click="audioControll()" />
31
+ <font-awesome-icon v-else :icon="['fas', 'pause']" @click="audioControll()" />
32
+ </template>
33
+ <font-awesome-icon v-if="musicList.length > 1" :icon="['fas', 'forward']" @click="switchMusic(currentIndex + 1, true)" />
34
+ <span>{{ minutesDur < 10 ? '0' + minutesDur : minutesDur }}:{{ secondsDur < 10 ? '0' + secondsDur : secondsDur }} / {{ minutes < 10 ? '0' + minutes : minutes }}:{{ seconds < 10 ? '0' + seconds : seconds }}</span>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ <span v-if="nowLyric &&
40
+ nowLyric.text &&
41
+ nowLyric.text.trim() != ''"
42
+ class="music-lyric">{{ nowLyric.text }}</span>
43
+ <div class="music-list">
44
+ <template v-if="false">
45
+ 播放列表为空
46
+ </template>
47
+ <template v-else>
48
+ <div
49
+ v-for="(music, index) in musicList"
50
+ :key="`${music.type}-${music.url}-${index}`"
51
+ @click="switchMusic(index)">
52
+ <div :style="{ opacity: index === currentIndex ? 1 : 0 }" />
53
+ <img :src="music.cover">
54
+ <span>{{ music.title }} - {{ music.author.join('/') }}</span>
55
+ </div>
56
+ </template>
57
+ </div>
58
+ </template>
59
+
60
+ <script lang="ts">
61
+ import { PopInfo, PopType } from '../function/base'
62
+
63
+ type LyricLine = { [key: number]: string }
64
+
65
+ export interface MusicInfo {
66
+ title: string, // 标题
67
+ author: string[], // 作者
68
+ url: string, // 音乐链接
69
+ type: 'default' | 'music163', // 音乐类型(用于特殊功能)
70
+ cover: string, // 封面链接
71
+ free?: boolean // 试听标识
72
+ time?: number // 音频时长
73
+ data?: any // 额外数据(如歌曲ID等)
74
+ lyric?: LyricLine[] // 歌词(可选)
75
+ }
76
+
77
+ const emitRef = ref(undefined as any)
78
+ const resetController = ref(() => {})
79
+
80
+ const musicListState = ref<MusicInfo[]>([])
81
+ const currentIndexState = ref(0)
82
+ const readyToPlayState = ref(false)
83
+ const audoState = ref(null as HTMLAudioElement | null)
84
+ const nowLyricState = ref(undefined as { index: number, text: string } | undefined)
85
+
86
+ const lyricTime = (line: LyricLine) => parseFloat(Object.keys(line)[0])
87
+ const lyricText = (line: LyricLine) => Object.values(line)[0]
88
+
89
+ const parseLyric = (lyricText: string) => {
90
+ return lyricText
91
+ .split('\n')
92
+ .flatMap((line: string) => {
93
+ const timeMatches = [...line.matchAll(/\[(\d{1,2}):(\d{1,2}(?:\.\d+)?)\]/g)]
94
+ if (timeMatches.length === 0) {
95
+ return []
96
+ }
97
+
98
+ const text = line.replace(/\[[^\]]+\]/g, '').trim()
99
+ if (!text) {
100
+ return []
101
+ }
102
+
103
+ return timeMatches.map(match => {
104
+ const time = parseFloat(match[1]) * 60 + parseFloat(match[2])
105
+ return {
106
+ [time]: text,
107
+ }
108
+ })
109
+ })
110
+ .sort((a, b) => lyricTime(a) - lyricTime(b))
111
+ }
112
+
113
+ const mergeLyric = (originalLyric: LyricLine[], translatedLyric: LyricLine[]) => {
114
+ const merged = new Map<number, string>()
115
+
116
+ originalLyric.forEach(line => {
117
+ merged.set(lyricTime(line), lyricText(line))
118
+ })
119
+ translatedLyric.forEach(line => {
120
+ merged.set(lyricTime(line), lyricText(line))
121
+ })
122
+
123
+ return [...merged.entries()]
124
+ .sort((a, b) => a[0] - b[0])
125
+ .map(([time, text]) => ({ [time]: text }))
126
+ }
127
+
128
+ const findLyricIndex = (lyrics: LyricLine[], currentTime: number) => {
129
+ let left = 0
130
+ let right = lyrics.length - 1
131
+ let ans = -1
132
+
133
+ while (left <= right) {
134
+ const mid = Math.floor((left + right) / 2)
135
+ const midTime = lyricTime(lyrics[mid])
136
+ if (midTime <= currentTime) {
137
+ ans = mid
138
+ left = mid + 1
139
+ } else {
140
+ right = mid - 1
141
+ }
142
+ }
143
+
144
+ return ans
145
+ }
146
+
147
+ export const getCurrentMusic = () => {
148
+ if (currentIndexState.value < 0 || currentIndexState.value >= musicListState.value.length) {
149
+ return null
150
+ }
151
+
152
+ return musicListState.value[currentIndexState.value]
153
+ }
154
+
155
+ export const addMusic = (info: MusicInfo | null | undefined, type: 'top' | 'bottom' | 'current' = 'bottom', open: boolean = false) => {
156
+ if (!info) {
157
+ return
158
+ }
159
+ // 筛选掉同 title 的
160
+ musicListState.value = musicListState.value.filter(item => {
161
+ return item.title !== info.title
162
+ })
163
+ if (type === 'top') {
164
+ musicListState.value.unshift(info)
165
+ currentIndexState.value++
166
+ } else if (type === 'current') {
167
+ if(audoState.value?.played) {
168
+ audoState.value.pause()
169
+ musicListState.value.unshift(info)
170
+ currentIndexState.value = 0
171
+ } else {
172
+ musicListState.value.unshift(info)
173
+ }
174
+ nowLyricState.value = undefined
175
+ emitRef.value('update-lyric', '')
176
+ readyToPlayState.value = true
177
+ } else {
178
+ musicListState.value.push(info)
179
+ }
180
+
181
+ if (open) {
182
+ emitRef.value('open-panel', true)
183
+ }
184
+ }
185
+ </script>
186
+
187
+ <script setup lang="ts">
188
+ import { computed, ref } from 'vue'
189
+ import { i18n } from '../main'
190
+ import { backend } from '../runtime/backend'
191
+ import { useSettingsStore } from '../state/settings'
192
+
193
+ const settingsStore = useSettingsStore()
194
+ const $t = i18n.global.t
195
+
196
+ const audio = audoState
197
+
198
+ const musicList = musicListState
199
+ const currentIndex = currentIndexState
200
+ const readyToPlay = readyToPlayState
201
+ const isLoaded = ref(false)
202
+
203
+ const sizeMax = ref(0)
204
+ const sizeReal = ref(0)
205
+ const sizeDur = ref(0)
206
+
207
+ const minutes = ref(0)
208
+ const seconds = ref(0)
209
+ const minutesDur = ref(0)
210
+ const secondsDur = ref(0)
211
+
212
+ const nowLyric = nowLyricState
213
+
214
+ const currentMusic = computed(() => {
215
+ if (currentIndex.value < 0 || currentIndex.value >= musicList.value.length) {
216
+ return null
217
+ }
218
+ return musicList.value[currentIndex.value]
219
+ })
220
+
221
+ // 定义 emits
222
+ const emit = defineEmits<{
223
+ 'open-panel': [value: boolean]
224
+ 'update-lyric': [value: string]
225
+ 'update-status': [value: boolean]
226
+ }>()
227
+
228
+ // 设置 emit 引用
229
+ emitRef.value = emit
230
+
231
+ resetController.value = () => {
232
+ isLoaded.value = false
233
+ sizeMax.value = 0
234
+ sizeReal.value = 0
235
+ sizeDur.value = 0
236
+ minutes.value = 0
237
+ seconds.value = 0
238
+ minutesDur.value = 0
239
+ secondsDur.value = 0
240
+ nowLyric.value = undefined
241
+ emit('update-lyric', '')
242
+ }
243
+
244
+ const switchMusic = (index: number, pass: boolean = false) => {
245
+ if(pass) {
246
+ musicList.value.splice(currentIndex.value, 1)
247
+ if(musicList.value.length === 0) {
248
+ resetController.value()
249
+ emit('open-panel', false)
250
+ return
251
+ }
252
+
253
+ if(currentIndex.value >= musicList.value.length) {
254
+ currentIndex.value = musicList.value.length - 1
255
+ }
256
+
257
+ resetController.value()
258
+ addMusic(musicList.value[currentIndex.value], 'current')
259
+ return
260
+ }
261
+
262
+ const safeIndex = Math.min(Math.max(index, 0), musicList.value.length - 1)
263
+ const music = musicList.value[safeIndex]
264
+ if (!music) {
265
+ return
266
+ }
267
+
268
+ resetController.value()
269
+ addMusic(music, 'current')
270
+ }
271
+
272
+ const audioLoaded = (event: any) => {
273
+ isLoaded.value = true
274
+ audio.value = event.target as HTMLAudioElement
275
+ sizeMax.value = currentMusic.value?.time ?? audio.value.duration
276
+ sizeReal.value = audio.value.duration
277
+ minutes.value = Math.floor(sizeMax.value / 60)
278
+ seconds.value = Math.floor(sizeMax.value % 60)
279
+ if(readyToPlay.value) {
280
+ audio.value.play()
281
+ readyToPlay.value = false
282
+ }
283
+ // 更新媒体信息
284
+ if ('mediaSession' in navigator) {
285
+ navigator.mediaSession.metadata = new MediaMetadata({
286
+ title : currentMusic.value?.title ?? '',
287
+ artist: currentMusic.value?.author?.join('/') ?? '',
288
+ artwork: [
289
+ { src: currentMusic.value?.cover ?? '', sizes: '130x130', type: 'image/png' },
290
+ ]
291
+ });
292
+
293
+ navigator.mediaSession.setActionHandler('nexttrack', () => {
294
+ switchMusic(currentIndex.value + 1, true)
295
+ });
296
+ }
297
+ // 如果是网易云音乐,异步获取歌词
298
+ if (currentMusic.value?.type === 'music163' && currentMusic.value.data && typeof currentMusic.value.data === 'string') {
299
+ const musicRef = currentMusic.value
300
+ fetch(import.meta.env.VITE_APP_163_MUSIC_API + '/lyric?id=' + currentMusic.value.data)
301
+ .then(res => res.json())
302
+ .then(data => {
303
+ const originalLyric = parseLyric(data.lrc?.lyric || '')
304
+ const translatedLyric = parseLyric(data.tlyric?.lyric || '')
305
+ // 默认展示"翻译 + 原文"合并结果:同时间点优先使用翻译歌词
306
+ // 当用户开启"显示原歌词"时,仅展示原文歌词。
307
+ const final = settingsStore.sysConfig.original_lyrics ? originalLyric : mergeLyric(originalLyric, translatedLyric)
308
+
309
+ if (currentMusic.value === musicRef) {
310
+ musicRef.lyric = final
311
+ }
312
+ })
313
+ }
314
+ }
315
+
316
+ const audioLoadFail = () => {
317
+ if(currentMusic.value?.title != undefined) {
318
+ new PopInfo().add(PopType.INFO, '加载资源 ' + currentMusic.value?.title + ' 失败', false)
319
+ switchMusic(currentIndex.value + 1, true)
320
+ }
321
+ }
322
+
323
+ const audioUpdate = () => {
324
+ if(audio.value) {
325
+ emit('update-status', audio.value.paused)
326
+
327
+ const bar = document.getElementById('music-controller-bar') as HTMLInputElement
328
+ if(bar) {
329
+ bar.value = audio.value.currentTime.toString()
330
+ }
331
+ sizeDur.value = audio.value.currentTime
332
+ minutesDur.value = Math.floor(audio.value.currentTime / 60)
333
+ secondsDur.value = Math.floor(audio.value.currentTime % 60)
334
+
335
+ if(audio.value.currentTime >= audio.value.duration) {
336
+ // 从列表移除并播放下一首
337
+ musicList.value.splice(currentIndex.value, 1)
338
+ if(currentIndex.value >= musicList.value.length) {
339
+ currentIndex.value = musicList.value.length - 1
340
+ }
341
+ if(currentIndex.value >= 0) {
342
+ resetController.value()
343
+ audio.value.src = backend.proxyUrl(musicList.value[currentIndex.value].url)
344
+ readyToPlay.value = true
345
+ } else {
346
+ resetController.value()
347
+ emit('open-panel', false)
348
+ }
349
+ }
350
+
351
+ if(currentMusic.value?.lyric && currentMusic.value.lyric.length > 0) {
352
+ const lyricIndex = findLyricIndex(currentMusic.value.lyric, audio.value.currentTime)
353
+ if(lyricIndex >= 0) {
354
+ const text = lyricText(currentMusic.value.lyric[lyricIndex])
355
+ if(!nowLyric.value || nowLyric.value.index !== lyricIndex || nowLyric.value.text !== text) {
356
+ nowLyric.value = {
357
+ index: lyricIndex,
358
+ text,
359
+ }
360
+ }
361
+ } else {
362
+ nowLyric.value = undefined
363
+ }
364
+
365
+ if(settingsStore.sysConfig.glabal_lyric == true) {
366
+ emit('update-lyric', nowLyric.value?.text ?? currentMusic.value.title)
367
+ } else {
368
+ emit('update-lyric', '')
369
+ }
370
+ }
371
+ }
372
+ }
373
+
374
+ const audioChange = (event: any) => {
375
+ const bar = event.target as HTMLInputElement
376
+ if (audio.value) {
377
+ const value = parseFloat(bar.value)
378
+ if(value <= audio.value.duration) {
379
+ if(audio.value.paused) {
380
+ audio.value.currentTime = value
381
+ } else {
382
+ audio.value.pause()
383
+ audio.value.currentTime = value
384
+ audio.value.play()
385
+ }
386
+ } else {
387
+ bar.value = audio.value.currentTime.toString()
388
+ }
389
+ }
390
+ }
391
+
392
+ const audioControll = () => {
393
+ if (audio.value) {
394
+ if (audio.value.paused) {
395
+ audio.value.play()
396
+ } else {
397
+ audio.value.pause()
398
+ }
399
+ }
400
+ }
401
+
402
+ </script>
403
+
404
+ <style scoped>
405
+ .music-controller {
406
+ background: var(--color-card-1);
407
+ border-radius: 7px 7px 0 0;
408
+ flex-direction: column;
409
+ padding: 15px 15px 10px 15px;
410
+ display: flex;
411
+ }
412
+ .music-controller > div:first-child {
413
+ align-items: flex-start;
414
+ display: flex;
415
+ }
416
+ .music-controller > div:first-child > img {
417
+ border-radius: 7px;
418
+ margin-right: 20px;
419
+ max-height: 60px;
420
+ width: 60px;
421
+ }
422
+ .music-controller > div:first-child > div {
423
+ flex-direction: column;
424
+ display: flex;
425
+ width: 100%;
426
+ }
427
+ .music-controller > div:first-child > div > a {
428
+ font-size: 0.9rem;
429
+ font-weight: bold;
430
+ margin-bottom: 5px;
431
+ }
432
+ .music-controller > div:first-child > div > a > a {
433
+ font-size: 0.7rem;
434
+ font-weight: normal;
435
+ }
436
+ .music-controller > div:first-child > div > a > span {
437
+ font-size: 0.8rem;
438
+ opacity: 0.7;
439
+ }
440
+ .music-controller > div:first-child > div > div {
441
+ flex-direction: row;
442
+ margin-top: 5px;
443
+ flex-wrap: wrap;
444
+ display: flex;
445
+ }
446
+ .music-controller > div:first-child > div > div > input {
447
+ appearance: none;
448
+ -webkit-appearance: none;
449
+ width: 100%;
450
+ background: transparent;
451
+ margin-right: 10px;
452
+ }
453
+ .music-controller > div:first-child > div > div > input::-webkit-slider-thumb {
454
+ background: var(--color-main);
455
+ -webkit-appearance: none;
456
+ border-radius: 100%;
457
+ margin-top: -3px;
458
+ height: 12px;
459
+ width: 12px;
460
+ }
461
+ .music-controller > div:first-child > div.me > div > input::-webkit-slider-thumb {
462
+ background: var(--color-font-r);
463
+ }
464
+ .music-controller > div:first-child > div > div > input::-webkit-slider-runnable-track {
465
+ background: var(--color-card-1);
466
+ border-radius: 10px;
467
+ height: 6px;
468
+ }
469
+ .music-controller > div:first-child > div.me > div > input::-webkit-slider-runnable-track {
470
+ background: var(--color-font-2);
471
+ }
472
+ .music-controller > div:first-child > div > div > svg {
473
+ font-size: 0.75rem;
474
+ margin-left: 4px;
475
+ margin-right: 10px;
476
+ cursor: pointer;
477
+ }
478
+ .music-controller > div:first-child > div > div > span {
479
+ font-size: 0.7rem;
480
+ text-align: right;
481
+ margin-right: 10px;
482
+ flex: 1;
483
+ }
484
+ .music-controller > div:first-child > div > div > div {
485
+ width: calc(100% - 20px);
486
+ margin-bottom: -6px;
487
+ margin-right: 20px;
488
+ margin-left: 3px;
489
+ }
490
+ .music-controller > div:first-child > div > div > div > div {
491
+ transform: translateY(calc(-100% - 2px));
492
+ background: var(--color-main);
493
+ pointer-events: none;
494
+ border-radius: 6px;
495
+ height: 6px;
496
+ width: 0%;
497
+ }
498
+ .music-controller > div:first-child > div > div > div > div:nth-child(2) {
499
+ transform: translateY(calc(-100% - 16px));
500
+ background: var(--color-card-2);
501
+ border-radius: 0 6px 6px 0;
502
+ }
503
+ .music-controller > div:first-child > div.me > div > div > div:nth-child(2) {
504
+ background: var(--color-font-1);
505
+ }
506
+ .music-controller > div:first-child > div.me > div > div > div {
507
+ background: var(--color-font-r);
508
+ }
509
+
510
+ .music-lyric {
511
+ display: block;
512
+ text-align: center;
513
+ padding: 5px;
514
+ border-radius: 0 0 7px 7px;
515
+ color: var(--color-font-2);
516
+ background: var(--color-card-2);
517
+ font-size: 0.8rem;
518
+ }
519
+
520
+ .music-list {
521
+ padding: 10px;
522
+ margin: 10px 0 -15px 0;
523
+ border-radius: 7px;
524
+ max-height: 20vh;
525
+ overflow-y: scroll;
526
+ }
527
+ .music-list > div {
528
+ display: flex;
529
+ align-items: center;
530
+ margin-bottom: 5px;
531
+ }
532
+ .music-list > div > div {
533
+ width: 4px;
534
+ height: 20px;
535
+ background: var(--color-main);
536
+ border-radius: 4px;
537
+ margin-right: 10px;
538
+ }
539
+ .music-list img {
540
+ width: 30px;
541
+ height: 30px;
542
+ margin-right: 10px;
543
+ border-radius: 7px;
544
+ }
545
+ .music-list span {
546
+ text-overflow: ellipsis;
547
+ font-size: 0.8rem;
548
+ }
549
+ </style>