1
This commit is contained in:
parent
62546bc937
commit
33913c29d2
20
index.html
20
index.html
@ -8,6 +8,26 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
<script>
|
||||||
|
;(() => {
|
||||||
|
const makeVisitVersion = () => {
|
||||||
|
if (window.crypto && typeof window.crypto.getRandomValues === 'function') {
|
||||||
|
const bytes = new Uint32Array(2)
|
||||||
|
window.crypto.getRandomValues(bytes)
|
||||||
|
return `${Date.now().toString(36)}-${bytes[0].toString(36)}${bytes[1].toString(36)}`
|
||||||
|
}
|
||||||
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set('v', makeVisitVersion())
|
||||||
|
const nextUrl = `${url.pathname}${url.search}${url.hash}`
|
||||||
|
const currentUrl = `${window.location.pathname}${window.location.search}${window.location.hash}`
|
||||||
|
if (nextUrl !== currentUrl) {
|
||||||
|
window.history.replaceState(null, '', nextUrl)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
</script>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef } from 'ag-grid-community'
|
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { majorList } from '@/sql'
|
import { majorList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
@ -11,7 +11,7 @@ import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPrici
|
|||||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -49,9 +49,15 @@ interface DetailRow {
|
|||||||
majorName: string
|
majorName: string
|
||||||
amount: number | null
|
amount: number | null
|
||||||
benchmarkBudget: number | null
|
benchmarkBudget: number | null
|
||||||
|
benchmarkBudgetBasic: number | null
|
||||||
|
benchmarkBudgetOptional: number | null
|
||||||
|
basicFormula: string
|
||||||
|
optionalFormula: string
|
||||||
consultCategoryFactor: number | null
|
consultCategoryFactor: number | null
|
||||||
majorFactor: number | null
|
majorFactor: number | null
|
||||||
budgetFee: number | null
|
budgetFee: number | null
|
||||||
|
budgetFeeBasic: number | null
|
||||||
|
budgetFeeOptional: number | null
|
||||||
remark: string
|
remark: string
|
||||||
path: string[]
|
path: string[]
|
||||||
}
|
}
|
||||||
@ -197,9 +203,15 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
majorName: child.name,
|
majorName: child.name,
|
||||||
amount: null,
|
amount: null,
|
||||||
benchmarkBudget: null,
|
benchmarkBudget: null,
|
||||||
|
benchmarkBudgetBasic: null,
|
||||||
|
benchmarkBudgetOptional: null,
|
||||||
|
basicFormula: '',
|
||||||
|
optionalFormula: '',
|
||||||
consultCategoryFactor: null,
|
consultCategoryFactor: null,
|
||||||
majorFactor: null,
|
majorFactor: null,
|
||||||
budgetFee: null,
|
budgetFee: null,
|
||||||
|
budgetFeeBasic: null,
|
||||||
|
budgetFeeOptional: null,
|
||||||
remark: '',
|
remark: '',
|
||||||
path: [group.id, child.id]
|
path: [group.id, child.id]
|
||||||
})
|
})
|
||||||
@ -208,7 +220,24 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
type SourceRow = Pick<DetailRow, 'id'> & Partial<Pick<DetailRow, 'amount' | 'benchmarkBudget' | 'consultCategoryFactor' | 'majorFactor' | 'budgetFee' | 'remark'>>
|
type SourceRow = Pick<DetailRow, 'id'> &
|
||||||
|
Partial<
|
||||||
|
Pick<
|
||||||
|
DetailRow,
|
||||||
|
| 'amount'
|
||||||
|
| 'benchmarkBudget'
|
||||||
|
| 'benchmarkBudgetBasic'
|
||||||
|
| 'benchmarkBudgetOptional'
|
||||||
|
| 'basicFormula'
|
||||||
|
| 'optionalFormula'
|
||||||
|
| 'consultCategoryFactor'
|
||||||
|
| 'majorFactor'
|
||||||
|
| 'budgetFee'
|
||||||
|
| 'budgetFeeBasic'
|
||||||
|
| 'budgetFeeOptional'
|
||||||
|
| 'remark'
|
||||||
|
>
|
||||||
|
>
|
||||||
const mergeWithDictRows = (
|
const mergeWithDictRows = (
|
||||||
rowsFromDb: SourceRow[] | undefined,
|
rowsFromDb: SourceRow[] | undefined,
|
||||||
options?: { includeAmount?: boolean; includeFactorValues?: boolean }
|
options?: { includeAmount?: boolean; includeFactorValues?: boolean }
|
||||||
@ -230,6 +259,10 @@ const mergeWithDictRows = (
|
|||||||
...row,
|
...row,
|
||||||
amount: includeAmount && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
amount: includeAmount && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
||||||
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
||||||
|
benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
|
||||||
|
benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
|
||||||
|
basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
|
||||||
|
optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
|
||||||
consultCategoryFactor:
|
consultCategoryFactor:
|
||||||
!includeFactorValues
|
!includeFactorValues
|
||||||
? null
|
? null
|
||||||
@ -247,6 +280,8 @@ const mergeWithDictRows = (
|
|||||||
? null
|
? null
|
||||||
: getDefaultMajorFactorById(row.id),
|
: getDefaultMajorFactorById(row.id),
|
||||||
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
||||||
|
budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
|
||||||
|
budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
|
||||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -254,7 +289,7 @@ const mergeWithDictRows = (
|
|||||||
|
|
||||||
const formatEditableNumber = (params: any) => {
|
const formatEditableNumber = (params: any) => {
|
||||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||||
return '点击输入'
|
return '请输入'
|
||||||
}
|
}
|
||||||
if (params.value == null) return ''
|
if (params.value == null) return ''
|
||||||
return Number(params.value).toFixed(2)
|
return Number(params.value).toFixed(2)
|
||||||
@ -281,18 +316,34 @@ const formatReadonlyMoney = (params: any) => {
|
|||||||
return formatThousands(roundTo(params.value, 2))
|
return formatThousands(roundTo(params.value, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
const getBenchmarkBudgetSplitByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
||||||
getBenchmarkBudgetByScale(row?.amount, 'cost')
|
getBenchmarkBudgetSplitByScale(row?.amount, 'cost')
|
||||||
|
|
||||||
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
return getScaleBudgetFee({
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||||
benchmarkBudget: getBenchmarkBudgetByAmount(row),
|
if (!benchmarkBudgetSplit) return null
|
||||||
|
|
||||||
|
const splitBudgetFee = getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
|
majorFactor: row?.majorFactor,
|
||||||
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
|
})
|
||||||
|
return splitBudgetFee ? splitBudgetFee.total : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBudgetFeeSplit = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||||
|
if (!benchmarkBudgetSplit) return null
|
||||||
|
return getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
majorFactor: row?.majorFactor,
|
majorFactor: row?.majorFactor,
|
||||||
consultCategoryFactor: row?.consultCategoryFactor
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnDefs: ColDef<DetailRow>[] = [
|
const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
|
||||||
{
|
{
|
||||||
headerName: '造价金额(万元)',
|
headerName: '造价金额(万元)',
|
||||||
field: 'amount',
|
field: 'amount',
|
||||||
@ -310,18 +361,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableMoney
|
valueFormatter: formatEditableMoney
|
||||||
},
|
},
|
||||||
{
|
|
||||||
headerName: '基准预算(元)',
|
|
||||||
field: 'benchmarkBudget',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 100,
|
|
||||||
flex: 2,
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
aggFunc: decimalAggSum,
|
|
||||||
valueGetter: params => getBenchmarkBudgetByAmount(params.data),
|
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
|
||||||
valueFormatter: formatReadonlyMoney
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
headerName: '咨询分类系数',
|
headerName: '咨询分类系数',
|
||||||
field: 'consultCategoryFactor',
|
field: 'consultCategoryFactor',
|
||||||
@ -352,17 +392,89 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatMajorFactor
|
valueFormatter: formatMajorFactor
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
headerName: '基准预算(元)',
|
||||||
|
marryChildren: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
headerName: '基本工作',
|
||||||
|
field: 'benchmarkBudgetBasic',
|
||||||
|
colId: 'benchmarkBudgetBasic',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 130,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.benchmarkBudgetBasic ?? null
|
||||||
|
: getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '可选工作',
|
||||||
|
field: 'benchmarkBudgetOptional',
|
||||||
|
colId: 'benchmarkBudgetOptional',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 130,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.benchmarkBudgetOptional ?? null
|
||||||
|
: getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
headerName: '预算费用',
|
headerName: '预算费用',
|
||||||
field: 'budgetFee',
|
marryChildren: true,
|
||||||
headerClass: 'ag-right-aligned-header',
|
children: [
|
||||||
minWidth: 100,
|
{
|
||||||
flex:2,
|
headerName: '基本工作',
|
||||||
cellClass: 'ag-right-aligned-cell',
|
field: 'budgetFeeBasic',
|
||||||
aggFunc: decimalAggSum,
|
colId: 'budgetFeeBasic',
|
||||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
headerClass: 'ag-right-aligned-header',
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
minWidth: 120,
|
||||||
valueFormatter: formatReadonlyMoney
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.budgetFeeBasic ?? null
|
||||||
|
: getBudgetFeeSplit(params.data)?.basic ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '可选工作',
|
||||||
|
field: 'budgetFeeOptional',
|
||||||
|
colId: 'budgetFeeOptional',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 120,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.budgetFeeOptional ?? null
|
||||||
|
: getBudgetFeeSplit(params.data)?.optional ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '合计',
|
||||||
|
field: 'budgetFee',
|
||||||
|
colId: 'budgetFeeTotal',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 130,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: '说明',
|
headerName: '说明',
|
||||||
@ -416,8 +528,12 @@ const autoGroupColumnDef: ColDef = {
|
|||||||
|
|
||||||
const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
|
const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amount))
|
||||||
|
|
||||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetByAmount(row)))
|
const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.basic))
|
||||||
|
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.optional))
|
||||||
|
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByAmount(row)?.total))
|
||||||
|
|
||||||
|
const totalBudgetFeeBasic = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
|
||||||
|
const totalBudgetFeeOptional = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
|
||||||
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
||||||
const pinnedTopRowData = computed(() => [
|
const pinnedTopRowData = computed(() => [
|
||||||
{
|
{
|
||||||
@ -428,21 +544,50 @@ const pinnedTopRowData = computed(() => [
|
|||||||
majorName: '',
|
majorName: '',
|
||||||
amount: totalAmount.value,
|
amount: totalAmount.value,
|
||||||
benchmarkBudget: totalBenchmarkBudget.value,
|
benchmarkBudget: totalBenchmarkBudget.value,
|
||||||
|
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value,
|
||||||
|
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value,
|
||||||
|
basicFormula: '',
|
||||||
|
optionalFormula: '',
|
||||||
consultCategoryFactor: null,
|
consultCategoryFactor: null,
|
||||||
majorFactor: null,
|
majorFactor: null,
|
||||||
budgetFee: totalBudgetFee.value,
|
budgetFee: totalBudgetFee.value,
|
||||||
|
budgetFeeBasic: totalBudgetFeeBasic.value,
|
||||||
|
budgetFeeOptional: totalBudgetFeeOptional.value,
|
||||||
remark: '',
|
remark: '',
|
||||||
path: ['TOTAL']
|
path: ['TOTAL']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const buildPersistDetailRows = () =>
|
||||||
|
detailRows.value.map(row => {
|
||||||
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
|
||||||
|
const budgetFeeSplit = benchmarkBudgetSplit
|
||||||
|
? getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
|
majorFactor: row.majorFactor,
|
||||||
|
consultCategoryFactor: row.consultCategoryFactor
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
benchmarkBudget: benchmarkBudgetSplit?.total ?? null,
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null,
|
||||||
|
basicFormula: benchmarkBudgetSplit?.basicFormula ?? '',
|
||||||
|
optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '',
|
||||||
|
budgetFee: budgetFeeSplit?.total ?? null,
|
||||||
|
budgetFeeBasic: budgetFeeSplit?.basic ?? null,
|
||||||
|
budgetFeeOptional: budgetFeeSplit?.optional ?? null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
if (shouldSkipPersist()) return
|
if (shouldSkipPersist()) return
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||||
}
|
}
|
||||||
console.log('Saving to IndexedDB:', payload)
|
console.log('Saving to IndexedDB:', payload)
|
||||||
await localforage.setItem(DB_KEY.value, payload)
|
await localforage.setItem(DB_KEY.value, payload)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
import { AgGridVue } from 'ag-grid-vue3'
|
import { AgGridVue } from 'ag-grid-vue3'
|
||||||
import type { ColDef } from 'ag-grid-community'
|
import type { ColDef, ColGroupDef } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { majorList } from '@/sql'
|
import { majorList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
@ -11,7 +11,7 @@ import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPrici
|
|||||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||||
import { parseNumberOrNull } from '@/lib/number'
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
import { getBenchmarkBudgetSplitByScale, getScaleBudgetFeeSplit } from '@/lib/pricingScaleFee'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -50,9 +50,15 @@ interface DetailRow {
|
|||||||
amount: number | null
|
amount: number | null
|
||||||
landArea: number | null
|
landArea: number | null
|
||||||
benchmarkBudget: number | null
|
benchmarkBudget: number | null
|
||||||
|
benchmarkBudgetBasic: number | null
|
||||||
|
benchmarkBudgetOptional: number | null
|
||||||
|
basicFormula: string
|
||||||
|
optionalFormula: string
|
||||||
consultCategoryFactor: number | null
|
consultCategoryFactor: number | null
|
||||||
majorFactor: number | null
|
majorFactor: number | null
|
||||||
budgetFee: number | null
|
budgetFee: number | null
|
||||||
|
budgetFeeBasic: number | null
|
||||||
|
budgetFeeOptional: number | null
|
||||||
remark: string
|
remark: string
|
||||||
path: string[]
|
path: string[]
|
||||||
}
|
}
|
||||||
@ -199,9 +205,15 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
amount: null,
|
amount: null,
|
||||||
landArea: null,
|
landArea: null,
|
||||||
benchmarkBudget: null,
|
benchmarkBudget: null,
|
||||||
|
benchmarkBudgetBasic: null,
|
||||||
|
benchmarkBudgetOptional: null,
|
||||||
|
basicFormula: '',
|
||||||
|
optionalFormula: '',
|
||||||
consultCategoryFactor: null,
|
consultCategoryFactor: null,
|
||||||
majorFactor: null,
|
majorFactor: null,
|
||||||
budgetFee: null,
|
budgetFee: null,
|
||||||
|
budgetFeeBasic: null,
|
||||||
|
budgetFeeOptional: null,
|
||||||
remark: '',
|
remark: '',
|
||||||
path: [group.id, child.id]
|
path: [group.id, child.id]
|
||||||
})
|
})
|
||||||
@ -210,7 +222,25 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
type SourceRow = Pick<DetailRow, 'id'> & Partial<Pick<DetailRow, 'amount' | 'landArea' | 'benchmarkBudget' | 'consultCategoryFactor' | 'majorFactor' | 'budgetFee' | 'remark'>>
|
type SourceRow = Pick<DetailRow, 'id'> &
|
||||||
|
Partial<
|
||||||
|
Pick<
|
||||||
|
DetailRow,
|
||||||
|
| 'amount'
|
||||||
|
| 'landArea'
|
||||||
|
| 'benchmarkBudget'
|
||||||
|
| 'benchmarkBudgetBasic'
|
||||||
|
| 'benchmarkBudgetOptional'
|
||||||
|
| 'basicFormula'
|
||||||
|
| 'optionalFormula'
|
||||||
|
| 'consultCategoryFactor'
|
||||||
|
| 'majorFactor'
|
||||||
|
| 'budgetFee'
|
||||||
|
| 'budgetFeeBasic'
|
||||||
|
| 'budgetFeeOptional'
|
||||||
|
| 'remark'
|
||||||
|
>
|
||||||
|
>
|
||||||
const mergeWithDictRows = (
|
const mergeWithDictRows = (
|
||||||
rowsFromDb: SourceRow[] | undefined,
|
rowsFromDb: SourceRow[] | undefined,
|
||||||
options?: { includeScaleValues?: boolean; includeFactorValues?: boolean }
|
options?: { includeScaleValues?: boolean; includeFactorValues?: boolean }
|
||||||
@ -233,6 +263,10 @@ const mergeWithDictRows = (
|
|||||||
amount: includeScaleValues && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
amount: includeScaleValues && typeof fromDb.amount === 'number' ? fromDb.amount : null,
|
||||||
landArea: includeScaleValues && typeof fromDb.landArea === 'number' ? fromDb.landArea : null,
|
landArea: includeScaleValues && typeof fromDb.landArea === 'number' ? fromDb.landArea : null,
|
||||||
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
|
||||||
|
benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
|
||||||
|
benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
|
||||||
|
basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
|
||||||
|
optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
|
||||||
consultCategoryFactor:
|
consultCategoryFactor:
|
||||||
!includeFactorValues
|
!includeFactorValues
|
||||||
? null
|
? null
|
||||||
@ -250,6 +284,8 @@ const mergeWithDictRows = (
|
|||||||
? null
|
? null
|
||||||
: getDefaultMajorFactorById(row.id),
|
: getDefaultMajorFactorById(row.id),
|
||||||
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
|
||||||
|
budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
|
||||||
|
budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
|
||||||
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
remark: typeof fromDb.remark === 'string' ? fromDb.remark : ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -257,7 +293,7 @@ const mergeWithDictRows = (
|
|||||||
|
|
||||||
const formatEditableNumber = (params: any) => {
|
const formatEditableNumber = (params: any) => {
|
||||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||||
return '点击输入'
|
return '请输入'
|
||||||
}
|
}
|
||||||
if (params.value == null) return ''
|
if (params.value == null) return ''
|
||||||
return Number(params.value).toFixed(2)
|
return Number(params.value).toFixed(2)
|
||||||
@ -276,12 +312,28 @@ const formatReadonlyMoney = (params: any) => {
|
|||||||
return formatThousands(roundTo(params.value, 2))
|
return formatThousands(roundTo(params.value, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
const getBenchmarkBudgetSplitByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
||||||
getBenchmarkBudgetByScale(row?.landArea, 'area')
|
getBenchmarkBudgetSplitByScale(row?.landArea, 'area')
|
||||||
|
|
||||||
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
return getScaleBudgetFee({
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||||
benchmarkBudget: getBenchmarkBudgetByLandArea(row),
|
if (!benchmarkBudgetSplit) return null
|
||||||
|
|
||||||
|
const splitBudgetFee = getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
|
majorFactor: row?.majorFactor,
|
||||||
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
|
})
|
||||||
|
return splitBudgetFee ? splitBudgetFee.total : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBudgetFeeSplit = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||||
|
if (!benchmarkBudgetSplit) return null
|
||||||
|
return getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
majorFactor: row?.majorFactor,
|
majorFactor: row?.majorFactor,
|
||||||
consultCategoryFactor: row?.consultCategoryFactor
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
})
|
})
|
||||||
@ -295,7 +347,7 @@ const formatEditableFlexibleNumber = (params: any) => {
|
|||||||
return String(Number(params.value))
|
return String(Number(params.value))
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnDefs: ColDef<DetailRow>[] = [
|
const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
|
||||||
{
|
{
|
||||||
headerName: '用地面积(亩)',
|
headerName: '用地面积(亩)',
|
||||||
field: 'landArea',
|
field: 'landArea',
|
||||||
@ -312,18 +364,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableFlexibleNumber
|
valueFormatter: formatEditableFlexibleNumber
|
||||||
},
|
},
|
||||||
{
|
|
||||||
headerName: '基准预算(元)',
|
|
||||||
field: 'benchmarkBudget',
|
|
||||||
headerClass: 'ag-right-aligned-header',
|
|
||||||
minWidth: 170,
|
|
||||||
flex: 1,
|
|
||||||
cellClass: 'ag-right-aligned-cell',
|
|
||||||
aggFunc: decimalAggSum,
|
|
||||||
valueGetter: params => getBenchmarkBudgetByLandArea(params.data),
|
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
|
||||||
valueFormatter: formatReadonlyMoney
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
headerName: '咨询分类系数',
|
headerName: '咨询分类系数',
|
||||||
field: 'consultCategoryFactor',
|
field: 'consultCategoryFactor',
|
||||||
@ -354,17 +395,89 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatMajorFactor
|
valueFormatter: formatMajorFactor
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
headerName: '基准预算(元)',
|
||||||
|
marryChildren: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
headerName: '基本工作',
|
||||||
|
field: 'benchmarkBudgetBasic',
|
||||||
|
colId: 'benchmarkBudgetBasic',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 140,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.benchmarkBudgetBasic ?? null
|
||||||
|
: getBenchmarkBudgetSplitByLandArea(params.data)?.basic ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '可选工作',
|
||||||
|
field: 'benchmarkBudgetOptional',
|
||||||
|
colId: 'benchmarkBudgetOptional',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 140,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.benchmarkBudgetOptional ?? null
|
||||||
|
: getBenchmarkBudgetSplitByLandArea(params.data)?.optional ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
headerName: '预算费用',
|
headerName: '预算费用',
|
||||||
field: 'budgetFee',
|
marryChildren: true,
|
||||||
headerClass: 'ag-right-aligned-header',
|
children: [
|
||||||
minWidth: 150,
|
{
|
||||||
flex: 1,
|
headerName: '基本工作',
|
||||||
cellClass: 'ag-right-aligned-cell',
|
field: 'budgetFeeBasic',
|
||||||
aggFunc: decimalAggSum,
|
colId: 'budgetFeeBasic',
|
||||||
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
headerClass: 'ag-right-aligned-header',
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
minWidth: 130,
|
||||||
valueFormatter: formatReadonlyMoney
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.budgetFeeBasic ?? null
|
||||||
|
: getBudgetFeeSplit(params.data)?.basic ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '可选工作',
|
||||||
|
field: 'budgetFeeOptional',
|
||||||
|
colId: 'budgetFeeOptional',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 130,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params =>
|
||||||
|
params.node?.rowPinned
|
||||||
|
? params.data?.budgetFeeOptional ?? null
|
||||||
|
: getBudgetFeeSplit(params.data)?.optional ?? null,
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headerName: '合计',
|
||||||
|
field: 'budgetFee',
|
||||||
|
colId: 'budgetFeeTotal',
|
||||||
|
headerClass: 'ag-right-aligned-header',
|
||||||
|
minWidth: 140,
|
||||||
|
flex: 1,
|
||||||
|
cellClass: 'ag-right-aligned-cell',
|
||||||
|
aggFunc: decimalAggSum,
|
||||||
|
valueGetter: params => (params.node?.rowPinned ? params.data?.budgetFee ?? null : getBudgetFee(params.data)),
|
||||||
|
valueFormatter: formatReadonlyMoney
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headerName: '说明',
|
headerName: '说明',
|
||||||
@ -418,8 +531,12 @@ const totalAmount = computed(() => sumByNumber(detailRows.value, row => row.amou
|
|||||||
|
|
||||||
const totalLandArea = computed(() => sumByNumber(detailRows.value, row => row.landArea))
|
const totalLandArea = computed(() => sumByNumber(detailRows.value, row => row.landArea))
|
||||||
|
|
||||||
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetByLandArea(row)))
|
const totalBenchmarkBudgetBasic = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.basic))
|
||||||
|
const totalBenchmarkBudgetOptional = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.optional))
|
||||||
|
const totalBenchmarkBudget = computed(() => sumByNumber(detailRows.value, row => getBenchmarkBudgetSplitByLandArea(row)?.total))
|
||||||
|
|
||||||
|
const totalBudgetFeeBasic = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.basic))
|
||||||
|
const totalBudgetFeeOptional = computed(() => sumByNumber(detailRows.value, row => getBudgetFeeSplit(row)?.optional))
|
||||||
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
const totalBudgetFee = computed(() => sumByNumber(detailRows.value, row => getBudgetFee(row)))
|
||||||
const pinnedTopRowData = computed(() => [
|
const pinnedTopRowData = computed(() => [
|
||||||
{
|
{
|
||||||
@ -431,21 +548,50 @@ const pinnedTopRowData = computed(() => [
|
|||||||
amount: totalAmount.value,
|
amount: totalAmount.value,
|
||||||
landArea: totalLandArea.value,
|
landArea: totalLandArea.value,
|
||||||
benchmarkBudget: totalBenchmarkBudget.value,
|
benchmarkBudget: totalBenchmarkBudget.value,
|
||||||
|
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value,
|
||||||
|
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value,
|
||||||
|
basicFormula: '',
|
||||||
|
optionalFormula: '',
|
||||||
consultCategoryFactor: null,
|
consultCategoryFactor: null,
|
||||||
majorFactor: null,
|
majorFactor: null,
|
||||||
budgetFee: totalBudgetFee.value,
|
budgetFee: totalBudgetFee.value,
|
||||||
|
budgetFeeBasic: totalBudgetFeeBasic.value,
|
||||||
|
budgetFeeOptional: totalBudgetFeeOptional.value,
|
||||||
remark: '',
|
remark: '',
|
||||||
path: ['TOTAL']
|
path: ['TOTAL']
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
|
const buildPersistDetailRows = () =>
|
||||||
|
detailRows.value.map(row => {
|
||||||
|
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByLandArea(row)
|
||||||
|
const budgetFeeSplit = benchmarkBudgetSplit
|
||||||
|
? getScaleBudgetFeeSplit({
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
|
||||||
|
majorFactor: row.majorFactor,
|
||||||
|
consultCategoryFactor: row.consultCategoryFactor
|
||||||
|
})
|
||||||
|
: null
|
||||||
|
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
benchmarkBudget: benchmarkBudgetSplit?.total ?? null,
|
||||||
|
benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null,
|
||||||
|
benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null,
|
||||||
|
basicFormula: benchmarkBudgetSplit?.basicFormula ?? '',
|
||||||
|
optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '',
|
||||||
|
budgetFee: budgetFeeSplit?.total ?? null,
|
||||||
|
budgetFeeBasic: budgetFeeSplit?.basic ?? null,
|
||||||
|
budgetFeeOptional: budgetFeeSplit?.optional ?? null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
if (shouldSkipPersist()) return
|
if (shouldSkipPersist()) return
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||||
}
|
}
|
||||||
console.log('Saving to IndexedDB:', payload)
|
console.log('Saving to IndexedDB:', payload)
|
||||||
await localforage.setItem(DB_KEY.value, payload)
|
await localforage.setItem(DB_KEY.value, payload)
|
||||||
|
|||||||
@ -21,6 +21,7 @@ interface DetailRow {
|
|||||||
unit: string
|
unit: string
|
||||||
conversion: number | null
|
conversion: number | null
|
||||||
workload: number | null
|
workload: number | null
|
||||||
|
basicFee: number | null
|
||||||
budgetBase: string
|
budgetBase: string
|
||||||
budgetReferenceUnitPrice: string
|
budgetReferenceUnitPrice: string
|
||||||
budgetAdoptedUnitPrice: number | null
|
budgetAdoptedUnitPrice: number | null
|
||||||
@ -142,6 +143,7 @@ const buildDefaultRows = (): DetailRow[] => {
|
|||||||
unit: task.unit || '',
|
unit: task.unit || '',
|
||||||
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
|
conversion: typeof task.conversion === 'number' && Number.isFinite(task.conversion) ? task.conversion : null,
|
||||||
workload: null,
|
workload: null,
|
||||||
|
basicFee: null,
|
||||||
budgetBase: task.basicParam || '',
|
budgetBase: task.basicParam || '',
|
||||||
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
|
budgetReferenceUnitPrice: formatTaskReferenceUnitPrice(task),
|
||||||
budgetAdoptedUnitPrice:
|
budgetAdoptedUnitPrice:
|
||||||
@ -171,6 +173,7 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
|||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
workload: typeof fromDb.workload === 'number' ? fromDb.workload : null,
|
||||||
|
basicFee: typeof fromDb.basicFee === 'number' ? fromDb.basicFee : null,
|
||||||
budgetAdoptedUnitPrice:
|
budgetAdoptedUnitPrice:
|
||||||
typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
|
typeof fromDb.budgetAdoptedUnitPrice === 'number' ? fromDb.budgetAdoptedUnitPrice : null,
|
||||||
consultCategoryFactor:
|
consultCategoryFactor:
|
||||||
@ -184,25 +187,36 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
|||||||
const parseSanitizedNumberOrNull = (value: unknown) =>
|
const parseSanitizedNumberOrNull = (value: unknown) =>
|
||||||
parseNumberOrNull(value, { sanitize: true })
|
parseNumberOrNull(value, { sanitize: true })
|
||||||
|
|
||||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
const calcBasicFee = (row: DetailRow | undefined) => {
|
||||||
if (!row || isNoTaskRow(row)) return null
|
if (!row || isNoTaskRow(row)) return null
|
||||||
const price = row.budgetAdoptedUnitPrice
|
const price = row.budgetAdoptedUnitPrice
|
||||||
const conversion = row.conversion
|
const conversion = row.conversion
|
||||||
const workload = row.workload
|
const workload = row.workload
|
||||||
const factor = row.consultCategoryFactor
|
|
||||||
if (
|
if (
|
||||||
typeof price !== 'number' ||
|
typeof price !== 'number' ||
|
||||||
!Number.isFinite(price) ||
|
!Number.isFinite(price) ||
|
||||||
typeof conversion !== 'number' ||
|
typeof conversion !== 'number' ||
|
||||||
!Number.isFinite(conversion) ||
|
!Number.isFinite(conversion) ||
|
||||||
typeof workload !== 'number' ||
|
typeof workload !== 'number' ||
|
||||||
!Number.isFinite(workload) ||
|
!Number.isFinite(workload)
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return roundTo(toDecimal(price).mul(conversion).mul(workload), 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||||
|
if (!row || isNoTaskRow(row)) return null
|
||||||
|
const factor = row.consultCategoryFactor
|
||||||
|
const basicFee = calcBasicFee(row)
|
||||||
|
if (
|
||||||
|
basicFee == null ||
|
||||||
typeof factor !== 'number' ||
|
typeof factor !== 'number' ||
|
||||||
!Number.isFinite(factor)
|
!Number.isFinite(factor)
|
||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return roundTo(toDecimal(price).mul(conversion).mul(workload).mul(factor), 2)
|
return roundTo(toDecimal(basicFee).mul(factor), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatEditableNumber = (params: any) => {
|
const formatEditableNumber = (params: any) => {
|
||||||
@ -368,6 +382,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
|
const totalWorkload = computed(() => sumByNumber(detailRows.value, row => row.workload))
|
||||||
|
const totalBasicFee = computed(() => sumByNumber(detailRows.value, row => calcBasicFee(row)))
|
||||||
|
|
||||||
const totalServiceFee = computed(() => sumByNumber(detailRows.value, row => calcServiceFee(row)))
|
const totalServiceFee = computed(() => sumByNumber(detailRows.value, row => calcServiceFee(row)))
|
||||||
const pinnedTopRowData = computed(() => [
|
const pinnedTopRowData = computed(() => [
|
||||||
@ -378,6 +393,7 @@ const pinnedTopRowData = computed(() => [
|
|||||||
unit: '',
|
unit: '',
|
||||||
conversion: null,
|
conversion: null,
|
||||||
workload: totalWorkload.value,
|
workload: totalWorkload.value,
|
||||||
|
basicFee: totalBasicFee.value,
|
||||||
budgetBase: '',
|
budgetBase: '',
|
||||||
budgetReferenceUnitPrice: '',
|
budgetReferenceUnitPrice: '',
|
||||||
budgetAdoptedUnitPrice: null,
|
budgetAdoptedUnitPrice: null,
|
||||||
@ -390,12 +406,18 @@ const pinnedTopRowData = computed(() => [
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const buildPersistDetailRows = () =>
|
||||||
|
detailRows.value.map(row => ({
|
||||||
|
...row,
|
||||||
|
basicFee: calcBasicFee(row),
|
||||||
|
serviceFee: calcServiceFee(row)
|
||||||
|
}))
|
||||||
const saveToIndexedDB = async () => {
|
const saveToIndexedDB = async () => {
|
||||||
if (!isWorkloadMethodApplicable.value) return
|
if (!isWorkloadMethodApplicable.value) return
|
||||||
if (shouldSkipPersist()) return
|
if (shouldSkipPersist()) return
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
detailRows: JSON.parse(JSON.stringify(detailRows.value))
|
detailRows: JSON.parse(JSON.stringify(buildPersistDetailRows()))
|
||||||
}
|
}
|
||||||
console.log('Saving to IndexedDB:', payload)
|
console.log('Saving to IndexedDB:', payload)
|
||||||
await localforage.setItem(DB_KEY.value, payload)
|
await localforage.setItem(DB_KEY.value, payload)
|
||||||
@ -537,3 +559,4 @@ const mydiyTheme = myTheme.withParams({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
AlertDialogTrigger,
|
AlertDialogTrigger,
|
||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
|
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
|
||||||
|
import { exportFile, serviceList } from '@/sql'
|
||||||
|
|
||||||
interface DataEntry {
|
interface DataEntry {
|
||||||
key: string
|
key: string
|
||||||
@ -43,6 +44,176 @@ type XmInfoLike = {
|
|||||||
projectName?: unknown
|
projectName?: unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ScaleRowLike {
|
||||||
|
id: string
|
||||||
|
amount?: unknown
|
||||||
|
landArea?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface XmInfoStorageLike extends XmInfoLike {
|
||||||
|
detailRows?: ScaleRowLike[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ContractCardItem {
|
||||||
|
id: string
|
||||||
|
name?: string
|
||||||
|
order?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ZxFwRowLike {
|
||||||
|
id: string
|
||||||
|
subtotal?: unknown
|
||||||
|
investScale?: unknown
|
||||||
|
landScale?: unknown
|
||||||
|
workload?: unknown
|
||||||
|
hourly?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ZxFwStorageLike {
|
||||||
|
detailRows?: ZxFwRowLike[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ScaleMethodRowLike extends ScaleRowLike {
|
||||||
|
basicFormula?: unknown
|
||||||
|
optionalFormula?: unknown
|
||||||
|
budgetFee?: unknown
|
||||||
|
budgetFeeBasic?: unknown
|
||||||
|
budgetFeeOptional?: unknown
|
||||||
|
consultCategoryFactor?: unknown
|
||||||
|
majorFactor?: unknown
|
||||||
|
remark?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WorkloadMethodRowLike {
|
||||||
|
id: string
|
||||||
|
budgetAdoptedUnitPrice?: unknown
|
||||||
|
workload?: unknown
|
||||||
|
basicFee?: unknown
|
||||||
|
consultCategoryFactor?: unknown
|
||||||
|
serviceFee?: unknown
|
||||||
|
remark?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HourlyMethodRowLike {
|
||||||
|
id: string
|
||||||
|
adoptedBudgetUnitPrice?: unknown
|
||||||
|
personnelCount?: unknown
|
||||||
|
workdayCount?: unknown
|
||||||
|
serviceBudget?: unknown
|
||||||
|
remark?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DetailRowsStorageLike<T> {
|
||||||
|
detailRows?: T[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportScaleRow {
|
||||||
|
major: number
|
||||||
|
cost: number
|
||||||
|
area: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod1Detail {
|
||||||
|
major: number
|
||||||
|
cost: number
|
||||||
|
basicFee: number
|
||||||
|
basicFormula: string
|
||||||
|
basicFee_basic: number
|
||||||
|
optionalFormula: string
|
||||||
|
basicFee_optional: number
|
||||||
|
serviceCoe: number
|
||||||
|
majorCoe: number
|
||||||
|
fee: number
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod1 {
|
||||||
|
cost: number
|
||||||
|
basicFee: number
|
||||||
|
basicFee_basic: number
|
||||||
|
basicFee_optional: number
|
||||||
|
fee: number
|
||||||
|
det: ExportMethod1Detail[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod2Detail {
|
||||||
|
major: number
|
||||||
|
area: number
|
||||||
|
basicFee: number
|
||||||
|
basicFormula: string
|
||||||
|
basicFee_basic: number
|
||||||
|
optionalFormula: string
|
||||||
|
basicFee_optional: number
|
||||||
|
serviceCoe: number
|
||||||
|
majorCoe: number
|
||||||
|
fee: number
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod2 {
|
||||||
|
area: number
|
||||||
|
basicFee: number
|
||||||
|
basicFee_basic: number
|
||||||
|
basicFee_optional: number
|
||||||
|
fee: number
|
||||||
|
det: ExportMethod2Detail[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod3Detail {
|
||||||
|
task: number
|
||||||
|
price: number
|
||||||
|
amount: number
|
||||||
|
basicFee: number
|
||||||
|
serviceCoe: number
|
||||||
|
fee: number
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod3 {
|
||||||
|
basicFee: number
|
||||||
|
fee: number
|
||||||
|
det: ExportMethod3Detail[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod4Detail {
|
||||||
|
expert: number
|
||||||
|
price: number
|
||||||
|
person_num: number
|
||||||
|
work_day: number
|
||||||
|
fee: number
|
||||||
|
desc: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportMethod4 {
|
||||||
|
person_num: number
|
||||||
|
work_day: number
|
||||||
|
fee: number
|
||||||
|
det: ExportMethod4Detail[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportService {
|
||||||
|
id: number
|
||||||
|
fee: number
|
||||||
|
method1?: ExportMethod1
|
||||||
|
method2?: ExportMethod2
|
||||||
|
method3?: ExportMethod3
|
||||||
|
method4?: ExportMethod4
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportContract {
|
||||||
|
name: string
|
||||||
|
fee: number
|
||||||
|
scale: ExportScaleRow[]
|
||||||
|
services: ExportService[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExportReportPayload {
|
||||||
|
name: string
|
||||||
|
fee: number
|
||||||
|
scale: ExportScaleRow[]
|
||||||
|
contracts: ExportContract[]
|
||||||
|
}
|
||||||
|
|
||||||
const USER_GUIDE_COMPLETED_KEY = 'jgjs-user-guide-completed-v1'
|
const USER_GUIDE_COMPLETED_KEY = 'jgjs-user-guide-completed-v1'
|
||||||
const userGuideSteps: UserGuideStep[] = [
|
const userGuideSteps: UserGuideStep[] = [
|
||||||
{
|
{
|
||||||
@ -453,6 +624,321 @@ const getExportProjectName = (entries: DataEntry[]): string => {
|
|||||||
return typeof data.projectName === 'string' ? sanitizeFileNamePart(data.projectName) : '造价项目'
|
return typeof data.projectName === 'string' ? sanitizeFileNamePart(data.projectName) : '造价项目'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toFiniteNumber = (value: unknown): number | null => {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isFinite(num) ? num : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const toFiniteNumberOrZero = (value: unknown): number => toFiniteNumber(value) ?? 0
|
||||||
|
|
||||||
|
const toSafeInteger = (value: unknown): number | null => {
|
||||||
|
const num = Number(value)
|
||||||
|
if (!Number.isInteger(num)) return null
|
||||||
|
if (!Number.isSafeInteger(num)) return null
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
const sumNumbers = (values: Array<number | null | undefined>): number =>
|
||||||
|
values.reduce<number>(
|
||||||
|
(sum, value) => sum + (typeof value === 'number' && Number.isFinite(value) ? value : 0),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
const isNonEmptyString = (value: unknown): value is string =>
|
||||||
|
typeof value === 'string' && value.trim().length > 0
|
||||||
|
|
||||||
|
const getTaskIdFromRowId = (value: string): number | null => {
|
||||||
|
const match = /^task-(\d+)-\d+$/.exec(value)
|
||||||
|
return match ? toSafeInteger(match[1]) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const getExpertIdFromRowId = (value: string): number | null => {
|
||||||
|
const match = /^expert-(\d+)$/.exec(value)
|
||||||
|
return match ? toSafeInteger(match[1]) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasServiceId = (serviceId: string) =>
|
||||||
|
Object.prototype.hasOwnProperty.call(serviceList as Record<string, unknown>, serviceId)
|
||||||
|
|
||||||
|
const buildScaleRows = (rows: ScaleRowLike[] | undefined): ExportScaleRow[] => {
|
||||||
|
if (!Array.isArray(rows)) return []
|
||||||
|
return rows
|
||||||
|
.map(row => {
|
||||||
|
const major = toSafeInteger(row.id)
|
||||||
|
const cost = toFiniteNumber(row.amount)
|
||||||
|
const area = toFiniteNumber(row.landArea)
|
||||||
|
if (major == null || (cost == null && area == null)) return null
|
||||||
|
return {
|
||||||
|
major,
|
||||||
|
cost: cost ?? 0,
|
||||||
|
area: area ?? 0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is ExportScaleRow => Boolean(item))
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | null => {
|
||||||
|
if (!Array.isArray(rows)) return null
|
||||||
|
const det = rows
|
||||||
|
.map(row => {
|
||||||
|
const major = toSafeInteger(row.id)
|
||||||
|
if (major == null) return null
|
||||||
|
const cost = toFiniteNumber(row.amount)
|
||||||
|
const basicFee = toFiniteNumber(row.budgetFee)
|
||||||
|
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
||||||
|
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
||||||
|
const desc = typeof row.remark === 'string' ? row.remark : ''
|
||||||
|
const hasValue =
|
||||||
|
cost != null ||
|
||||||
|
basicFee != null ||
|
||||||
|
basicFeeBasic != null ||
|
||||||
|
basicFeeOptional != null ||
|
||||||
|
isNonEmptyString(desc)
|
||||||
|
if (!hasValue) return null
|
||||||
|
return {
|
||||||
|
major,
|
||||||
|
cost: cost ?? 0,
|
||||||
|
basicFee: basicFee ?? 0,
|
||||||
|
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
||||||
|
basicFee_basic: basicFeeBasic ?? 0,
|
||||||
|
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
||||||
|
basicFee_optional: basicFeeOptional ?? 0,
|
||||||
|
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||||
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
|
fee: basicFee ?? 0,
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is ExportMethod1Detail => Boolean(item))
|
||||||
|
|
||||||
|
if (det.length === 0) return null
|
||||||
|
return {
|
||||||
|
cost: sumNumbers(det.map(item => item.cost)),
|
||||||
|
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
||||||
|
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
||||||
|
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
||||||
|
fee: sumNumbers(det.map(item => item.fee)),
|
||||||
|
det
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMethod2 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod2 | null => {
|
||||||
|
if (!Array.isArray(rows)) return null
|
||||||
|
const det = rows
|
||||||
|
.map(row => {
|
||||||
|
const major = toSafeInteger(row.id)
|
||||||
|
if (major == null) return null
|
||||||
|
const area = toFiniteNumber(row.landArea)
|
||||||
|
const basicFee = toFiniteNumber(row.budgetFee)
|
||||||
|
const basicFeeBasic = toFiniteNumber(row.budgetFeeBasic)
|
||||||
|
const basicFeeOptional = toFiniteNumber(row.budgetFeeOptional)
|
||||||
|
const desc = typeof row.remark === 'string' ? row.remark : ''
|
||||||
|
const hasValue =
|
||||||
|
area != null ||
|
||||||
|
basicFee != null ||
|
||||||
|
basicFeeBasic != null ||
|
||||||
|
basicFeeOptional != null ||
|
||||||
|
isNonEmptyString(desc)
|
||||||
|
if (!hasValue) return null
|
||||||
|
return {
|
||||||
|
major,
|
||||||
|
area: area ?? 0,
|
||||||
|
basicFee: basicFee ?? 0,
|
||||||
|
basicFormula: typeof row.basicFormula === 'string' ? row.basicFormula : '',
|
||||||
|
basicFee_basic: basicFeeBasic ?? 0,
|
||||||
|
optionalFormula: typeof row.optionalFormula === 'string' ? row.optionalFormula : '',
|
||||||
|
basicFee_optional: basicFeeOptional ?? 0,
|
||||||
|
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||||
|
majorCoe: toFiniteNumberOrZero(row.majorFactor),
|
||||||
|
fee: basicFee ?? 0,
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is ExportMethod2Detail => Boolean(item))
|
||||||
|
|
||||||
|
if (det.length === 0) return null
|
||||||
|
return {
|
||||||
|
area: sumNumbers(det.map(item => item.area)),
|
||||||
|
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
||||||
|
basicFee_basic: sumNumbers(det.map(item => item.basicFee_basic)),
|
||||||
|
basicFee_optional: sumNumbers(det.map(item => item.basicFee_optional)),
|
||||||
|
fee: sumNumbers(det.map(item => item.fee)),
|
||||||
|
det
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMethod3 = (rows: WorkloadMethodRowLike[] | undefined): ExportMethod3 | null => {
|
||||||
|
if (!Array.isArray(rows)) return null
|
||||||
|
const det = rows
|
||||||
|
.map(row => {
|
||||||
|
const task = getTaskIdFromRowId(row.id)
|
||||||
|
if (task == null) return null
|
||||||
|
const amount = toFiniteNumber(row.workload)
|
||||||
|
const basicFee = toFiniteNumber(row.basicFee)
|
||||||
|
const fee = toFiniteNumber(row.serviceFee)
|
||||||
|
const desc = typeof row.remark === 'string' ? row.remark : ''
|
||||||
|
const hasValue = amount != null || basicFee != null || fee != null || isNonEmptyString(desc)
|
||||||
|
if (!hasValue) return null
|
||||||
|
return {
|
||||||
|
task,
|
||||||
|
price: toFiniteNumberOrZero(row.budgetAdoptedUnitPrice),
|
||||||
|
amount: amount ?? 0,
|
||||||
|
basicFee: basicFee ?? 0,
|
||||||
|
serviceCoe: toFiniteNumberOrZero(row.consultCategoryFactor),
|
||||||
|
fee: fee ?? 0,
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is ExportMethod3Detail => Boolean(item))
|
||||||
|
|
||||||
|
if (det.length === 0) return null
|
||||||
|
return {
|
||||||
|
basicFee: sumNumbers(det.map(item => item.basicFee)),
|
||||||
|
fee: sumNumbers(det.map(item => item.fee)),
|
||||||
|
det
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildMethod4 = (rows: HourlyMethodRowLike[] | undefined): ExportMethod4 | null => {
|
||||||
|
if (!Array.isArray(rows)) return null
|
||||||
|
const det = rows
|
||||||
|
.map(row => {
|
||||||
|
const expert = getExpertIdFromRowId(row.id)
|
||||||
|
if (expert == null) return null
|
||||||
|
const personNum = toFiniteNumber(row.personnelCount)
|
||||||
|
const workDay = toFiniteNumber(row.workdayCount)
|
||||||
|
const fee = toFiniteNumber(row.serviceBudget)
|
||||||
|
const desc = typeof row.remark === 'string' ? row.remark : ''
|
||||||
|
const hasValue = personNum != null || workDay != null || fee != null || isNonEmptyString(desc)
|
||||||
|
if (!hasValue) return null
|
||||||
|
return {
|
||||||
|
expert,
|
||||||
|
price: toFiniteNumberOrZero(row.adoptedBudgetUnitPrice),
|
||||||
|
person_num: personNum ?? 0,
|
||||||
|
work_day: workDay ?? 0,
|
||||||
|
fee: fee ?? 0,
|
||||||
|
desc
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter((item): item is ExportMethod4Detail => Boolean(item))
|
||||||
|
|
||||||
|
if (det.length === 0) return null
|
||||||
|
return {
|
||||||
|
person_num: sumNumbers(det.map(item => item.person_num)),
|
||||||
|
work_day: sumNumbers(det.map(item => item.work_day)),
|
||||||
|
fee: sumNumbers(det.map(item => item.fee)),
|
||||||
|
det
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildServiceFee = (
|
||||||
|
row: ZxFwRowLike,
|
||||||
|
method1: ExportMethod1 | null,
|
||||||
|
method2: ExportMethod2 | null,
|
||||||
|
method3: ExportMethod3 | null,
|
||||||
|
method4: ExportMethod4 | null
|
||||||
|
) => {
|
||||||
|
const subtotal = toFiniteNumber(row.subtotal)
|
||||||
|
if (subtotal != null) return subtotal
|
||||||
|
|
||||||
|
const methodSum = sumNumbers([method1?.fee, method2?.fee, method3?.fee, method4?.fee])
|
||||||
|
if (methodSum !== 0) return methodSum
|
||||||
|
|
||||||
|
return sumNumbers([
|
||||||
|
toFiniteNumber(row.investScale),
|
||||||
|
toFiniteNumber(row.landScale),
|
||||||
|
toFiniteNumber(row.workload),
|
||||||
|
toFiniteNumber(row.hourly)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
|
||||||
|
const [xmInfoRaw, contractCardsRaw] = await Promise.all([
|
||||||
|
localforage.getItem<XmInfoStorageLike>('xm-info-v3'),
|
||||||
|
localforage.getItem<ContractCardItem[]>('ht-card-v1')
|
||||||
|
])
|
||||||
|
|
||||||
|
const xmInfo = xmInfoRaw || {}
|
||||||
|
const projectScale = buildScaleRows(xmInfo.detailRows)
|
||||||
|
const projectName = isNonEmptyString(xmInfo.projectName) ? xmInfo.projectName.trim() : '造价项目'
|
||||||
|
|
||||||
|
const contractCards = (Array.isArray(contractCardsRaw) ? contractCardsRaw : [])
|
||||||
|
.filter(item => item && typeof item.id === 'string')
|
||||||
|
.sort((a, b) => (typeof a.order === 'number' ? a.order : Number.MAX_SAFE_INTEGER) - (typeof b.order === 'number' ? b.order : Number.MAX_SAFE_INTEGER))
|
||||||
|
|
||||||
|
const contracts: ExportContract[] = []
|
||||||
|
|
||||||
|
for (let index = 0; index < contractCards.length; index++) {
|
||||||
|
const contract = contractCards[index]
|
||||||
|
const contractId = contract.id
|
||||||
|
const [htInfoRaw, zxFwRaw] = await Promise.all([
|
||||||
|
localforage.getItem<DetailRowsStorageLike<ScaleRowLike>>(`ht-info-v3-${contractId}`),
|
||||||
|
localforage.getItem<ZxFwStorageLike>(`zxFW-${contractId}`)
|
||||||
|
])
|
||||||
|
|
||||||
|
const zxRows = Array.isArray(zxFwRaw?.detailRows) ? zxFwRaw.detailRows : []
|
||||||
|
const fixedRow = zxRows.find(row => row.id === 'fixed-budget-c')
|
||||||
|
const serviceRows = zxRows.filter(row => row.id !== 'fixed-budget-c' && hasServiceId(String(row.id)))
|
||||||
|
|
||||||
|
const services = (
|
||||||
|
await Promise.all(
|
||||||
|
serviceRows.map(async row => {
|
||||||
|
const serviceIdText = String(row.id)
|
||||||
|
const serviceId = toSafeInteger(serviceIdText)
|
||||||
|
if (serviceId == null) return null
|
||||||
|
|
||||||
|
const [method1Raw, method2Raw, method3Raw, method4Raw] = await Promise.all([
|
||||||
|
localforage.getItem<DetailRowsStorageLike<ScaleMethodRowLike>>(`tzGMF-${contractId}-${serviceIdText}`),
|
||||||
|
localforage.getItem<DetailRowsStorageLike<ScaleMethodRowLike>>(`ydGMF-${contractId}-${serviceIdText}`),
|
||||||
|
localforage.getItem<DetailRowsStorageLike<WorkloadMethodRowLike>>(`gzlF-${contractId}-${serviceIdText}`),
|
||||||
|
localforage.getItem<DetailRowsStorageLike<HourlyMethodRowLike>>(`hourlyPricing-${contractId}-${serviceIdText}`)
|
||||||
|
])
|
||||||
|
|
||||||
|
const method1 = buildMethod1(method1Raw?.detailRows)
|
||||||
|
const method2 = buildMethod2(method2Raw?.detailRows)
|
||||||
|
const method3 = buildMethod3(method3Raw?.detailRows)
|
||||||
|
const method4 = buildMethod4(method4Raw?.detailRows)
|
||||||
|
const fee = buildServiceFee(row, method1, method2, method3, method4)
|
||||||
|
|
||||||
|
const service: ExportService = {
|
||||||
|
id: serviceId,
|
||||||
|
fee
|
||||||
|
}
|
||||||
|
if (method1) service.method1 = method1
|
||||||
|
if (method2) service.method2 = method2
|
||||||
|
if (method3) service.method3 = method3
|
||||||
|
if (method4) service.method4 = method4
|
||||||
|
return service
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).filter((item): item is ExportService => Boolean(item))
|
||||||
|
|
||||||
|
const fixedSubtotal = toFiniteNumber(fixedRow?.subtotal)
|
||||||
|
const serviceFeeSum = sumNumbers(services.map(item => item.fee))
|
||||||
|
const fixedMethodSum = sumNumbers([
|
||||||
|
toFiniteNumber(fixedRow?.investScale),
|
||||||
|
toFiniteNumber(fixedRow?.landScale),
|
||||||
|
toFiniteNumber(fixedRow?.workload),
|
||||||
|
toFiniteNumber(fixedRow?.hourly)
|
||||||
|
])
|
||||||
|
const contractFee = fixedSubtotal ?? (serviceFeeSum !== 0 ? serviceFeeSum : fixedMethodSum)
|
||||||
|
|
||||||
|
contracts.push({
|
||||||
|
name: isNonEmptyString(contract.name) ? contract.name : `合同段-${index + 1}`,
|
||||||
|
fee: contractFee,
|
||||||
|
scale: buildScaleRows(htInfoRaw?.detailRows),
|
||||||
|
services
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: projectName,
|
||||||
|
fee: sumNumbers(contracts.map(item => item.fee)),
|
||||||
|
scale: projectScale,
|
||||||
|
contracts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@ -485,8 +971,19 @@ const exportData = async () => {
|
|||||||
dataMenuOpen.value = false
|
dataMenuOpen.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const exportReport = async ()=>{
|
|
||||||
|
const exportReport = async () => {
|
||||||
|
try {
|
||||||
|
const now = new Date()
|
||||||
|
const payload = await buildExportReportPayload()
|
||||||
|
const fileName = `${sanitizeFileNamePart(payload.name)}-报表-${formatExportTimestamp(now)}`
|
||||||
|
await exportFile(fileName, payload)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('export report failed:', error)
|
||||||
|
window.alert('导出报表失败,请重试。')
|
||||||
|
} finally {
|
||||||
|
dataMenuOpen.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const triggerImport = () => {
|
const triggerImport = () => {
|
||||||
importFileRef.value?.click()
|
importFileRef.value?.click()
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,DialogDescription
|
DialogTrigger,DialogDescription
|
||||||
} from 'reka-ui'
|
} from 'reka-ui'
|
||||||
import { Icon } from '@iconify/vue'
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { animate, AnimatePresence, Motion, useMotionValue, useMotionValueEvent, useTransform } from 'motion-v'
|
import { animate, AnimatePresence, Motion, useMotionValue, useMotionValueEvent, useTransform } from 'motion-v'
|
||||||
interface TypeLineCategory {
|
interface TypeLineCategory {
|
||||||
@ -285,7 +284,16 @@ useMotionValueEvent(
|
|||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<DialogClose class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md border border-muted-foreground/30 text-muted-foreground transition-colors hover:border-foreground/40 hover:text-foreground">
|
<DialogClose class="inline-flex h-8 w-8 cursor-pointer items-center justify-center rounded-md border border-muted-foreground/30 text-muted-foreground transition-colors hover:border-foreground/40 hover:text-foreground">
|
||||||
<Icon icon="lucide:x" class="h-4 w-4" />
|
<svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M18 6L6 18M6 6l12 12"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
</div>
|
</div>
|
||||||
<DialogTitle class="mt-2">
|
<DialogTitle class="mt-2">
|
||||||
@ -314,7 +322,16 @@ useMotionValueEvent(
|
|||||||
aria-label="跳转到官网首页"
|
aria-label="跳转到官网首页"
|
||||||
title="官网首页"
|
title="官网首页"
|
||||||
>
|
>
|
||||||
<Icon icon="lucide:arrow-up-right" class="h-4 w-4" />
|
<svg viewBox="0 0 24 24" class="h-4 w-4" aria-hidden="true">
|
||||||
|
<path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M7 7h10v10M7 17L17 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
|
|||||||
@ -22,6 +22,7 @@ interface WorkloadRow {
|
|||||||
id: string
|
id: string
|
||||||
conversion: number | null
|
conversion: number | null
|
||||||
workload: number | null
|
workload: number | null
|
||||||
|
basicFee: number | null
|
||||||
budgetAdoptedUnitPrice: number | null
|
budgetAdoptedUnitPrice: number | null
|
||||||
consultCategoryFactor: number | null
|
consultCategoryFactor: number | null
|
||||||
}
|
}
|
||||||
@ -159,6 +160,7 @@ const buildDefaultWorkloadRows = (serviceId: string | number): WorkloadRow[] =>
|
|||||||
id: `task-${taskId}-${order}`,
|
id: `task-${taskId}-${order}`,
|
||||||
conversion: toFiniteNumberOrNull(task.conversion),
|
conversion: toFiniteNumberOrNull(task.conversion),
|
||||||
workload: null,
|
workload: null,
|
||||||
|
basicFee: null,
|
||||||
budgetAdoptedUnitPrice: toFiniteNumberOrNull(task.defPrice),
|
budgetAdoptedUnitPrice: toFiniteNumberOrNull(task.defPrice),
|
||||||
consultCategoryFactor: defaultConsultCategoryFactor
|
consultCategoryFactor: defaultConsultCategoryFactor
|
||||||
}))
|
}))
|
||||||
@ -177,23 +179,35 @@ const mergeWorkloadRows = (
|
|||||||
return {
|
return {
|
||||||
...row,
|
...row,
|
||||||
workload: toFiniteNumberOrNull(fromDb.workload),
|
workload: toFiniteNumberOrNull(fromDb.workload),
|
||||||
|
basicFee: toFiniteNumberOrNull(fromDb.basicFee),
|
||||||
budgetAdoptedUnitPrice: toFiniteNumberOrNull(fromDb.budgetAdoptedUnitPrice),
|
budgetAdoptedUnitPrice: toFiniteNumberOrNull(fromDb.budgetAdoptedUnitPrice),
|
||||||
consultCategoryFactor: toFiniteNumberOrNull(fromDb.consultCategoryFactor)
|
consultCategoryFactor: toFiniteNumberOrNull(fromDb.consultCategoryFactor)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcWorkloadServiceFee = (row: WorkloadRow) => {
|
const calcWorkloadBasicFee = (row: WorkloadRow) => {
|
||||||
if (
|
if (
|
||||||
row.budgetAdoptedUnitPrice == null ||
|
row.budgetAdoptedUnitPrice == null ||
|
||||||
row.conversion == null ||
|
row.conversion == null ||
|
||||||
row.workload == null ||
|
row.workload == null
|
||||||
row.consultCategoryFactor == null
|
|
||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return roundTo(
|
return roundTo(
|
||||||
toDecimal(row.budgetAdoptedUnitPrice).mul(row.conversion).mul(row.workload).mul(row.consultCategoryFactor),
|
toDecimal(row.budgetAdoptedUnitPrice).mul(row.conversion).mul(row.workload),
|
||||||
|
2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcWorkloadServiceFee = (row: WorkloadRow) => {
|
||||||
|
if (row.consultCategoryFactor == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const basicFee = row.basicFee ?? calcWorkloadBasicFee(row)
|
||||||
|
if (basicFee == null) return null
|
||||||
|
return roundTo(
|
||||||
|
toDecimal(basicFee).mul(row.consultCategoryFactor),
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,70 @@ import { toFiniteNumberOrNull } from '@/lib/number'
|
|||||||
|
|
||||||
type ScaleMode = 'cost' | 'area'
|
type ScaleMode = 'cost' | 'area'
|
||||||
|
|
||||||
export const getBenchmarkBudgetByScale = (value: unknown, mode: ScaleMode) => {
|
export interface ScaleFeeSplitResult {
|
||||||
|
basic: number
|
||||||
|
optional: number
|
||||||
|
total: number
|
||||||
|
basicFormula: string
|
||||||
|
optionalFormula: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBenchmarkBudgetSplitByScale = (
|
||||||
|
value: unknown,
|
||||||
|
mode: ScaleMode
|
||||||
|
): ScaleFeeSplitResult | null => {
|
||||||
const scaleValue = toFiniteNumberOrNull(value)
|
const scaleValue = toFiniteNumberOrNull(value)
|
||||||
const result = getBasicFeeFromScale(scaleValue, mode)
|
const result = getBasicFeeFromScale(scaleValue, mode)
|
||||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
if (!result) return null
|
||||||
|
|
||||||
|
const basic = roundTo(result.basic, 2)
|
||||||
|
const optional = roundTo(result.optional, 2)
|
||||||
|
const basicFormula = typeof result.basicFormula === 'string' ? result.basicFormula : ''
|
||||||
|
const optionalFormula = typeof result.optionalFormula === 'string' ? result.optionalFormula : ''
|
||||||
|
return {
|
||||||
|
basic,
|
||||||
|
optional,
|
||||||
|
total: roundTo(addNumbers(basic, optional), 2),
|
||||||
|
basicFormula,
|
||||||
|
optionalFormula
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBenchmarkBudgetByScale = (value: unknown, mode: ScaleMode) => {
|
||||||
|
const splitResult = getBenchmarkBudgetSplitByScale(value, mode)
|
||||||
|
return splitResult ? splitResult.total : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getScaleBudgetFeeSplit = (params: {
|
||||||
|
benchmarkBudgetBasic: unknown
|
||||||
|
benchmarkBudgetOptional: unknown
|
||||||
|
majorFactor: unknown
|
||||||
|
consultCategoryFactor: unknown
|
||||||
|
}): ScaleFeeSplitResult | null => {
|
||||||
|
const benchmarkBudgetBasic = toFiniteNumberOrNull(params.benchmarkBudgetBasic)
|
||||||
|
const benchmarkBudgetOptional = toFiniteNumberOrNull(params.benchmarkBudgetOptional)
|
||||||
|
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
||||||
|
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||||
|
|
||||||
|
if (
|
||||||
|
benchmarkBudgetBasic == null ||
|
||||||
|
benchmarkBudgetOptional == null ||
|
||||||
|
majorFactor == null ||
|
||||||
|
consultCategoryFactor == null
|
||||||
|
) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const basic = roundTo(toDecimal(benchmarkBudgetBasic).mul(majorFactor).mul(consultCategoryFactor), 2)
|
||||||
|
const optional = roundTo(toDecimal(benchmarkBudgetOptional).mul(majorFactor).mul(consultCategoryFactor), 2)
|
||||||
|
|
||||||
|
return {
|
||||||
|
basic,
|
||||||
|
optional,
|
||||||
|
total: roundTo(addNumbers(basic, optional), 2),
|
||||||
|
basicFormula: '',
|
||||||
|
optionalFormula: ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getScaleBudgetFee = (params: {
|
export const getScaleBudgetFee = (params: {
|
||||||
|
|||||||
310
src/sql.ts
310
src/sql.ts
@ -1,5 +1,14 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { roundTo, toDecimal } from '@/lib/decimal'
|
import { addNumbers, roundTo, toDecimal } from '@/lib/decimal'
|
||||||
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
|
|
||||||
|
const numberFormatter = (value: unknown, fractionDigits = 2) =>
|
||||||
|
formatThousands(value, fractionDigits)
|
||||||
|
|
||||||
|
const toFiniteNumber = (value: unknown) => {
|
||||||
|
const num = Number(value)
|
||||||
|
return Number.isFinite(num) ? num : 0
|
||||||
|
}
|
||||||
|
|
||||||
export const majorList = {
|
export const majorList = {
|
||||||
0: { code: 'E1', name: '交通运输工程通用专业', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
0: { code: 'E1', name: '交通运输工程通用专业', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
||||||
@ -176,7 +185,29 @@ const calcScaleFee = (params: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBasicFeeFromScale1(scaleValue: unknown, scaleType: 'cost' | 'area' | 'amount') {
|
const scaleRatePermillage = (rate: number) => roundTo(toDecimal(rate).mul(1000), 2)
|
||||||
|
|
||||||
|
const buildScaleFormula = (params: {
|
||||||
|
staPrice: number
|
||||||
|
sv: number
|
||||||
|
staLine: number
|
||||||
|
rate: number
|
||||||
|
multiplier?: number
|
||||||
|
showPermillage?: boolean
|
||||||
|
}) => {
|
||||||
|
const multiplier = params.multiplier ?? 1
|
||||||
|
const currentValue = toDecimal(params.sv).mul(multiplier).toNumber()
|
||||||
|
const staLineValue = toDecimal(params.staLine).mul(multiplier).toNumber()
|
||||||
|
const rateText = params.showPermillage ? `${scaleRatePermillage(params.rate)}‰` : params.rate
|
||||||
|
if (params.staPrice) {
|
||||||
|
return `${numberFormatter(params.staPrice, 0)}+(${numberFormatter(currentValue, 0)}-${numberFormatter(staLineValue, 0)})×${rateText}`
|
||||||
|
}
|
||||||
|
return `${numberFormatter(currentValue, 0)}×${rateText}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function getBasicFeeFromScale(scaleValue: unknown, scaleType: 'cost' | 'area' | 'amount') {
|
||||||
const sv = Number(scaleValue)
|
const sv = Number(scaleValue)
|
||||||
if (!Number.isFinite(sv) || sv <= 0) return null
|
if (!Number.isFinite(sv) || sv <= 0) return null
|
||||||
|
|
||||||
@ -197,6 +228,22 @@ export function getBasicFeeFromScale1(scaleValue: unknown, scaleType: 'cost' | '
|
|||||||
staLine: targetRange.staLine,
|
staLine: targetRange.staLine,
|
||||||
rate: targetRange.optional.rate,
|
rate: targetRange.optional.rate,
|
||||||
multiplier: 10000
|
multiplier: 10000
|
||||||
|
}),
|
||||||
|
basicFormula: buildScaleFormula({
|
||||||
|
staPrice: targetRange.basic.staPrice,
|
||||||
|
sv,
|
||||||
|
staLine: targetRange.staLine,
|
||||||
|
rate: targetRange.basic.rate,
|
||||||
|
multiplier: 10000,
|
||||||
|
showPermillage: true
|
||||||
|
}),
|
||||||
|
optionalFormula: buildScaleFormula({
|
||||||
|
staPrice: targetRange.optional.staPrice,
|
||||||
|
sv,
|
||||||
|
staLine: targetRange.staLine,
|
||||||
|
rate: targetRange.optional.rate,
|
||||||
|
multiplier: 10000,
|
||||||
|
showPermillage: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -216,6 +263,18 @@ export function getBasicFeeFromScale1(scaleValue: unknown, scaleType: 'cost' | '
|
|||||||
sv,
|
sv,
|
||||||
staLine: targetRange.staLine,
|
staLine: targetRange.staLine,
|
||||||
rate: targetRange.optional.rate
|
rate: targetRange.optional.rate
|
||||||
|
}),
|
||||||
|
basicFormula: buildScaleFormula({
|
||||||
|
staPrice: targetRange.basic.staPrice,
|
||||||
|
sv,
|
||||||
|
staLine: targetRange.staLine,
|
||||||
|
rate: targetRange.basic.rate
|
||||||
|
}),
|
||||||
|
optionalFormula: buildScaleFormula({
|
||||||
|
staPrice: targetRange.optional.staPrice,
|
||||||
|
sv,
|
||||||
|
staLine: targetRange.staLine,
|
||||||
|
rate: targetRange.optional.rate
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -229,13 +288,17 @@ export function getBasicFeeFromScale1(scaleValue: unknown, scaleType: 'cost' | '
|
|||||||
staLine: targetRange.staLine,
|
staLine: targetRange.staLine,
|
||||||
rate: targetRange.rate
|
rate: targetRange.rate
|
||||||
}),
|
}),
|
||||||
optional: 0
|
optional: 0,
|
||||||
|
basicFormula: buildScaleFormula({
|
||||||
|
staPrice: targetRange.staPrice,
|
||||||
|
sv,
|
||||||
|
staLine: targetRange.staLine,
|
||||||
|
rate: targetRange.rate
|
||||||
|
}),
|
||||||
|
optionalFormula: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getBasicFeeFromScale(scaleValue: unknown, scaleType: 'cost' | 'area' | 'amount') {
|
|
||||||
return getBasicFeeFromScale1(scaleValue, scaleType)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export async function exportFile(fileName, data) {
|
export async function exportFile(fileName, data) {
|
||||||
@ -281,8 +344,9 @@ export async function exportFile(fileName, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function generateTemplate(data) {
|
async function generateTemplate(data) {
|
||||||
|
console.log(data)
|
||||||
// 获取模板
|
// 获取模板
|
||||||
let templateExcel = 'template20260226001test009';
|
let templateExcel = 'template20260226001test010';
|
||||||
let templateUrl = `https://oa.zwgczx.com/myExcelTemplate/${templateExcel}.xlsx`;
|
let templateUrl = `https://oa.zwgczx.com/myExcelTemplate/${templateExcel}.xlsx`;
|
||||||
let buf = await (await fetch(templateUrl)).arrayBuffer();
|
let buf = await (await fetch(templateUrl)).arrayBuffer();
|
||||||
let workbook = new ExcelJS.Workbook();
|
let workbook = new ExcelJS.Workbook();
|
||||||
@ -327,12 +391,6 @@ async function generateTemplate(data) {
|
|||||||
insertAndCopyColumn(7 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7], yz01_sheet);
|
insertAndCopyColumn(7 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7], yz01_sheet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < yz01Num; i++) {
|
|
||||||
yz01_sheet.mergeCells(6, i * 7 + 2, 6, i * 7 + 7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (yz01Mod > 0) {
|
|
||||||
yz01_sheet.mergeCells(6, yz01Num * 7 + 2, 6, yz01Num * 7 + 3 + yz01Mod);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let f01Mod = (data.contracts.length) % 3;
|
let f01Mod = (data.contracts.length) % 3;
|
||||||
@ -362,8 +420,11 @@ async function generateTemplate(data) {
|
|||||||
f01_sheet.mergeCells(1, i * 10 + 3, 2, i * 10 + 3);
|
f01_sheet.mergeCells(1, i * 10 + 3, 2, i * 10 + 3);
|
||||||
f01_sheet.mergeCells(1, i * 10 + 10, 2, i * 10 + 10);
|
f01_sheet.mergeCells(1, i * 10 + 10, 2, i * 10 + 10);
|
||||||
f01_sheet.mergeCells(1, i * 10 + 4, 1, i * 10 + 5);
|
f01_sheet.mergeCells(1, i * 10 + 4, 1, i * 10 + 5);
|
||||||
|
f01_sheet.getRow(1).getCell(i * 10 + 4).value = data.contracts[i * 3].name;
|
||||||
f01_sheet.mergeCells(1, i * 10 + 6, 1, i * 10 + 7);
|
f01_sheet.mergeCells(1, i * 10 + 6, 1, i * 10 + 7);
|
||||||
|
f01_sheet.getRow(1).getCell(i * 10 + 6).value = data.contracts[i * 3 + 1].name;
|
||||||
f01_sheet.mergeCells(1, i * 10 + 8, 1, i * 10 + 9);
|
f01_sheet.mergeCells(1, i * 10 + 8, 1, i * 10 + 9);
|
||||||
|
f01_sheet.getRow(1).getCell(i * 10 + 8).value = data.contracts[i * 3 + 2].name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (f01Mod > 0) {
|
if (f01Mod > 0) {
|
||||||
@ -372,7 +433,11 @@ async function generateTemplate(data) {
|
|||||||
f01_sheet.mergeCells(1, f01Num * 10 + 3, 2, f01Num * 10 + 3);
|
f01_sheet.mergeCells(1, f01Num * 10 + 3, 2, f01Num * 10 + 3);
|
||||||
f01_sheet.mergeCells(1, f01Num * 10 + 2 * f01Mod + 4, 2, f01Num * 10 + 2 * f01Mod + 4);
|
f01_sheet.mergeCells(1, f01Num * 10 + 2 * f01Mod + 4, 2, f01Num * 10 + 2 * f01Mod + 4);
|
||||||
f01_sheet.mergeCells(1, f01Num * 10 + 4, 1, f01Num * 10 + 5);
|
f01_sheet.mergeCells(1, f01Num * 10 + 4, 1, f01Num * 10 + 5);
|
||||||
if (f01Mod == 2) f01_sheet.mergeCells(1, f01Num * 10 + 6, 1, f01Num * 10 + 7);
|
f01_sheet.getRow(1).getCell(f01Num * 10 + 4).value = data.contracts[f01Num * 3].name;
|
||||||
|
if (f01Mod == 2) {
|
||||||
|
f01_sheet.mergeCells(1, f01Num * 10 + 6, 1, f01Num * 10 + 7);
|
||||||
|
f01_sheet.getRow(1).getCell(f01Num * 10 + 6).value = data.contracts[f01Num * 3 + 1].name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let ml_slotRow = 13;
|
let ml_slotRow = 13;
|
||||||
@ -396,6 +461,8 @@ async function generateTemplate(data) {
|
|||||||
|
|
||||||
let ml_sourceRows = [ml_sheet.getRow(6)];
|
let ml_sourceRows = [ml_sheet.getRow(6)];
|
||||||
let sheet_1 = copyWorksheet(workbook, '预i-1表', `预${index + 1}-1表`);
|
let sheet_1 = copyWorksheet(workbook, '预i-1表', `预${index + 1}-1表`);
|
||||||
|
sheet_1.headerFooter.oddHeader = sheet_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-1 表/g, `预 ${index + 1}-1 表`).replace(/第i合同/g, ci.name);
|
||||||
|
sheet_1.getRow(1).getCell(4).value = sheet_1.getRow(1).getCell(4).value.replace(/第i合同/g, ci.name);
|
||||||
let sheet_2;
|
let sheet_2;
|
||||||
let sheet_2_1;
|
let sheet_2_1;
|
||||||
let sheet_2_2;
|
let sheet_2_2;
|
||||||
@ -405,24 +472,47 @@ async function generateTemplate(data) {
|
|||||||
if (ci.method1.length || ci.method2.length) {
|
if (ci.method1.length || ci.method2.length) {
|
||||||
ml_sourceRows.push(ml_sheet.getRow(7));
|
ml_sourceRows.push(ml_sheet.getRow(7));
|
||||||
sheet_2 = copyWorksheet(workbook, '预i-2表', `预${index + 1}-2表`);
|
sheet_2 = copyWorksheet(workbook, '预i-2表', `预${index + 1}-2表`);
|
||||||
|
sheet_2.headerFooter.oddHeader = sheet_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2 表/g, `预 ${index + 1}-2 表`).replace(/第i合同/g, ci.name);
|
||||||
if (ci.method1.length) {
|
if (ci.method1.length) {
|
||||||
ml_sourceRows.push(ml_sheet.getRow(8));
|
ml_sourceRows.push(ml_sheet.getRow(8));
|
||||||
sheet_2_1 = copyWorksheet(workbook, '预i-2-1表', `预${index + 1}-2-1表`);
|
sheet_2_1 = copyWorksheet(workbook, '预i-2-1表', `预${index + 1}-2-1表`);
|
||||||
|
sheet_2_1.headerFooter.oddHeader = sheet_2_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-1 表/g, `预 ${index + 1}-2-1 表`).replace(/第i合同/g, ci.name);
|
||||||
}
|
}
|
||||||
if (ci.method2.length) {
|
if (ci.method2.length) {
|
||||||
ml_sourceRows.push(ml_sheet.getRow(9));
|
ml_sourceRows.push(ml_sheet.getRow(9));
|
||||||
sheet_2_2 = copyWorksheet(workbook, '预i-2-2表', `预${index + 1}-2-2表`);
|
sheet_2_2 = copyWorksheet(workbook, '预i-2-2表', `预${index + 1}-2-2表`);
|
||||||
|
sheet_2_2.headerFooter.oddHeader = sheet_2_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-2 表/g, `预 ${index + 1}-2-2 表`).replace(/第i合同/g, ci.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ci.method3.length) {
|
if (ci.method3.length) {
|
||||||
ml_sourceRows.push(ml_sheet.getRow(10));
|
ml_sourceRows.push(ml_sheet.getRow(10));
|
||||||
sheet_3 = copyWorksheet(workbook, '预i-3表', `预${index + 1}-3表`);
|
sheet_3 = copyWorksheet(workbook, '预i-3表', `预${index + 1}-3表`);
|
||||||
|
sheet_3.headerFooter.oddHeader = sheet_3.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-3 表/g, `预 ${index + 1}-3 表`).replace(/第i合同/g, ci.name);
|
||||||
}
|
}
|
||||||
if (ci.method4.length) {
|
if (ci.method4.length) {
|
||||||
ml_sourceRows.push(ml_sheet.getRow(11));
|
ml_sourceRows.push(ml_sheet.getRow(11));
|
||||||
ml_sourceRows.push(ml_sheet.getRow(12));
|
ml_sourceRows.push(ml_sheet.getRow(12));
|
||||||
sheet_4 = copyWorksheet(workbook, '预i-4表', `预${index + 1}-4表`);
|
sheet_4 = copyWorksheet(workbook, '预i-4表', `预${index + 1}-4表`);
|
||||||
|
sheet_4.headerFooter.oddHeader = sheet_4.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4 表/g, `预 ${index + 1}-4 表`).replace(/第i合同/g, ci.name);
|
||||||
sheet_4_1 = copyWorksheet(workbook, '预i-4-1表', `预${index + 1}-4-1表`);
|
sheet_4_1 = copyWorksheet(workbook, '预i-4-1表', `预${index + 1}-4-1表`);
|
||||||
|
sheet_4_1.headerFooter.oddHeader = sheet_4_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4-1 表/g, `预 ${index + 1}-4-1 表`).replace(/第i合同/g, ci.name);
|
||||||
|
let sumObj = ci.method4.reduce((a, b) => {
|
||||||
|
const m4 = ci.services.find(f => f.id == b)?.method4;
|
||||||
|
return {
|
||||||
|
person_num: addNumbers(a.person_num, toFiniteNumber(m4?.person_num)),
|
||||||
|
work_day: addNumbers(a.work_day, toFiniteNumber(m4?.work_day)),
|
||||||
|
fee: addNumbers(a.fee, toFiniteNumber(m4?.fee))
|
||||||
|
};
|
||||||
|
}, { person_num: 0, work_day: 0, fee: 0 });
|
||||||
|
sheet_4.getRow(3).getCell(4).value = sumObj.person_num;
|
||||||
|
sheet_4.getRow(3).getCell(5).value = sumObj.work_day;
|
||||||
|
sheet_4.getRow(3).getCell(6).value = sumObj.fee;
|
||||||
|
sheet_4_1.getRow(4).getCell(4).value = '/';
|
||||||
|
sheet_4_1.getRow(4).getCell(5).value = '/';
|
||||||
|
sheet_4_1.getRow(4).getCell(6).value = '/';
|
||||||
|
sheet_4_1.getRow(4).getCell(7).value = sumObj.person_num;
|
||||||
|
sheet_4_1.getRow(4).getCell(8).value = sumObj.work_day;
|
||||||
|
sheet_4_1.getRow(4).getCell(9).value = sumObj.fee
|
||||||
}
|
}
|
||||||
|
|
||||||
cusInsertRowFunc(ml_slotRow, ml_sourceRows, ml_sheet, () => ml_slotRow++, (targetCell, sourceCell, colNumber) => {
|
cusInsertRowFunc(ml_slotRow, ml_sourceRows, ml_sheet, () => ml_slotRow++, (targetCell, sourceCell, colNumber) => {
|
||||||
@ -614,7 +704,7 @@ async function generateTemplate(data) {
|
|||||||
targetRow.getCell(2).value = expertX.ref;
|
targetRow.getCell(2).value = expertX.ref;
|
||||||
targetRow.getCell(3).value = expertX.name;
|
targetRow.getCell(3).value = expertX.name;
|
||||||
targetRow.getCell(4).value = `${expertX.minPrice}~${expertX.maxPrice}`;
|
targetRow.getCell(4).value = `${expertX.minPrice}~${expertX.maxPrice}`;
|
||||||
targetRow.getCell(5).value = `${Math.round(expertX.minPrice * expertX.manageCoe)}~${Math.round(expertX.maxPrice * expertX.manageCoe)}`;
|
targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}~${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
|
||||||
targetRow.getCell(6).value = eobj.price;
|
targetRow.getCell(6).value = eobj.price;
|
||||||
targetRow.getCell(7).value = eobj.person_num;
|
targetRow.getCell(7).value = eobj.person_num;
|
||||||
targetRow.getCell(8).value = eobj.work_day;
|
targetRow.getCell(8).value = eobj.work_day;
|
||||||
@ -630,7 +720,6 @@ async function generateTemplate(data) {
|
|||||||
allServices.forEach((s, sindex) => {
|
allServices.forEach((s, sindex) => {
|
||||||
const serviceX = serviceList[s.id];
|
const serviceX = serviceList[s.id];
|
||||||
cusInsertRowFunc(3 + sindex, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
|
cusInsertRowFunc(3 + sindex, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
|
||||||
const sumCol = data.contracts.length;
|
|
||||||
let siSum = 0;
|
let siSum = 0;
|
||||||
for (let i = 0; i < yz01Num; i++) {
|
for (let i = 0; i < yz01Num; i++) {
|
||||||
targetRow.getCell(i * 7 + 1).value = sindex + 1;
|
targetRow.getCell(i * 7 + 1).value = sindex + 1;
|
||||||
@ -639,12 +728,17 @@ async function generateTemplate(data) {
|
|||||||
targetRow.getCell(i * 7 + 4).value = s.contracts[i * 4];
|
targetRow.getCell(i * 7 + 4).value = s.contracts[i * 4];
|
||||||
targetRow.getCell(i * 7 + 5).value = s.contracts[i * 4 + 1];
|
targetRow.getCell(i * 7 + 5).value = s.contracts[i * 4 + 1];
|
||||||
targetRow.getCell(i * 7 + 6).value = s.contracts[i * 4 + 2];
|
targetRow.getCell(i * 7 + 6).value = s.contracts[i * 4 + 2];
|
||||||
siSum = siSum + (Number(s.contracts[i * 4]) || 0 + Number(s.contracts[i * 4 + 1]) || 0 + Number(s.contracts[i * 4 + 2]) || 0);
|
siSum = addNumbers(
|
||||||
if (i * 4 + 3 == sumCol) {
|
siSum,
|
||||||
|
toFiniteNumber(s.contracts[i * 4]),
|
||||||
|
toFiniteNumber(s.contracts[i * 4 + 1]),
|
||||||
|
toFiniteNumber(s.contracts[i * 4 + 2])
|
||||||
|
);
|
||||||
|
if (i == yz01Num - 1 && yz01Mod == 0) {
|
||||||
targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
|
targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
|
||||||
} else {
|
} else {
|
||||||
targetRow.getCell(i * 7 + 7).value = s.contracts[i * 4 + 3];
|
targetRow.getCell(i * 7 + 7).value = s.contracts[i * 4 + 3];
|
||||||
siSum = siSum + (Number(s.contracts[i * 4 + 3]) || 0);
|
siSum = addNumbers(siSum, toFiniteNumber(s.contracts[i * 4 + 3]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (yz01Mod) {
|
if (yz01Mod) {
|
||||||
@ -655,12 +749,16 @@ async function generateTemplate(data) {
|
|||||||
targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
|
targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
|
||||||
} else if (yz01Mod == 2) {
|
} else if (yz01Mod == 2) {
|
||||||
targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
|
targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
|
||||||
siSum = siSum + (Number(s.contracts[yz01Num * 4]) || 0);
|
siSum = addNumbers(siSum, toFiniteNumber(s.contracts[yz01Num * 4]));
|
||||||
targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
|
targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
|
||||||
} else {
|
} else {
|
||||||
targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
|
targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
|
||||||
targetRow.getCell(yz01Num * 7 + 5).value = s.contracts[yz01Num * 4 + 1];
|
targetRow.getCell(yz01Num * 7 + 5).value = s.contracts[yz01Num * 4 + 1];
|
||||||
siSum = siSum + (Number(s.contracts[yz01Num * 4]) || 0 + Number(s.contracts[yz01Num * 4 + 1]) || 0);
|
siSum = addNumbers(
|
||||||
|
siSum,
|
||||||
|
toFiniteNumber(s.contracts[yz01Num * 4]),
|
||||||
|
toFiniteNumber(s.contracts[yz01Num * 4 + 1])
|
||||||
|
);
|
||||||
targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
|
targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -669,6 +767,31 @@ async function generateTemplate(data) {
|
|||||||
|
|
||||||
ml_sheet.spliceRows(6, 6);
|
ml_sheet.spliceRows(6, 6);
|
||||||
ml_sheet.spliceRows(6, 1);
|
ml_sheet.spliceRows(6, 1);
|
||||||
|
|
||||||
|
// 合并说明
|
||||||
|
if (yz01Num) {
|
||||||
|
for (let i = 0; i < yz01Num; i++) {
|
||||||
|
yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4].name}预算\n(元)`;
|
||||||
|
yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 1].name}预算\n(元)`;
|
||||||
|
yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 2].name}预算\n(元)`;
|
||||||
|
if (i == yz01Num - 1 && yz01Mod == 0) {
|
||||||
|
yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `预算小计\n(元)`;
|
||||||
|
} else {
|
||||||
|
yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 3].name}预算\n(元)`;
|
||||||
|
}
|
||||||
|
yz01_sheet.mergeCells(6, i * 7 + 2, 6, i * 7 + 7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yz01Mod) {
|
||||||
|
for (let i = 0; i < yz01Mod; i++) {
|
||||||
|
if (i == yz01Mod - 1) {
|
||||||
|
yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `预算小计\n(元)`;
|
||||||
|
} else {
|
||||||
|
yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `${data.contracts[yz01Num * 4 + i].name}预算\n(元)`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
yz01_sheet.mergeCells(6, yz01Num * 7 + 2, 6, yz01Num * 7 + 3 + yz01Mod);
|
||||||
|
}
|
||||||
ml_sheet.mergeCells(ml_slotRow - 7, 1, ml_slotRow - 7, 4);
|
ml_sheet.mergeCells(ml_slotRow - 7, 1, ml_slotRow - 7, 4);
|
||||||
|
|
||||||
workbook.removeWorksheet('预i-1表');
|
workbook.removeWorksheet('预i-1表');
|
||||||
@ -682,18 +805,19 @@ async function generateTemplate(data) {
|
|||||||
workbook.getWorksheet('辅02表').orderNo = ml_number + 3 + 10;
|
workbook.getWorksheet('辅02表').orderNo = ml_number + 3 + 10;
|
||||||
workbook.getWorksheet('辅03表').orderNo = ml_number + 4 + 10;
|
workbook.getWorksheet('辅03表').orderNo = ml_number + 4 + 10;
|
||||||
|
|
||||||
// workbook._worksheets.forEach(sheet => {
|
workbook._worksheets.forEach(sheet => {
|
||||||
// if (sheet) {
|
if (sheet) {
|
||||||
// if (sheet.headerFooter.oddHeader) sheet.headerFooter.oddHeader = sheet.headerFooter.oddHeader.replace(/&([CLR])&/g, '&$1&"宋体"&');
|
if (sheet.headerFooter.oddHeader) sheet.headerFooter.oddHeader = sheet.headerFooter.oddHeader.replace(/&([CLR])&/g, '&$1&"宋体"&');
|
||||||
// if (sheet.headerFooter.oddFooter) sheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter.replace(/&([CLR])&/g, '&$1&"宋体"&');
|
if (sheet.headerFooter.oddFooter) sheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter.replace(/&([CLR])&/g, '&$1&"宋体"&');
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
|
|
||||||
window.workbook = workbook;
|
window.workbook = workbook;
|
||||||
|
|
||||||
return workbook;
|
return workbook;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function cusInsertRowFunc(insertRowNum, sourceRows, worksheet, RowFun, cellFun) {
|
function cusInsertRowFunc(insertRowNum, sourceRows, worksheet, RowFun, cellFun) {
|
||||||
// 插入行
|
// 插入行
|
||||||
let newRows = [];
|
let newRows = [];
|
||||||
@ -817,135 +941,3 @@ function cloneCellValue(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//demo
|
|
||||||
let data1 = {
|
|
||||||
name: 'test001',//项目名称
|
|
||||||
fee: 10000, //所有合同段总费用
|
|
||||||
scale: [//项目明细aggrid数据
|
|
||||||
{
|
|
||||||
major: 0, //专业id,对应专业majorList中的key
|
|
||||||
cost: 100000,//造价金额
|
|
||||||
area: 200,//用地面积
|
|
||||||
},
|
|
||||||
{
|
|
||||||
major: 1,
|
|
||||||
cost: 100000,
|
|
||||||
area: 200,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
contracts: [//合同段数据
|
|
||||||
{
|
|
||||||
name: 'A合同段',//合同段名称
|
|
||||||
fee: 10000,//合同段费用(该合同段咨询服务zxfw.vue里面aggrid的合同预算行的小计)
|
|
||||||
scale: [//合同段明细aggrid数据(htinfo.vue)
|
|
||||||
{
|
|
||||||
major: 0, //专业id,对应专业majorList中的key
|
|
||||||
cost: 100000,//造价金额
|
|
||||||
area: 200,//用地面积
|
|
||||||
},
|
|
||||||
{
|
|
||||||
major: 1,
|
|
||||||
cost: 100000,
|
|
||||||
area: 200,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: [//咨询服务数据(zxfw.vue里面aggrid的数据,不用输出合同预算)
|
|
||||||
{
|
|
||||||
id: 0, //服务id,对应serviceList中的key
|
|
||||||
fee: 100000 ,//服务费用(该服务咨询服务小计)
|
|
||||||
method1: { // 投资规模法InvestmentScalePricingPane.vue的数据
|
|
||||||
cost: 100000, //zxfw.vue里面aggrid该数据的投资规模法金额
|
|
||||||
basicFee: 200,
|
|
||||||
basicFee_basic: 200,
|
|
||||||
basicFee_optional: 0,
|
|
||||||
fee: 250000,
|
|
||||||
det: [
|
|
||||||
{
|
|
||||||
major: 0,
|
|
||||||
cost: 100000,
|
|
||||||
basicFee: 200,
|
|
||||||
basicFormula: '856,000+(1,000,000,000-500,000,000)×1‰',
|
|
||||||
basicFee_basic: 200,
|
|
||||||
optionalFormula: '171,200+(1,000,000,000-500,000,000)×0.2‰',
|
|
||||||
basicFee_optional: 0,
|
|
||||||
serviceCoe: 1.1,
|
|
||||||
majorCoe: 1.2,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
method2: { // 用地规模法
|
|
||||||
area: 1200,
|
|
||||||
basicFee: 200,
|
|
||||||
basicFee_basic: 200,
|
|
||||||
basicFee_optional: 0,
|
|
||||||
fee: 250000,
|
|
||||||
det: [
|
|
||||||
{
|
|
||||||
major: 0,
|
|
||||||
area: 1200,
|
|
||||||
basicFee: 200,
|
|
||||||
basicFormula: '106,000+(1,200-1,000)×60',
|
|
||||||
basicFee_basic: 200,
|
|
||||||
optionalFormula: '21,200+(1,200-1,000)×12',
|
|
||||||
basicFee_optional: 0,
|
|
||||||
serviceCoe: 1.1,
|
|
||||||
majorCoe: 1.2,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
method3: { // 工作量法
|
|
||||||
basicFee: 200,
|
|
||||||
fee: 250000,
|
|
||||||
det: [
|
|
||||||
{
|
|
||||||
task: 0,
|
|
||||||
price: 100000,
|
|
||||||
amount: 10,
|
|
||||||
basicFee: 200,
|
|
||||||
serviceCoe: 1.1,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
task: 1,
|
|
||||||
price: 100000,
|
|
||||||
amount: 10,
|
|
||||||
basicFee: 200,
|
|
||||||
serviceCoe: 1.1,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
method4: { // 工时法
|
|
||||||
person_num: 10,
|
|
||||||
work_day: 10,
|
|
||||||
fee: 250000,
|
|
||||||
det: [
|
|
||||||
{
|
|
||||||
expert: 0,
|
|
||||||
price: 100000,
|
|
||||||
person_num: 10,
|
|
||||||
work_day: 3,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
expert: 1,
|
|
||||||
price: 100000,
|
|
||||||
person_num: 10,
|
|
||||||
work_day: 3,
|
|
||||||
fee: 100000,
|
|
||||||
desc: '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user