This commit is contained in:
wintsa 2026-02-27 18:05:10 +08:00
parent badf131dde
commit 9849801e46
2 changed files with 78 additions and 69 deletions

View File

@ -5,7 +5,7 @@ import type { ColDef } from 'ag-grid-community'
import localforage from 'localforage' import localforage from 'localforage'
import { serviceList, taskList } from '@/sql' import { serviceList, taskList } from '@/sql'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { decimalAggSum, sumByNumber } from '@/lib/decimal' import { decimalAggSum } from '@/lib/decimal'
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload' import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'; import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale';
@ -15,6 +15,7 @@ interface DetailRow {
taskCode: string taskCode: string
taskName: string taskName: string
unit: string unit: string
conversion: number | null
workload: number | null workload: number | null
budgetBase: string budgetBase: string
budgetReferenceUnitPrice: string budgetReferenceUnitPrice: string
@ -68,9 +69,11 @@ type taskLite = {
name: string name: string
basicParam: string basicParam: string
unit: string unit: string
conversion: number | null
maxPrice: number | null maxPrice: number | null
minPrice: number | null minPrice: number | null
defPrice: number | null defPrice: number | null
desc: string | null
} }
const defaultConsultCategoryFactor = computed<number | null>(() => { const defaultConsultCategoryFactor = computed<number | null>(() => {
@ -106,6 +109,7 @@ const buildDefaultRows = (): DetailRow[] => {
taskCode: task.code, taskCode: task.code,
taskName: task.name, taskName: task.name,
unit: task.unit || '', unit: task.unit || '',
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
workload: null, workload: null,
budgetBase: task.basicParam || '', budgetBase: task.basicParam || '',
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task), budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
@ -113,7 +117,7 @@ const buildDefaultRows = (): DetailRow[] => {
typeof task.defPrice === 'number' && Number.isFinite(task.defPrice) ? task.defPrice : null, typeof task.defPrice === 'number' && Number.isFinite(task.defPrice) ? task.defPrice : null,
consultCategoryFactor: defaultConsultCategoryFactor.value, consultCategoryFactor: defaultConsultCategoryFactor.value,
serviceFee: null, serviceFee: null,
remark: '', remark: task.desc|| '',
path: [rowId] path: [rowId]
}) })
} }
@ -125,6 +129,7 @@ const buildDefaultRows = (): DetailRow[] => {
taskCode: '无', taskCode: '无',
taskName: '无', taskName: '无',
unit: '', unit: '',
conversion: null,
workload: null, workload: null,
budgetBase: '无', budgetBase: '无',
budgetReferenceUnitPrice: '无', budgetReferenceUnitPrice: '无',
@ -171,6 +176,27 @@ const parseNumberOrNull = (value: unknown) => {
return Number.isFinite(v) ? v : null return Number.isFinite(v) ? v : null
} }
const calcServiceFee = (row: DetailRow | undefined) => {
if (!row || isNoTaskRow(row)) return null
const price = row.budgetAdoptedUnitPrice
const conversion = row.conversion
const workload = row.workload
const factor = row.consultCategoryFactor
if (
typeof price !== 'number' ||
!Number.isFinite(price) ||
typeof conversion !== 'number' ||
!Number.isFinite(conversion) ||
typeof workload !== 'number' ||
!Number.isFinite(workload) ||
typeof factor !== 'number' ||
!Number.isFinite(factor)
) {
return null
}
return price * conversion * workload * factor
}
const formatEditableNumber = (params: any) => { const formatEditableNumber = (params: any) => {
if (isNoTaskRow(params.data)) return '无' if (isNoTaskRow(params.data)) return '无'
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) { if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
@ -180,52 +206,45 @@ const formatEditableNumber = (params: any) => {
return Number(params.value).toFixed(2) return Number(params.value).toFixed(2)
} }
const isRepeatedTaskNameRow = (params: any) => { const spanRowsByTaskName = (params: any) => {
const rowIndex = params.node?.rowIndex const rowA = params?.nodeA?.data as DetailRow | undefined
if (typeof rowIndex !== 'number' || rowIndex <= 0) return false const rowB = params?.nodeB?.data as DetailRow | undefined
const prevData = params.api.getDisplayedRowAtIndex(rowIndex - 1)?.data as DetailRow | undefined // debugger
return prevData?.taskName === params.data?.taskName if (!rowA || !rowB) return false
} if (isNoTaskRow(rowA) || isNoTaskRow(rowB)) return false
const getTaskNameRowSpan = (params: any) => { return Boolean(rowA.taskName) && Boolean(rowA.budgetBase) && rowA.taskName === rowB.taskName && rowA.budgetBase === rowB.budgetBase
if (params.node?.rowPinned) return 1
if (isRepeatedTaskNameRow(params)) return 1
const currentName = params.data?.taskName
if (!currentName) return 1
let span = 1
let offset = 1
while (true) {
const nextData = params.api.getDisplayedRowAtIndex((params.node?.rowIndex ?? 0) + offset)?.data as DetailRow | undefined
if (!nextData || nextData.taskName !== currentName) break
span += 1
offset += 1
}
return span
} }
const columnDefs: ColDef<DetailRow>[] = [ const columnDefs: ColDef<DetailRow>[] = [
{ {
headerName: '编码', headerName: '编码',
field: 'taskCode', field: 'taskCode',
minWidth: 150, minWidth: 100,
width: 170, width: 120,
pinned: 'left', pinned: 'left',
valueFormatter: params => params.value || '' valueFormatter: params => params.value || ''
}, },
{ {
headerName: '名称', headerName: '名称',
field: 'taskName', field: 'taskName',
minWidth: 220, minWidth: 150,
width: 260, width: 220,
pinned: 'left', pinned: 'left',
rowSpan: params => getTaskNameRowSpan(params), autoHeight: true,
valueFormatter: params => (isRepeatedTaskNameRow(params) ? '' : params.value || '')
spanRows: true,
valueFormatter: params => params.value || ''
}, },
{ {
headerName: '预算基数', headerName: '预算基数',
field: 'budgetBase', field: 'budgetBase',
minWidth: 170, minWidth: 150,
flex: 1, autoHeight: true,
width: 180,
pinned: 'left',
spanRows: spanRowsByTaskName,
valueFormatter: params => params.value || '' valueFormatter: params => params.value || ''
}, },
{ {
@ -300,18 +319,10 @@ const columnDefs: ColDef<DetailRow>[] = [
field: 'serviceFee', field: 'serviceFee',
minWidth: 150, minWidth: 150,
flex: 1, flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data), editable: false,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''), valueGetter: params => calcServiceFee(params.data),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group &&
!params.node?.rowPinned &&
!isNoTaskRow(params.data) &&
(params.value == null || params.value === '')
},
aggFunc: decimalAggSum, aggFunc: decimalAggSum,
valueParser: params => parseNumberOrNull(params.newValue), // valueFormatter: formatEditableNumber
valueFormatter: formatEditableNumber
}, },
{ {
headerName: '说明', headerName: '说明',
@ -323,10 +334,9 @@ const columnDefs: ColDef<DetailRow>[] = [
autoHeight: true, autoHeight: true,
cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' }, cellStyle: { whiteSpace: 'normal', lineHeight: '1.4' },
editable: params => !params.node?.group && !params.node?.rowPinned && !isNoTaskRow(params.data), editable: false,
valueFormatter: params => { valueFormatter: params => {
if (isNoTaskRow(params.data)) return '无'
if (!params.node?.group && !params.node?.rowPinned && !params.value) return '点击输入'
return params.value || '' return params.value || ''
}, },
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''), cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line remark-wrap-cell' : ''),
@ -340,26 +350,6 @@ const columnDefs: ColDef<DetailRow>[] = [
} }
] ]
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
const totalServiceFee = computed(() => sumByNumber(detailRows.value, row => row.serviceFee))
const pinnedTopRowData = computed(() => [
{
id: 'pinned-total-row',
taskCode: '',
taskName: '',
unit: '',
workload: totalWorkload.value,
budgetBase: '',
budgetReferenceUnitPrice: '',
budgetAdoptedUnitPrice: null,
consultCategoryFactor: null,
serviceFee: totalServiceFee.value,
remark: '',
path: ['TOTAL']
}
])
const saveToIndexedDB = async () => { const saveToIndexedDB = async () => {
@ -441,6 +431,18 @@ const processCellFromClipboard = (params: any) => {
} }
return params.value; return params.value;
}; };
const mydiyTheme = myTheme.withParams({
rowBorder: {
style: "solid",
width: 0.8,
color: "#d8d8dd"
},
columnBorder: {
style: "solid",
width: 0.8,
color: "#d8d8dd"
}
})
</script> </script>
<template> <template>
@ -454,11 +456,15 @@ const processCellFromClipboard = (params: any) => {
</div> </div>
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1"> <div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
<AgGridVue :style="{ height: '100%' }" :rowData="detailRows" :pinnedTopRowData="pinnedTopRowData" <AgGridVue :style="{ height: '100%' }" :rowData="detailRows"
:columnDefs="columnDefs" :gridOptions="gridOptions" :theme="myTheme" :treeData="false" :columnDefs="columnDefs" :gridOptions="gridOptions" :theme="mydiyTheme" :treeData="false"
:enableCellSpan="true"
@cell-value-changed="handleCellValueChanged" :suppressColumnVirtualisation="true" @cell-value-changed="handleCellValueChanged" :suppressColumnVirtualisation="true"
:suppressRowVirtualisation="true" :suppressRowTransform="true" :suppressRowVirtualisation="true"
:cellSelection="{ handle: { mode: 'range' } }" :enableClipboard="true"
:enableClipboard="true"
:localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="50" :localeText="AG_GRID_LOCALE_CN" :tooltipShowDelay="500" :headerHeight="50"
:processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard" :processCellForClipboard="processCellForClipboard" :processCellFromClipboard="processCellFromClipboard"
:undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" /> :undoRedoCellEditing="true" :undoRedoCellEditingLimit="20" />
@ -466,3 +472,6 @@ const processCellFromClipboard = (params: any) => {
</div> </div>
</div> </div>
</template> </template>
<!-- :rowSelection="'multiple'"
:enableClickSelection="false" -->
<!-- :suppressRowTransform="true" -->

View File

@ -15,7 +15,7 @@ export const myTheme = themeQuartz.withParams({
wrapperBorder: false, wrapperBorder: false,
// 表头样式(柔和浅蓝,无加粗,更轻盈) // 表头样式(柔和浅蓝,无加粗,更轻盈)
headerBackgroundColor: "#f9fafb", // 极浅的背景色,替代深一点的 #e7f3fc headerBackgroundColor: "#f0f2f3", // 极浅的背景色,替代深一点的 #e7f3fc
headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和 headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和
headerFontSize: 15, // 字体稍大一点,更易读 headerFontSize: 15, // 字体稍大一点,更易读
headerFontWeight: "normal", // 取消加粗,降低视觉重量 headerFontWeight: "normal", // 取消加粗,降低视觉重量