修复重置功能
This commit is contained in:
parent
303d6d6185
commit
1a0e97011f
437
src/components/ht/HtContractSummary.vue
Normal file
437
src/components/ht/HtContractSummary.vue
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, defineComponent, h, onActivated, onMounted, ref, shallowRef, watch, type PropType } from 'vue'
|
||||||
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
|
import type { ColDef, GridApi, GridOptions, GridReadyEvent, ICellRendererParams } from 'ag-grid-community'
|
||||||
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||||
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
|
import { formatThousands, formatThousandsFlexible } from '@/lib/numberFormat'
|
||||||
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
import { roundTo } from '@/lib/decimal'
|
||||||
|
import { useZxFwPricingStore } from '@/pinia/zxFwPricing'
|
||||||
|
import { additionalWorkList, reserveList } from '@/sql'
|
||||||
|
|
||||||
|
type SummaryRowType = 'service' | 'additional' | 'reserve' | 'total'
|
||||||
|
|
||||||
|
interface SummaryRow {
|
||||||
|
id: string
|
||||||
|
rowType: SummaryRowType
|
||||||
|
code: string | { richText?: Array<{ text?: string; font?: { italic?: boolean; vertAlign?: string } }> }
|
||||||
|
name: string
|
||||||
|
investScale: number | null
|
||||||
|
landScale: number | null
|
||||||
|
workload: number | null
|
||||||
|
hourly: number | null
|
||||||
|
subtotal: number | null
|
||||||
|
finalFee: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RateMethodStateLike {
|
||||||
|
rate?: unknown
|
||||||
|
budgetFee?: unknown
|
||||||
|
}
|
||||||
|
interface HourlyMethodRowLike {
|
||||||
|
serviceBudget?: unknown
|
||||||
|
adoptedBudgetUnitPrice?: unknown
|
||||||
|
personnelCount?: unknown
|
||||||
|
workdayCount?: unknown
|
||||||
|
}
|
||||||
|
interface HourlyMethodStateLike {
|
||||||
|
detailRows?: HourlyMethodRowLike[]
|
||||||
|
}
|
||||||
|
interface QuantityMethodRowLike {
|
||||||
|
id?: unknown
|
||||||
|
budgetFee?: unknown
|
||||||
|
quantity?: unknown
|
||||||
|
unitPrice?: unknown
|
||||||
|
}
|
||||||
|
interface QuantityMethodStateLike {
|
||||||
|
detailRows?: QuantityMethodRowLike[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
contractId: string
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const zxFwPricingStore = useZxFwPricingStore()
|
||||||
|
const gridApi = shallowRef<GridApi<SummaryRow> | null>(null)
|
||||||
|
const rowData = ref<SummaryRow[]>([])
|
||||||
|
const explanationText = ref('')
|
||||||
|
let reloadTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
const toFinite = (value: unknown): number | null => toFiniteNumberOrNull(value)
|
||||||
|
const sum3 = (values: Array<number | null | undefined>) => {
|
||||||
|
const valid = values.filter((v): v is number => typeof v === 'number' && Number.isFinite(v))
|
||||||
|
if (valid.length === 0) return null
|
||||||
|
return roundTo(valid.reduce((a, b) => a + b, 0), 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sumHourlyMethodFee = (state: HourlyMethodStateLike | null): number | null => {
|
||||||
|
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||||
|
if (rows.length === 0) return null
|
||||||
|
let total = 0
|
||||||
|
let hasValid = false
|
||||||
|
for (const row of rows) {
|
||||||
|
const rowBudget = toFinite(row?.serviceBudget)
|
||||||
|
if (rowBudget != null) {
|
||||||
|
total += rowBudget
|
||||||
|
hasValid = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const adopted = toFinite(row?.adoptedBudgetUnitPrice)
|
||||||
|
const personnel = toFinite(row?.personnelCount)
|
||||||
|
const workday = toFinite(row?.workdayCount)
|
||||||
|
if (adopted == null || personnel == null || workday == null) continue
|
||||||
|
total += adopted * personnel * workday
|
||||||
|
hasValid = true
|
||||||
|
}
|
||||||
|
return hasValid ? roundTo(total, 3) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const sumQuantityMethodFee = (state: QuantityMethodStateLike | null): number | null => {
|
||||||
|
const rows = Array.isArray(state?.detailRows) ? state.detailRows : []
|
||||||
|
if (rows.length === 0) return null
|
||||||
|
let total = 0
|
||||||
|
let hasValid = false
|
||||||
|
for (const row of rows) {
|
||||||
|
if (String(row?.id || '') === 'fee-subtotal-fixed') continue
|
||||||
|
const budget = toFinite(row?.budgetFee)
|
||||||
|
if (budget != null) {
|
||||||
|
total += budget
|
||||||
|
hasValid = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const quantity = toFinite(row?.quantity)
|
||||||
|
const unitPrice = toFinite(row?.unitPrice)
|
||||||
|
if (quantity == null || unitPrice == null) continue
|
||||||
|
total += quantity * unitPrice
|
||||||
|
hasValid = true
|
||||||
|
}
|
||||||
|
return hasValid ? roundTo(total, 3) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadHtMethodSummaryByRow = async (mainStorageKey: string, rowId: string): Promise<{
|
||||||
|
subtotal: number | null
|
||||||
|
m0: { coe: string; fee: number } | null
|
||||||
|
m4: { fee: number } | null
|
||||||
|
m5: { fee: number } | null
|
||||||
|
}> => {
|
||||||
|
const [rateState, hourlyState, quantityState] = await Promise.all([
|
||||||
|
zxFwPricingStore.loadHtFeeMethodState<RateMethodStateLike>(mainStorageKey, rowId, 'rate-fee'),
|
||||||
|
zxFwPricingStore.loadHtFeeMethodState<HourlyMethodStateLike>(mainStorageKey, rowId, 'hourly-fee'),
|
||||||
|
zxFwPricingStore.loadHtFeeMethodState<QuantityMethodStateLike>(mainStorageKey, rowId, 'quantity-unit-price-fee')
|
||||||
|
])
|
||||||
|
const rateFee = toFinite(rateState?.budgetFee)
|
||||||
|
const rateValue = toFinite(rateState?.rate)
|
||||||
|
const hourlyFee = sumHourlyMethodFee(hourlyState)
|
||||||
|
const quantityFee = sumQuantityMethodFee(quantityState)
|
||||||
|
const subtotal = sum3([rateFee, hourlyFee, quantityFee])
|
||||||
|
return {
|
||||||
|
subtotal,
|
||||||
|
m0: rateFee != null
|
||||||
|
? {
|
||||||
|
coe: rateValue == null ? '--' : String(rateValue),
|
||||||
|
fee: roundTo(rateFee, 2)
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
m4: hourlyFee != null ? { fee: roundTo(hourlyFee, 2) } : null,
|
||||||
|
m5: quantityFee != null ? { fee: roundTo(quantityFee, 2) } : null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildFeeRows = async (
|
||||||
|
rowType: 'additional' | 'reserve',
|
||||||
|
list: Array<{ id: string | number; name: string; code: unknown }>
|
||||||
|
): Promise<{ rows: SummaryRow[]; explainLines: string[] }> => {
|
||||||
|
const mainStorageKey = `htExtraFee-${props.contractId}-${rowType === 'additional' ? 'additional-work' : 'reserve'}`
|
||||||
|
await zxFwPricingStore.loadHtFeeMainState(mainStorageKey)
|
||||||
|
const tuples = await Promise.all(
|
||||||
|
list.map(async item => {
|
||||||
|
const summary = await loadHtMethodSummaryByRow(mainStorageKey, String(item.id))
|
||||||
|
const lineParts: string[] = []
|
||||||
|
if (summary.m0) {
|
||||||
|
lineParts.push(`按费率${summary.m0.coe}%计得${summary.m0.fee}元`)
|
||||||
|
}
|
||||||
|
if (summary.m4) {
|
||||||
|
lineParts.push(`按工时法计得${summary.m4.fee}元`)
|
||||||
|
}
|
||||||
|
if (summary.m5) {
|
||||||
|
lineParts.push(`按数量单价计得${summary.m5.fee}元`)
|
||||||
|
}
|
||||||
|
const linePrefix = rowType === 'additional' ? '附加工作费' : '预备费'
|
||||||
|
const explainLine = lineParts.length > 0 ? `${linePrefix}-${item.name}:${lineParts.join(';')}` : ''
|
||||||
|
const row: SummaryRow = {
|
||||||
|
id: `${rowType}-${item.id}`,
|
||||||
|
rowType,
|
||||||
|
code: item.code as SummaryRow['code'],
|
||||||
|
name: item.name,
|
||||||
|
investScale: null,
|
||||||
|
landScale: null,
|
||||||
|
workload: null,
|
||||||
|
hourly: null,
|
||||||
|
subtotal: summary.subtotal,
|
||||||
|
finalFee: summary.subtotal
|
||||||
|
}
|
||||||
|
return { row, explainLine }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
const rows = tuples.map(item => item.row).filter(row => row.subtotal != null)
|
||||||
|
const explainLines = tuples
|
||||||
|
.filter(item => item.row.subtotal != null && item.explainLine)
|
||||||
|
.map(item => item.explainLine)
|
||||||
|
return { rows, explainLines }
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildServiceRows = (): SummaryRow[] => {
|
||||||
|
const contractState = zxFwPricingStore.getContractState(props.contractId)
|
||||||
|
const selectedSet = new Set((contractState?.selectedIds || []).map(id => String(id)))
|
||||||
|
const rows = Array.isArray(contractState?.detailRows) ? contractState!.detailRows : []
|
||||||
|
return rows
|
||||||
|
.filter(row => String(row.id) !== 'fixed-budget-c' && selectedSet.has(String(row.id)))
|
||||||
|
.map(row => ({
|
||||||
|
id: `service-${row.id}`,
|
||||||
|
rowType: 'service' as const,
|
||||||
|
code: row.code || '',
|
||||||
|
name: row.name || '',
|
||||||
|
investScale: toFinite(row.investScale),
|
||||||
|
landScale: toFinite(row.landScale),
|
||||||
|
workload: toFinite(row.workload),
|
||||||
|
hourly: toFinite(row.hourly),
|
||||||
|
subtotal: toFinite(row.subtotal),
|
||||||
|
finalFee: toFinite((row as { finalFee?: unknown }).finalFee) ?? toFinite(row.subtotal)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadRows = async () => {
|
||||||
|
await zxFwPricingStore.loadContract(props.contractId)
|
||||||
|
const [additionalResult, reserveResult] = await Promise.all([
|
||||||
|
buildFeeRows(
|
||||||
|
'additional',
|
||||||
|
additionalWorkList.map(item => ({ id: item.id, name: item.name, code: item.code }))
|
||||||
|
),
|
||||||
|
buildFeeRows(
|
||||||
|
'reserve',
|
||||||
|
reserveList.map(item => ({ id: item.id, name: item.name, code: item.code }))
|
||||||
|
)
|
||||||
|
])
|
||||||
|
rowData.value = [...buildServiceRows(), ...additionalResult.rows, ...reserveResult.rows]
|
||||||
|
const lines = [...additionalResult.explainLines, ...reserveResult.explainLines]
|
||||||
|
explanationText.value = lines.join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
const scheduleReload = () => {
|
||||||
|
if (reloadTimer) clearTimeout(reloadTimer)
|
||||||
|
reloadTimer = setTimeout(() => {
|
||||||
|
void reloadRows()
|
||||||
|
}, 80)
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshSignature = computed(() => {
|
||||||
|
const additionalKey = `htExtraFee-${props.contractId}-additional-work`
|
||||||
|
const reserveKey = `htExtraFee-${props.contractId}-reserve`
|
||||||
|
return JSON.stringify({
|
||||||
|
contract: zxFwPricingStore.contracts[props.contractId] || null,
|
||||||
|
addMain: zxFwPricingStore.htFeeMainStates[additionalKey] || null,
|
||||||
|
reserveMain: zxFwPricingStore.htFeeMainStates[reserveKey] || null,
|
||||||
|
addMethods: zxFwPricingStore.htFeeMethodStates[additionalKey] || null,
|
||||||
|
reserveMethods: zxFwPricingStore.htFeeMethodStates[reserveKey] || null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalRow = computed<SummaryRow | null>(() => {
|
||||||
|
if (rowData.value.length === 0) return null
|
||||||
|
const sumField = (pick: (row: SummaryRow) => number | null | undefined) =>
|
||||||
|
sum3(rowData.value.map(pick))
|
||||||
|
return {
|
||||||
|
id: 'summary-total-row',
|
||||||
|
rowType: 'total',
|
||||||
|
code: '',
|
||||||
|
name: '合计',
|
||||||
|
investScale: sumField(row => row.investScale),
|
||||||
|
landScale: sumField(row => row.landScale),
|
||||||
|
workload: sumField(row => row.workload),
|
||||||
|
hourly: sumField(row => row.hourly),
|
||||||
|
subtotal: sumField(row => row.subtotal),
|
||||||
|
finalFee: sumField(row => row.finalFee)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const RichCodeRenderer = defineComponent({
|
||||||
|
name: 'RichCodeRenderer',
|
||||||
|
props: {
|
||||||
|
params: {
|
||||||
|
type: Object as PropType<ICellRendererParams<SummaryRow>>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
return () => {
|
||||||
|
const value = props.params.value as SummaryRow['code']
|
||||||
|
if (!value || typeof value === 'string') {
|
||||||
|
return h('span', value || '')
|
||||||
|
}
|
||||||
|
const runs = Array.isArray(value.richText) ? value.richText : []
|
||||||
|
return h(
|
||||||
|
'span',
|
||||||
|
{ class: 'inline-flex items-baseline gap-[1px]' },
|
||||||
|
runs.map((run, idx) =>
|
||||||
|
h(
|
||||||
|
'span',
|
||||||
|
{
|
||||||
|
key: `${idx}-${run.text || ''}`,
|
||||||
|
style: {
|
||||||
|
fontStyle: run?.font?.italic ? 'italic' : 'normal',
|
||||||
|
verticalAlign: run?.font?.vertAlign === 'subscript' ? 'sub' : run?.font?.vertAlign === 'superscript' ? 'super' : 'baseline',
|
||||||
|
fontSize: run?.font?.vertAlign ? '0.85em' : '1em'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
run?.text || ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const columnDefs: ColDef<SummaryRow>[] = [
|
||||||
|
{
|
||||||
|
headerName: '编码',
|
||||||
|
field: 'code',
|
||||||
|
minWidth: 90,
|
||||||
|
maxWidth: 140,
|
||||||
|
cellRenderer: RichCodeRenderer
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '名称',
|
||||||
|
field: 'name',
|
||||||
|
minWidth: 220,
|
||||||
|
flex: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '投资规模法',
|
||||||
|
field: 'investScale',
|
||||||
|
minWidth: 120,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
colSpan: params => (params.data && params.data.rowType !== 'service' ? 5 : 1),
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '用地规模法',
|
||||||
|
field: 'landScale',
|
||||||
|
minWidth: 120,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '工作量法',
|
||||||
|
field: 'workload',
|
||||||
|
minWidth: 110,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '工时法',
|
||||||
|
field: 'hourly',
|
||||||
|
minWidth: 110,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousandsFlexible(params.value, 3))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '小计',
|
||||||
|
field: 'subtotal',
|
||||||
|
minWidth: 120,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2))
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '确认金额',
|
||||||
|
field: 'finalFee',
|
||||||
|
minWidth: 120,
|
||||||
|
flex: 1.2,
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
valueFormatter: params => (params.value == null ? '' : formatThousands(params.value, 2))
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const summaryGridOptions: GridOptions<SummaryRow> = {
|
||||||
|
...gridOptions,
|
||||||
|
treeData: false,
|
||||||
|
getDataPath: undefined,
|
||||||
|
domLayout: 'autoHeight',
|
||||||
|
rowSelection: {
|
||||||
|
mode: 'singleRow',
|
||||||
|
checkboxes: false,
|
||||||
|
enableClickSelection: false
|
||||||
|
},
|
||||||
|
getRowId: params => params.data.id,
|
||||||
|
getRowClass: params => (params.data?.rowType === 'additional' || params.data?.rowType === 'reserve' ? 'ht-summary-fee-row' : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
const onGridReady = (event: GridReadyEvent<SummaryRow>) => {
|
||||||
|
gridApi.value = event.api
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(refreshSignature, (next, prev) => {
|
||||||
|
if (next === prev) return
|
||||||
|
scheduleReload()
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void reloadRows()
|
||||||
|
})
|
||||||
|
|
||||||
|
onActivated(() => {
|
||||||
|
void reloadRows()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col gap-3">
|
||||||
|
<div class="rounded-lg border bg-card xmMx flex flex-col overflow-hidden">
|
||||||
|
<div class="flex items-center justify-between border-b px-3 py-2">
|
||||||
|
<h3 class="text-xs font-semibold text-foreground leading-none">
|
||||||
|
合同段汇总
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="ag-theme-quartz w-full" :style="{ minHeight: '120px' }">
|
||||||
|
<AgGridVue
|
||||||
|
:style="{ width: '100%' }"
|
||||||
|
:rowData="rowData"
|
||||||
|
:pinnedBottomRowData="totalRow ? [totalRow] : []"
|
||||||
|
:columnDefs="columnDefs"
|
||||||
|
:gridOptions="summaryGridOptions"
|
||||||
|
:theme="myTheme"
|
||||||
|
:localeText="AG_GRID_LOCALE_CN"
|
||||||
|
@grid-ready="onGridReady"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form class="rounded-lg border bg-card p-3 space-y-2">
|
||||||
|
<div class="text-xs font-semibold text-foreground">说明</div>
|
||||||
|
<textarea
|
||||||
|
:value="explanationText"
|
||||||
|
rows="3"
|
||||||
|
placeholder="自动生成说明"
|
||||||
|
readonly
|
||||||
|
class="w-full rounded-md border bg-muted/40 px-3 py-2 text-sm text-foreground outline-none"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.ht-summary-fee-row .ag-cell) {
|
||||||
|
background: color-mix(in oklab, var(--muted) 45%, transparent);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -54,7 +54,7 @@ const baseLabel = computed(() =>
|
|||||||
|
|
||||||
const budgetFee = computed<number | null>(() => {
|
const budgetFee = computed<number | null>(() => {
|
||||||
if (baseValue.value == null || rate.value == null) return null
|
if (baseValue.value == null || rate.value == null) return null
|
||||||
return Number((baseValue.value * rate.value).toFixed(3))
|
return Number((baseValue.value * rate.value/100).toFixed(3))
|
||||||
})
|
})
|
||||||
|
|
||||||
const formatAmount = (value: number | null) =>
|
const formatAmount = (value: number | null) =>
|
||||||
|
|||||||
@ -317,6 +317,21 @@ const reserveFeeView = markRaw(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const summaryView = markRaw(
|
||||||
|
defineComponent({
|
||||||
|
name: 'HtContractSummaryWithProps',
|
||||||
|
setup() {
|
||||||
|
const AsyncSummary = defineAsyncComponent({
|
||||||
|
loader: () => import('@/components/ht/HtContractSummary.vue'),
|
||||||
|
onError: (err) => {
|
||||||
|
console.error('加载 HtContractSummary 组件失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return () => h(AsyncSummary, { contractId: props.contractId })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
// 4. 给分类数组添加严格类型标注
|
// 4. 给分类数组添加严格类型标注
|
||||||
const xmCategories: XmCategoryItem[] = [
|
const xmCategories: XmCategoryItem[] = [
|
||||||
{ key: 'base-info', label: '基础信息', component: htBaseInfoView },
|
{ key: 'base-info', label: '基础信息', component: htBaseInfoView },
|
||||||
@ -326,7 +341,7 @@ const xmCategories: XmCategoryItem[] = [
|
|||||||
{ key: 'contract', label: '咨询服务', component: zxfwView },
|
{ key: 'contract', label: '咨询服务', component: zxfwView },
|
||||||
{ key: 'additional-work-fee', label: '附加工作费', component: additionalWorkFeeView },
|
{ key: 'additional-work-fee', label: '附加工作费', component: additionalWorkFeeView },
|
||||||
{ key: 'reserve-fee', label: '预备费', component: reserveFeeView },
|
{ key: 'reserve-fee', label: '预备费', component: reserveFeeView },
|
||||||
{ key: 'all', label: '汇总', component: reserveFeeView },
|
{ key: 'all', label: '汇总', component: summaryView },
|
||||||
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@ -632,7 +632,6 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
editable: params => !isFixedRow(params.data),
|
editable: params => !isFixedRow(params.data),
|
||||||
valueGetter: params => {
|
valueGetter: params => {
|
||||||
if (!params.data) return null
|
if (!params.data) return null
|
||||||
console.log(detailRows.value)
|
|
||||||
return params.data.finalFee
|
return params.data.finalFee
|
||||||
},
|
},
|
||||||
// valueSetter: params => {
|
// valueSetter: params => {
|
||||||
|
|||||||
@ -1732,20 +1732,51 @@ const confirmImportOverride = async () => {
|
|||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
try {
|
try {
|
||||||
|
dataMenuOpen.value = false
|
||||||
|
|
||||||
|
// 1) 先清运行时内存态,避免旧状态在清库过程被再次持久化
|
||||||
|
tabStore.resetTabs()
|
||||||
|
zxFwPricingStore.$patch({
|
||||||
|
contracts: {},
|
||||||
|
contractLoaded: {},
|
||||||
|
servicePricingStates: {},
|
||||||
|
htFeeMainStates: {},
|
||||||
|
htFeeMethodStates: {},
|
||||||
|
keyedStates: {}
|
||||||
|
} as any)
|
||||||
|
await kvStore.clear()
|
||||||
|
|
||||||
|
// 2) 清浏览器存储与默认 localforage 数据
|
||||||
localStorage.clear()
|
localStorage.clear()
|
||||||
sessionStorage.clear()
|
sessionStorage.clear()
|
||||||
await localforage.clear()
|
await localforage.clear()
|
||||||
|
|
||||||
|
// 3) 清 pinia 分库持久化
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
getPiniaPersistStores().map(async ({ store }) => {
|
getPiniaPersistStores().map(async ({ store }) => {
|
||||||
await store.clear()
|
await store.clear()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 4) 清插件按 store 维护的持久化 key(双保险)
|
||||||
|
const clearTasks: Promise<void>[] = []
|
||||||
|
if (tabStore.$clearPersisted) clearTasks.push(tabStore.$clearPersisted())
|
||||||
|
if (zxFwPricingStore.$clearPersisted) clearTasks.push(zxFwPricingStore.$clearPersisted())
|
||||||
|
if (kvStore.$clearPersisted) clearTasks.push(kvStore.$clearPersisted())
|
||||||
|
await Promise.all(clearTasks)
|
||||||
|
|
||||||
|
// 5) 需要保留的引导标记恢复
|
||||||
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
|
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
|
||||||
|
|
||||||
|
// 6) 持久化当前“空状态”并刷新页面
|
||||||
|
const persistTasks: Promise<void>[] = []
|
||||||
|
if (tabStore.$persistNow) persistTasks.push(tabStore.$persistNow())
|
||||||
|
if (zxFwPricingStore.$persistNow) persistTasks.push(zxFwPricingStore.$persistNow())
|
||||||
|
if (kvStore.$persistNow) persistTasks.push(kvStore.$persistNow())
|
||||||
|
await Promise.all(persistTasks)
|
||||||
|
window.location.reload()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('reset failed:', error)
|
console.error('reset failed:', error)
|
||||||
} finally {
|
|
||||||
tabStore.resetTabs()
|
|
||||||
await tabStore.$persistNow?.()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import {
|
|||||||
RowAutoHeightModule,
|
RowAutoHeightModule,
|
||||||
TextEditorModule,
|
TextEditorModule,
|
||||||
TooltipModule,
|
TooltipModule,
|
||||||
UndoRedoEditModule,RenderApiModule ,ColumnApiModule ,CellSpanModule
|
UndoRedoEditModule,RenderApiModule ,ColumnApiModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule
|
||||||
|
|
||||||
} from 'ag-grid-community'
|
} from 'ag-grid-community'
|
||||||
import {
|
import {
|
||||||
@ -47,7 +47,7 @@ const AG_GRID_MODULES = [
|
|||||||
RowGroupingModule,
|
RowGroupingModule,
|
||||||
CellSelectionModule,
|
CellSelectionModule,
|
||||||
ClipboardModule,
|
ClipboardModule,
|
||||||
LocaleModule,ValidationModule ,CellSpanModule
|
LocaleModule,ValidationModule ,CellSpanModule ,RowStyleModule ,RowSelectionModule
|
||||||
]
|
]
|
||||||
|
|
||||||
const pinia = createPinia()
|
const pinia = createPinia()
|
||||||
|
|||||||
@ -635,7 +635,9 @@ export const useZxFwPricingStore = defineStore('zxFwPricing', () => {
|
|||||||
if (!changed) return false
|
if (!changed) return false
|
||||||
|
|
||||||
const rowsWithSyncedFinalFee = updatedRows.map(row => {
|
const rowsWithSyncedFinalFee = updatedRows.map(row => {
|
||||||
if (String(row.id || '') === FIXED_ROW_ID) return row
|
const rowId = String(row.id || '')
|
||||||
|
if (rowId === FIXED_ROW_ID) return row
|
||||||
|
if (rowId !== targetServiceId) return row
|
||||||
const rowSubtotal = sumNullableNumbers([
|
const rowSubtotal = sumNullableNumbers([
|
||||||
toFiniteNumberOrNull(row.investScale),
|
toFiniteNumberOrNull(row.investScale),
|
||||||
toFiniteNumberOrNull(row.landScale),
|
toFiniteNumberOrNull(row.landScale),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user