From a9373e468abafd307ed0c04b7feba1edd502c81c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Fri, 6 Nov 2026 21:02:20 +0800 Subject: [PATCH] =?UTF-8?q?fix(driver):=20=E9=99=90=E5=88=B6=E9=A9=BE?= =?UTF-8?q?=E9=A9=B6=E8=AF=81=E4=B8=8A=E4=BC=A0=E4=B8=BA=20JPG=20=E5=92=8C?= =?UTF-8?q?=20PNG=20=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../overseas-master-data/api/drivers.test.ts | 14 ++++++++++- .../overseas-master-data/api/drivers.ts | 3 +++ .../components/DriverEditorDialog.vue | 6 ++--- .../pages/DriversBehavior.test.ts | 23 ++++++++++++++++--- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/modules/overseas-master-data/api/drivers.test.ts b/src/modules/overseas-master-data/api/drivers.test.ts index 81292d19..b8edc9a7 100644 --- a/src/modules/overseas-master-data/api/drivers.test.ts +++ b/src/modules/overseas-master-data/api/drivers.test.ts @@ -41,7 +41,7 @@ describe('driver APIs', () => { it('uploads a real multipart file with the version and leaves multipart boundary to the browser', async () => { vi.mocked(http.post).mockResolvedValue(ok(record)) - const file = new File(['%PDF-1.7'], 'license.pdf', { type: 'application/pdf' }) + const file = new File(['image'], 'license.png', { type: 'image/png' }) await uploadDriverLicense(id, 2, file) const [url, body] = vi.mocked(http.post).mock.calls[0]! expect(url).toBe(`${base}/${id}/license`) @@ -50,6 +50,18 @@ describe('driver APIs', () => { expect((body as FormData).get('file')).toBe(file) }) + it.each(['application/pdf', 'image/gif', 'text/plain'])('rejects %s driver license uploads before sending a request', async type => { + await expect(uploadDriverLicense(id, 2, new File(['content'], 'license', { type }))).rejects.toThrow('JPG 或 PNG') + expect(http.post).not.toHaveBeenCalled() + }) + + it('rejects empty and oversized driver license images before sending a request', async () => { + const empty = new File([], 'empty.png', { type: 'image/png' }) + const oversized = new File([new Uint8Array(10 * 1024 * 1024 + 1)], 'large.jpg', { type: 'image/jpeg' }) + for (const file of [empty, oversized]) await expect(uploadDriverLicense(id, 2, file)).rejects.toThrow('最大 10MiB') + expect(http.post).not.toHaveBeenCalled() + }) + it('takes task qualification from server and keeps history snapshots distinct', async () => { vi.mocked(http.get).mockResolvedValueOnce(ok(record.qualification)).mockResolvedValueOnce(ok({ page: '1', size: '20', total: '1', pages: '1', records: [{ recordVersion: '1', action: 'UPLOAD_LICENSE', snapshot: { ...record, recordVersion: '1', reviewStatus: 'PENDING' } }] })) const expectedEnd = '2026-10-20T18:00:00Z' diff --git a/src/modules/overseas-master-data/api/drivers.ts b/src/modules/overseas-master-data/api/drivers.ts index 7c60645a..74c87b9c 100644 --- a/src/modules/overseas-master-data/api/drivers.ts +++ b/src/modules/overseas-master-data/api/drivers.ts @@ -67,6 +67,9 @@ export async function submitDriver(id: string, recordVersion: number): Promise, ApiEnvelope>(`${path(id)}/submit`, { recordVersion }))) } export async function uploadDriverLicense(id: string, recordVersion: number, file: File): Promise { + if (!['image/jpeg', 'image/png'].includes(file.type) || !file.size || file.size > 10 * 1024 * 1024) { + throw new Error('请选择非空 JPG 或 PNG,最大 10MiB') + } const form = new FormData() form.append('recordVersion', String(recordVersion)) form.append('file', file) diff --git a/src/modules/overseas-master-data/components/DriverEditorDialog.vue b/src/modules/overseas-master-data/components/DriverEditorDialog.vue index bc360884..317c6132 100644 --- a/src/modules/overseas-master-data/components/DriverEditorDialog.vue +++ b/src/modules/overseas-master-data/components/DriverEditorDialog.vue @@ -43,8 +43,8 @@ watch([open, () => props.driver?.id], ([value]) => { function selectFile(event: Event) { const input = event.target as HTMLInputElement const file = input.files?.[0] || null - if (file && (!['application/pdf', 'image/jpeg', 'image/png'].includes(file.type) || !file.size || file.size > 10 * 1024 * 1024)) { - ElMessage.warning('请选择非空 PDF、JPG 或 PNG,最大 10MiB'); input.value = ''; selectedFile.value = null; return + if (file && (!['image/jpeg', 'image/png'].includes(file.type) || !file.size || file.size > 10 * 1024 * 1024)) { + ElMessage.warning('请选择非空 JPG 或 PNG,最大 10MiB'); input.value = ''; selectedFile.value = null; return } selectedFile.value = file } @@ -107,7 +107,7 @@ onBeforeUnmount(() => { warehouseRequest++; editSession++ })

证照附件已有附件

- +
diff --git a/src/modules/overseas-master-data/pages/DriversBehavior.test.ts b/src/modules/overseas-master-data/pages/DriversBehavior.test.ts index d49b8b36..1352d305 100644 --- a/src/modules/overseas-master-data/pages/DriversBehavior.test.ts +++ b/src/modules/overseas-master-data/pages/DriversBehavior.test.ts @@ -307,6 +307,23 @@ describe('driver page workflows', () => { expect(api.getDriverQualification).toHaveBeenCalledTimes(1) }) + it('accepts image selection and clears it when a PDF is selected instead', async () => { + vi.mocked(api.updateDriver).mockResolvedValue(structuredClone(driver)) + const wrapper = mount(DriverEditorDialog, { modelValue: false, driver }) + await wrapper.setProps({ modelValue: true }); await flushPromises() + const picker = wrapper.get('input[type="file"]') + expect(picker.attributes('accept')).toBe('image/jpeg,image/png') + Object.defineProperty(picker.element, 'files', { value: [new File(['image'], 'license.png', { type: 'image/png' })], configurable: true }) + await picker.trigger('change') + expect(ElMessage.warning).not.toHaveBeenCalled() + Object.defineProperty(picker.element, 'files', { value: [new File(['%PDF'], 'license.pdf', { type: 'application/pdf' })], configurable: true }) + await picker.trigger('change') + expect(ElMessage.warning).toHaveBeenCalledWith('请选择非空 JPG 或 PNG,最大 10MiB') + wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() + expect(api.updateDriver).toHaveBeenCalledTimes(1) + expect(api.uploadDriverLicense).not.toHaveBeenCalled() + }) + it('does not create a second draft when attachment upload fails after saving', async () => { vi.mocked(api.createDriver).mockResolvedValue({ ...structuredClone(driver), recordVersion: 0 }) vi.mocked(api.uploadDriverLicense).mockRejectedValueOnce(new Error('文件服务不可用')).mockResolvedValueOnce({ ...structuredClone(driver), recordVersion: 1 }) @@ -317,7 +334,7 @@ describe('driver page workflows', () => { inputs[1]!.vm.$emit('update:modelValue', '123') const selects = wrapper.findAllComponents({ name: 'ElSelect' }) selects.at(-1)!.vm.$emit('update:modelValue', 'UTC') - const file = new File(['%PDF-test'], 'license.pdf', { type: 'application/pdf' }) + const file = new File(['image'], 'license.png', { type: 'image/png' }) const picker = wrapper.get('input[type="file"]') Object.defineProperty(picker.element, 'files', { value: [file] }) await picker.trigger('change') @@ -342,7 +359,7 @@ describe('driver page workflows', () => { expect(api.updateDriver).toHaveBeenCalledWith(driver.id, 2, expect.anything()) await wrapper.setProps({ modelValue: false }); await flushPromises() await wrapper.setProps({ driver: other, modelValue: true }); await flushPromises() - const file = new File(['%PDF-other'], 'other-driver.pdf', { type: 'application/pdf' }) + const file = new File(['image'], 'other-driver.jpg', { type: 'image/jpeg' }) const picker = wrapper.get('input[type="file"]') Object.defineProperty(picker.element, 'files', { value: [file], configurable: true }) await picker.trigger('change') @@ -365,7 +382,7 @@ describe('driver page workflows', () => { const wrapper = mount(DriverEditorDialog, { modelValue: false, driver }) await wrapper.setProps({ modelValue: true }); await flushPromises() const picker = wrapper.get('input[type="file"]') - Object.defineProperty(picker.element, 'files', { value: [new File(['%PDF'], 'driver.pdf', { type: 'application/pdf' })] }) + Object.defineProperty(picker.element, 'files', { value: [new File(['image'], 'driver.jpg', { type: 'image/jpeg' })] }) await picker.trigger('change') wrapper.findComponent({ name: 'AppDialog' }).vm.$emit('confirm'); await flushPromises() wrapper.unmount()