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 || '—' }}
上次保存结果未确认,将按原请求重试。
-
+