From f8c869f1ff70b544fb64f9089954ebd651703747 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Fri, 22 May 2026 08:41:54 +0800 Subject: [PATCH] =?UTF-8?q?feat=EF=BC=88mes=EF=BC=89=EF=BC=9Amd=20client?= =?UTF-8?q?=20select=20=E8=BF=81=E7=A7=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/md-client-select-dialog.vue | 125 ++++++++----- apps/web-antd/src/views/mes/md/client/data.ts | 6 +- .../components/md-client-select-dialog.vue | 173 ++++++++++++------ apps/web-ele/src/views/mes/md/client/data.ts | 6 +- 4 files changed, 208 insertions(+), 102 deletions(-) diff --git a/apps/web-antd/src/views/mes/md/client/components/md-client-select-dialog.vue b/apps/web-antd/src/views/mes/md/client/components/md-client-select-dialog.vue index 88b094580..c90d1cc42 100644 --- a/apps/web-antd/src/views/mes/md/client/components/md-client-select-dialog.vue +++ b/apps/web-antd/src/views/mes/md/client/components/md-client-select-dialog.vue @@ -22,33 +22,29 @@ const emit = defineEmits<{ const open = ref(false); // 弹窗是否打开 const multiple = ref(true); // 是否多选 -const syncingSingleSelection = ref(false); // 是否同步单选勾选状态 const selectedRows = ref([]); // 已选客户列表 const preSelectedIds = ref([]); // 预选客户编号列表 +const latestQueryRows = ref([]); // 最近一次查询返回的客户列表 +const queryFinished = ref(false); // 最近一次查询是否完成 -/** 单选模式下同步 VXE 勾选状态,避免跨页残留多选 */ -async function syncSingleSelection(row?: MesMdClientApi.Client) { - syncingSingleSelection.value = true; - await nextTick(); - await gridApi.grid.clearCheckboxRow(); - if (row) { - await gridApi.grid.setCheckboxRow(row, true); - } - await nextTick(); - syncingSingleSelection.value = false; +// TODO @芋艿:是否有必要搞的这么复杂???后续测试看看。 +const MAX_TABLE_READY_FRAMES = 60; + +/** 等待下一帧 */ +function waitNextFrame(): Promise { + return new Promise((resolve) => { + requestAnimationFrame(() => resolve()); + }); } -/** 处理勾选变化,单选模式只保留最后一条 */ -async function handleCheckboxChange({ - checked, - records, - row, -}: { - checked: boolean; - records: MesMdClientApi.Client[]; - row?: MesMdClientApi.Client; -}) { - if (syncingSingleSelection.value) { +/** 获取当前表格数据 */ +function getTableRows() { + return gridApi.grid.getTableData().fullData as MesMdClientApi.Client[]; +} + +/** 等待 VXE 将当前查询结果写入表格数据后再回显选中 */ +async function waitTableReady(): Promise { + if (preSelectedIds.value.length === 0) { return; } if (!multiple.value) { @@ -60,27 +56,55 @@ async function handleCheckboxChange({ selectedRows.value = records; } -/** 处理全选变化 */ -function handleCheckboxAll({ records }: { records: MesMdClientApi.Client[] }) { - if (syncingSingleSelection.value) { +/** 处理勾选变化 */ +function handleCheckboxSelectChange() { + selectedRows.value = getMultipleSelectedRows(); +} + +/** 处理单选变化 */ +function handleRadioChange(row: MesMdClientApi.Client) { + selectedRows.value = [row]; +} + +/** 多选模式下切换行勾选 */ +async function toggleMultipleRow(row: MesMdClientApi.Client) { + const selected = gridApi.grid.isCheckedByCheckboxRow(row); + await gridApi.grid.setCheckboxRow(row, !selected); + selectedRows.value = getMultipleSelectedRows(); +} + +/** 处理行双击 */ +async function handleCellDblclick({ row }: { row: MesMdClientApi.Client }) { + if (multiple.value) { + await toggleMultipleRow(row); return; } - selectedRows.value = records; + selectedRows.value = [row]; + await gridApi.grid.setRadioRow(row); + handleConfirm(); } /** 回显预选客户 */ -function applyPreSelection() { +async function applyPreSelection() { if (preSelectedIds.value.length === 0) { return; } - const rows = gridApi.grid.getData() as MesMdClientApi.Client[]; + // proxy 表格回显选中时要读取 fullData,否则首次打开可能读不到刚查询出的数据。 + const rows = getTableRows(); for (const row of rows) { - if (row.id && preSelectedIds.value.includes(row.id)) { - gridApi.grid.setCheckboxRow(row, true); - if (!multiple.value) { - selectedRows.value = [row]; - } + if (row.id == null || !preSelectedIds.value.includes(row.id)) { + continue; } + if (multiple.value) { + await gridApi.grid.setCheckboxRow(row, true); + } else { + await gridApi.grid.setRadioRow(row); + selectedRows.value = [row]; + return; + } + } + if (multiple.value) { + selectedRows.value = getMultipleSelectedRows(); } } @@ -89,7 +113,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ schema: useClientSelectGridFormSchema(), }, gridOptions: { - columns: useClientSelectGridColumns(), + columns: useClientSelectGridColumns(true), height: 520, keepSource: true, checkboxConfig: { @@ -97,15 +121,22 @@ const [Grid, gridApi] = useVbenVxeGrid({ range: true, reserve: true, }, + radioConfig: { + highlight: true, + trigger: 'row', + }, proxyConfig: { ajax: { query: async ({ page }, formValues) => { - return await getClientPage({ + const data = await getClientPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues, status: CommonStatusEnum.ENABLE, }); + latestQueryRows.value = data.list || []; + queryFinished.value = true; + return data; }, }, }, @@ -119,8 +150,12 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: handleCheckboxAll, - checkboxChange: handleCheckboxChange, + cellDblclick: handleCellDblclick, + checkboxAll: handleCheckboxSelectChange, + checkboxChange: handleCheckboxSelectChange, + radioChange: ({ row }: { row: MesMdClientApi.Client }) => { + handleRadioChange(row); + }, }, }); @@ -128,6 +163,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ async function resetQueryState() { selectedRows.value = []; await gridApi.grid.clearCheckboxRow(); + await gridApi.grid.clearCheckboxReserve(); + await gridApi.grid.clearRadioRow(); await gridApi.formApi.resetForm(); } @@ -136,26 +173,30 @@ async function openModal(selectedIds?: number[], options?: { multiple?: boolean open.value = true; multiple.value = options?.multiple ?? true; preSelectedIds.value = selectedIds || []; + latestQueryRows.value = []; + queryFinished.value = false; await nextTick(); + gridApi.setGridOptions({ columns: useClientSelectGridColumns(multiple.value) }); await resetQueryState(); await gridApi.query(); await nextTick(); - applyPreSelection(); + await waitTableReady(); + await applyPreSelection(); } /** 关闭客户选择弹窗 */ -async function closeModal() { +function closeModal() { open.value = false; - await resetQueryState(); } /** 确认选择客户 */ function handleConfirm() { - if (selectedRows.value.length === 0) { + const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; + if (rows.length === 0) { message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据'); return; } - emit('selected', multiple.value ? selectedRows.value : [selectedRows.value[0]!]); + emit('selected', multiple.value ? rows : [rows[0]!]); open.value = false; } diff --git a/apps/web-antd/src/views/mes/md/client/data.ts b/apps/web-antd/src/views/mes/md/client/data.ts index 977f23d76..11b04c915 100644 --- a/apps/web-antd/src/views/mes/md/client/data.ts +++ b/apps/web-antd/src/views/mes/md/client/data.ts @@ -424,9 +424,11 @@ export function useClientSelectGridFormSchema(): VbenFormSchema[] { } /** 客户选择弹窗的字段 */ -export function useClientSelectGridColumns(): VxeTableGridOptions['columns'] { +export function useClientSelectGridColumns( + multiple = true, +): VxeTableGridOptions['columns'] { return [ - { type: 'checkbox', width: 50 }, + { type: multiple ? 'checkbox' : 'radio', width: 50 }, { field: 'code', title: '客户编码', diff --git a/apps/web-ele/src/views/mes/md/client/components/md-client-select-dialog.vue b/apps/web-ele/src/views/mes/md/client/components/md-client-select-dialog.vue index 37d273f00..ca48c71fd 100644 --- a/apps/web-ele/src/views/mes/md/client/components/md-client-select-dialog.vue +++ b/apps/web-ele/src/views/mes/md/client/components/md-client-select-dialog.vue @@ -22,65 +22,109 @@ const emit = defineEmits<{ const open = ref(false); // 弹窗是否打开 const multiple = ref(true); // 是否多选 -const syncingSingleSelection = ref(false); // 是否同步单选勾选状态 const selectedRows = ref([]); // 已选客户列表 const preSelectedIds = ref([]); // 预选客户编号列表 +const latestQueryRows = ref([]); // 最近一次查询返回的客户列表 +const queryFinished = ref(false); // 最近一次查询是否完成 -/** 单选模式下同步 VXE 勾选状态,避免跨页残留多选 */ -async function syncSingleSelection(row?: MesMdClientApi.Client) { - syncingSingleSelection.value = true; - await nextTick(); - await gridApi.grid.clearCheckboxRow(); - if (row) { - await gridApi.grid.setCheckboxRow(row, true); - } - await nextTick(); - syncingSingleSelection.value = false; +// TODO @芋艿:是否有必要搞的这么复杂???后续测试看看。 +const MAX_TABLE_READY_FRAMES = 60; + +/** 等待下一帧 */ +function waitNextFrame(): Promise { + return new Promise((resolve) => { + requestAnimationFrame(() => resolve()); + }); } -/** 处理勾选变化,单选模式只保留最后一条 */ -async function handleCheckboxChange({ - checked, - records, - row, -}: { - checked: boolean; - records: MesMdClientApi.Client[]; - row?: MesMdClientApi.Client; -}) { - if (syncingSingleSelection.value) { - return; - } - if (!multiple.value) { - const selected = checked && row ? [row] : []; - selectedRows.value = selected; - await syncSingleSelection(selected[0]); - return; - } - selectedRows.value = records; +/** 获取当前表格数据 */ +function getTableRows() { + return gridApi.grid.getTableData().fullData as MesMdClientApi.Client[]; } -/** 处理全选变化 */ -function handleCheckboxAll({ records }: { records: MesMdClientApi.Client[] }) { - if (syncingSingleSelection.value) { - return; - } - selectedRows.value = records; -} - -/** 回显预选客户 */ -function applyPreSelection() { +/** 等待 VXE 将当前查询结果写入表格数据后再回显选中 */ +async function waitTableReady(): Promise { if (preSelectedIds.value.length === 0) { return; } - const rows = gridApi.grid.getData() as MesMdClientApi.Client[]; - for (const row of rows) { - if (row.id && preSelectedIds.value.includes(row.id)) { - gridApi.grid.setCheckboxRow(row, true); - if (!multiple.value) { - selectedRows.value = [row]; + for (let index = 0; index < MAX_TABLE_READY_FRAMES; index += 1) { + if (queryFinished.value) { + const rows = getTableRows(); + if (latestQueryRows.value.length === 0 && rows.length === 0) { + return; + } + if (latestQueryRows.value.length > 0 && rows.length > 0) { + return; } } + await waitNextFrame(); + } +} + +/** 获取多选记录,包含 VXE reserve 跨页记录 */ +function getMultipleSelectedRows() { + const selectedMap = new Map(); + const records = [ + ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []), + ...(gridApi.grid.getCheckboxRecords?.() ?? []), + ] as MesMdClientApi.Client[]; + records.forEach((row) => { + if (row.id != null) { + selectedMap.set(row.id, row); + } + }); + return [...selectedMap.values()]; +} + +/** 处理勾选变化 */ +function handleCheckboxSelectChange() { + selectedRows.value = getMultipleSelectedRows(); +} + +/** 处理单选变化 */ +function handleRadioChange(row: MesMdClientApi.Client) { + selectedRows.value = [row]; +} + +/** 多选模式下切换行勾选 */ +async function toggleMultipleRow(row: MesMdClientApi.Client) { + const selected = gridApi.grid.isCheckedByCheckboxRow(row); + await gridApi.grid.setCheckboxRow(row, !selected); + selectedRows.value = getMultipleSelectedRows(); +} + +/** 处理行双击 */ +async function handleCellDblclick({ row }: { row: MesMdClientApi.Client }) { + if (multiple.value) { + await toggleMultipleRow(row); + return; + } + selectedRows.value = [row]; + await gridApi.grid.setRadioRow(row); + handleConfirm(); +} + +/** 回显预选客户 */ +async function applyPreSelection() { + if (preSelectedIds.value.length === 0) { + return; + } + // proxy 表格回显选中时要读取 fullData,否则首次打开可能读不到刚查询出的数据。 + const rows = getTableRows(); + for (const row of rows) { + if (row.id == null || !preSelectedIds.value.includes(row.id)) { + continue; + } + if (multiple.value) { + await gridApi.grid.setCheckboxRow(row, true); + } else { + await gridApi.grid.setRadioRow(row); + selectedRows.value = [row]; + return; + } + } + if (multiple.value) { + selectedRows.value = getMultipleSelectedRows(); } } @@ -89,7 +133,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ schema: useClientSelectGridFormSchema(), }, gridOptions: { - columns: useClientSelectGridColumns(), + columns: useClientSelectGridColumns(true), height: 520, keepSource: true, checkboxConfig: { @@ -97,15 +141,22 @@ const [Grid, gridApi] = useVbenVxeGrid({ range: true, reserve: true, }, + radioConfig: { + highlight: true, + trigger: 'row', + }, proxyConfig: { ajax: { query: async ({ page }, formValues) => { - return await getClientPage({ + const data = await getClientPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues, status: CommonStatusEnum.ENABLE, }); + latestQueryRows.value = data.list || []; + queryFinished.value = true; + return data; }, }, }, @@ -119,8 +170,12 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, } as VxeTableGridOptions, gridEvents: { - checkboxAll: handleCheckboxAll, - checkboxChange: handleCheckboxChange, + cellDblclick: handleCellDblclick, + checkboxAll: handleCheckboxSelectChange, + checkboxChange: handleCheckboxSelectChange, + radioChange: ({ row }: { row: MesMdClientApi.Client }) => { + handleRadioChange(row); + }, }, }); @@ -128,6 +183,8 @@ const [Grid, gridApi] = useVbenVxeGrid({ async function resetQueryState() { selectedRows.value = []; await gridApi.grid.clearCheckboxRow(); + await gridApi.grid.clearCheckboxReserve(); + await gridApi.grid.clearRadioRow(); await gridApi.formApi.resetForm(); } @@ -136,26 +193,30 @@ async function openModal(selectedIds?: number[], options?: { multiple?: boolean open.value = true; multiple.value = options?.multiple ?? true; preSelectedIds.value = selectedIds || []; + latestQueryRows.value = []; + queryFinished.value = false; await nextTick(); + gridApi.setGridOptions({ columns: useClientSelectGridColumns(multiple.value) }); await resetQueryState(); await gridApi.query(); await nextTick(); - applyPreSelection(); + await waitTableReady(); + await applyPreSelection(); } /** 关闭客户选择弹窗 */ -async function closeModal() { +function closeModal() { open.value = false; - await resetQueryState(); } /** 确认选择客户 */ function handleConfirm() { - if (selectedRows.value.length === 0) { + const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value; + if (rows.length === 0) { ElMessage.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据'); return; } - emit('selected', multiple.value ? selectedRows.value : [selectedRows.value[0]!]); + emit('selected', multiple.value ? rows : [rows[0]!]); open.value = false; } diff --git a/apps/web-ele/src/views/mes/md/client/data.ts b/apps/web-ele/src/views/mes/md/client/data.ts index a320fc433..0a7597229 100644 --- a/apps/web-ele/src/views/mes/md/client/data.ts +++ b/apps/web-ele/src/views/mes/md/client/data.ts @@ -422,9 +422,11 @@ export function useClientSelectGridFormSchema(): VbenFormSchema[] { } /** 客户选择弹窗的字段 */ -export function useClientSelectGridColumns(): VxeTableGridOptions['columns'] { +export function useClientSelectGridColumns( + multiple = true, +): VxeTableGridOptions['columns'] { return [ - { type: 'checkbox', width: 50 }, + { type: multiple ? 'checkbox' : 'radio', width: 50 }, { field: 'code', title: '客户编码',