diff --git a/debug-screenshot-1.png b/debug-screenshot-1.png deleted file mode 100644 index cc9000e..0000000 Binary files a/debug-screenshot-1.png and /dev/null differ diff --git a/home-entry-after.png b/home-entry-after.png deleted file mode 100644 index 0a1f1f5..0000000 Binary files a/home-entry-after.png and /dev/null differ diff --git a/home-entry-before.png b/home-entry-before.png deleted file mode 100644 index 8dde8e0..0000000 Binary files a/home-entry-before.png and /dev/null differ diff --git a/src/components/shared/WorkContentGrid.vue b/src/components/shared/WorkContentGrid.vue index 0aff63f..af7297d 100644 --- a/src/components/shared/WorkContentGrid.vue +++ b/src/components/shared/WorkContentGrid.vue @@ -8,6 +8,8 @@ import type { GridApi, GridReadyEvent, ICellRendererParams, + IGroupCellRendererParams, + IRowNode, ValueFormatterParams } from 'ag-grid-community' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale' @@ -74,21 +76,19 @@ const groupedServiceGroups = ref([]) const syncGroupedRowsRender = async () => { await nextTick() const api = gridApi.value - if (!api) return + if (!api || api.isDestroyed?.()) return if (isWholeProcessGroupedMode.value) { api.expandAll() } api.refreshClientSideRowModel('group') - api.resetRowHeights() api.refreshCells({ force: true }) api.redrawRows() setTimeout(() => { const liveApi = gridApi.value - if (!liveApi) return + if (!liveApi || liveApi.isDestroyed?.()) return if (isWholeProcessGroupedMode.value) { liveApi.expandAll() } - liveApi.resetRowHeights() liveApi.refreshCells({ force: true }) liveApi.redrawRows() }, 16) @@ -100,6 +100,15 @@ const toServiceId = (value: unknown): number | null => { return parsed } +const getDictRowMergeKey = (row: Pick) => { + const content = String(row.content || '').trim() + const serviceid = toServiceId(row.serviceid) + if (serviceid != null) return `sid:${serviceid}|content:${content}` + const groupName = String(row.serviceGroup || '').trim() + if (groupName) return `group:${groupName}|content:${content}` + return `content:${content}` +} + const loadProjectIndustryId = async () => { @@ -156,17 +165,7 @@ const buildDefaultRowsFromDict = async (): Promise => { } else { filtered = entries.filter(e => e.serviceid === sid) } - console.log('[WorkContentGrid][init-group]', { - storageKey: props.storageKey, - dictMode: props.dictMode, - serviceId: sid, - industryId, - groupedBy, - matchedWholeProcessGroup, - groupedServiceIds, - groupedEnabled: isWholeProcessGroupedMode.value, - filteredCount: filtered.length - }) + } else if (props.dictMode === 'additional') { filtered = entries.filter(e => e.serviceid === -1 && props.storageKey.split('-').at(-1) =='2') } else { @@ -202,7 +201,7 @@ const buildDefaultRowsFromDict = async (): Promise => { serviceGroup, serviceid: toServiceId(entry.serviceid), remark: '', - checked: false, + checked: true, custom: false, path: isWholeProcessGroupedMode.value && serviceGroup ? [serviceGroup, content] @@ -255,6 +254,7 @@ const loadFromStore = async () => { const persistedRows = state.detailRows.map(item => ({ ...item, type: item.custom ? '自定义' : (item.type || '基本工作'), + checked: item.custom ? false : item.checked !== false, serviceid: toServiceId(item.serviceid), path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名'] })) as WorkContentRow[] @@ -280,16 +280,16 @@ const loadFromStore = async () => { if (defaultRows.length > 0) { const persistedCustomRows = persistedRows.filter(item => item.custom) const persistedDictRows = persistedRows.filter(item => !item.custom) - const persistedByContent = new Map( - persistedDictRows.map(item => [String(item.content || '').trim(), item]) + const persistedByKey = new Map( + persistedDictRows.map(item => [getDictRowMergeKey(item), item]) ) const mergedDictRows = defaultRows.map(item => { - const key = String(item.content || '').trim() - const old = persistedByContent.get(key) + const key = getDictRowMergeKey(item) + const old = persistedByKey.get(key) if (!old) return item return { ...item, - checked: Boolean(old.checked), + checked: old.checked !== false, remark: String(old.remark || ''), type: old.type || item.type } @@ -318,6 +318,60 @@ const handleCheckedToggle = (id: string, checked: boolean) => { saveToStore() } +const getGroupCheckableRows = (node?: IRowNode | null) => { + if (!node) return [] + return (node.allLeafChildren || []) + .map(item => item.data) + .filter((item): item is WorkContentRow => Boolean(item && !isAddTriggerRow(item) && !item.custom)) +} + +const handleGroupCheckedToggle = (node: IRowNode, checked: boolean) => { + const groupRows = getGroupCheckableRows(node) + if (groupRows.length === 0) return + const targetIds = new Set(groupRows.map(item => item.id)) + let changed = false + for (const row of rowData.value) { + if (!targetIds.has(row.id)) continue + if (row.checked === checked) continue + row.checked = checked + changed = true + } + if (!changed) return + gridApi.value?.refreshCells({ force: true }) + gridApi.value?.redrawRows() + saveToStore() +} + +const groupRowRendererParams = computed | undefined>(() => { + if (!isWholeProcessGroupedMode.value) return undefined + return { + suppressCount: true, + innerRenderer: (params: ICellRendererParams) => { + const wrapper = document.createElement('div') + wrapper.className = 'work-content-group-row' + const checkbox = document.createElement('input') + checkbox.type = 'checkbox' + checkbox.className = 'work-content-group-check' + const rows = getGroupCheckableRows(params.node) + const checkedCount = rows.filter(item => item.checked).length + checkbox.checked = rows.length > 0 && checkedCount === rows.length + checkbox.indeterminate = checkedCount > 0 && checkedCount < rows.length + checkbox.disabled = rows.length === 0 + checkbox.addEventListener('mousedown', event => event.stopPropagation()) + checkbox.addEventListener('click', event => event.stopPropagation()) + checkbox.addEventListener('change', event => { + event.stopPropagation() + handleGroupCheckedToggle(params.node, checkbox.checked) + }) + const label = document.createElement('span') + label.className = 'work-content-group-label' + label.textContent = String(params.valueFormatted || params.value || params.node.key || '') + wrapper.append(checkbox, label) + return wrapper + } + } +}) + const contentCellRenderer = (params: ICellRendererParams) => { const data = params.data if (!data) return '' @@ -649,7 +703,7 @@ const confirmDeleteRow = () => { :getDataPath="getDataPath" :groupDefaultExpanded="isWholeProcessGroupedMode ? -1 : 0" :groupDisplayType="isWholeProcessGroupedMode ? 'groupRows' : undefined" - :groupRowRendererParams="isWholeProcessGroupedMode ? { suppressCount: true } : undefined" + :groupRowRendererParams="groupRowRendererParams" :animateRows="true" :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" @@ -728,4 +782,26 @@ const confirmDeleteRow = () => { height: 14px; cursor: pointer; } + +:deep(.work-content-group-row) { + display: flex; + align-items: center; + gap: 8px; +} + +:deep(.work-content-group-check) { + width: 14px; + height: 14px; + cursor: pointer; +} + +:deep(.work-content-group-check:disabled) { + cursor: not-allowed; + opacity: 0.5; +} + +:deep(.work-content-group-label) { + min-width: 0; + word-break: break-word; +} diff --git a/src/components/shared/xmCommonAgGrid.vue b/src/components/shared/xmCommonAgGrid.vue index a7e0fd5..fbf0172 100644 --- a/src/components/shared/xmCommonAgGrid.vue +++ b/src/components/shared/xmCommonAgGrid.vue @@ -615,7 +615,7 @@ onMounted(() => { class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary"> {{ props.title }} -
+
diff --git a/src/pinia/zxFwPricing.ts b/src/pinia/zxFwPricing.ts index 8ff7c0e..dbe2368 100644 --- a/src/pinia/zxFwPricing.ts +++ b/src/pinia/zxFwPricing.ts @@ -620,6 +620,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => { if (!targetServiceId) return false const nextValue = toFiniteNumberOrNull(params.value) + let changed = false const updatedRows = current.detailRows.map(row => { if (String(row.id || '') !== targetServiceId) return row @@ -674,6 +675,8 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => { if (isSameState(current, nextState)) return false contracts.value[contractId] = nextState contractLoaded.value[contractId] = true + const targetRow = nextState.detailRows.find(row => String(row.id || '') === targetServiceId) + return true }