From 2c2e19c4eac53e8ebbafc297c1180fa73f8fad66 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Sun, 4 Oct 2026 10:07:45 +0800 Subject: [PATCH] fix(fleet): mount measured controls after record loads --- .../TripOperationsRecordDialog.test.ts | 57 +++++++++++++++++-- .../components/TripOperationsRecordDialog.vue | 2 +- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/src/modules/overseas-master-data/components/TripOperationsRecordDialog.test.ts b/src/modules/overseas-master-data/components/TripOperationsRecordDialog.test.ts index 2e5ed9f6..107c22a7 100644 --- a/src/modules/overseas-master-data/components/TripOperationsRecordDialog.test.ts +++ b/src/modules/overseas-master-data/components/TripOperationsRecordDialog.test.ts @@ -1,5 +1,6 @@ import { afterEach, beforeEach, expect, it, vi } from 'vitest' -import { shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils' +import { mount as mountFull, shallowMount, flushPromises, type VueWrapper } from '@vue/test-utils' +import { ElForm, ElFormItem, ElInput, ElInputNumber } from 'element-plus' import TripOperationsRecordDialog from './TripOperationsRecordDialog.vue' import * as api from '../api/tripOperations' import { ApiResponseError } from '@/shared/api/http' @@ -9,16 +10,60 @@ const record: api.TripOperationsRecord = { tripNo: 'TR1', recordVersion: '0', de const wrappers: VueWrapper[] = [] const slot = { template: '
' } const input = { name: 'ElInputNumber', props: ['modelValue'], emits: ['update:modelValue'], template: '' } -async function mount(readonly = false) { - const wrapper = shallowMount(TripOperationsRecordDialog, { props: { modelValue: false, row, readonly }, global: { stubs: { - AppDialog: { props: ['modelValue'], template: '
' }, ElForm: slot, ElFormItem: slot, - ElTabs: { name: 'ElTabs', props: ['modelValue'], emits: ['update:modelValue'], template: '
' }, ElTabPane: slot, ElInputNumber: input, - ElInput: { ...input, name: 'ElInput' }, ElButton: { props: ['disabled', 'loading'], template: '' }, +async function mount(readonly = false, realControls = false) { + const wrapper = (realControls ? mountFull : shallowMount)(TripOperationsRecordDialog, { props: { modelValue: false, row, readonly }, global: { components: { ElForm, ElFormItem, ElInput, ElInputNumber }, stubs: { + AppDialog: { props: ['modelValue'], template: '
' }, ElForm: realControls ? false : slot, ElFormItem: realControls ? false : slot, + ElTabs: { name: 'ElTabs', props: ['modelValue'], emits: ['update:modelValue'], template: '
' }, ElTabPane: slot, ElInputNumber: realControls ? false : input, + ElInput: realControls ? false : { ...input, name: 'ElInput' }, ElButton: { props: ['disabled', 'loading'], template: '' }, ElAlert: { props: ['title'], template: '
{{ title }}
' }, ApiErrorMeta: true, DataTable: true, } } }); wrappers.push(wrapper); await wrapper.setProps({ modelValue: true }); await flushPromises(); return wrapper } beforeEach(() => { vi.clearAllMocks(); vi.mocked(api.getTripOperationsRecord).mockResolvedValue(record); vi.mocked(api.listTripOperationsRecordHistory).mockResolvedValue({ records: [], total: 0, page: 1, size: 20, pages: 0 }) }) afterEach(() => wrappers.splice(0).forEach(w => w.unmount())) +it.each([[null, 0], [0, null]])('delayed records keep distance %s and fuel %s editable in real number controls', async (distance, fuel) => { + let resolve!: (value: api.TripOperationsRecord) => void + vi.mocked(api.getTripOperationsRecord).mockReturnValue(new Promise(done => { resolve = done })) + const wrapper = await mount(false, true) + expect(wrapper.findComponent(ElForm).exists()).toBe(false) + expect(wrapper.findAll('button').find(button => button.text() === '保存记录')!.attributes('disabled')).toBeDefined() + resolve({ ...record, details: { ...record.details, actualDistanceMiles: distance, actualFuelLitres: fuel } }) + await flushPromises() + const controls = wrapper.findAllComponents(ElInputNumber) + expect(controls.map(control => control.props('modelValue'))).toEqual([distance, fuel]) + const inputs = controls.map(control => control.find('input')) + expect(inputs.map(input => (input.element as HTMLInputElement).value)).toEqual([distance === null ? '' : '0.0', fuel === null ? '' : '0.0']) + for (const input of inputs) { + expect((input.element as HTMLInputElement).disabled).toBe(false) + expect((input.element as HTMLInputElement).readOnly).toBe(false) + expect(input.attributes('aria-disabled')).toBe('false') + } + await inputs[0]!.setValue('12.5') + await inputs[0]!.trigger('change') + expect(controls[0]!.props('modelValue')).toBe(12.5) + await inputs[0]!.setValue(distance === null ? '' : '0') + await inputs[0]!.trigger('change') + expect(controls[0]!.props('modelValue')).toBe(distance) + vi.mocked(api.saveTripOperationsRecord).mockResolvedValue(record) + await wrapper.findAll('button').find(button => button.text() === '保存记录')!.trigger('click') + await flushPromises() + expect(vi.mocked(api.saveTripOperationsRecord).mock.calls[0]![1].details).toMatchObject({ actualDistanceMiles: distance, actualFuelLitres: fuel }) +}) +it('delayed read-only records keep real number controls disabled', async () => { + let resolve!: (value: api.TripOperationsRecord) => void + vi.mocked(api.getTripOperationsRecord).mockReturnValue(new Promise(done => { resolve = done })) + const wrapper = await mount(true, true) + expect(wrapper.findComponent(ElForm).exists()).toBe(false) + resolve({ ...record, details: { ...record.details, actualFuelLitres: 0 } }) + await flushPromises() + expect(wrapper.findAllComponents(ElInputNumber).map(control => control.props('modelValue'))).toEqual([null, 0]) + for (const control of wrapper.findAllComponents(ElInputNumber)) { + const input = control.find('input') + expect((input.element as HTMLInputElement).disabled).toBe(true) + expect(input.attributes('aria-disabled')).toBe('true') + } + expect(wrapper.text()).not.toContain('保存记录') + expect(api.saveTripOperationsRecord).not.toHaveBeenCalled() +}) it('loads current record on open, history only when selected, and hides writes for readers', async () => { const wrapper = await mount(true) expect(api.getTripOperationsRecord).toHaveBeenCalledExactlyOnceWith('TR1'); expect(api.listTripOperationsRecordHistory).not.toHaveBeenCalled() diff --git a/src/modules/overseas-master-data/components/TripOperationsRecordDialog.vue b/src/modules/overseas-master-data/components/TripOperationsRecordDialog.vue index 3b6ac1f9..1020ee8f 100644 --- a/src/modules/overseas-master-data/components/TripOperationsRecordDialog.vue +++ b/src/modules/overseas-master-data/components/TripOperationsRecordDialog.vue @@ -85,7 +85,7 @@ watch([tab, page, size], () => { clearPreview(); if (tab.value === 'history') vo

{{ row.tripNo }} · {{ row.vehiclePlate || '—' }} · 实际发车 {{ row.departedAt }} · 货物结案 {{ row.completedAt || '—' }}

上次保存结果未确认,将按原请求重试。

- +