优化
This commit is contained in:
parent
3950057707
commit
a10359f7e0
@ -3,9 +3,5 @@ import Tab from '@/layout/tab.vue'
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<tab></tab>
|
<Tab />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { AgGridVue } from 'ag-grid-vue3'
|
|||||||
import type { ColDef, FirstDataRenderedEvent, GridApi, GridReadyEvent, GridSizeChangedEvent } from 'ag-grid-community'
|
import type { ColDef, FirstDataRenderedEvent, GridApi, GridReadyEvent, GridSizeChangedEvent } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||||
|
|
||||||
interface DictItem {
|
interface DictItem {
|
||||||
@ -41,12 +42,6 @@ const props = defineProps<{
|
|||||||
const detailRows = ref<FactorRow[]>([])
|
const detailRows = ref<FactorRow[]>([])
|
||||||
const gridApi = ref<GridApi<FactorRow> | null>(null)
|
const gridApi = ref<GridApi<FactorRow> | null>(null)
|
||||||
|
|
||||||
const parseNumberOrNull = (value: unknown) => {
|
|
||||||
if (value === '' || value == null) return null
|
|
||||||
const v = Number(value)
|
|
||||||
return Number.isFinite(v) ? v : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatReadonlyFactor = (value: unknown) => {
|
const formatReadonlyFactor = (value: unknown) => {
|
||||||
if (value == null || value === '') return ''
|
if (value == null || value === '') return ''
|
||||||
return Number(value).toFixed(2)
|
return Number(value).toFixed(2)
|
||||||
@ -277,7 +272,7 @@ onBeforeUnmount(() => {
|
|||||||
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">{{ title }}</h3>
|
<h3 class="text-sm font-semibold text-foreground">{{ title }}</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||||
|
|||||||
@ -327,7 +327,7 @@ const processCellFromClipboard = (params:any) => {
|
|||||||
<div class="rounded-lg border bg-card xmMx flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">合同规模明细</h3>
|
<h3 class="text-sm font-semibold text-foreground">合同规模明细</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ag-theme-quartz h-full min-h-0 min-w-0 w-full flex-1 overflow-hidden">
|
<div class="ag-theme-quartz h-full min-h-0 min-w-0 w-full flex-1 overflow-hidden">
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { expertList } from '@/sql'
|
|||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||||
import { formatThousands } from '@/lib/numberFormat'
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
|
|
||||||
@ -173,12 +174,6 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseNumberOrNull = (value: unknown) => {
|
|
||||||
if (value === '' || value == null) return null
|
|
||||||
const v = Number(value)
|
|
||||||
return Number.isFinite(v) ? v : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const parseNonNegativeIntegerOrNull = (value: unknown) => {
|
const parseNonNegativeIntegerOrNull = (value: unknown) => {
|
||||||
if (value === '' || value == null) return null
|
if (value === '' || value == null) return null
|
||||||
if (typeof value === 'number') {
|
if (typeof value === 'number') {
|
||||||
@ -482,7 +477,7 @@ const handleGridReady = (params: any) => {
|
|||||||
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">工时法明细</h3>
|
<h3 class="text-sm font-semibold text-foreground">工时法明细</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||||
|
|||||||
@ -3,13 +3,15 @@ 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 } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { getBasicFeeFromScale, majorList } from '@/sql'
|
import { majorList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { addNumbers, decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { formatThousands } from '@/lib/numberFormat'
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||||
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 { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -250,12 +252,6 @@ const mergeWithDictRows = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseNumberOrNull = (value: unknown) => {
|
|
||||||
if (value === '' || value == null) return null
|
|
||||||
const v = Number(value)
|
|
||||||
return Number.isFinite(v) ? v : null
|
|
||||||
}
|
|
||||||
|
|
||||||
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 '点击输入'
|
||||||
@ -285,15 +281,15 @@ const formatReadonlyMoney = (params: any) => {
|
|||||||
return formatThousands(roundTo(params.value, 2))
|
return formatThousands(roundTo(params.value, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) => {
|
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
||||||
const result = getBasicFeeFromScale(row?.amount, 'cost')
|
getBenchmarkBudgetByScale(row?.amount, 'cost')
|
||||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByAmount(row)
|
return getScaleBudgetFee({
|
||||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
benchmarkBudget: getBenchmarkBudgetByAmount(row),
|
||||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
majorFactor: row?.majorFactor,
|
||||||
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const columnDefs: ColDef<DetailRow>[] = [
|
const columnDefs: ColDef<DetailRow>[] = [
|
||||||
|
|||||||
@ -3,13 +3,15 @@ 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 } from 'ag-grid-community'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { getBasicFeeFromScale, majorList } from '@/sql'
|
import { majorList } from '@/sql'
|
||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { addNumbers, decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber } from '@/lib/decimal'
|
||||||
import { formatThousands } from '@/lib/numberFormat'
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||||
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 { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import {
|
import {
|
||||||
AlertDialogAction,
|
AlertDialogAction,
|
||||||
@ -253,12 +255,6 @@ const mergeWithDictRows = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseNumberOrNull = (value: unknown) => {
|
|
||||||
if (value === '' || value == null) return null
|
|
||||||
const v = Number(value)
|
|
||||||
return Number.isFinite(v) ? v : null
|
|
||||||
}
|
|
||||||
|
|
||||||
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 '点击输入'
|
||||||
@ -280,15 +276,15 @@ const formatReadonlyMoney = (params: any) => {
|
|||||||
return formatThousands(roundTo(params.value, 2))
|
return formatThousands(roundTo(params.value, 2))
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) => {
|
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
||||||
const result = getBasicFeeFromScale(row?.landArea, 'area')
|
getBenchmarkBudgetByScale(row?.landArea, 'area')
|
||||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByLandArea(row)
|
return getScaleBudgetFee({
|
||||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
benchmarkBudget: getBenchmarkBudgetByLandArea(row),
|
||||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
majorFactor: row?.majorFactor,
|
||||||
|
consultCategoryFactor: row?.consultCategoryFactor
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatEditableFlexibleNumber = (params: any) => {
|
const formatEditableFlexibleNumber = (params: any) => {
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { taskList } from '@/sql'
|
|||||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||||
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||||
import { formatThousands } from '@/lib/numberFormat'
|
import { formatThousands } from '@/lib/numberFormat'
|
||||||
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||||
import { loadConsultCategoryFactorMap } from '@/lib/xmFactorDefaults'
|
import { loadConsultCategoryFactorMap } from '@/lib/xmFactorDefaults'
|
||||||
@ -180,12 +181,8 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const parseNumberOrNull = (value: unknown) => {
|
const parseSanitizedNumberOrNull = (value: unknown) =>
|
||||||
if (value === '' || value == null) return null
|
parseNumberOrNull(value, { sanitize: true })
|
||||||
const normalized = typeof value === 'string' ? value.replace(/[^0-9.\-]/g, '') : value
|
|
||||||
const v = Number(normalized)
|
|
||||||
return Number.isFinite(v) ? v : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||||
if (!row || isNoTaskRow(row)) return null
|
if (!row || isNoTaskRow(row)) return null
|
||||||
@ -281,7 +278,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
!isNoTaskRow(params.data) &&
|
!isNoTaskRow(params.data) &&
|
||||||
(params.value == null || params.value === '')
|
(params.value == null || params.value === '')
|
||||||
},
|
},
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||||
valueFormatter: params => {
|
valueFormatter: params => {
|
||||||
if (isNoTaskRow(params.data)) return '无'
|
if (isNoTaskRow(params.data)) return '无'
|
||||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||||
@ -307,7 +304,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
(params.value == null || params.value === '')
|
(params.value == null || params.value === '')
|
||||||
},
|
},
|
||||||
aggFunc: decimalAggSum,
|
aggFunc: decimalAggSum,
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableNumber
|
valueFormatter: formatEditableNumber
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -325,7 +322,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
|||||||
!isNoTaskRow(params.data) &&
|
!isNoTaskRow(params.data) &&
|
||||||
(params.value == null || params.value === '')
|
(params.value == null || params.value === '')
|
||||||
},
|
},
|
||||||
valueParser: params => parseNumberOrNull(params.newValue),
|
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||||
valueFormatter: formatEditableNumber
|
valueFormatter: formatEditableNumber
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -511,7 +508,7 @@ const mydiyTheme = myTheme.withParams({
|
|||||||
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
<div class="rounded-lg border bg-card xmMx flex min-h-0 flex-1 flex-col">
|
||||||
<div class="flex items-center justify-between border-b px-4 py-3">
|
<div class="flex items-center justify-between border-b px-4 py-3">
|
||||||
<h3 class="text-sm font-semibold text-foreground">工作量明细</h3>
|
<h3 class="text-sm font-semibold text-foreground">工作量明细</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isWorkloadMethodApplicable" class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
<div v-if="isWorkloadMethodApplicable" class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||||
|
|||||||
@ -433,7 +433,7 @@ const scrollToGridSection = () => {
|
|||||||
>
|
>
|
||||||
项目明细
|
项目明细
|
||||||
</h3>
|
</h3>
|
||||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
<div class="text-xs text-muted-foreground"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref="agGridRef" class="ag-theme-quartz w-full flex-1 min-h-0">
|
<div ref="agGridRef" class="ag-theme-quartz w-full flex-1 min-h-0">
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Decimal from 'decimal.js'
|
import Decimal from 'decimal.js'
|
||||||
|
import { isFiniteNumber } from '@/lib/number'
|
||||||
|
|
||||||
type MaybeNumber = number | null | undefined
|
type MaybeNumber = number | null | undefined
|
||||||
type DecimalInput = Decimal.Value
|
type DecimalInput = Decimal.Value
|
||||||
@ -8,30 +9,26 @@ export const toDecimal = (value: DecimalInput) => new Decimal(value)
|
|||||||
export const roundTo = (value: DecimalInput, decimalPlaces = 2) =>
|
export const roundTo = (value: DecimalInput, decimalPlaces = 2) =>
|
||||||
new Decimal(value).toDecimalPlaces(decimalPlaces, Decimal.ROUND_HALF_UP).toNumber()
|
new Decimal(value).toDecimalPlaces(decimalPlaces, Decimal.ROUND_HALF_UP).toNumber()
|
||||||
|
|
||||||
export const addNumbers = (...values: MaybeNumber[]) => {
|
const sumFiniteValues = (values: Iterable<unknown>) => {
|
||||||
let total = new Decimal(0)
|
let total = new Decimal(0)
|
||||||
for (const value of values) {
|
for (const value of values) {
|
||||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
if (!isFiniteNumber(value)) continue
|
||||||
total = total.plus(value)
|
total = total.plus(value)
|
||||||
}
|
}
|
||||||
return total.toNumber()
|
return total.toNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const addNumbers = (...values: MaybeNumber[]) => sumFiniteValues(values)
|
||||||
|
|
||||||
export const sumByNumber = <T>(list: T[], pick: (item: T) => MaybeNumber) => {
|
export const sumByNumber = <T>(list: T[], pick: (item: T) => MaybeNumber) => {
|
||||||
let total = new Decimal(0)
|
let total = new Decimal(0)
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
const value = pick(item)
|
const value = pick(item)
|
||||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
if (!isFiniteNumber(value)) continue
|
||||||
total = total.plus(value)
|
total = total.plus(value)
|
||||||
}
|
}
|
||||||
return total.toNumber()
|
return total.toNumber()
|
||||||
}
|
}
|
||||||
|
|
||||||
export const decimalAggSum = (params: { values?: unknown[] }) => {
|
export const decimalAggSum = (params: { values?: unknown[] }) =>
|
||||||
let total = new Decimal(0)
|
sumFiniteValues(params.values || [])
|
||||||
for (const value of params.values || []) {
|
|
||||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
|
||||||
total = total.plus(value)
|
|
||||||
}
|
|
||||||
return total.toNumber()
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,30 +1,25 @@
|
|||||||
import { GridOptions, themeQuartz } from "ag-grid-community"
|
import type { GridOptions } from 'ag-grid-community'
|
||||||
|
import { themeQuartz } from 'ag-grid-community'
|
||||||
|
|
||||||
const borderConfig = {
|
const borderConfig = {
|
||||||
style: "solid", // 虚线改实线更简洁,也可保留 dotted 但建议用 solid
|
style: 'solid',
|
||||||
width: 0.3, // 更细的边框,减少视觉干扰
|
width: 0.3,
|
||||||
color: "#d3d3d3" // 浅灰色边框,清新不刺眼
|
color: '#d3d3d3'
|
||||||
};
|
}
|
||||||
|
|
||||||
// 简洁清新风格的主题配置
|
|
||||||
export const myTheme = themeQuartz.withParams({
|
export const myTheme = themeQuartz.withParams({
|
||||||
// 核心:移除外边框,减少视觉包裹感
|
|
||||||
wrapperBorder: false,
|
wrapperBorder: false,
|
||||||
|
headerBackgroundColor: '#f0f2f3',
|
||||||
// 表头样式(柔和浅蓝,无加粗,更轻盈)
|
headerTextColor: '#374151',
|
||||||
headerBackgroundColor: "#f0f2f3", // 极浅的背景色,替代深一点的 #e7f3fc
|
headerFontSize: 15,
|
||||||
headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和
|
headerFontWeight: 'normal',
|
||||||
headerFontSize: 15, // 字体稍大一点,更易读
|
|
||||||
headerFontWeight: "normal", // 取消加粗,降低视觉重量
|
|
||||||
|
|
||||||
// 行/列/表头边框(统一浅灰细边框)
|
|
||||||
rowBorder: borderConfig,
|
rowBorder: borderConfig,
|
||||||
columnBorder: borderConfig,
|
columnBorder: borderConfig,
|
||||||
headerRowBorder: borderConfig,
|
headerRowBorder: borderConfig,
|
||||||
|
dataBackgroundColor: '#fefefe'
|
||||||
|
})
|
||||||
|
|
||||||
// 可选:偶数行背景色(轻微区分,更清新)
|
export const gridOptions: GridOptions = {
|
||||||
dataBackgroundColor: "#fefefe"
|
|
||||||
});
|
|
||||||
export const gridOptions: GridOptions<any> = {
|
|
||||||
treeData: true,
|
treeData: true,
|
||||||
animateRows: true,
|
animateRows: true,
|
||||||
tooltipShowMode: 'whenTruncated',
|
tooltipShowMode: 'whenTruncated',
|
||||||
@ -32,13 +27,9 @@ export const gridOptions: GridOptions<any> = {
|
|||||||
singleClickEdit: true,
|
singleClickEdit: true,
|
||||||
suppressClickEdit: false,
|
suppressClickEdit: false,
|
||||||
suppressContextMenu: false,
|
suppressContextMenu: false,
|
||||||
// autoSizeStrategy: {
|
|
||||||
// type: 'fitGridWidth',
|
|
||||||
// defaultMinWidth: 100,
|
|
||||||
// },
|
|
||||||
groupDefaultExpanded: -1,
|
groupDefaultExpanded: -1,
|
||||||
suppressFieldDotNotation: true,
|
suppressFieldDotNotation: true,
|
||||||
// Keep group expand/collapse state when rowData updates after edits/saves.
|
// rowData 更新后通过稳定 ID 维持展开状态和编辑上下文。
|
||||||
getRowId: params => String(params.data?.id ?? params.data?.path?.join('/') ?? ''),
|
getRowId: params => String(params.data?.id ?? params.data?.path?.join('/') ?? ''),
|
||||||
getDataPath: data => data.path,
|
getDataPath: data => data.path,
|
||||||
getContextMenuItems: () => ['copy', 'paste', 'separator', 'export'],
|
getContextMenuItems: () => ['copy', 'paste', 'separator', 'export'],
|
||||||
|
|||||||
20
src/lib/number.ts
Normal file
20
src/lib/number.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const isFiniteNumber = (value: unknown): value is number =>
|
||||||
|
typeof value === 'number' && Number.isFinite(value)
|
||||||
|
|
||||||
|
export const toFiniteNumberOrNull = (value: unknown): number | null =>
|
||||||
|
isFiniteNumber(value) ? value : null
|
||||||
|
|
||||||
|
export const parseNumberOrNull = (
|
||||||
|
value: unknown,
|
||||||
|
options?: { sanitize?: boolean }
|
||||||
|
): number | null => {
|
||||||
|
if (value === '' || value == null) return null
|
||||||
|
|
||||||
|
const normalized =
|
||||||
|
options?.sanitize && typeof value === 'string'
|
||||||
|
? value.replace(/[^0-9.\-]/g, '')
|
||||||
|
: value
|
||||||
|
|
||||||
|
const numericValue = Number(normalized)
|
||||||
|
return Number.isFinite(numericValue) ? numericValue : null
|
||||||
|
}
|
||||||
@ -1,19 +1,42 @@
|
|||||||
export const formatThousands = (value: unknown, fractionDigits = 2) => {
|
import { parseNumberOrNull } from '@/lib/number'
|
||||||
if (value === '' || value == null) return ''
|
|
||||||
const numericValue = Number(value)
|
const fixedFormatterCache = new Map<number, Intl.NumberFormat>()
|
||||||
if (!Number.isFinite(numericValue)) return ''
|
const flexibleFormatterCache = new Map<number, Intl.NumberFormat>()
|
||||||
return numericValue.toLocaleString('zh-CN', {
|
|
||||||
|
const getFixedFormatter = (fractionDigits: number) => {
|
||||||
|
if (!fixedFormatterCache.has(fractionDigits)) {
|
||||||
|
fixedFormatterCache.set(
|
||||||
|
fractionDigits,
|
||||||
|
new Intl.NumberFormat('zh-CN', {
|
||||||
minimumFractionDigits: fractionDigits,
|
minimumFractionDigits: fractionDigits,
|
||||||
maximumFractionDigits: fractionDigits
|
maximumFractionDigits: fractionDigits
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return fixedFormatterCache.get(fractionDigits)!
|
||||||
}
|
}
|
||||||
|
|
||||||
export const formatThousandsFlexible = (value: unknown, maxFractionDigits = 20) => {
|
const getFlexibleFormatter = (maxFractionDigits: number) => {
|
||||||
if (value === '' || value == null) return ''
|
if (!flexibleFormatterCache.has(maxFractionDigits)) {
|
||||||
const numericValue = Number(value)
|
flexibleFormatterCache.set(
|
||||||
if (!Number.isFinite(numericValue)) return ''
|
maxFractionDigits,
|
||||||
return numericValue.toLocaleString('zh-CN', {
|
new Intl.NumberFormat('zh-CN', {
|
||||||
minimumFractionDigits: 0,
|
minimumFractionDigits: 0,
|
||||||
maximumFractionDigits: maxFractionDigits
|
maximumFractionDigits: maxFractionDigits
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return flexibleFormatterCache.get(maxFractionDigits)!
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatThousands = (value: unknown, fractionDigits = 2) => {
|
||||||
|
const numericValue = parseNumberOrNull(value)
|
||||||
|
if (numericValue == null) return ''
|
||||||
|
return getFixedFormatter(fractionDigits).format(numericValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatThousandsFlexible = (value: unknown, maxFractionDigits = 20) => {
|
||||||
|
const numericValue = parseNumberOrNull(value)
|
||||||
|
if (numericValue == null) return ''
|
||||||
|
return getFlexibleFormatter(maxFractionDigits).format(numericValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { expertList, getBasicFeeFromScale, majorList, serviceList, taskList } from '@/sql'
|
import { expertList, majorList, serviceList, taskList } from '@/sql'
|
||||||
import { addNumbers, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||||
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||||
|
|
||||||
interface StoredDetailRowsState<T = any> {
|
interface StoredDetailRowsState<T = any> {
|
||||||
detailRows?: T[]
|
detailRows?: T[]
|
||||||
@ -58,12 +60,17 @@ export interface PricingMethodTotals {
|
|||||||
hourly: number | null
|
hourly: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const toFiniteNumberOrNull = (value: unknown): number | null =>
|
|
||||||
typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
||||||
|
|
||||||
const hasOwn = (obj: unknown, key: string) =>
|
const hasOwn = (obj: unknown, key: string) =>
|
||||||
Object.prototype.hasOwnProperty.call(obj || {}, key)
|
Object.prototype.hasOwnProperty.call(obj || {}, key)
|
||||||
|
|
||||||
|
const toRowMap = <TRow extends { id: string }>(rows?: TRow[]) => {
|
||||||
|
const map = new Map<string, TRow>()
|
||||||
|
for (const row of rows || []) {
|
||||||
|
map.set(String(row.id), row)
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
|
||||||
const service = (serviceList as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
const service = (serviceList as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||||
return toFiniteNumberOrNull(service?.defCoe)
|
return toFiniteNumberOrNull(service?.defCoe)
|
||||||
@ -95,10 +102,7 @@ const mergeScaleRows = (
|
|||||||
serviceId: string | number,
|
serviceId: string | number,
|
||||||
rowsFromDb: Array<Partial<ScaleRow> & Pick<ScaleRow, 'id'>> | undefined
|
rowsFromDb: Array<Partial<ScaleRow> & Pick<ScaleRow, 'id'>> | undefined
|
||||||
): ScaleRow[] => {
|
): ScaleRow[] => {
|
||||||
const dbValueMap = new Map<string, Partial<ScaleRow> & Pick<ScaleRow, 'id'>>()
|
const dbValueMap = toRowMap(rowsFromDb)
|
||||||
for (const row of rowsFromDb || []) {
|
|
||||||
dbValueMap.set(String(row.id), row)
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId)
|
const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId)
|
||||||
return buildDefaultScaleRows(serviceId).map(row => {
|
return buildDefaultScaleRows(serviceId).map(row => {
|
||||||
@ -122,26 +126,26 @@ const mergeScaleRows = (
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBenchmarkBudgetByAmount = (amount: MaybeNumber) => {
|
const getBenchmarkBudgetByAmount = (amount: MaybeNumber) =>
|
||||||
const result = getBasicFeeFromScale(amount, 'cost')
|
getBenchmarkBudgetByScale(amount, 'cost')
|
||||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getBenchmarkBudgetByLandArea = (landArea: MaybeNumber) => {
|
const getBenchmarkBudgetByLandArea = (landArea: MaybeNumber) =>
|
||||||
const result = getBasicFeeFromScale(landArea, 'area')
|
getBenchmarkBudgetByScale(landArea, 'area')
|
||||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const getInvestmentBudgetFee = (row: ScaleRow) => {
|
const getInvestmentBudgetFee = (row: ScaleRow) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByAmount(row.amount)
|
return getScaleBudgetFee({
|
||||||
if (benchmarkBudget == null || row.majorFactor == null || row.consultCategoryFactor == null) return null
|
benchmarkBudget: getBenchmarkBudgetByAmount(row.amount),
|
||||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
majorFactor: row.majorFactor,
|
||||||
|
consultCategoryFactor: row.consultCategoryFactor
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getLandBudgetFee = (row: ScaleRow) => {
|
const getLandBudgetFee = (row: ScaleRow) => {
|
||||||
const benchmarkBudget = getBenchmarkBudgetByLandArea(row.landArea)
|
return getScaleBudgetFee({
|
||||||
if (benchmarkBudget == null || row.majorFactor == null || row.consultCategoryFactor == null) return null
|
benchmarkBudget: getBenchmarkBudgetByLandArea(row.landArea),
|
||||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
majorFactor: row.majorFactor,
|
||||||
|
consultCategoryFactor: row.consultCategoryFactor
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTaskEntriesByServiceId = (serviceId: string | number) =>
|
const getTaskEntriesByServiceId = (serviceId: string | number) =>
|
||||||
@ -164,10 +168,7 @@ const mergeWorkloadRows = (
|
|||||||
serviceId: string | number,
|
serviceId: string | number,
|
||||||
rowsFromDb: Array<Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>> | undefined
|
rowsFromDb: Array<Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>> | undefined
|
||||||
): WorkloadRow[] => {
|
): WorkloadRow[] => {
|
||||||
const dbValueMap = new Map<string, Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>>()
|
const dbValueMap = toRowMap(rowsFromDb)
|
||||||
for (const row of rowsFromDb || []) {
|
|
||||||
dbValueMap.set(String(row.id), row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildDefaultWorkloadRows(serviceId).map(row => {
|
return buildDefaultWorkloadRows(serviceId).map(row => {
|
||||||
const fromDb = dbValueMap.get(row.id)
|
const fromDb = dbValueMap.get(row.id)
|
||||||
@ -216,10 +217,7 @@ const buildDefaultHourlyRows = (): HourlyRow[] =>
|
|||||||
const mergeHourlyRows = (
|
const mergeHourlyRows = (
|
||||||
rowsFromDb: Array<Partial<HourlyRow> & Pick<HourlyRow, 'id'>> | undefined
|
rowsFromDb: Array<Partial<HourlyRow> & Pick<HourlyRow, 'id'>> | undefined
|
||||||
): HourlyRow[] => {
|
): HourlyRow[] => {
|
||||||
const dbValueMap = new Map<string, Partial<HourlyRow> & Pick<HourlyRow, 'id'>>()
|
const dbValueMap = toRowMap(rowsFromDb)
|
||||||
for (const row of rowsFromDb || []) {
|
|
||||||
dbValueMap.set(String(row.id), row)
|
|
||||||
}
|
|
||||||
|
|
||||||
return buildDefaultHourlyRows().map(row => {
|
return buildDefaultHourlyRows().map(row => {
|
||||||
const fromDb = dbValueMap.get(row.id)
|
const fromDb = dbValueMap.get(row.id)
|
||||||
@ -239,6 +237,20 @@ const calcHourlyServiceBudget = (row: HourlyRow) => {
|
|||||||
return roundTo(toDecimal(row.adoptedBudgetUnitPrice).mul(row.personnelCount).mul(row.workdayCount), 2)
|
return roundTo(toDecimal(row.adoptedBudgetUnitPrice).mul(row.personnelCount).mul(row.workdayCount), 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resolveScaleRows = (
|
||||||
|
serviceId: string,
|
||||||
|
pricingData: StoredDetailRowsState | null,
|
||||||
|
htData: StoredDetailRowsState | null
|
||||||
|
) => {
|
||||||
|
if (pricingData?.detailRows != null) {
|
||||||
|
return mergeScaleRows(serviceId, pricingData.detailRows as any)
|
||||||
|
}
|
||||||
|
if (htData?.detailRows != null) {
|
||||||
|
return mergeScaleRows(serviceId, htData.detailRows as any)
|
||||||
|
}
|
||||||
|
return buildDefaultScaleRows(serviceId)
|
||||||
|
}
|
||||||
|
|
||||||
export const getPricingMethodTotalsForService = async (params: {
|
export const getPricingMethodTotalsForService = async (params: {
|
||||||
contractId: string
|
contractId: string
|
||||||
serviceId: string | number
|
serviceId: string | number
|
||||||
@ -258,20 +270,11 @@ export const getPricingMethodTotalsForService = async (params: {
|
|||||||
localforage.getItem<StoredDetailRowsState>(htDbKey)
|
localforage.getItem<StoredDetailRowsState>(htDbKey)
|
||||||
])
|
])
|
||||||
|
|
||||||
const investRows =
|
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||||
investData?.detailRows != null
|
const investRows = resolveScaleRows(serviceId, investData, htData)
|
||||||
? mergeScaleRows(serviceId, investData.detailRows as any)
|
|
||||||
: htData?.detailRows != null
|
|
||||||
? mergeScaleRows(serviceId, htData.detailRows as any)
|
|
||||||
: buildDefaultScaleRows(serviceId)
|
|
||||||
const investScale = sumByNumber(investRows, row => getInvestmentBudgetFee(row))
|
const investScale = sumByNumber(investRows, row => getInvestmentBudgetFee(row))
|
||||||
|
|
||||||
const landRows =
|
const landRows = resolveScaleRows(serviceId, landData, htData)
|
||||||
landData?.detailRows != null
|
|
||||||
? mergeScaleRows(serviceId, landData.detailRows as any)
|
|
||||||
: htData?.detailRows != null
|
|
||||||
? mergeScaleRows(serviceId, htData.detailRows as any)
|
|
||||||
: buildDefaultScaleRows(serviceId)
|
|
||||||
const landScale = sumByNumber(landRows, row => getLandBudgetFee(row))
|
const landScale = sumByNumber(landRows, row => getLandBudgetFee(row))
|
||||||
|
|
||||||
const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId)
|
const defaultWorkloadRows = buildDefaultWorkloadRows(serviceId)
|
||||||
|
|||||||
27
src/lib/pricingScaleFee.ts
Normal file
27
src/lib/pricingScaleFee.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { getBasicFeeFromScale } from '@/sql'
|
||||||
|
import { addNumbers, roundTo, toDecimal } from '@/lib/decimal'
|
||||||
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
|
||||||
|
type ScaleMode = 'cost' | 'area'
|
||||||
|
|
||||||
|
export const getBenchmarkBudgetByScale = (value: unknown, mode: ScaleMode) => {
|
||||||
|
const scaleValue = toFiniteNumberOrNull(value)
|
||||||
|
const result = getBasicFeeFromScale(scaleValue, mode)
|
||||||
|
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getScaleBudgetFee = (params: {
|
||||||
|
benchmarkBudget: unknown
|
||||||
|
majorFactor: unknown
|
||||||
|
consultCategoryFactor: unknown
|
||||||
|
}) => {
|
||||||
|
const benchmarkBudget = toFiniteNumberOrNull(params.benchmarkBudget)
|
||||||
|
const majorFactor = toFiniteNumberOrNull(params.majorFactor)
|
||||||
|
const consultCategoryFactor = toFiniteNumberOrNull(params.consultCategoryFactor)
|
||||||
|
|
||||||
|
if (benchmarkBudget == null || majorFactor == null || consultCategoryFactor == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return roundTo(toDecimal(benchmarkBudget).mul(majorFactor).mul(consultCategoryFactor), 2)
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
import { majorList, serviceList } from '@/sql'
|
import { majorList, serviceList } from '@/sql'
|
||||||
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
|
||||||
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||||
const MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
const MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||||
@ -20,9 +21,6 @@ type FactorDictItem = {
|
|||||||
|
|
||||||
type FactorDict = Record<string, FactorDictItem>
|
type FactorDict = Record<string, FactorDictItem>
|
||||||
|
|
||||||
const toFiniteNumberOrNull = (value: unknown): number | null =>
|
|
||||||
typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
||||||
|
|
||||||
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
const buildStandardFactorMap = (dict: FactorDict): Map<string, number | null> => {
|
||||||
const map = new Map<string, number | null>()
|
const map = new Map<string, number | null>()
|
||||||
for (const [id, item] of Object.entries(dict)) {
|
for (const [id, item] of Object.entries(dict)) {
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||||
|
|
||||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
||||||
|
|
||||||
@ -18,9 +19,6 @@ interface ZxFwState {
|
|||||||
|
|
||||||
export const ZXFW_RELOAD_SERVICE_KEY = 'zxfw-main'
|
export const ZXFW_RELOAD_SERVICE_KEY = 'zxfw-main'
|
||||||
|
|
||||||
const toFiniteNumberOrNull = (value: number | null | undefined) =>
|
|
||||||
typeof value === 'number' && Number.isFinite(value) ? value : null
|
|
||||||
|
|
||||||
export const syncPricingTotalToZxFw = async (params: {
|
export const syncPricingTotalToZxFw = async (params: {
|
||||||
contractId: string
|
contractId: string
|
||||||
serviceId: string | number
|
serviceId: string | number
|
||||||
|
|||||||
45
src/main.ts
45
src/main.ts
@ -1,18 +1,18 @@
|
|||||||
import { createApp } from 'vue'
|
|
||||||
import './style.css'
|
|
||||||
import App from './App.vue'
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
import {
|
import {
|
||||||
ModuleRegistry,
|
CellStyleModule,
|
||||||
ClientSideRowModelModule,
|
ClientSideRowModelModule,
|
||||||
ColumnAutoSizeModule,
|
ColumnAutoSizeModule,
|
||||||
CsvExportModule,
|
CsvExportModule,
|
||||||
LargeTextEditorModule,
|
LargeTextEditorModule,
|
||||||
|
LocaleModule,
|
||||||
|
ModuleRegistry,
|
||||||
NumberEditorModule,
|
NumberEditorModule,
|
||||||
PinnedRowModule,
|
PinnedRowModule,
|
||||||
|
RowAutoHeightModule,
|
||||||
TextEditorModule,
|
TextEditorModule,
|
||||||
TooltipModule,
|
TooltipModule,
|
||||||
UndoRedoEditModule,ValidationModule,LocaleModule ,CellStyleModule ,RowAutoHeightModule
|
UndoRedoEditModule,
|
||||||
|
ValidationModule
|
||||||
} from 'ag-grid-community'
|
} from 'ag-grid-community'
|
||||||
import {
|
import {
|
||||||
AggregationModule,
|
AggregationModule,
|
||||||
@ -24,18 +24,26 @@ import {
|
|||||||
RowGroupingModule,
|
RowGroupingModule,
|
||||||
TreeDataModule
|
TreeDataModule
|
||||||
} from 'ag-grid-enterprise'
|
} from 'ag-grid-enterprise'
|
||||||
LicenseManager.setLicenseKey("[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b")
|
import { createPinia } from 'pinia'
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
const pinia = createPinia()
|
import { createApp } from 'vue'
|
||||||
pinia.use(piniaPluginPersistedstate)
|
import App from './App.vue'
|
||||||
ModuleRegistry.registerModules([
|
import './style.css'
|
||||||
|
|
||||||
|
LicenseManager.setLicenseKey(
|
||||||
|
'[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'
|
||||||
|
)
|
||||||
|
|
||||||
|
const AG_GRID_MODULES = [
|
||||||
ClientSideRowModelModule,
|
ClientSideRowModelModule,
|
||||||
ColumnAutoSizeModule,
|
ColumnAutoSizeModule,
|
||||||
CsvExportModule,
|
CsvExportModule,
|
||||||
TextEditorModule,
|
TextEditorModule,
|
||||||
NumberEditorModule,RowAutoHeightModule,
|
NumberEditorModule,
|
||||||
|
RowAutoHeightModule,
|
||||||
LargeTextEditorModule,
|
LargeTextEditorModule,
|
||||||
UndoRedoEditModule,CellStyleModule ,
|
UndoRedoEditModule,
|
||||||
|
CellStyleModule,
|
||||||
PinnedRowModule,
|
PinnedRowModule,
|
||||||
TooltipModule,
|
TooltipModule,
|
||||||
TreeDataModule,
|
TreeDataModule,
|
||||||
@ -44,8 +52,15 @@ ModuleRegistry.registerModules([
|
|||||||
MenuModule,
|
MenuModule,
|
||||||
CellSelectionModule,
|
CellSelectionModule,
|
||||||
ContextMenuModule,
|
ContextMenuModule,
|
||||||
ClipboardModule,LocaleModule ,
|
ClipboardModule,
|
||||||
|
LocaleModule,
|
||||||
ValidationModule
|
ValidationModule
|
||||||
])
|
]
|
||||||
|
|
||||||
|
const pinia = createPinia()
|
||||||
|
pinia.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
|
// 在应用启动时一次性注册 AG Grid 运行所需模块。
|
||||||
|
ModuleRegistry.registerModules(AG_GRID_MODULES)
|
||||||
|
|
||||||
createApp(App).use(pinia).mount('#app')
|
createApp(App).use(pinia).mount('#app')
|
||||||
|
|||||||
113
src/pinia/tab.ts
113
src/pinia/tab.ts
@ -1,94 +1,96 @@
|
|||||||
// src/stores/tab.ts
|
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export const useTabStore = defineStore('tabs', () => {
|
export interface TabItem<TProps = Record<string, unknown>> {
|
||||||
interface TabItem<T = Record<string, any>> {
|
id: string
|
||||||
id: string; // 标签唯一标识
|
title: string
|
||||||
title: string; // 标签标题
|
componentName: string
|
||||||
componentName: string; // 组件名称
|
props?: TProps
|
||||||
props?: T; // 传递给组件的 props(可选,泛型适配不同组件)
|
|
||||||
}
|
}
|
||||||
const defaultTabs :TabItem[]= [
|
|
||||||
{ id: 'XmView', title: '项目卡片', componentName: 'XmView' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const tabs = ref([
|
const HOME_TAB_ID = 'XmView'
|
||||||
...defaultTabs
|
const DEFAULT_TAB: TabItem = {
|
||||||
])
|
id: HOME_TAB_ID,
|
||||||
const activeTabId = ref('XmView')
|
title: '项目卡片',
|
||||||
|
componentName: HOME_TAB_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
const createDefaultTabs = (): TabItem[] => [{ ...DEFAULT_TAB }]
|
||||||
|
|
||||||
|
export const useTabStore = defineStore(
|
||||||
|
'tabs',
|
||||||
|
() => {
|
||||||
|
const tabs = ref<TabItem[]>(createDefaultTabs())
|
||||||
|
const activeTabId = ref(HOME_TAB_ID)
|
||||||
|
|
||||||
|
const ensureHomeTab = () => {
|
||||||
|
if (tabs.value.some(tab => tab.id === HOME_TAB_ID)) return
|
||||||
|
tabs.value = [...createDefaultTabs(), ...tabs.value]
|
||||||
|
}
|
||||||
|
|
||||||
const ensureActiveValid = () => {
|
const ensureActiveValid = () => {
|
||||||
const activeExists = tabs.value.some(t => t.id === activeTabId.value)
|
ensureHomeTab()
|
||||||
if (!activeExists) {
|
if (tabs.value.length === 0) tabs.value = createDefaultTabs()
|
||||||
activeTabId.value = tabs.value[0]?.id || 'XmView'
|
if (!tabs.value.some(tab => tab.id === activeTabId.value)) {
|
||||||
|
activeTabId.value = tabs.value[0]?.id ?? HOME_TAB_ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openTab = (config: { id: string; title: string; componentName: string; props?: any }) => {
|
const openTab = (config: TabItem) => {
|
||||||
const exists = tabs.value.some(t => t.id === config.id)
|
if (!tabs.value.some(tab => tab.id === config.id)) {
|
||||||
if (!exists) {
|
|
||||||
tabs.value = [...tabs.value, config]
|
tabs.value = [...tabs.value, config]
|
||||||
}
|
}
|
||||||
activeTabId.value = config.id
|
activeTabId.value = config.id
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTab = (id: string) => {
|
const removeTab = (id: string) => {
|
||||||
if (id === 'XmView') return // 首页不可删除
|
// 首页标签固定保留,不允许关闭。
|
||||||
const index = tabs.value.findIndex(t => t.id === id)
|
if (id === HOME_TAB_ID) return
|
||||||
if (index < 0) return
|
|
||||||
const wasActive = activeTabId.value === id
|
|
||||||
tabs.value = tabs.value.filter(t => t.id !== id)
|
|
||||||
|
|
||||||
if (tabs.value.length === 0) {
|
const index = tabs.value.findIndex(tab => tab.id === id)
|
||||||
tabs.value = [...defaultTabs]
|
if (index < 0) return
|
||||||
}
|
|
||||||
|
const wasActive = activeTabId.value === id
|
||||||
|
tabs.value = tabs.value.filter(tab => tab.id !== id)
|
||||||
|
ensureHomeTab()
|
||||||
|
|
||||||
if (wasActive) {
|
if (wasActive) {
|
||||||
const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1))
|
const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1))
|
||||||
activeTabId.value = tabs.value[fallbackIndex]?.id || 'XmView'
|
activeTabId.value = tabs.value[fallbackIndex]?.id ?? HOME_TAB_ID
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeStillExists = tabs.value.some(t => t.id === activeTabId.value)
|
ensureActiveValid()
|
||||||
if (!activeStillExists) {
|
|
||||||
activeTabId.value = tabs.value[0]?.id || 'XmView'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeAllTabs = () => {
|
const closeAllTabs = () => {
|
||||||
tabs.value = tabs.value.filter(t => t.id === 'XmView')
|
tabs.value = createDefaultTabs()
|
||||||
if (tabs.value.length === 0) tabs.value = [...defaultTabs]
|
activeTabId.value = HOME_TAB_ID
|
||||||
activeTabId.value = 'XmView'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeLeftTabs = (targetId: string) => {
|
const closeLeftTabs = (targetId: string) => {
|
||||||
const targetIndex = tabs.value.findIndex(t => t.id === targetId)
|
const targetIndex = tabs.value.findIndex(tab => tab.id === targetId)
|
||||||
if (targetIndex < 0) return
|
if (targetIndex < 0) return
|
||||||
tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index >= targetIndex)
|
tabs.value = tabs.value.filter((tab, index) => tab.id === HOME_TAB_ID || index >= targetIndex)
|
||||||
ensureActiveValid()
|
ensureActiveValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeRightTabs = (targetId: string) => {
|
const closeRightTabs = (targetId: string) => {
|
||||||
const targetIndex = tabs.value.findIndex(t => t.id === targetId)
|
const targetIndex = tabs.value.findIndex(tab => tab.id === targetId)
|
||||||
if (targetIndex < 0) return
|
if (targetIndex < 0) return
|
||||||
tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index <= targetIndex)
|
tabs.value = tabs.value.filter((tab, index) => tab.id === HOME_TAB_ID || index <= targetIndex)
|
||||||
ensureActiveValid()
|
ensureActiveValid()
|
||||||
}
|
}
|
||||||
|
|
||||||
const closeOtherTabs = (targetId: string) => {
|
const closeOtherTabs = (targetId: string) => {
|
||||||
tabs.value = tabs.value.filter(tab => tab.id === 'XmView' || tab.id === targetId)
|
tabs.value = tabs.value.filter(tab => tab.id === HOME_TAB_ID || tab.id === targetId)
|
||||||
if (tabs.value.length === 0) tabs.value = [...defaultTabs]
|
ensureHomeTab()
|
||||||
if (targetId === 'XmView') {
|
activeTabId.value = tabs.value.some(tab => tab.id === targetId) ? targetId : HOME_TAB_ID
|
||||||
activeTabId.value = 'XmView'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
activeTabId.value = tabs.value.some(t => t.id === targetId) ? targetId : 'XmView'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetTabs = () => {
|
const resetTabs = () => {
|
||||||
tabs.value = [...defaultTabs]
|
tabs.value = createDefaultTabs()
|
||||||
activeTabId.value = 'XmView'
|
activeTabId.value = HOME_TAB_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -102,11 +104,12 @@ export const useTabStore = defineStore('tabs', () => {
|
|||||||
closeOtherTabs,
|
closeOtherTabs,
|
||||||
resetTabs
|
resetTabs
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
// --- 关键配置:开启持久化 ---
|
{
|
||||||
persist: {
|
persist: {
|
||||||
key: 'tabs', // 存储在 localStorage 里的 key
|
key: 'tabs',
|
||||||
storage: localStorage, // 也可以改用 sessionStorage
|
storage: localStorage,
|
||||||
pick: ['tabs', 'activeTabId'], // 指定哪些变量需要持久化
|
pick: ['tabs', 'activeTabId']
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|||||||
52
src/sql.ts
52
src/sql.ts
@ -53,8 +53,8 @@ export const serviceList = {
|
|||||||
14: { code: 'D4', name: '专项造价咨询', maxCoe: null, minCoe: null, defCoe: null, desc: '' },
|
14: { code: 'D4', name: '专项造价咨询', maxCoe: null, minCoe: null, defCoe: null, desc: '' },
|
||||||
15: { code: 'D4-1', name: '工程造价顾问', maxCoe: null, minCoe: null, defCoe: 1, desc: '本表系数适用于采用工作量计价法基准预算的调整系数。' },
|
15: { code: 'D4-1', name: '工程造价顾问', maxCoe: null, minCoe: null, defCoe: 1, desc: '本表系数适用于采用工作量计价法基准预算的调整系数。' },
|
||||||
16: { code: 'D4-2', name: '造价政策制(修)订', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
16: { code: 'D4-2', name: '造价政策制(修)订', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
||||||
17: { code: 'D4-3', name: '造价科学与技术研究', maxCoe: null, minCoe: null, defCoe: 1, desc: ''},
|
17: { code: 'D4-3', name: '造价科学与技术研究', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
||||||
18: { code: 'D4-4', name: '定额测定', maxCoe: null, minCoe: null, defCoe: 1, desc: ''},
|
18: { code: 'D4-4', name: '定额测定', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
||||||
19: { code: 'D4-5', name: '造价信息咨询', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
19: { code: 'D4-5', name: '造价信息咨询', maxCoe: null, minCoe: null, defCoe: 1, desc: '' },
|
||||||
20: { code: 'D4-6', name: '造价鉴定', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '本表系数适用于采用规模计价法基准预算的调整系数。' },
|
20: { code: 'D4-6', name: '造价鉴定', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '本表系数适用于采用规模计价法基准预算的调整系数。' },
|
||||||
21: { code: 'D4-7', name: '工程成本测算', maxCoe: null, minCoe: null, defCoe: 0.1, desc: '' },
|
21: { code: 'D4-7', name: '工程成本测算', maxCoe: null, minCoe: null, defCoe: 0.1, desc: '' },
|
||||||
@ -63,8 +63,8 @@ export const serviceList = {
|
|||||||
24: { code: 'D4-10', name: '工程变更费用咨询', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '' },
|
24: { code: 'D4-10', name: '工程变更费用咨询', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '' },
|
||||||
25: { code: 'D4-11', name: '调整估算', maxCoe: 0.2, minCoe: 0.1, defCoe: 0.15, desc: '' },
|
25: { code: 'D4-11', name: '调整估算', maxCoe: 0.2, minCoe: 0.1, defCoe: 0.15, desc: '' },
|
||||||
26: { code: 'D4-12', name: '调整概算', maxCoe: 0.3, minCoe: 0.15, defCoe: 0.225, desc: '本表系数适用于采用规模计价法基准预算的系数;依据其调整时期所在建设阶段和基础资料的不同,其系数取值不同。' },
|
26: { code: 'D4-12', name: '调整概算', maxCoe: 0.3, minCoe: 0.15, defCoe: 0.225, desc: '本表系数适用于采用规模计价法基准预算的系数;依据其调整时期所在建设阶段和基础资料的不同,其系数取值不同。' },
|
||||||
27: { code: 'D4-13', name: '造价检查', maxCoe: null, minCoe: null, defCoe: null, desc: '可按照服务工日数量×服务工日人工单价×综合预算系数;也可按照服务工日数量×服务工日综合预算单价。' ,notshowByzxflxs:true},
|
27: { code: 'D4-13', name: '造价检查', maxCoe: null, minCoe: null, defCoe: null, desc: '可按照服务工日数量×服务工日人工单价×综合预算系数;也可按照服务工日数量×服务工日综合预算单价。', notshowByzxflxs: true },
|
||||||
28: { code: 'D4-14', name: '其他专项咨询', maxCoe: null, minCoe: null, defCoe: null, desc: '可参照相同或相似服务的系数。' ,notshowByzxflxs:true},
|
28: { code: 'D4-14', name: '其他专项咨询', maxCoe: null, minCoe: null, defCoe: null, desc: '可参照相同或相似服务的系数。', notshowByzxflxs: true },
|
||||||
};
|
};
|
||||||
//basicParam预算基数
|
//basicParam预算基数
|
||||||
|
|
||||||
@ -124,7 +124,7 @@ export const expertList = {
|
|||||||
7: { code: 'C9-3-2', name: '一级造价工程师且具备高级工程师资格', maxPrice: 2000, minPrice: 1800, defPrice: 1900, manageCoe: 2.05 },
|
7: { code: 'C9-3-2', name: '一级造价工程师且具备高级工程师资格', maxPrice: 2000, minPrice: 1800, defPrice: 1900, manageCoe: 2.05 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const costScaleCal = [
|
const costScaleCal = [
|
||||||
{ code: 'C1-1', staLine: 0, endLine: 100, basic: { staPrice: 0, rate: 0.01 }, optional: { staPrice: 0, rate: 0.002 } },
|
{ code: 'C1-1', staLine: 0, endLine: 100, basic: { staPrice: 0, rate: 0.01 }, optional: { staPrice: 0, rate: 0.002 } },
|
||||||
{ code: 'C1-2', staLine: 100, endLine: 300, basic: { staPrice: 10000, rate: 0.008 }, optional: { staPrice: 2000, rate: 0.0016 } },
|
{ code: 'C1-2', staLine: 100, endLine: 300, basic: { staPrice: 10000, rate: 0.008 }, optional: { staPrice: 2000, rate: 0.0016 } },
|
||||||
{ code: 'C1-3', staLine: 300, endLine: 500, basic: { staPrice: 26000, rate: 0.005 }, optional: { staPrice: 5200, rate: 0.001 } },
|
{ code: 'C1-3', staLine: 300, endLine: 500, basic: { staPrice: 26000, rate: 0.005 }, optional: { staPrice: 5200, rate: 0.001 } },
|
||||||
@ -144,7 +144,7 @@ export const expertList = {
|
|||||||
{ code: 'C1-17', staLine: 1000000, endLine: null, basic: { staPrice: 5906000, rate: 0.00025 }, optional: { staPrice: 1181200, rate: 0.00005 } },
|
{ code: 'C1-17', staLine: 1000000, endLine: null, basic: { staPrice: 5906000, rate: 0.00025 }, optional: { staPrice: 1181200, rate: 0.00005 } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const areaScaleCal = [
|
const areaScaleCal = [
|
||||||
{ code: 'C2-1', staLine: 0, endLine: 50, basic: { staPrice: 0, rate: 200 }, optional: { staPrice: 0, rate: 40 } },
|
{ code: 'C2-1', staLine: 0, endLine: 50, basic: { staPrice: 0, rate: 200 }, optional: { staPrice: 0, rate: 40 } },
|
||||||
{ code: 'C2-2', staLine: 50, endLine: 100, basic: { staPrice: 10000, rate: 160 }, optional: { staPrice: 2000, rate: 32 } },
|
{ code: 'C2-2', staLine: 50, endLine: 100, basic: { staPrice: 10000, rate: 160 }, optional: { staPrice: 2000, rate: 32 } },
|
||||||
{ code: 'C2-3', staLine: 100, endLine: 500, basic: { staPrice: 18000, rate: 120 }, optional: { staPrice: 3600, rate: 24 } },
|
{ code: 'C2-3', staLine: 100, endLine: 500, basic: { staPrice: 18000, rate: 120 }, optional: { staPrice: 3600, rate: 24 } },
|
||||||
@ -238,7 +238,7 @@ export function getBasicFeeFromScale(scaleValue: unknown, scaleType: 'cost' | 'a
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function exportFile(fileName, data) {
|
export async function exportFile(fileName, data) {
|
||||||
if (window.showSaveFilePicker) {
|
if (window.showSaveFilePicker) {
|
||||||
const handle = await window.showSaveFilePicker({
|
const handle = await window.showSaveFilePicker({
|
||||||
suggestedName: fileName,
|
suggestedName: fileName,
|
||||||
@ -817,15 +817,15 @@ function cloneCellValue(value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//demo
|
||||||
let data1 = {
|
let data1 = {
|
||||||
name: 'test001',
|
name: 'test001',//项目名称
|
||||||
fee: 10000,
|
fee: 10000, //所有合同段总费用
|
||||||
scale: [
|
scale: [//项目明细aggrid数据
|
||||||
{
|
{
|
||||||
major: 0,
|
major: 0, //专业id,对应专业majorList中的key
|
||||||
cost: 100000,
|
cost: 100000,//造价金额
|
||||||
area: 200,
|
area: 200,//用地面积
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
major: 1,
|
major: 1,
|
||||||
@ -833,15 +833,15 @@ let data1 = {
|
|||||||
area: 200,
|
area: 200,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
contracts: [
|
contracts: [//合同段数据
|
||||||
{
|
{
|
||||||
name: 'A合同段',
|
name: 'A合同段',//合同段名称
|
||||||
fee: 10000,
|
fee: 10000,//合同段费用(该合同段咨询服务zxfw.vue里面aggrid的合同预算行的小计)
|
||||||
scale: [
|
scale: [//合同段明细aggrid数据(htinfo.vue)
|
||||||
{
|
{
|
||||||
major: 0,
|
major: 0, //专业id,对应专业majorList中的key
|
||||||
cost: 100000,
|
cost: 100000,//造价金额
|
||||||
area: 200,
|
area: 200,//用地面积
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
major: 1,
|
major: 1,
|
||||||
@ -849,12 +849,12 @@ let data1 = {
|
|||||||
area: 200,
|
area: 200,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
services: [
|
services: [//咨询服务数据(zxfw.vue里面aggrid的数据,不用输出合同预算)
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0, //服务id,对应serviceList中的key
|
||||||
fee: 100000,
|
fee: 100000 ,//服务费用(该服务咨询服务小计)
|
||||||
method1: { // 投资规模法
|
method1: { // 投资规模法InvestmentScalePricingPane.vue的数据
|
||||||
cost: 100000,
|
cost: 100000, //zxfw.vue里面aggrid该数据的投资规模法金额
|
||||||
basicFee: 200,
|
basicFee: 200,
|
||||||
basicFee_basic: 200,
|
basicFee_basic: 200,
|
||||||
basicFee_optional: 0,
|
basicFee_optional: 0,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user