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,532 @@
1
+ <template>
2
+ <div class="file-manager">
3
+ <BcTab class="file-manager-tabs">
4
+ <div :name="$t('下载')">
5
+ <div v-if="downloadTasks.length === 0" class="empty-tip">
6
+ {{ $t('暂无下载任务') }}
7
+ </div>
8
+ <div v-for="task in downloadTasks" :key="'dl-' + task.id" class="task-item">
9
+ <div class="task-icon">
10
+ <font-awesome-icon :icon="['fas', getFileIcon(task.fileName)]" />
11
+ </div>
12
+ <div class="task-info">
13
+ <div class="task-name">
14
+ <span>{{ getStatusText(task.status) }}</span>
15
+ {{ task.fileName }}
16
+ </div>
17
+ <div class="task-progress">
18
+ <div class="progress-bar">
19
+ <div :style="{ width: task.progress + '%' }" />
20
+ </div>
21
+ <span>{{ formatSize(task.downloaded) }} / {{ formatSize(task.fileSize) }}</span>
22
+ </div>
23
+ </div>
24
+ <div class="task-actions">
25
+ <font-awesome-icon v-if="task.status === 'downloading'"
26
+ :icon="['fas', 'times']" @click="cancelDownload(task.id)" />
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <div :name="$t('上传')">
31
+ <div v-if="uploadTasks.length === 0" class="empty-tip">
32
+ {{ $t('暂无上传任务') }}
33
+ </div>
34
+ <div v-for="task in uploadTasks" :key="'ul-' + task.id" class="task-item">
35
+ <div class="task-icon">
36
+ <font-awesome-icon :icon="['fas', getFileIcon(task.fileName)]" />
37
+ </div>
38
+ <div class="task-info">
39
+ <div class="task-name">
40
+ <span>{{ getStatusText(task.status) }}</span>
41
+ {{ task.fileName }}
42
+ </div>
43
+ <div class="task-progress">
44
+ <div class="progress-bar">
45
+ <div :style="{ width: task.progress + '%' }" />
46
+ </div>
47
+ <span>{{ formatSize(task.uploaded) }} / {{ formatSize(task.fileSize) }}</span>
48
+ </div>
49
+ </div>
50
+ <div class="task-actions">
51
+ <font-awesome-icon v-if="task.status === 'uploading'"
52
+ :icon="['fas', 'times']" @click="cancelUpload(task.id)" />
53
+ </div>
54
+ </div>
55
+ </div>
56
+ </BcTab>
57
+ </div>
58
+ </template>
59
+
60
+ <script lang="ts">
61
+ import { ref } from 'vue'
62
+ import { downloadFile } from '../function/utils/appUtil'
63
+
64
+ export type TaskStatus = 'pending' | 'downloading' | 'uploading' | 'completed' | 'failed' | 'cancelled'
65
+
66
+ export interface TransferTask {
67
+ id: string
68
+ fileName: string
69
+ fileSize: number
70
+ filePath: string
71
+ url: string
72
+ status: TaskStatus
73
+ progress: number
74
+ downloaded: number
75
+ uploaded: number
76
+ createdAt: number
77
+ updatedAt: number
78
+ error?: string
79
+ }
80
+
81
+ // 模块级别状态
82
+ const downloadTasksState = ref<TransferTask[]>([])
83
+ const uploadTasksState = ref<TransferTask[]>([])
84
+ const panelVisibleState = ref(false)
85
+
86
+ // 存储下载任务的取消回调
87
+ const downloadCancelCallbacks = new Map<string, () => void>()
88
+
89
+ let taskCounter = 0
90
+ const generateTaskId = () => {
91
+ taskCounter++
92
+ return `task_${Date.now()}_${taskCounter}`
93
+ }
94
+
95
+ /**
96
+ * 打开文件传输管理器面板
97
+ */
98
+ export const openPanel = () => {
99
+ panelVisibleState.value = true
100
+ }
101
+
102
+ /**
103
+ * 关闭文件传输管理器面板
104
+ */
105
+ export const closePanel = () => {
106
+ panelVisibleState.value = false
107
+ }
108
+
109
+ /**
110
+ * 获取面板显示状态
111
+ */
112
+ export const panelVisible = panelVisibleState
113
+
114
+ export const addDownloadTask = (info: {
115
+ fileName: string,
116
+ fileSize: number,
117
+ filePath: string,
118
+ url: string,
119
+ onProgress?: (progress: number) => void,
120
+ onComplete?: () => void,
121
+ onError?: (error: string) => void
122
+ }) => {
123
+ const task: TransferTask = {
124
+ id: generateTaskId(),
125
+ fileName: info.fileName,
126
+ fileSize: info.fileSize,
127
+ filePath: info.filePath,
128
+ url: info.url,
129
+ status: 'downloading',
130
+ progress: 0,
131
+ downloaded: 0,
132
+ uploaded: 0,
133
+ createdAt: Date.now(),
134
+ updatedAt: Date.now()
135
+ }
136
+ downloadTasksState.value.push(task)
137
+ openPanel()
138
+
139
+ // 处理进度回调
140
+ const onprocess = (event: ProgressEvent & { [key: string]: any }) => {
141
+ const index = downloadTasksState.value.findIndex(t => t.id === task.id)
142
+ // 忽略已取消、已完成或不存在的任务
143
+ if (index === -1 ||
144
+ downloadTasksState.value[index].status === 'cancelled' ||
145
+ downloadTasksState.value[index].status === 'completed') {
146
+ return undefined
147
+ }
148
+
149
+ const loaded = event.loaded || 0
150
+ const total = event.total || info.fileSize
151
+
152
+ // 创建新对象以触发响应式更新
153
+ const currentTask = { ...downloadTasksState.value[index] }
154
+ currentTask.downloaded = loaded
155
+ currentTask.fileSize = total
156
+ currentTask.progress = total > 0 ? Math.round((loaded / total) * 100) : 0
157
+ currentTask.updatedAt = Date.now()
158
+
159
+ // 调用外部回调
160
+ if (info.onProgress) {
161
+ info.onProgress(currentTask.progress)
162
+ }
163
+
164
+ // 下载完成
165
+ if (loaded >= total && total > 0) {
166
+ currentTask.status = 'completed'
167
+ currentTask.progress = 100
168
+ currentTask.updatedAt = Date.now()
169
+ downloadCancelCallbacks.delete(task.id)
170
+ // 清理监听器
171
+ cleanup()
172
+ if (info.onComplete) {
173
+ info.onComplete()
174
+ }
175
+ }
176
+
177
+ // 替换数组中的对象以触发响应式更新
178
+ downloadTasksState.value[index] = currentTask
179
+ downloadTasksState.value = [...downloadTasksState.value]
180
+
181
+ return undefined
182
+ }
183
+
184
+ // 处理取消回调
185
+ const oncancel = (_: ProgressEvent & { [key: string]: any }) => {
186
+ const currentTask = downloadTasksState.value.find(t => t.id === task.id)
187
+ // 忽略已完成或已取消的任务
188
+ if (currentTask && currentTask.status !== 'completed' && currentTask.status !== 'cancelled') {
189
+ currentTask.status = 'cancelled'
190
+ currentTask.updatedAt = Date.now()
191
+ downloadCancelCallbacks.delete(task.id)
192
+ // 清理监听器
193
+ cleanup()
194
+ if (info.onError) {
195
+ info.onError('下载已取消')
196
+ }
197
+ }
198
+ return undefined
199
+ }
200
+
201
+ // 存储取消回调以便后续调用
202
+ downloadCancelCallbacks.set(task.id, () => {
203
+ oncancel({} as ProgressEvent)
204
+ })
205
+
206
+ // 用于存储清理函数
207
+ let cleanup: () => void = () => {}
208
+
209
+ // 开始下载
210
+ try {
211
+ cleanup = downloadFile(info.url, info.fileName, onprocess, oncancel)
212
+ } catch (e) {
213
+ const currentTask = downloadTasksState.value.find(t => t.id === task.id)
214
+ if (currentTask) {
215
+ currentTask.status = 'failed'
216
+ currentTask.error = String(e)
217
+ currentTask.updatedAt = Date.now()
218
+ }
219
+ downloadCancelCallbacks.delete(task.id)
220
+ if (info.onError) {
221
+ info.onError(String(e))
222
+ }
223
+ }
224
+
225
+ return task.id
226
+ }
227
+
228
+ export const addUploadTask = (info: {
229
+ fileName: string,
230
+ fileSize: number,
231
+ // 执行上传的函数,接收 onProgress 回调
232
+ execute: (onProgress: (loaded: number, total: number) => void) => void
233
+ }) => {
234
+ const task: TransferTask = {
235
+ id: generateTaskId(),
236
+ fileName: info.fileName,
237
+ fileSize: info.fileSize,
238
+ filePath: '',
239
+ url: '',
240
+ status: 'uploading',
241
+ progress: 0,
242
+ downloaded: 0,
243
+ uploaded: 0,
244
+ createdAt: Date.now(),
245
+ updatedAt: Date.now()
246
+ }
247
+ uploadTasksState.value.push(task)
248
+ openPanel()
249
+
250
+ // 存储回调供 completeUploadTask/failUploadTask 调用
251
+ uploadCallbacks.set(task.id, {})
252
+
253
+ // 执行上传
254
+ const onProgress = (loaded: number, total: number) => {
255
+ const index = uploadTasksState.value.findIndex(t => t.id === task.id)
256
+ if (index === -1 || uploadTasksState.value[index].status === 'cancelled') {
257
+ return
258
+ }
259
+ // 创建新对象以触发响应式更新
260
+ const currentTask = { ...uploadTasksState.value[index] }
261
+ currentTask.uploaded = loaded
262
+ currentTask.fileSize = total
263
+ currentTask.progress = total > 0 ? Math.round((loaded / total) * 100) : 0
264
+ currentTask.updatedAt = Date.now()
265
+ // 替换数组中的对象以触发响应式更新
266
+ uploadTasksState.value[index] = currentTask
267
+ uploadTasksState.value = [...uploadTasksState.value]
268
+ }
269
+
270
+ info.execute(onProgress)
271
+
272
+ return task.id
273
+ }
274
+
275
+ // 存储上传任务的回调
276
+ const uploadCallbacks = new Map<string, Record<string, never>>()
277
+
278
+ /**
279
+ * 标记上传任务完成(由外部调用,如 sendFileBack 回调)
280
+ * @param taskId 任务ID
281
+ */
282
+ export const completeUploadTask = (taskId: string) => {
283
+ const index = uploadTasksState.value.findIndex(t => t.id === taskId)
284
+ if (index !== -1 && uploadTasksState.value[index].status !== 'cancelled') {
285
+ const task = { ...uploadTasksState.value[index] }
286
+ task.status = 'completed'
287
+ task.progress = 100
288
+ task.updatedAt = Date.now()
289
+ uploadTasksState.value[index] = task
290
+ uploadTasksState.value = [...uploadTasksState.value]
291
+ uploadCallbacks.delete(taskId)
292
+ }
293
+ }
294
+
295
+ /**
296
+ * 标记上传任务失败(由外部调用)
297
+ * @param taskId 任务ID
298
+ * @param error 错误信息
299
+ */
300
+ export const failUploadTask = (taskId: string, error: string) => {
301
+ const index = uploadTasksState.value.findIndex(t => t.id === taskId)
302
+ if (index !== -1 && uploadTasksState.value[index].status !== 'cancelled') {
303
+ const task = { ...uploadTasksState.value[index] }
304
+ task.status = 'failed'
305
+ task.error = error
306
+ task.updatedAt = Date.now()
307
+ uploadTasksState.value[index] = task
308
+ uploadTasksState.value = [...uploadTasksState.value]
309
+ uploadCallbacks.delete(taskId)
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 取消上传任务(供外部调用)
315
+ * @param taskId 任务ID
316
+ */
317
+ export const cancelUploadTask = (taskId: string) => {
318
+ const index = uploadTasksState.value.findIndex(t => t.id === taskId)
319
+ if (index !== -1) {
320
+ const task = { ...uploadTasksState.value[index] }
321
+ task.status = 'cancelled'
322
+ task.updatedAt = Date.now()
323
+ uploadTasksState.value[index] = task
324
+ uploadTasksState.value = [...uploadTasksState.value]
325
+ setTimeout(() => removeUploadTask(taskId), 1000)
326
+ }
327
+ }
328
+
329
+ export const getDownloadTasks = () => downloadTasksState.value
330
+ export const getUploadTasks = () => uploadTasksState.value
331
+
332
+ export const removeDownloadTask = (taskId: string) => {
333
+ downloadCancelCallbacks.delete(taskId)
334
+ const index = downloadTasksState.value.findIndex(t => t.id === taskId)
335
+ if (index !== -1) {
336
+ downloadTasksState.value.splice(index, 1)
337
+ }
338
+ }
339
+
340
+ export const removeUploadTask = (taskId: string) => {
341
+ const index = uploadTasksState.value.findIndex(t => t.id === taskId)
342
+ if (index !== -1) {
343
+ uploadTasksState.value.splice(index, 1)
344
+ }
345
+ }
346
+ </script>
347
+
348
+ <script setup lang="ts">
349
+ import { computed } from 'vue'
350
+ import BcTab from 'vue3-bcui/packages/bc-tab'
351
+
352
+ const downloadTasks = computed(() => downloadTasksState.value)
353
+ const uploadTasks = computed(() => uploadTasksState.value)
354
+
355
+ const formatSize = (bytes: number) => {
356
+ if (bytes === 0) return '0 B'
357
+ const k = 1024
358
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
359
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
360
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
361
+ }
362
+
363
+ const getFileIcon = (fileName: string) => {
364
+ const ext = fileName.split('.').pop()?.toLowerCase() || ''
365
+ if (['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'].includes(ext)) return 'image'
366
+ if (['mp4', 'avi', 'mkv', 'mov', 'wmv'].includes(ext)) return 'video'
367
+ if (['mp3', 'wav', 'flac', 'aac', 'ogg'].includes(ext)) return 'music'
368
+ if (['zip', 'rar', '7z', 'tar', 'gz'].includes(ext)) return 'file-zipper'
369
+ if (['pdf'].includes(ext)) return 'file-pdf'
370
+ if (['doc', 'docx'].includes(ext)) return 'file-word'
371
+ if (['xls', 'xlsx'].includes(ext)) return 'file-excel'
372
+ return 'file'
373
+ }
374
+
375
+ const getStatusText = (status: TaskStatus) => {
376
+ const statusMap: Record<TaskStatus, string> = {
377
+ pending: '等待中',
378
+ downloading: '下载中',
379
+ uploading: '上传中',
380
+ completed: '已完成',
381
+ failed: '失败',
382
+ cancelled: '已取消'
383
+ }
384
+ return statusMap[status] || status
385
+ }
386
+
387
+ const cancelDownload = (taskId: string) => {
388
+ const task = downloadTasks.value.find(t => t.id === taskId)
389
+ if (task) {
390
+ const cancelCallback = downloadCancelCallbacks.get(taskId)
391
+ if (cancelCallback) {
392
+ cancelCallback()
393
+ } else {
394
+ task.status = 'cancelled'
395
+ task.updatedAt = Date.now()
396
+ }
397
+ setTimeout(() => removeDownloadTask(taskId), 1000)
398
+ }
399
+ }
400
+
401
+ const cancelUpload = (taskId: string) => {
402
+ cancelUploadTask(taskId)
403
+ }
404
+ </script>
405
+
406
+ <style scoped>
407
+ .file-manager {
408
+ flex-direction: column;
409
+ max-height: 50vh;
410
+ padding: 5px 0 0 0;
411
+ display: flex;
412
+ }
413
+ .file-manager-tabs {
414
+ overflow: hidden;
415
+ flex: 1;
416
+ }
417
+ .empty-tip {
418
+ text-align: center;
419
+ color: var(--color-font-2);
420
+ font-size: 0.85rem;
421
+ padding: 30px 0;
422
+ }
423
+ .task-item {
424
+ display: flex;
425
+ align-items: center;
426
+ padding: 10px;
427
+ margin-bottom: 8px;
428
+ border-radius: 7px;
429
+ background: var(--color-card-2);
430
+ }
431
+ .task-item:last-child {
432
+ margin-bottom: 0;
433
+ }
434
+ .task-icon {
435
+ width: 35px;
436
+ height: 35px;
437
+ align-items: center;
438
+ justify-content: center;
439
+ border-radius: 7px;
440
+ background: var(--color-main);
441
+ color: white;
442
+ margin-right: 12px;
443
+ display: flex;
444
+ flex-shrink: 0;
445
+ }
446
+ .task-icon svg {
447
+ font-size: 0.9rem;
448
+ }
449
+ .task-info {
450
+ flex: 1;
451
+ min-width: 0;
452
+ display: flex;
453
+ flex-direction: column;
454
+ gap: 4px;
455
+ }
456
+ .task-name {
457
+ font-size: 0.85rem;
458
+ font-weight: 500;
459
+ white-space: nowrap;
460
+ overflow: hidden;
461
+ text-overflow: ellipsis;
462
+ }
463
+ .task-name > span {
464
+ background: var(--color-main);
465
+ color: var(--color-font-r);
466
+ padding: 0.05rem 5px;
467
+ border-radius: 1rem;
468
+ font-size: 0.7rem;
469
+ }
470
+ .task-progress {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 8px;
474
+ }
475
+ .progress-bar {
476
+ flex: 1;
477
+ height: 4px;
478
+ border-radius: 2px;
479
+ background: var(--color-card-1);
480
+ overflow: hidden;
481
+ }
482
+ .progress-bar > div {
483
+ height: 100%;
484
+ background: var(--color-main);
485
+ border-radius: 2px;
486
+ transition: width 0.3s;
487
+ }
488
+ .task-progress span {
489
+ font-size: 0.7rem;
490
+ color: var(--color-font-2);
491
+ white-space: nowrap;
492
+ }
493
+ .task-actions {
494
+ display: flex;
495
+ gap: 10px;
496
+ margin-left: 10px;
497
+ }
498
+ .task-actions svg {
499
+ cursor: pointer;
500
+ font-size: 0.9rem;
501
+ opacity: 0.6;
502
+ transition: opacity 0.2s;
503
+ }
504
+ .task-actions svg:hover {
505
+ opacity: 1;
506
+ }
507
+ </style>
508
+ <style>
509
+ .file-manager-tabs > div,
510
+ .file-manager-tabs > div:hover {
511
+ background: transparent !important;
512
+ box-shadow: unset !important;
513
+ }
514
+ .file-manager-tabs ul.tab-bar {
515
+ --bc-tab-margin: 10px;
516
+ justify-content: start;
517
+ padding-left: 10px;
518
+ }
519
+ .file-manager-tabs > div:first-child {
520
+ margin-bottom: 0 !important;
521
+ }
522
+ .file-manager-tabs ul.tab-bar > li span {
523
+ font-size: 0.8rem;
524
+ }
525
+ .file-manager-tabs div.tab-body > div {
526
+ padding-right: 5px;
527
+ height: 40vh;
528
+ }
529
+ .file-manager-tabs div.tab-body > div::-webkit-scrollbar {
530
+ display: none;
531
+ }
532
+ </style>
@@ -0,0 +1,54 @@
1
+ <!--
2
+ * @FileDescription: 联系人 / 消息列表项模板
3
+ * @Author: Stapxs
4
+ * @Date: 2022/08/14
5
+ * @Version: 1.0
6
+ -->
7
+
8
+ <template>
9
+ <div :id="'user-' + (data.user_id ?? data.group_id)"
10
+ :class="'friend-body' + (select ? ' active' : menu ? ' onmenu' : '')"
11
+ :data-name="data.user_id ? data.nickname : data.group_name"
12
+ :data-nickname="data.user_id ? data.nickname : ''"
13
+ :data-type="data.user_id ? 'friend' : 'group'">
14
+ <div :class="data.new_msg === true ? 'new' : ''" />
15
+ <font-awesome-icon v-if="data.user_id == -10000" :icon="['fas', 'bell']" />
16
+ <font-awesome-icon v-else-if="data.user_id == -10001" :icon="['fas', 'user-group']" />
17
+ <img v-else loading="lazy" :title="getShowName(data.group_name || data.nickname, data.remark)"
18
+ :src="data.avatar || '/img/icons/icon.svg'" @error="avatarError">
19
+ <div>
20
+ <div>
21
+ <p>{{ getShowName(data.group_name || data.nickname, data.remark) }}</p>
22
+ <div style="flex: 1" />
23
+ <a class="time">{{ formatSessionTime(data.time) }}</a>
24
+ </div>
25
+ <div>
26
+ <a v-if="data.highlight" class="highlight">
27
+ {{ data.highlight }}
28
+ </a>
29
+ <a :class="from == 'friend' ? 'nick' : ''">{{
30
+ from == 'friend' ? (data.longNick ?? '') : data.raw_msg
31
+ }}</a>
32
+ <div v-if="from == 'message'" style="margin-left: 10px; display: flex">
33
+ <font-awesome-icon v-if="data.always_top === true" :icon="['fas', 'thumbtack']" />
34
+ </div>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { formatSessionTime } from '../function/utils/systemUtil'
42
+ import { getShowName } from '../function/utils/msgUtil'
43
+ import { avatarError } from '../function/utils/avatarUtil'
44
+
45
+ defineOptions({ name: 'FriendBody' })
46
+
47
+ defineProps<{
48
+ data: any
49
+ select?: boolean
50
+ menu?: boolean
51
+ from?: string
52
+ }>()
53
+
54
+ </script>