diff --git a/src/core/navigation/adminNavigation.ts b/src/core/navigation/adminNavigation.ts index 9fc1a023..015ac169 100644 --- a/src/core/navigation/adminNavigation.ts +++ b/src/core/navigation/adminNavigation.ts @@ -158,7 +158,7 @@ export const adminNavigation: AdminNavigationNode[] = [ icon: PrototypeCustomerServiceIcon, children: [ { key: 'exception-orders', title: '异常订单', icon: ClipboardList, path: '/overseas-warehouse/exception-orders', permission: 'platform:overseas-warehouse:exception-order:list' }, - { key: 'customer-visits', title: '客户回访', icon: ContactRound, path: '/overseas-warehouse/customer-visits', permission: 'platform:overseas-warehouse:customer-visit:list', hidden: true }, + { key: 'customer-visits', title: '客户回访', icon: ContactRound, path: '/overseas-warehouse/customer-visits', permission: 'platform:overseas-warehouse:customer-visit:list' }, ], }, { diff --git a/src/modules/dashboard/pages/DashboardView.vue b/src/modules/dashboard/pages/DashboardView.vue index 9e1483c9..498ef173 100644 --- a/src/modules/dashboard/pages/DashboardView.vue +++ b/src/modules/dashboard/pages/DashboardView.vue @@ -94,7 +94,7 @@ onBeforeUnmount(() => document.removeEventListener('fullscreenchange', syncFulls - @@ -462,9 +456,11 @@ onMounted(() => { diff --git a/src/modules/iam/pages/RolePermissionDialogs.test.ts b/src/modules/iam/pages/RolePermissionDialogs.test.ts index ade7811d..752cfd26 100644 --- a/src/modules/iam/pages/RolePermissionDialogs.test.ts +++ b/src/modules/iam/pages/RolePermissionDialogs.test.ts @@ -68,7 +68,7 @@ beforeEach(() => { describe('roles and permission dialogs', () => { it('keeps saved and hidden formal grants across fullscreen and excludes navigation group IDs', async () => { const wrapper = await roleEditor(); const dialog = wrapper.findComponent(AppDialog); const tree = wrapper.findComponent(ElTree) - expect(dialog.props()).toMatchObject({ title: '配置角色 · TEST_OPERATOR', width: 'min(860px, calc(100vw - 28px))', fullscreenable: true }) + expect(dialog.props()).toMatchObject({ title: '配置角色 · 测试角色', width: 'min(860px, calc(100vw - 28px))', fullscreenable: true }) expect(document.querySelector('.el-dialog [placeholder="如:ORDER_OPERATOR"]')).toBeNull() expect(tree.vm.getCheckedKeys()).toContain(childId) expect(tree.vm.getNode(hiddenId)).toBeTruthy() diff --git a/src/modules/iam/pages/RolesView.test.ts b/src/modules/iam/pages/RolesView.test.ts index 7f377d5d..1d72d324 100644 --- a/src/modules/iam/pages/RolesView.test.ts +++ b/src/modules/iam/pages/RolesView.test.ts @@ -117,11 +117,11 @@ describe('RolesView', () => { expect(rolesSource).toContain('aria-label="平台角色列表"') }) - it('uses the prototype-sized centered role editor with responsive split panes', () => { + it('uses the prototype-sized centered role editor with stacked information and permissions', () => { expect(rolesSource).toContain(' form.id ? `配置角色 · ${form.code.trim() || form.name.trim() || '—'}` : '新增角色') +const editorLabel = computed(() => form.id ? `配置角色 · ${form.name.trim() || '—'}` : '新增角色') const canEdit = computed(() => access.can('platform:role:edit')) const canGrant = computed(() => access.can('platform:role:grant')) const requiredPermission = computed(() => form.id && canEdit.value ? 'platform:role:edit' : form.id ? 'platform:role:grant' : 'platform:role:create') @@ -263,13 +263,13 @@ onMounted(loadRoles) - -
+ +

基本信息

- - - - +
{{form.name || '—'}}
{{form.code || '—'}}
{{form.description || '—'}}
+ + +
{{form.name || '—'}}
{{form.description || '—'}}
@@ -282,10 +282,12 @@ onMounted(loadRoles) diff --git a/src/modules/overseas-master-data/components/FleetQuoteEditor.vue b/src/modules/overseas-master-data/components/FleetQuoteEditor.vue index 290e5e1e..82391758 100644 --- a/src/modules/overseas-master-data/components/FleetQuoteEditor.vue +++ b/src/modules/overseas-master-data/components/FleetQuoteEditor.vue @@ -53,8 +53,8 @@ async function save() {

报价资料

{{fact[0]}}
{{fact[1]}}
- -
+ +

基础信息

@@ -62,14 +62,14 @@ async function save() {
{{form.kind === 'PURCHASE' ? '车队采购报价' : '外接销售报价'}}
-
+

报价金额

留空表示未报价;报价变更不自动覆盖已确认承运价格,也不生成应收或应付。

- - + +
-
+

有效期与备注

diff --git a/src/modules/overseas-master-data/pages/ExchangeRatesView.vue b/src/modules/overseas-master-data/pages/ExchangeRatesView.vue index f5708d87..f916d7c5 100644 --- a/src/modules/overseas-master-data/pages/ExchangeRatesView.vue +++ b/src/modules/overseas-master-data/pages/ExchangeRatesView.vue @@ -13,7 +13,7 @@ import QueryToolbar, { type QueryField, type QueryModel } from '@/shared/compone import LoadErrorState from '@/shared/components/LoadErrorState.vue' import { getApiErrorDetails, type ApiErrorDetails } from '@/shared/api/http' import { useAccessPreviewStore } from '@/core/access/accessPreview' -import { changeExchangeRateStatus, createExchangeRate, getExchangeRate, getExchangeRateSummary, listExchangeRates, updateExchangeRate } from '../api/overseasMasterData' +import { changeExchangeRateStatus, createExchangeRate, getExchangeRate, listExchangeRates, updateExchangeRate } from '../api/overseasMasterData' import { overseasMasterDataPermissions as permission } from '../permissions' import { masterDataErrorMessage } from '../apiError' import type { MasterDataStatus, MonthlyExchangeRate } from '../types' @@ -33,10 +33,9 @@ const columns: DataTableColumn[] = [ { key: 'status', label: '状态', width: 90, slotName: 'status' }, { key: 'updatedBy', prop: 'updatedBy', label: '更新人', minWidth: 120 }, { key: 'updatedAt', prop: 'updatedAt', label: '更新时间', minWidth: 150 }, { key: 'remark', prop: 'remark', label: '备注', minWidth: 200 }, ] -const summary = reactive({ latestMonth: '', latestUsdToCnyAverage: null as number | null, updatedBy: '', updatedAt: '' }) const form = reactive({ month: '', usdToCnyAverage: 0, remark: '', recordVersion: 0 }) function params() { const month = String(filters.value.month || ''); return { page: page.value, size: pageSize.value, monthFrom: month || undefined, monthTo: month || undefined, status: (filters.value.status || undefined) as MasterDataStatus | undefined } } -async function load() { loading.value = true; error.value = null; try { const [result, latest] = await Promise.all([listExchangeRates(params()), getExchangeRateSummary()]); rows.value = result.records; total.value = result.total; Object.assign(summary, latest) } catch (cause) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '月度汇率加载失败') } finally { loading.value = false } } +async function load() { loading.value = true; error.value = null; try { const result = await listExchangeRates(params()); rows.value = result.records; total.value = result.total } catch (cause) { rows.value = []; total.value = 0; error.value = getApiErrorDetails(cause, '月度汇率加载失败') } finally { loading.value = false } } function search() { if (page.value === 1) void load(); else page.value = 1 } function openCreate() { editingMonth.value = ''; Object.assign(form, { month: '', usdToCnyAverage: 0, remark: '', recordVersion: 0 }); editorOpen.value = true } async function openEdit(row: MonthlyExchangeRate) { try { const value = await getExchangeRate(row.month); editingMonth.value = value.month; Object.assign(form, { month: value.month, usdToCnyAverage: Number(value.usdToCnyAverage), remark: value.remark || '', recordVersion: value.recordVersion }); editorOpen.value = true } catch (cause) { ElMessage.error(masterDataErrorMessage(cause, '汇率详情加载失败')) } } @@ -54,7 +53,6 @@ watch([page, pageSize], load); onMounted(load) - @@ -62,10 +60,10 @@ watch([page, pageSize], load); onMounted(load) - +
- +
{{ form.month }}

汇率方向:1 USD = X CNY

diff --git a/src/modules/overseas-master-data/pages/FleetQuotesView.vue b/src/modules/overseas-master-data/pages/FleetQuotesView.vue index 958ff881..90289f8c 100644 --- a/src/modules/overseas-master-data/pages/FleetQuotesView.vue +++ b/src/modules/overseas-master-data/pages/FleetQuotesView.vue @@ -46,7 +46,7 @@ watch(kind, () => { if (page.value !== 1) page.value = 1; else void load() }); w - + diff --git a/src/modules/overseas-master-data/pages/OverseasQuotesView.vue b/src/modules/overseas-master-data/pages/OverseasQuotesView.vue index 93bf9a4b..79d3bfb1 100644 --- a/src/modules/overseas-master-data/pages/OverseasQuotesView.vue +++ b/src/modules/overseas-master-data/pages/OverseasQuotesView.vue @@ -85,8 +85,8 @@ watch([page,pageSize],load);onMounted(()=>{void Promise.all([load(),loadOptions( - -
+ +

基础信息

@@ -95,14 +95,14 @@ watch([page,pageSize],load);onMounted(()=>{void Promise.all([load(),loadOptions(
{{ editingWarehouse }}
{{form.versionCode || '—'}}
-
+

仓储作业费用 + 添加费用

操作费用项目计费单位单价最低收费
删除
暂无仓储作业费用
-
+

中转派送价格 + 添加派送价格

diff --git a/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.test.ts b/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.test.ts index 6768977a..fd1a45f0 100644 --- a/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.test.ts +++ b/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.test.ts @@ -152,13 +152,14 @@ it('allows editing after unknown failure and submits a new command', async () => expect(vi.mocked(commandException).mock.calls[1]![3].idempotencyKey).not.toBe(payload.idempotencyKey); expect(vi.mocked(commandException).mock.calls[1]![3]).toEqual(expect.objectContaining({ handlingResult: 'changed' })) wrapper.unmount() }) -it('keeps a conflict visible and lets the operator refresh voluntarily', async () => { +it('keeps a conflict visible and reloads when the operator reopens the detail', async () => { vi.mocked(getExceptionDetail).mockResolvedValueOnce(snapshot()).mockResolvedValueOnce(snapshot('EX-1', 7, 2)) vi.mocked(commandException).mockRejectedValueOnce(new ApiResponseError('版本变化', 'A0409', 409)) const wrapper = open(); await flushPromises(); await fill(wrapper) await wrapper.get('[data-test="complete"]').trigger('click'); await flushPromises() expect(wrapper.text()).toContain('版本变化'); expect(commandException).toHaveBeenCalledTimes(1); expect(getExceptionDetail).toHaveBeenCalledTimes(1) - await wrapper.findAll('button').find(button => button.text() === '重新读取详情')!.trigger('click'); await flushPromises() + expect(wrapper.text()).not.toContain('重新读取详情') + await wrapper.setProps({ active: false }); await wrapper.setProps({ active: true }); await flushPromises() await fill(wrapper); await wrapper.get('[data-test="complete"]').trigger('click'); await flushPromises() expect(vi.mocked(commandException).mock.calls[1]![3].expectedVersion).toBe(7) expect(vi.mocked(commandException).mock.calls[1]![3].idempotencyKey).not.toBe(vi.mocked(commandException).mock.calls[0]![3].idempotencyKey) @@ -284,3 +285,21 @@ it('twenty-one historical files do not get resubmitted automatically with the ne expect(vi.mocked(commandException).mock.calls[0]![3].evidenceFileIds).toEqual(['991']) wrapper.unmount() }) + +it('moves processing controls into the dialog footer target', async () => { + const footer = document.createElement('div') + footer.id = 'exception-test-footer' + document.body.append(footer) + const wrapper = open() + try { + await flushPromises() + await wrapper.setProps({ actionsTarget: '#exception-test-footer' }) + await flushPromises() + expect(wrapper.find('[data-test="complete"]').exists()).toBe(false) + expect(footer.querySelector('[data-test="complete"]')).not.toBeNull() + expect(wrapper.text()).not.toContain('重新读取详情') + } finally { + wrapper.unmount() + footer.remove() + } +}) diff --git a/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.vue b/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.vue index b7a48937..a14496e8 100644 --- a/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.vue +++ b/src/modules/overseas-warehouse/components/ExceptionProcessingPanel.vue @@ -10,7 +10,7 @@ import { formatOverseasDateTime } from '../lastMilePresentation' import { overseasWarehousePermissions as permission } from '../permissions' import { returnSourceKey, returnSourceReference } from '../returnSource' -const props = defineProps<{ customerId: string; exceptionNo: string; active: boolean }>() +const props = defineProps<{ customerId: string; exceptionNo: string; active: boolean; actionsTarget?: string }>() const emit = defineEmits<{ changed: [] }>() const access = useAccessPreviewStore() const canProcess = computed(() => access.can('platform:overseas-warehouse:exception-order:process')) @@ -156,9 +156,8 @@ watch(() => [props.customerId, props.exceptionNo, props.active], () => { ++seque diff --git a/src/modules/overseas-warehouse/pages/ExceptionOrdersView.vue b/src/modules/overseas-warehouse/pages/ExceptionOrdersView.vue index 03d10e1f..007ce516 100644 --- a/src/modules/overseas-warehouse/pages/ExceptionOrdersView.vue +++ b/src/modules/overseas-warehouse/pages/ExceptionOrdersView.vue @@ -110,7 +110,8 @@ onMounted(load) - + + diff --git a/src/modules/suppliers/pages/SupplierFormView.vue b/src/modules/suppliers/pages/SupplierFormView.vue index 68e47338..66b7bf55 100644 --- a/src/modules/suppliers/pages/SupplierFormView.vue +++ b/src/modules/suppliers/pages/SupplierFormView.vue @@ -96,7 +96,7 @@ watch(() => form.category, (category) => { if (category !== 'BOOKING') form.carr
操作目的FBA仓车型/提货方式报价最低收费
删除