hookehuyr

feat(签到): 添加多附件功能开关

添加环境变量 VITE_CHECKIN_MULTI_ATTACHMENT 控制是否开启多附件功能。
当开关关闭时,混合类型附件提交会提示“请分别提交”。
当开关开启时,允许混合类型附件提交,并使用新的接口字段。
更新测试用例以覆盖开关开启和关闭的场景。
...@@ -15,3 +15,6 @@ VITE_CONSOLE = 0 ...@@ -15,3 +15,6 @@ VITE_CONSOLE = 0
15 15
16 # appID相关 16 # appID相关
17 VITE_APPID=微信appID 17 VITE_APPID=微信appID
18 +
19 +# 是否开启多附件功能
20 +VITE_CHECKIN_MULTI_ATTACHMENT = 0
......
1 import { describe, expect, it, vi, beforeEach } from 'vitest' 1 import { describe, expect, it, vi, beforeEach } from 'vitest'
2 2
3 +let mock_query = {}
4 +
3 vi.mock('vue-router', () => { 5 vi.mock('vue-router', () => {
4 const router = { 6 const router = {
5 push: vi.fn(), 7 push: vi.fn(),
...@@ -7,7 +9,7 @@ vi.mock('vue-router', () => { ...@@ -7,7 +9,7 @@ vi.mock('vue-router', () => {
7 } 9 }
8 return { 10 return {
9 useRoute: () => ({ 11 useRoute: () => ({
10 - query: {} 12 + query: mock_query
11 }), 13 }),
12 useRouter: () => router 14 useRouter: () => router
13 } 15 }
...@@ -58,6 +60,7 @@ import { addUploadTaskAPI } from '@/api/checkin' ...@@ -58,6 +60,7 @@ import { addUploadTaskAPI } from '@/api/checkin'
58 describe('useCheckin 上传大小限制', () => { 60 describe('useCheckin 上传大小限制', () => {
59 beforeEach(() => { 61 beforeEach(() => {
60 vi.clearAllMocks() 62 vi.clearAllMocks()
63 + mock_query = { enable_multi: '0' }
61 if (!globalThis.sessionStorage) { 64 if (!globalThis.sessionStorage) {
62 let store = {} 65 let store = {}
63 globalThis.sessionStorage = { 66 globalThis.sessionStorage = {
...@@ -136,6 +139,7 @@ describe('useCheckin 上传大小限制', () => { ...@@ -136,6 +139,7 @@ describe('useCheckin 上传大小限制', () => {
136 describe('useCheckin 提交兼容', () => { 139 describe('useCheckin 提交兼容', () => {
137 beforeEach(() => { 140 beforeEach(() => {
138 vi.clearAllMocks() 141 vi.clearAllMocks()
142 + mock_query = { enable_multi: '0' }
139 if (globalThis.sessionStorage?.clear) sessionStorage.clear() 143 if (globalThis.sessionStorage?.clear) sessionStorage.clear()
140 }) 144 })
141 145
...@@ -156,13 +160,31 @@ describe('useCheckin 提交兼容', () => { ...@@ -156,13 +160,31 @@ describe('useCheckin 提交兼容', () => {
156 160
157 expect(addUploadTaskAPI).toHaveBeenCalledTimes(2) 161 expect(addUploadTaskAPI).toHaveBeenCalledTimes(2)
158 expect(addUploadTaskAPI.mock.calls[0][0]).toHaveProperty('files') 162 expect(addUploadTaskAPI.mock.calls[0][0]).toHaveProperty('files')
163 + expect(addUploadTaskAPI.mock.calls[0][0]).toHaveProperty('meta_id')
159 expect(addUploadTaskAPI.mock.calls[1][0]).toHaveProperty('meta_id') 164 expect(addUploadTaskAPI.mock.calls[1][0]).toHaveProperty('meta_id')
160 expect(addUploadTaskAPI.mock.calls[1][0]).not.toHaveProperty('files') 165 expect(addUploadTaskAPI.mock.calls[1][0]).not.toHaveProperty('files')
161 expect(showToast).toHaveBeenCalledWith('提交成功') 166 expect(showToast).toHaveBeenCalledWith('提交成功')
162 }) 167 })
163 168
164 it('混合附件且新结构失败时会提示分别提交', async () => { 169 it('混合附件且新结构失败时会提示分别提交', async () => {
165 - addUploadTaskAPI.mockResolvedValueOnce({ code: 0, msg: '参数错误' }) 170 + const { activeType, fileList, message, onSubmit } = useCheckin()
171 +
172 + activeType.value = 'image'
173 + message.value = '随便写点内容'
174 + fileList.value = [
175 + { status: 'done', meta_id: 11, file_type: 'image' },
176 + { status: 'done', meta_id: 12, file_type: 'audio' },
177 + ]
178 +
179 + await onSubmit({ subtask_id: 's1' })
180 +
181 + expect(addUploadTaskAPI).toHaveBeenCalledTimes(0)
182 + expect(showToast).toHaveBeenCalledWith('当前接口暂不支持多类型附件,请分别提交')
183 + })
184 +
185 + it('开启多附件开关时允许 mixed files 提交', async () => {
186 + mock_query = { enable_multi: '1' }
187 + addUploadTaskAPI.mockResolvedValueOnce({ code: 1, data: { id: 1 } })
166 188
167 const { activeType, fileList, message, onSubmit } = useCheckin() 189 const { activeType, fileList, message, onSubmit } = useCheckin()
168 190
...@@ -176,6 +198,9 @@ describe('useCheckin 提交兼容', () => { ...@@ -176,6 +198,9 @@ describe('useCheckin 提交兼容', () => {
176 await onSubmit({ subtask_id: 's1' }) 198 await onSubmit({ subtask_id: 's1' })
177 199
178 expect(addUploadTaskAPI).toHaveBeenCalledTimes(1) 200 expect(addUploadTaskAPI).toHaveBeenCalledTimes(1)
179 - expect(showToast).toHaveBeenCalledWith('当前接口暂不支持多类型附件,请分别提交') 201 + expect(addUploadTaskAPI.mock.calls[0][0]).toHaveProperty('files')
202 + expect(addUploadTaskAPI.mock.calls[0][0]).not.toHaveProperty('meta_id')
203 + expect(addUploadTaskAPI.mock.calls[0][0]).toHaveProperty('file_type', 'mixed')
204 + expect(showToast).toHaveBeenCalledWith('提交成功')
180 }) 205 })
181 }) 206 })
......
...@@ -23,6 +23,16 @@ export function useCheckin() { ...@@ -23,6 +23,16 @@ export function useCheckin() {
23 const router = useRouter() 23 const router = useRouter()
24 const { currentUser } = useAuth() 24 const { currentUser } = useAuth()
25 25
26 + // TAG: 多附件功能开关
27 + const multiAttachmentEnabled = computed(() => {
28 + const query_value = String(route?.query?.enable_multi ?? '')
29 + if (query_value === '1') return true
30 + if (query_value === '0') return false
31 +
32 + const fromEnv = String(import.meta.env.VITE_CHECKIN_MULTI_ATTACHMENT || '') === '1'
33 + return fromEnv
34 + })
35 +
26 // ==================== 状态定义 ==================== 36 // ==================== 状态定义 ====================
27 37
28 /** @type {import('vue').Ref<boolean>} 上传中状态 */ 38 /** @type {import('vue').Ref<boolean>} 上传中状态 */
...@@ -470,19 +480,25 @@ export function useCheckin() { ...@@ -470,19 +480,25 @@ export function useCheckin() {
470 if (files.length === 0 && activeType.value === 'text') { 480 if (files.length === 0 && activeType.value === 'text') {
471 submitData.file_type = 'text' 481 submitData.file_type = 'text'
472 } else if (files.length > 0) { 482 } else if (files.length > 0) {
483 + const types = new Set(files.map(f => f.file_type).filter(Boolean))
484 + const hasMixedTypes = types.size > 1
485 + console.warn(multiAttachmentEnabled.value);
486 + if (hasMixedTypes && !multiAttachmentEnabled.value) {
487 + showToast('当前接口暂不支持多类型附件,请分别提交')
488 + return
489 + }
490 +
473 // 如果有文件,file_type 可能是 'mixed' 或者取第一个文件的类型作为主类型 491 // 如果有文件,file_type 可能是 'mixed' 或者取第一个文件的类型作为主类型
474 // 这里暂时保留 file_type 字段以防后端必须校验,取第一个文件的类型,或者传 'mixed' 492 // 这里暂时保留 file_type 字段以防后端必须校验,取第一个文件的类型,或者传 'mixed'
475 // 如果后端已更新为只看 files 数组,则此字段可能无效 493 // 如果后端已更新为只看 files 数组,则此字段可能无效
476 - const types = new Set(files.map(f => f.file_type)) 494 + submitData.file_type = hasMixedTypes ? 'mixed' : files[0].file_type
477 - if (types.size > 1) { 495 +
478 - submitData.file_type = 'mixed' // 假设后端支持 mixed 496 + if (!multiAttachmentEnabled.value) {
479 - } else { 497 + submitData.meta_id = files.map(f => f.meta_id)
480 - submitData.file_type = files[0].file_type
481 } 498 }
482 } 499 }
483 500
484 - // 如果有文件,添加文件ID (为了兼容可能还在用 meta_id 的旧接口逻辑,如果确定废弃可删除) 501 + // 说明:submitData.meta_id 为旧接口兜底字段,后端完全支持 files 后再移除
485 - // submitData.meta_id = files.map(f => f.meta_id)
486 502
487 let result 503 let result
488 if (route.query.status === 'edit') { 504 if (route.query.status === 'edit') {
...@@ -491,7 +507,7 @@ export function useCheckin() { ...@@ -491,7 +507,7 @@ export function useCheckin() {
491 i: route.query.post_id, 507 i: route.query.post_id,
492 subtask_id: submitData.subtask_id || route.query.subtask_id, 508 subtask_id: submitData.subtask_id || route.query.subtask_id,
493 note: submitData.note, 509 note: submitData.note,
494 - // meta_id: submitData.meta_id, 510 + ...(submitData.meta_id ? { meta_id: submitData.meta_id } : {}),
495 files: submitData.files, 511 files: submitData.files,
496 file_type: submitData.file_type, 512 file_type: submitData.file_type,
497 } 513 }
...@@ -515,7 +531,11 @@ export function useCheckin() { ...@@ -515,7 +531,11 @@ export function useCheckin() {
515 const hasMixedTypes = types.size > 1 531 const hasMixedTypes = types.size > 1
516 532
517 if (hasMixedTypes) { 533 if (hasMixedTypes) {
518 - showToast('当前接口暂不支持多类型附件,请分别提交') 534 + if (!multiAttachmentEnabled.value) {
535 + showToast('当前接口暂不支持多类型附件,请分别提交')
536 + return
537 + }
538 + showToast(result?.msg || '提交失败,请重试')
519 return 539 return
520 } 540 }
521 541
......