优化
This commit is contained in:
parent
3950057707
commit
a10359f7e0
@ -3,9 +3,5 @@ import Tab from '@/layout/tab.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<tab></tab>
|
||||
<Tab />
|
||||
</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 localforage from 'localforage'
|
||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
|
||||
|
||||
interface DictItem {
|
||||
@ -41,12 +42,6 @@ const props = defineProps<{
|
||||
const detailRows = ref<FactorRow[]>([])
|
||||
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) => {
|
||||
if (value == null || value === '') return ''
|
||||
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="flex items-center justify-between border-b px-4 py-3">
|
||||
<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 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="flex items-center justify-between border-b px-4 py-3">
|
||||
<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 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 { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||
import { formatThousands } from '@/lib/numberFormat'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||
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) => {
|
||||
if (value === '' || value == null) return null
|
||||
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="flex items-center justify-between border-b px-4 py-3">
|
||||
<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 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 type { ColDef } from 'ag-grid-community'
|
||||
import localforage from 'localforage'
|
||||
import { getBasicFeeFromScale, majorList } from '@/sql'
|
||||
import { majorList } from '@/sql'
|
||||
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 { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
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) => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
@ -285,15 +281,15 @@ const formatReadonlyMoney = (params: any) => {
|
||||
return formatThousands(roundTo(params.value, 2))
|
||||
}
|
||||
|
||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) => {
|
||||
const result = getBasicFeeFromScale(row?.amount, 'cost')
|
||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
||||
}
|
||||
const getBenchmarkBudgetByAmount = (row?: Pick<DetailRow, 'amount'>) =>
|
||||
getBenchmarkBudgetByScale(row?.amount, 'cost')
|
||||
|
||||
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
const benchmarkBudget = getBenchmarkBudgetByAmount(row)
|
||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByAmount(row),
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
}
|
||||
|
||||
const columnDefs: ColDef<DetailRow>[] = [
|
||||
|
||||
@ -3,13 +3,15 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
import { AgGridVue } from 'ag-grid-vue3'
|
||||
import type { ColDef } from 'ag-grid-community'
|
||||
import localforage from 'localforage'
|
||||
import { getBasicFeeFromScale, majorList } from '@/sql'
|
||||
import { majorList } from '@/sql'
|
||||
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 { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||
import { loadConsultCategoryFactorMap, loadMajorFactorMap } from '@/lib/xmFactorDefaults'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
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) => {
|
||||
if (!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')) {
|
||||
return '点击输入'
|
||||
@ -280,15 +276,15 @@ const formatReadonlyMoney = (params: any) => {
|
||||
return formatThousands(roundTo(params.value, 2))
|
||||
}
|
||||
|
||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) => {
|
||||
const result = getBasicFeeFromScale(row?.landArea, 'area')
|
||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
||||
}
|
||||
const getBenchmarkBudgetByLandArea = (row?: Pick<DetailRow, 'landArea'>) =>
|
||||
getBenchmarkBudgetByScale(row?.landArea, 'area')
|
||||
|
||||
const getBudgetFee = (row?: Pick<DetailRow, 'landArea' | 'majorFactor' | 'consultCategoryFactor'>) => {
|
||||
const benchmarkBudget = getBenchmarkBudgetByLandArea(row)
|
||||
if (benchmarkBudget == null || row?.majorFactor == null || row?.consultCategoryFactor == null) return null
|
||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByLandArea(row),
|
||||
majorFactor: row?.majorFactor,
|
||||
consultCategoryFactor: row?.consultCategoryFactor
|
||||
})
|
||||
}
|
||||
|
||||
const formatEditableFlexibleNumber = (params: any) => {
|
||||
|
||||
@ -7,6 +7,7 @@ import { taskList } from '@/sql'
|
||||
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
|
||||
import { decimalAggSum, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||
import { formatThousands } from '@/lib/numberFormat'
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
import { syncPricingTotalToZxFw, ZXFW_RELOAD_SERVICE_KEY } from '@/lib/zxFwPricingSync'
|
||||
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
|
||||
import { loadConsultCategoryFactorMap } from '@/lib/xmFactorDefaults'
|
||||
@ -180,12 +181,8 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
|
||||
})
|
||||
}
|
||||
|
||||
const parseNumberOrNull = (value: unknown) => {
|
||||
if (value === '' || value == null) return null
|
||||
const normalized = typeof value === 'string' ? value.replace(/[^0-9.\-]/g, '') : value
|
||||
const v = Number(normalized)
|
||||
return Number.isFinite(v) ? v : null
|
||||
}
|
||||
const parseSanitizedNumberOrNull = (value: unknown) =>
|
||||
parseNumberOrNull(value, { sanitize: true })
|
||||
|
||||
const calcServiceFee = (row: DetailRow | undefined) => {
|
||||
if (!row || isNoTaskRow(row)) return null
|
||||
@ -281,7 +278,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
!isNoTaskRow(params.data) &&
|
||||
(params.value == null || params.value === '')
|
||||
},
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
valueFormatter: params => {
|
||||
if (isNoTaskRow(params.data)) return '无'
|
||||
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 === '')
|
||||
},
|
||||
aggFunc: decimalAggSum,
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
valueFormatter: formatEditableNumber
|
||||
},
|
||||
{
|
||||
@ -325,7 +322,7 @@ const columnDefs: ColDef<DetailRow>[] = [
|
||||
!isNoTaskRow(params.data) &&
|
||||
(params.value == null || params.value === '')
|
||||
},
|
||||
valueParser: params => parseNumberOrNull(params.newValue),
|
||||
valueParser: params => parseSanitizedNumberOrNull(params.newValue),
|
||||
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="flex items-center justify-between border-b px-4 py-3">
|
||||
<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 v-if="isWorkloadMethodApplicable" class="ag-theme-quartz h-full min-h-0 w-full flex-1">
|
||||
|
||||
@ -433,7 +433,7 @@ const scrollToGridSection = () => {
|
||||
>
|
||||
项目明细
|
||||
</h3>
|
||||
<div class="text-xs text-muted-foreground">导入导出</div>
|
||||
<div class="text-xs text-muted-foreground"></div>
|
||||
</div>
|
||||
|
||||
<div ref="agGridRef" class="ag-theme-quartz w-full flex-1 min-h-0">
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Decimal from 'decimal.js'
|
||||
import { isFiniteNumber } from '@/lib/number'
|
||||
|
||||
type MaybeNumber = number | null | undefined
|
||||
type DecimalInput = Decimal.Value
|
||||
@ -8,30 +9,26 @@ export const toDecimal = (value: DecimalInput) => new Decimal(value)
|
||||
export const roundTo = (value: DecimalInput, decimalPlaces = 2) =>
|
||||
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)
|
||||
for (const value of values) {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
||||
if (!isFiniteNumber(value)) continue
|
||||
total = total.plus(value)
|
||||
}
|
||||
return total.toNumber()
|
||||
}
|
||||
|
||||
export const addNumbers = (...values: MaybeNumber[]) => sumFiniteValues(values)
|
||||
|
||||
export const sumByNumber = <T>(list: T[], pick: (item: T) => MaybeNumber) => {
|
||||
let total = new Decimal(0)
|
||||
for (const item of list) {
|
||||
const value = pick(item)
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
||||
if (!isFiniteNumber(value)) continue
|
||||
total = total.plus(value)
|
||||
}
|
||||
return total.toNumber()
|
||||
}
|
||||
|
||||
export const decimalAggSum = (params: { values?: unknown[] }) => {
|
||||
let total = new Decimal(0)
|
||||
for (const value of params.values || []) {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value)) continue
|
||||
total = total.plus(value)
|
||||
}
|
||||
return total.toNumber()
|
||||
}
|
||||
export const decimalAggSum = (params: { values?: unknown[] }) =>
|
||||
sumFiniteValues(params.values || [])
|
||||
|
||||
@ -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 = {
|
||||
style: "solid", // 虚线改实线更简洁,也可保留 dotted 但建议用 solid
|
||||
width: 0.3, // 更细的边框,减少视觉干扰
|
||||
color: "#d3d3d3" // 浅灰色边框,清新不刺眼
|
||||
};
|
||||
style: 'solid',
|
||||
width: 0.3,
|
||||
color: '#d3d3d3'
|
||||
}
|
||||
|
||||
// 简洁清新风格的主题配置
|
||||
export const myTheme = themeQuartz.withParams({
|
||||
// 核心:移除外边框,减少视觉包裹感
|
||||
wrapperBorder: false,
|
||||
|
||||
// 表头样式(柔和浅蓝,无加粗,更轻盈)
|
||||
headerBackgroundColor: "#f0f2f3", // 极浅的背景色,替代深一点的 #e7f3fc
|
||||
headerTextColor: "#374151", // 深灰色文字,比纯黑更柔和
|
||||
headerFontSize: 15, // 字体稍大一点,更易读
|
||||
headerFontWeight: "normal", // 取消加粗,降低视觉重量
|
||||
|
||||
// 行/列/表头边框(统一浅灰细边框)
|
||||
headerBackgroundColor: '#f0f2f3',
|
||||
headerTextColor: '#374151',
|
||||
headerFontSize: 15,
|
||||
headerFontWeight: 'normal',
|
||||
rowBorder: borderConfig,
|
||||
columnBorder: borderConfig,
|
||||
headerRowBorder: borderConfig,
|
||||
dataBackgroundColor: '#fefefe'
|
||||
})
|
||||
|
||||
// 可选:偶数行背景色(轻微区分,更清新)
|
||||
dataBackgroundColor: "#fefefe"
|
||||
});
|
||||
export const gridOptions: GridOptions<any> = {
|
||||
export const gridOptions: GridOptions = {
|
||||
treeData: true,
|
||||
animateRows: true,
|
||||
tooltipShowMode: 'whenTruncated',
|
||||
@ -32,13 +27,9 @@ export const gridOptions: GridOptions<any> = {
|
||||
singleClickEdit: true,
|
||||
suppressClickEdit: false,
|
||||
suppressContextMenu: false,
|
||||
// autoSizeStrategy: {
|
||||
// type: 'fitGridWidth',
|
||||
// defaultMinWidth: 100,
|
||||
// },
|
||||
groupDefaultExpanded: -1,
|
||||
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('/') ?? ''),
|
||||
getDataPath: data => data.path,
|
||||
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 @@
|
||||
import { parseNumberOrNull } from '@/lib/number'
|
||||
|
||||
const fixedFormatterCache = new Map<number, Intl.NumberFormat>()
|
||||
const flexibleFormatterCache = new Map<number, Intl.NumberFormat>()
|
||||
|
||||
const getFixedFormatter = (fractionDigits: number) => {
|
||||
if (!fixedFormatterCache.has(fractionDigits)) {
|
||||
fixedFormatterCache.set(
|
||||
fractionDigits,
|
||||
new Intl.NumberFormat('zh-CN', {
|
||||
minimumFractionDigits: fractionDigits,
|
||||
maximumFractionDigits: fractionDigits
|
||||
})
|
||||
)
|
||||
}
|
||||
return fixedFormatterCache.get(fractionDigits)!
|
||||
}
|
||||
|
||||
const getFlexibleFormatter = (maxFractionDigits: number) => {
|
||||
if (!flexibleFormatterCache.has(maxFractionDigits)) {
|
||||
flexibleFormatterCache.set(
|
||||
maxFractionDigits,
|
||||
new Intl.NumberFormat('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: maxFractionDigits
|
||||
})
|
||||
)
|
||||
}
|
||||
return flexibleFormatterCache.get(maxFractionDigits)!
|
||||
}
|
||||
|
||||
export const formatThousands = (value: unknown, fractionDigits = 2) => {
|
||||
if (value === '' || value == null) return ''
|
||||
const numericValue = Number(value)
|
||||
if (!Number.isFinite(numericValue)) return ''
|
||||
return numericValue.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: fractionDigits,
|
||||
maximumFractionDigits: fractionDigits
|
||||
})
|
||||
const numericValue = parseNumberOrNull(value)
|
||||
if (numericValue == null) return ''
|
||||
return getFixedFormatter(fractionDigits).format(numericValue)
|
||||
}
|
||||
|
||||
export const formatThousandsFlexible = (value: unknown, maxFractionDigits = 20) => {
|
||||
if (value === '' || value == null) return ''
|
||||
const numericValue = Number(value)
|
||||
if (!Number.isFinite(numericValue)) return ''
|
||||
return numericValue.toLocaleString('zh-CN', {
|
||||
minimumFractionDigits: 0,
|
||||
maximumFractionDigits: maxFractionDigits
|
||||
})
|
||||
const numericValue = parseNumberOrNull(value)
|
||||
if (numericValue == null) return ''
|
||||
return getFlexibleFormatter(maxFractionDigits).format(numericValue)
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import localforage from 'localforage'
|
||||
import { expertList, getBasicFeeFromScale, majorList, serviceList, taskList } from '@/sql'
|
||||
import { addNumbers, roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||
import { expertList, majorList, serviceList, taskList } from '@/sql'
|
||||
import { roundTo, sumByNumber, toDecimal } from '@/lib/decimal'
|
||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||
import { getBenchmarkBudgetByScale, getScaleBudgetFee } from '@/lib/pricingScaleFee'
|
||||
|
||||
interface StoredDetailRowsState<T = any> {
|
||||
detailRows?: T[]
|
||||
@ -58,12 +60,17 @@ export interface PricingMethodTotals {
|
||||
hourly: number | null
|
||||
}
|
||||
|
||||
const toFiniteNumberOrNull = (value: unknown): number | null =>
|
||||
typeof value === 'number' && Number.isFinite(value) ? value : null
|
||||
|
||||
const hasOwn = (obj: unknown, key: string) =>
|
||||
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 service = (serviceList as Record<string, ServiceLite | undefined>)[String(serviceId)]
|
||||
return toFiniteNumberOrNull(service?.defCoe)
|
||||
@ -95,10 +102,7 @@ const mergeScaleRows = (
|
||||
serviceId: string | number,
|
||||
rowsFromDb: Array<Partial<ScaleRow> & Pick<ScaleRow, 'id'>> | undefined
|
||||
): ScaleRow[] => {
|
||||
const dbValueMap = new Map<string, Partial<ScaleRow> & Pick<ScaleRow, 'id'>>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
dbValueMap.set(String(row.id), row)
|
||||
}
|
||||
const dbValueMap = toRowMap(rowsFromDb)
|
||||
|
||||
const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId)
|
||||
return buildDefaultScaleRows(serviceId).map(row => {
|
||||
@ -122,26 +126,26 @@ const mergeScaleRows = (
|
||||
})
|
||||
}
|
||||
|
||||
const getBenchmarkBudgetByAmount = (amount: MaybeNumber) => {
|
||||
const result = getBasicFeeFromScale(amount, 'cost')
|
||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
||||
}
|
||||
const getBenchmarkBudgetByAmount = (amount: MaybeNumber) =>
|
||||
getBenchmarkBudgetByScale(amount, 'cost')
|
||||
|
||||
const getBenchmarkBudgetByLandArea = (landArea: MaybeNumber) => {
|
||||
const result = getBasicFeeFromScale(landArea, 'area')
|
||||
return result ? roundTo(addNumbers(result.basic, result.optional), 2) : null
|
||||
}
|
||||
const getBenchmarkBudgetByLandArea = (landArea: MaybeNumber) =>
|
||||
getBenchmarkBudgetByScale(landArea, 'area')
|
||||
|
||||
const getInvestmentBudgetFee = (row: ScaleRow) => {
|
||||
const benchmarkBudget = getBenchmarkBudgetByAmount(row.amount)
|
||||
if (benchmarkBudget == null || row.majorFactor == null || row.consultCategoryFactor == null) return null
|
||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByAmount(row.amount),
|
||||
majorFactor: row.majorFactor,
|
||||
consultCategoryFactor: row.consultCategoryFactor
|
||||
})
|
||||
}
|
||||
|
||||
const getLandBudgetFee = (row: ScaleRow) => {
|
||||
const benchmarkBudget = getBenchmarkBudgetByLandArea(row.landArea)
|
||||
if (benchmarkBudget == null || row.majorFactor == null || row.consultCategoryFactor == null) return null
|
||||
return roundTo(toDecimal(benchmarkBudget).mul(row.majorFactor).mul(row.consultCategoryFactor), 2)
|
||||
return getScaleBudgetFee({
|
||||
benchmarkBudget: getBenchmarkBudgetByLandArea(row.landArea),
|
||||
majorFactor: row.majorFactor,
|
||||
consultCategoryFactor: row.consultCategoryFactor
|
||||
})
|
||||
}
|
||||
|
||||
const getTaskEntriesByServiceId = (serviceId: string | number) =>
|
||||
@ -164,10 +168,7 @@ const mergeWorkloadRows = (
|
||||
serviceId: string | number,
|
||||
rowsFromDb: Array<Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>> | undefined
|
||||
): WorkloadRow[] => {
|
||||
const dbValueMap = new Map<string, Partial<WorkloadRow> & Pick<WorkloadRow, 'id'>>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
dbValueMap.set(String(row.id), row)
|
||||
}
|
||||
const dbValueMap = toRowMap(rowsFromDb)
|
||||
|
||||
return buildDefaultWorkloadRows(serviceId).map(row => {
|
||||
const fromDb = dbValueMap.get(row.id)
|
||||
@ -216,10 +217,7 @@ const buildDefaultHourlyRows = (): HourlyRow[] =>
|
||||
const mergeHourlyRows = (
|
||||
rowsFromDb: Array<Partial<HourlyRow> & Pick<HourlyRow, 'id'>> | undefined
|
||||
): HourlyRow[] => {
|
||||
const dbValueMap = new Map<string, Partial<HourlyRow> & Pick<HourlyRow, 'id'>>()
|
||||
for (const row of rowsFromDb || []) {
|
||||
dbValueMap.set(String(row.id), row)
|
||||
}
|
||||
const dbValueMap = toRowMap(rowsFromDb)
|
||||
|
||||
return buildDefaultHourlyRows().map(row => {
|
||||
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)
|
||||
}
|
||||
|
||||
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: {
|
||||
contractId: string
|
||||
serviceId: string | number
|
||||
@ -258,20 +270,11 @@ export const getPricingMethodTotalsForService = async (params: {
|
||||
localforage.getItem<StoredDetailRowsState>(htDbKey)
|
||||
])
|
||||
|
||||
const investRows =
|
||||
investData?.detailRows != null
|
||||
? mergeScaleRows(serviceId, investData.detailRows as any)
|
||||
: htData?.detailRows != null
|
||||
? mergeScaleRows(serviceId, htData.detailRows as any)
|
||||
: buildDefaultScaleRows(serviceId)
|
||||
// 优先使用对应计费页的数据;不存在时回退合同段规模信息,再回退默认字典行。
|
||||
const investRows = resolveScaleRows(serviceId, investData, htData)
|
||||
const investScale = sumByNumber(investRows, row => getInvestmentBudgetFee(row))
|
||||
|
||||
const landRows =
|
||||
landData?.detailRows != null
|
||||
? mergeScaleRows(serviceId, landData.detailRows as any)
|
||||
: htData?.detailRows != null
|
||||
? mergeScaleRows(serviceId, htData.detailRows as any)
|
||||
: buildDefaultScaleRows(serviceId)
|
||||
const landRows = resolveScaleRows(serviceId, landData, htData)
|
||||
const landScale = sumByNumber(landRows, row => getLandBudgetFee(row))
|
||||
|
||||
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 { majorList, serviceList } from '@/sql'
|
||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||
|
||||
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
|
||||
const MAJOR_FACTOR_KEY = 'xm-major-factor-v1'
|
||||
@ -20,9 +21,6 @@ type 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 map = new Map<string, number | null>()
|
||||
for (const [id, item] of Object.entries(dict)) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import localforage from 'localforage'
|
||||
import { toFiniteNumberOrNull } from '@/lib/number'
|
||||
|
||||
export type ZxFwPricingField = 'investScale' | 'landScale' | 'workload' | 'hourly'
|
||||
|
||||
@ -18,9 +19,6 @@ interface ZxFwState {
|
||||
|
||||
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: {
|
||||
contractId: string
|
||||
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 {
|
||||
ModuleRegistry,
|
||||
CellStyleModule,
|
||||
ClientSideRowModelModule,
|
||||
ColumnAutoSizeModule,
|
||||
CsvExportModule,
|
||||
LargeTextEditorModule,
|
||||
LocaleModule,
|
||||
ModuleRegistry,
|
||||
NumberEditorModule,
|
||||
PinnedRowModule,
|
||||
RowAutoHeightModule,
|
||||
TextEditorModule,
|
||||
TooltipModule,
|
||||
UndoRedoEditModule,ValidationModule,LocaleModule ,CellStyleModule ,RowAutoHeightModule
|
||||
UndoRedoEditModule,
|
||||
ValidationModule
|
||||
} from 'ag-grid-community'
|
||||
import {
|
||||
AggregationModule,
|
||||
@ -24,18 +24,26 @@ import {
|
||||
RowGroupingModule,
|
||||
TreeDataModule
|
||||
} from 'ag-grid-enterprise'
|
||||
LicenseManager.setLicenseKey("[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b")
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' // 引入
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
ModuleRegistry.registerModules([
|
||||
import { createPinia } from 'pinia'
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import './style.css'
|
||||
|
||||
LicenseManager.setLicenseKey(
|
||||
'[v3][RELEASE][0102]_NDg2Njc4MzY3MDgzNw==16d78ca762fb5d2ff740aed081e2af7b'
|
||||
)
|
||||
|
||||
const AG_GRID_MODULES = [
|
||||
ClientSideRowModelModule,
|
||||
ColumnAutoSizeModule,
|
||||
CsvExportModule,
|
||||
TextEditorModule,
|
||||
NumberEditorModule,RowAutoHeightModule,
|
||||
NumberEditorModule,
|
||||
RowAutoHeightModule,
|
||||
LargeTextEditorModule,
|
||||
UndoRedoEditModule,CellStyleModule ,
|
||||
UndoRedoEditModule,
|
||||
CellStyleModule,
|
||||
PinnedRowModule,
|
||||
TooltipModule,
|
||||
TreeDataModule,
|
||||
@ -44,8 +52,15 @@ ModuleRegistry.registerModules([
|
||||
MenuModule,
|
||||
CellSelectionModule,
|
||||
ContextMenuModule,
|
||||
ClipboardModule,LocaleModule ,
|
||||
ClipboardModule,
|
||||
LocaleModule,
|
||||
ValidationModule
|
||||
])
|
||||
]
|
||||
|
||||
const pinia = createPinia()
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
// 在应用启动时一次性注册 AG Grid 运行所需模块。
|
||||
ModuleRegistry.registerModules(AG_GRID_MODULES)
|
||||
|
||||
createApp(App).use(pinia).mount('#app')
|
||||
|
||||
199
src/pinia/tab.ts
199
src/pinia/tab.ts
@ -1,112 +1,115 @@
|
||||
// src/stores/tab.ts
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useTabStore = defineStore('tabs', () => {
|
||||
interface TabItem<T = Record<string, any>> {
|
||||
id: string; // 标签唯一标识
|
||||
title: string; // 标签标题
|
||||
componentName: string; // 组件名称
|
||||
props?: T; // 传递给组件的 props(可选,泛型适配不同组件)
|
||||
export interface TabItem<TProps = Record<string, unknown>> {
|
||||
id: string
|
||||
title: string
|
||||
componentName: string
|
||||
props?: TProps
|
||||
}
|
||||
const defaultTabs :TabItem[]= [
|
||||
{ id: 'XmView', title: '项目卡片', componentName: 'XmView' }
|
||||
]
|
||||
|
||||
const tabs = ref([
|
||||
...defaultTabs
|
||||
])
|
||||
const activeTabId = ref('XmView')
|
||||
const HOME_TAB_ID = 'XmView'
|
||||
const DEFAULT_TAB: TabItem = {
|
||||
id: HOME_TAB_ID,
|
||||
title: '项目卡片',
|
||||
componentName: HOME_TAB_ID
|
||||
}
|
||||
|
||||
const ensureActiveValid = () => {
|
||||
const activeExists = tabs.value.some(t => t.id === activeTabId.value)
|
||||
if (!activeExists) {
|
||||
activeTabId.value = tabs.value[0]?.id || 'XmView'
|
||||
}
|
||||
}
|
||||
const createDefaultTabs = (): TabItem[] => [{ ...DEFAULT_TAB }]
|
||||
|
||||
const openTab = (config: { id: string; title: string; componentName: string; props?: any }) => {
|
||||
const exists = tabs.value.some(t => t.id === config.id)
|
||||
if (!exists) {
|
||||
tabs.value = [...tabs.value, config]
|
||||
}
|
||||
activeTabId.value = config.id
|
||||
}
|
||||
export const useTabStore = defineStore(
|
||||
'tabs',
|
||||
() => {
|
||||
const tabs = ref<TabItem[]>(createDefaultTabs())
|
||||
const activeTabId = ref(HOME_TAB_ID)
|
||||
|
||||
const removeTab = (id: string) => {
|
||||
if (id === 'XmView') return // 首页不可删除
|
||||
const index = tabs.value.findIndex(t => t.id === id)
|
||||
if (index < 0) return
|
||||
const wasActive = activeTabId.value === id
|
||||
tabs.value = tabs.value.filter(t => t.id !== id)
|
||||
|
||||
if (tabs.value.length === 0) {
|
||||
tabs.value = [...defaultTabs]
|
||||
const ensureHomeTab = () => {
|
||||
if (tabs.value.some(tab => tab.id === HOME_TAB_ID)) return
|
||||
tabs.value = [...createDefaultTabs(), ...tabs.value]
|
||||
}
|
||||
|
||||
if (wasActive) {
|
||||
const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1))
|
||||
activeTabId.value = tabs.value[fallbackIndex]?.id || 'XmView'
|
||||
return
|
||||
const ensureActiveValid = () => {
|
||||
ensureHomeTab()
|
||||
if (tabs.value.length === 0) tabs.value = createDefaultTabs()
|
||||
if (!tabs.value.some(tab => tab.id === activeTabId.value)) {
|
||||
activeTabId.value = tabs.value[0]?.id ?? HOME_TAB_ID
|
||||
}
|
||||
}
|
||||
|
||||
const activeStillExists = tabs.value.some(t => t.id === activeTabId.value)
|
||||
if (!activeStillExists) {
|
||||
activeTabId.value = tabs.value[0]?.id || 'XmView'
|
||||
const openTab = (config: TabItem) => {
|
||||
if (!tabs.value.some(tab => tab.id === config.id)) {
|
||||
tabs.value = [...tabs.value, config]
|
||||
}
|
||||
activeTabId.value = config.id
|
||||
}
|
||||
|
||||
const removeTab = (id: string) => {
|
||||
// 首页标签固定保留,不允许关闭。
|
||||
if (id === HOME_TAB_ID) return
|
||||
|
||||
const index = tabs.value.findIndex(tab => tab.id === id)
|
||||
if (index < 0) return
|
||||
|
||||
const wasActive = activeTabId.value === id
|
||||
tabs.value = tabs.value.filter(tab => tab.id !== id)
|
||||
ensureHomeTab()
|
||||
|
||||
if (wasActive) {
|
||||
const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1))
|
||||
activeTabId.value = tabs.value[fallbackIndex]?.id ?? HOME_TAB_ID
|
||||
return
|
||||
}
|
||||
|
||||
ensureActiveValid()
|
||||
}
|
||||
|
||||
const closeAllTabs = () => {
|
||||
tabs.value = createDefaultTabs()
|
||||
activeTabId.value = HOME_TAB_ID
|
||||
}
|
||||
|
||||
const closeLeftTabs = (targetId: string) => {
|
||||
const targetIndex = tabs.value.findIndex(tab => tab.id === targetId)
|
||||
if (targetIndex < 0) return
|
||||
tabs.value = tabs.value.filter((tab, index) => tab.id === HOME_TAB_ID || index >= targetIndex)
|
||||
ensureActiveValid()
|
||||
}
|
||||
|
||||
const closeRightTabs = (targetId: string) => {
|
||||
const targetIndex = tabs.value.findIndex(tab => tab.id === targetId)
|
||||
if (targetIndex < 0) return
|
||||
tabs.value = tabs.value.filter((tab, index) => tab.id === HOME_TAB_ID || index <= targetIndex)
|
||||
ensureActiveValid()
|
||||
}
|
||||
|
||||
const closeOtherTabs = (targetId: string) => {
|
||||
tabs.value = tabs.value.filter(tab => tab.id === HOME_TAB_ID || tab.id === targetId)
|
||||
ensureHomeTab()
|
||||
activeTabId.value = tabs.value.some(tab => tab.id === targetId) ? targetId : HOME_TAB_ID
|
||||
}
|
||||
|
||||
const resetTabs = () => {
|
||||
tabs.value = createDefaultTabs()
|
||||
activeTabId.value = HOME_TAB_ID
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTabId,
|
||||
openTab,
|
||||
removeTab,
|
||||
closeAllTabs,
|
||||
closeLeftTabs,
|
||||
closeRightTabs,
|
||||
closeOtherTabs,
|
||||
resetTabs
|
||||
}
|
||||
},
|
||||
{
|
||||
persist: {
|
||||
key: 'tabs',
|
||||
storage: localStorage,
|
||||
pick: ['tabs', 'activeTabId']
|
||||
}
|
||||
}
|
||||
|
||||
const closeAllTabs = () => {
|
||||
tabs.value = tabs.value.filter(t => t.id === 'XmView')
|
||||
if (tabs.value.length === 0) tabs.value = [...defaultTabs]
|
||||
activeTabId.value = 'XmView'
|
||||
}
|
||||
|
||||
const closeLeftTabs = (targetId: string) => {
|
||||
const targetIndex = tabs.value.findIndex(t => t.id === targetId)
|
||||
if (targetIndex < 0) return
|
||||
tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index >= targetIndex)
|
||||
ensureActiveValid()
|
||||
}
|
||||
|
||||
const closeRightTabs = (targetId: string) => {
|
||||
const targetIndex = tabs.value.findIndex(t => t.id === targetId)
|
||||
if (targetIndex < 0) return
|
||||
tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index <= targetIndex)
|
||||
ensureActiveValid()
|
||||
}
|
||||
|
||||
const closeOtherTabs = (targetId: string) => {
|
||||
tabs.value = tabs.value.filter(tab => tab.id === 'XmView' || tab.id === targetId)
|
||||
if (tabs.value.length === 0) tabs.value = [...defaultTabs]
|
||||
if (targetId === 'XmView') {
|
||||
activeTabId.value = 'XmView'
|
||||
return
|
||||
}
|
||||
activeTabId.value = tabs.value.some(t => t.id === targetId) ? targetId : 'XmView'
|
||||
}
|
||||
|
||||
const resetTabs = () => {
|
||||
tabs.value = [...defaultTabs]
|
||||
activeTabId.value = 'XmView'
|
||||
}
|
||||
|
||||
return {
|
||||
tabs,
|
||||
activeTabId,
|
||||
openTab,
|
||||
removeTab,
|
||||
closeAllTabs,
|
||||
closeLeftTabs,
|
||||
closeRightTabs,
|
||||
closeOtherTabs,
|
||||
resetTabs
|
||||
}
|
||||
}, {
|
||||
// --- 关键配置:开启持久化 ---
|
||||
persist: {
|
||||
key: 'tabs', // 存储在 localStorage 里的 key
|
||||
storage: localStorage, // 也可以改用 sessionStorage
|
||||
pick: ['tabs', 'activeTabId'], // 指定哪些变量需要持久化
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
54
src/sql.ts
54
src/sql.ts
@ -53,8 +53,8 @@ export const serviceList = {
|
||||
14: { code: 'D4', name: '专项造价咨询', maxCoe: null, minCoe: null, defCoe: null, 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: '' },
|
||||
17: { code: 'D4-3', name: '造价科学与技术研究', maxCoe: null, minCoe: null, defCoe: 1, desc: ''},
|
||||
18: { code: 'D4-4', 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: '' },
|
||||
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: '本表系数适用于采用规模计价法基准预算的调整系数。' },
|
||||
21: { code: 'D4-7', name: '工程成本测算', maxCoe: null, minCoe: null, defCoe: 0.1, desc: '' },
|
||||
@ -63,12 +63,12 @@ export const serviceList = {
|
||||
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: '' },
|
||||
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},
|
||||
28: { code: 'D4-14', 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 },
|
||||
};
|
||||
//basicParam预算基数
|
||||
|
||||
export const taskList = {
|
||||
export const taskList = {
|
||||
0: { serviceID: 15, ref: 'C4-1', name: '工程造价日常顾问', basicParam: '服务月份数', required: true, unit: '万元/月', conversion: 10000, maxPrice: 0.5, minPrice: 0.3, defPrice: 0.4, desc: '' },
|
||||
1: { serviceID: 15, ref: 'C4-2', name: '工程造价专项顾问', basicParam: '服务项目的造价金额', required: true, unit: '%', conversion: 0.01, maxPrice: null, minPrice: null, defPrice: 0.01, desc: '适用于涉及造价费用类的顾问' },
|
||||
2: { serviceID: 16, ref: 'C5-1', name: '组织与调研工作', basicParam: '调研次数', required: true, unit: '万元/次', conversion: 10000, maxPrice: 2, minPrice: 1, defPrice: 1.5, desc: '' },
|
||||
@ -124,7 +124,7 @@ export const expertList = {
|
||||
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-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 } },
|
||||
@ -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 } },
|
||||
];
|
||||
|
||||
const areaScaleCal = [
|
||||
const areaScaleCal = [
|
||||
{ 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-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) {
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: fileName,
|
||||
@ -817,15 +817,15 @@ function cloneCellValue(value) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
//demo
|
||||
let data1 = {
|
||||
name: 'test001',
|
||||
fee: 10000,
|
||||
scale: [
|
||||
name: 'test001',//项目名称
|
||||
fee: 10000, //所有合同段总费用
|
||||
scale: [//项目明细aggrid数据
|
||||
{
|
||||
major: 0,
|
||||
cost: 100000,
|
||||
area: 200,
|
||||
major: 0, //专业id,对应专业majorList中的key
|
||||
cost: 100000,//造价金额
|
||||
area: 200,//用地面积
|
||||
},
|
||||
{
|
||||
major: 1,
|
||||
@ -833,15 +833,15 @@ let data1 = {
|
||||
area: 200,
|
||||
},
|
||||
],
|
||||
contracts: [
|
||||
contracts: [//合同段数据
|
||||
{
|
||||
name: 'A合同段',
|
||||
fee: 10000,
|
||||
scale: [
|
||||
name: 'A合同段',//合同段名称
|
||||
fee: 10000,//合同段费用(该合同段咨询服务zxfw.vue里面aggrid的合同预算行的小计)
|
||||
scale: [//合同段明细aggrid数据(htinfo.vue)
|
||||
{
|
||||
major: 0,
|
||||
cost: 100000,
|
||||
area: 200,
|
||||
major: 0, //专业id,对应专业majorList中的key
|
||||
cost: 100000,//造价金额
|
||||
area: 200,//用地面积
|
||||
},
|
||||
{
|
||||
major: 1,
|
||||
@ -849,12 +849,12 @@ let data1 = {
|
||||
area: 200,
|
||||
},
|
||||
],
|
||||
services: [
|
||||
services: [//咨询服务数据(zxfw.vue里面aggrid的数据,不用输出合同预算)
|
||||
{
|
||||
id: 0,
|
||||
fee: 100000,
|
||||
method1: { // 投资规模法
|
||||
cost: 100000,
|
||||
id: 0, //服务id,对应serviceList中的key
|
||||
fee: 100000 ,//服务费用(该服务咨询服务小计)
|
||||
method1: { // 投资规模法InvestmentScalePricingPane.vue的数据
|
||||
cost: 100000, //zxfw.vue里面aggrid该数据的投资规模法金额
|
||||
basicFee: 200,
|
||||
basicFee_basic: 200,
|
||||
basicFee_optional: 0,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user