This commit is contained in:
wintsa 2026-03-20 17:21:33 +08:00
parent 32fffa4f89
commit 3dce3646e1
6 changed files with 103 additions and 24 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

View File

@ -8,6 +8,8 @@ import type {
GridApi, GridApi,
GridReadyEvent, GridReadyEvent,
ICellRendererParams, ICellRendererParams,
IGroupCellRendererParams,
IRowNode,
ValueFormatterParams ValueFormatterParams
} from 'ag-grid-community' } from 'ag-grid-community'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
@ -74,21 +76,19 @@ const groupedServiceGroups = ref<string[]>([])
const syncGroupedRowsRender = async () => { const syncGroupedRowsRender = async () => {
await nextTick() await nextTick()
const api = gridApi.value const api = gridApi.value
if (!api) return if (!api || api.isDestroyed?.()) return
if (isWholeProcessGroupedMode.value) { if (isWholeProcessGroupedMode.value) {
api.expandAll() api.expandAll()
} }
api.refreshClientSideRowModel('group') api.refreshClientSideRowModel('group')
api.resetRowHeights()
api.refreshCells({ force: true }) api.refreshCells({ force: true })
api.redrawRows() api.redrawRows()
setTimeout(() => { setTimeout(() => {
const liveApi = gridApi.value const liveApi = gridApi.value
if (!liveApi) return if (!liveApi || liveApi.isDestroyed?.()) return
if (isWholeProcessGroupedMode.value) { if (isWholeProcessGroupedMode.value) {
liveApi.expandAll() liveApi.expandAll()
} }
liveApi.resetRowHeights()
liveApi.refreshCells({ force: true }) liveApi.refreshCells({ force: true })
liveApi.redrawRows() liveApi.redrawRows()
}, 16) }, 16)
@ -100,6 +100,15 @@ const toServiceId = (value: unknown): number | null => {
return parsed return parsed
} }
const getDictRowMergeKey = (row: Pick<WorkContentRow, 'content' | 'serviceid' | 'serviceGroup'>) => {
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 () => { const loadProjectIndustryId = async () => {
@ -156,17 +165,7 @@ const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
} else { } else {
filtered = entries.filter(e => e.serviceid === sid) 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') { } else if (props.dictMode === 'additional') {
filtered = entries.filter(e => e.serviceid === -1 && props.storageKey.split('-').at(-1) =='2') filtered = entries.filter(e => e.serviceid === -1 && props.storageKey.split('-').at(-1) =='2')
} else { } else {
@ -202,7 +201,7 @@ const buildDefaultRowsFromDict = async (): Promise<WorkContentRow[]> => {
serviceGroup, serviceGroup,
serviceid: toServiceId(entry.serviceid), serviceid: toServiceId(entry.serviceid),
remark: '', remark: '',
checked: false, checked: true,
custom: false, custom: false,
path: isWholeProcessGroupedMode.value && serviceGroup path: isWholeProcessGroupedMode.value && serviceGroup
? [serviceGroup, content] ? [serviceGroup, content]
@ -255,6 +254,7 @@ const loadFromStore = async () => {
const persistedRows = state.detailRows.map(item => ({ const persistedRows = state.detailRows.map(item => ({
...item, ...item,
type: item.custom ? '自定义' : (item.type || '基本工作'), type: item.custom ? '自定义' : (item.type || '基本工作'),
checked: item.custom ? false : item.checked !== false,
serviceid: toServiceId(item.serviceid), serviceid: toServiceId(item.serviceid),
path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名'] path: Array.isArray(item.path) && item.path.length ? item.path : ['自定义', item.content || '未命名']
})) as WorkContentRow[] })) as WorkContentRow[]
@ -280,16 +280,16 @@ const loadFromStore = async () => {
if (defaultRows.length > 0) { if (defaultRows.length > 0) {
const persistedCustomRows = persistedRows.filter(item => item.custom) const persistedCustomRows = persistedRows.filter(item => item.custom)
const persistedDictRows = persistedRows.filter(item => !item.custom) const persistedDictRows = persistedRows.filter(item => !item.custom)
const persistedByContent = new Map( const persistedByKey = new Map(
persistedDictRows.map(item => [String(item.content || '').trim(), item]) persistedDictRows.map(item => [getDictRowMergeKey(item), item])
) )
const mergedDictRows = defaultRows.map(item => { const mergedDictRows = defaultRows.map(item => {
const key = String(item.content || '').trim() const key = getDictRowMergeKey(item)
const old = persistedByContent.get(key) const old = persistedByKey.get(key)
if (!old) return item if (!old) return item
return { return {
...item, ...item,
checked: Boolean(old.checked), checked: old.checked !== false,
remark: String(old.remark || ''), remark: String(old.remark || ''),
type: old.type || item.type type: old.type || item.type
} }
@ -318,6 +318,60 @@ const handleCheckedToggle = (id: string, checked: boolean) => {
saveToStore() saveToStore()
} }
const getGroupCheckableRows = (node?: IRowNode<WorkContentRow> | 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<WorkContentRow>, 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<IGroupCellRendererParams<WorkContentRow> | undefined>(() => {
if (!isWholeProcessGroupedMode.value) return undefined
return {
suppressCount: true,
innerRenderer: (params: ICellRendererParams<WorkContentRow>) => {
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<WorkContentRow>) => { const contentCellRenderer = (params: ICellRendererParams<WorkContentRow>) => {
const data = params.data const data = params.data
if (!data) return '' if (!data) return ''
@ -649,7 +703,7 @@ const confirmDeleteRow = () => {
:getDataPath="getDataPath" :getDataPath="getDataPath"
:groupDefaultExpanded="isWholeProcessGroupedMode ? -1 : 0" :groupDefaultExpanded="isWholeProcessGroupedMode ? -1 : 0"
:groupDisplayType="isWholeProcessGroupedMode ? 'groupRows' : undefined" :groupDisplayType="isWholeProcessGroupedMode ? 'groupRows' : undefined"
:groupRowRendererParams="isWholeProcessGroupedMode ? { suppressCount: true } : undefined" :groupRowRendererParams="groupRowRendererParams"
:animateRows="true" :animateRows="true"
:localeText="AG_GRID_LOCALE_CN" :localeText="AG_GRID_LOCALE_CN"
:tooltipShowDelay="500" :tooltipShowDelay="500"
@ -728,4 +782,26 @@ const confirmDeleteRow = () => {
height: 14px; height: 14px;
cursor: pointer; 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;
}
</style> </style>

View File

@ -615,7 +615,7 @@ onMounted(() => {
class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary"> class="text-sm font-semibold text-foreground cursor-pointer select-none transition-colors hover:text-primary">
{{ props.title }} {{ props.title }}
</h3> </h3>
<div class="flex items-center gap-2"> <!-- <div class="flex items-center gap-2">
<span class=" text-xs text-muted-foreground">简要计算</span> <span class=" text-xs text-muted-foreground">简要计算</span>
<SwitchRoot <SwitchRoot
class="cursor-pointer peer h-5 w-9 shrink-0 rounded-full border border-transparent bg-muted shadow-sm transition-colors data-[state=checked]:bg-primary" class="cursor-pointer peer h-5 w-9 shrink-0 rounded-full border border-transparent bg-muted shadow-sm transition-colors data-[state=checked]:bg-primary"
@ -623,7 +623,7 @@ onMounted(() => {
<SwitchThumb <SwitchThumb
class="block h-4 w-4 translate-x-0.5 rounded-full bg-background shadow transition-transform data-[state=checked]:translate-x-4" /> class="block h-4 w-4 translate-x-0.5 rounded-full bg-background shadow transition-transform data-[state=checked]:translate-x-4" />
</SwitchRoot> </SwitchRoot>
</div> </div> -->
</div> </div>
<div :class="agGridWrapClass"> <div :class="agGridWrapClass">

View File

@ -620,6 +620,7 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
if (!targetServiceId) return false if (!targetServiceId) return false
const nextValue = toFiniteNumberOrNull(params.value) const nextValue = toFiniteNumberOrNull(params.value)
let changed = false let changed = false
const updatedRows = current.detailRows.map(row => { const updatedRows = current.detailRows.map(row => {
if (String(row.id || '') !== targetServiceId) return row if (String(row.id || '') !== targetServiceId) return row
@ -674,6 +675,8 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
if (isSameState(current, nextState)) return false if (isSameState(current, nextState)) return false
contracts.value[contractId] = nextState contracts.value[contractId] = nextState
contractLoaded.value[contractId] = true contractLoaded.value[contractId] = true
const targetRow = nextState.detailRows.find(row => String(row.id || '') === targetServiceId)
return true return true
} }