diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..7c06ddf
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,19 @@
+# AGENTS.md — Encoding & Chinese Safety Rules
+
+## Absolute rules (must follow)
+1. Never corrupt non-ASCII text (Chinese, emoji, etc.). Preserve exact Unicode characters.
+2. NEVER rewrite entire files when only small edits are needed. Always apply minimal diffs/patches.
+3. If a file contains Chinese characters, do not “normalize”, “escape”, “re-encode”, or “replace” them.
+4. When reading/writing files via scripts/tools, always use UTF-8 explicitly (no platform default encoding).
+
+## Windows / PowerShell rules
+- If you need to run PowerShell, force UTF-8 output/input:
+ - Use: `[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()`
+ - Prefer `Set-Content -Encoding utf8` / `Out-File -Encoding utf8`
+- Avoid commands that may round-trip through ANSI/CP936/CP1252 without explicit encoding.
+
+## Workflow
+- Before editing: inspect the target lines only.
+- Apply changes as a patch (line-level edits), not full-file regeneration.
+- After editing: verify the edited lines still show correct Chinese.
+- If uncertain: stop and ask rather than guessing and corrupting text.
\ No newline at end of file
diff --git a/index.html b/index.html
index cf2bef4..e76de1c 100644
--- a/index.html
+++ b/index.html
@@ -9,6 +9,7 @@
-
-
-
-
diff --git a/src/components/common/XmFactorGrid.vue b/src/components/common/XmFactorGrid.vue
index 1707f7a..04dce77 100644
--- a/src/components/common/XmFactorGrid.vue
+++ b/src/components/common/XmFactorGrid.vue
@@ -13,6 +13,7 @@ interface DictItem {
defCoe: number | null
desc?: string | null
notshowByzxflxs?: boolean
+ order?: number | null
}
interface FactorRow {
@@ -61,10 +62,12 @@ const sortedDictEntries = () =>
return true
})
.sort((a, b) => {
- const aNum = Number(a[0])
- const bNum = Number(b[0])
- if (Number.isFinite(aNum) && Number.isFinite(bNum)) return aNum - bNum
- return String(a[0]).localeCompare(String(b[0]))
+ const aOrder = Number(a[1]?.order)
+ const bOrder = Number(b[1]?.order)
+ if (Number.isFinite(aOrder) && Number.isFinite(bOrder) && aOrder !== bOrder) return aOrder - bOrder
+ if (Number.isFinite(aOrder) && !Number.isFinite(bOrder)) return -1
+ if (!Number.isFinite(aOrder) && Number.isFinite(bOrder)) return 1
+ return String(a[1]?.code || a[0]).localeCompare(String(b[1]?.code || b[0]))
})
const buildCodePath = (code: string, selfId: string, codeIdMap: Map) => {
diff --git a/src/components/common/xmCommonAgGrid.vue b/src/components/common/xmCommonAgGrid.vue
new file mode 100644
index 0000000..8862673
--- /dev/null
+++ b/src/components/common/xmCommonAgGrid.vue
@@ -0,0 +1,244 @@
+
+
+
+
+
diff --git a/src/components/views/Ht.vue b/src/components/views/Ht.vue
index 79ccf8c..3848438 100644
--- a/src/components/views/Ht.vue
+++ b/src/components/views/Ht.vue
@@ -9,7 +9,7 @@ import { TooltipContent, TooltipProvider, TooltipRoot, TooltipTrigger } from '@/
import { useTabStore } from '@/pinia/tab'
import { ArrowUp, Edit3, GripVertical, MoreHorizontal, Plus, Trash2, X } from 'lucide-vue-next'
import { decodeZwArchive, encodeZwArchive } from '@/lib/zwArchive'
-import { majorList } from '@/sql'
+import { industryTypeList } from '@/sql'
import {
AlertDialogAction,
AlertDialogCancel,
@@ -90,7 +90,7 @@ const contractListViewportRef = ref(null)
const showScrollTopFab = ref(false)
const isDraggingContracts = ref(false)
const cardMotionState = ref<'enter' | 'ready'>('ready')
-const canManageContracts = ref(false)
+const canManageContracts = ref(true)
let contractAutoScrollRaf = 0
let dragPointerClientY: number | null = null
let cardEnterTimer: ReturnType | null = null
@@ -196,10 +196,8 @@ const formatExportTimestamp = (date: Date): string => {
const industryNameByCode = (() => {
const map = new Map()
- for (const item of Object.values(majorList as Record)) {
- if (!item?.code || !item?.name) continue
- if (item.code.includes('-')) continue
- map.set(item.code, item.name)
+ for (const item of industryTypeList) {
+ map.set(item.id, item.name)
}
return map
})()
diff --git a/src/components/views/ServiceCheckboxSelector.vue b/src/components/views/ServiceCheckboxSelector.vue
index 84d4cb1..4caa947 100644
--- a/src/components/views/ServiceCheckboxSelector.vue
+++ b/src/components/views/ServiceCheckboxSelector.vue
@@ -36,7 +36,7 @@ const clearAll = () => {
选择服务
清空
diff --git a/src/components/views/Xm.vue b/src/components/views/Xm.vue
index 6e4a671..447e228 100644
--- a/src/components/views/Xm.vue
+++ b/src/components/views/Xm.vue
@@ -9,7 +9,8 @@
diff --git a/src/components/views/XmMajorFactor.vue b/src/components/views/XmMajorFactor.vue
index 530e9bb..e62a351 100644
--- a/src/components/views/XmMajorFactor.vue
+++ b/src/components/views/XmMajorFactor.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/src/components/views/htInfo.vue b/src/components/views/htInfo.vue
index 604706a..b9305cd 100644
--- a/src/components/views/htInfo.vue
+++ b/src/components/views/htInfo.vue
@@ -1,17 +1,15 @@
-
+
diff --git a/src/components/views/info.vue b/src/components/views/info.vue
index 73f2eac..86c08c4 100644
--- a/src/components/views/info.vue
+++ b/src/components/views/info.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/src/components/views/zxFw.vue b/src/components/views/zxFw.vue
index 7a6cc5e..0222914 100644
--- a/src/components/views/zxFw.vue
+++ b/src/components/views/zxFw.vue
@@ -32,7 +32,7 @@ import {
} from 'reka-ui'
import { Button } from '@/components/ui/button'
import { TooltipProvider } from '@/components/ui/tooltip'
-import { serviceList } from '@/sql'
+import { getServiceDictEntries } from '@/sql'
import { useTabStore } from '@/pinia/tab'
import { usePricingPaneReloadStore } from '@/pinia/pricingPaneReload'
import ServiceCheckboxSelector from '@/components/views/ServiceCheckboxSelector.vue'
@@ -73,15 +73,14 @@ const PRICING_FORCE_DEFAULT_PREFIX = 'pricing-force-default:'
const PRICING_CLEAR_SKIP_TTL_MS = 5000
type ServiceListItem = { code?: string; ref?: string; name: string; defCoe: number | null }
-const serviceDict: ServiceItem[] = Object.entries(serviceList as Record)
- .sort((a, b) => Number(a[0]) - Number(b[0]))
- .filter((entry): entry is [string, ServiceListItem] => {
- const item = entry[1]
+const serviceDict: ServiceItem[] = getServiceDictEntries()
+ .map(({ id, item }) => ({ id, item: item as ServiceListItem }))
+ .filter(({ item }) => {
const itemCode = item?.code || item?.ref
return Boolean(itemCode && item?.name) && item.defCoe !== null
})
- .map(([key, item]) => ({
- id: key,
+ .map(({ id, item }) => ({
+ id,
code: item.code || item.ref || '',
name: item.name
}))
diff --git a/src/layout/tab.vue b/src/layout/tab.vue
index d445d1e..5f80074 100644
--- a/src/layout/tab.vue
+++ b/src/layout/tab.vue
@@ -1260,6 +1260,7 @@ const handleReset = async () => {
localStorage.clear()
sessionStorage.clear()
await localforage.clear()
+ localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
} catch (error) {
console.error('reset failed:', error)
} finally {
@@ -1427,7 +1428,7 @@ watch(
(rows?: TRow[]) => {
}
const getDefaultConsultCategoryFactor = (serviceId: string | number) => {
- const service = (serviceList as Record)[String(serviceId)]
+ const service = (getServiceDictById() as Record)[String(serviceId)]
return toFiniteNumberOrNull(service?.defCoe)
}
+const majorById = new Map(getMajorDictEntries().map(({ id, item }) => [id, item as MajorLite]))
+const majorIdAliasMap = getMajorIdAliasMap()
+
const getDefaultMajorFactorById = (id: string) => {
- const major = (majorList as Record)[id]
+ const resolvedId = majorById.has(id) ? id : majorIdAliasMap.get(id) || id
+ const major = majorById.get(resolvedId)
return toFiniteNumberOrNull(major?.defCoe)
}
const getMajorLeafIds = () =>
- Object.entries(majorList as Record)
- .sort((a, b) => Number(a[0]) - Number(b[0]))
- .filter(([, item]) => Boolean(item?.code && item.code.includes('-')))
- .map(([id]) => id)
+ getMajorDictEntries()
+ .filter(({ item }) => Boolean(item?.code && String(item.code).includes('-')))
+ .map(({ id }) => id)
const buildDefaultScaleRows = (serviceId: string | number): ScaleRow[] => {
const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId)
@@ -104,6 +113,13 @@ const mergeScaleRows = (
rowsFromDb: Array & Pick> | undefined
): ScaleRow[] => {
const dbValueMap = toRowMap(rowsFromDb)
+ for (const row of rowsFromDb || []) {
+ const rowId = String(row.id)
+ const nextId = majorIdAliasMap.get(rowId)
+ if (nextId && !dbValueMap.has(nextId)) {
+ dbValueMap.set(nextId, row as ScaleRow)
+ }
+ }
const defaultConsultCategoryFactor = getDefaultConsultCategoryFactor(serviceId)
return buildDefaultScaleRows(serviceId).map(row => {
diff --git a/src/lib/xmFactorDefaults.ts b/src/lib/xmFactorDefaults.ts
index 0caef46..c6d0b5a 100644
--- a/src/lib/xmFactorDefaults.ts
+++ b/src/lib/xmFactorDefaults.ts
@@ -1,5 +1,5 @@
import localforage from 'localforage'
-import { majorList, serviceList } from '@/sql'
+import { getMajorDictById, getMajorIdAliasMap, getServiceDictById } from '@/sql'
import { toFiniteNumberOrNull } from '@/lib/number'
const CONSULT_CATEGORY_FACTOR_KEY = 'xm-consult-category-factor-v1'
@@ -43,20 +43,22 @@ const resolveFactorValue = (
const loadFactorMap = async (
storageKey: string,
- dict: FactorDict
+ dict: FactorDict,
+ aliases?: Map
): Promise> => {
const data = await localforage.getItem(storageKey)
const map = buildStandardFactorMap(dict)
for (const row of data?.detailRows || []) {
if (!row?.id) continue
- const id = String(row.id)
+ const rowId = String(row.id)
+ const id = map.has(rowId) ? rowId : aliases?.get(rowId) || rowId
map.set(id, resolveFactorValue(row, map.get(id) ?? null))
}
return map
}
export const loadConsultCategoryFactorMap = async () =>
- loadFactorMap(CONSULT_CATEGORY_FACTOR_KEY, serviceList as FactorDict)
+ loadFactorMap(CONSULT_CATEGORY_FACTOR_KEY, getServiceDictById() as FactorDict)
export const loadMajorFactorMap = async () =>
- loadFactorMap(MAJOR_FACTOR_KEY, majorList as FactorDict)
+ loadFactorMap(MAJOR_FACTOR_KEY, getMajorDictById() as FactorDict, getMajorIdAliasMap())
diff --git a/src/sql.ts b/src/sql.ts
index fd7ecdf..b51da2c 100644
--- a/src/sql.ts
+++ b/src/sql.ts
@@ -1,16 +1,18 @@
// @ts-nocheck
import { addNumbers, roundTo, toDecimal } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat'
-import ExcelJS from "ExcelJS";
+import ExcelJS from "ExcelJS";
+// 统一数字千分位格式化,默认保留 2 位小数。
const numberFormatter = (value: unknown, fractionDigits = 2) =>
formatThousands(value, fractionDigits)
+// 将任意输入安全转为有限数字;无效值统一按 0 处理。
const toFiniteNumber = (value: unknown) => {
const num = Number(value)
return Number.isFinite(num) ? num : 0
}
-export const majorList = {
- 0: { code: 'E1', name: '交通运输工程通用专业',hideInIndustrySelector: true , maxCoe: null, minCoe: null, defCoe: null, desc: '', isRoad: true, isRailway: true, isWaterway: true, order: 1, hasCost: false, hasArea: false },
+export const majorList = {
+ 0: { code: 'E1', name: '交通运输工程通用专业',hideInIndustrySelector: true, maxCoe: null, minCoe: null, defCoe: null, desc: '', isRoad: true, isRailway: true, isWaterway: true, order: 1, hasCost: false, hasArea: false },
1: { code: 'E1-1', name: '征地(用海)补偿', maxCoe: null, minCoe: null, defCoe: 1, desc: '适用于交通建设项目征地(用海)补偿的施工图预算、招标工程量清单及清单预算(或最高投标限价)、清理概算(仅限铁路工程)、合同(工程)结算和造价鉴定、计算工程量、工程变更费用咨询、工程成本测(核)算', isRoad: true, isRailway: true, isWaterway: true, order: 2, hasCost: true, hasArea: true },
2: { code: 'E1-2', name: '拆迁补偿', maxCoe: null, minCoe: null, defCoe: 2.5, desc: '适用于交通建设项目拆迁补偿的施工图预算、招标工程量清单及清单预算(或最高投标限价)、清理概算(仅限铁路工程)、合同(工程)结算和造价鉴定、计算工程量、工程变更费用咨询、工程成本测(核)算', isRoad: true, isRailway: true, isWaterway: true, order: 3, hasCost: true, hasArea: true },
3: { code: 'E1-3', name: '迁改工程', maxCoe: null, minCoe: null, defCoe: 2, desc: '适用于交通建设项目迁改工程的施工图预算、招标工程量清单及清单预算(或最高投标限价)、清理概算(仅限铁路工程)、合同(工程)结算和造价鉴定、计算工程量、工程变更费用咨询、工程成本测(核)算', isRoad: true, isRailway: true, isWaterway: true, order: 4, hasCost: true, hasArea: false },
@@ -46,7 +48,10 @@ export const majorList = {
33: { code: 'E4-5', name: '附属房建工程(房屋建筑及附属工程)', maxCoe: null, minCoe: null, defCoe: 2.5, desc: '适用于房屋建筑与水运附属工程专业的施工图预算、招标工程量清单及清单预算(或最高投标限价)、合同(工程)结算和造价鉴定、计算工程量、工程变更费用咨询、工程成本测(核)算', isRoad: false, isRailway: false, isWaterway: true, order: 34, hasCost: true, hasArea: false },
};
-export const serviceList = {
+
+
+
+export const serviceList = {
0: { code: 'D1', name: '全过程造价咨询', maxCoe: null, minCoe: null, defCoe: 1, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 1, scale: true, onlyCostScale: true, amount: false, workDay: true },
1: { code: 'D2', name: '分阶段造价咨询', maxCoe: null, minCoe: null, defCoe: null, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 2, scale: null, onlyCostScale: null, amount: null, workDay: null },
2: { code: 'D2-1', name: '前期阶段造价咨询', maxCoe: null, minCoe: null, defCoe: 0.5, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 3, scale: true, onlyCostScale: true, amount: false, workDay: true },
@@ -86,7 +91,7 @@ export const serviceList = {
36: { code: 'D4-15-8', name: '造价数据测试验证(竣工决算)', maxCoe: null, minCoe: null, defCoe: 0.04, desc: '', isRoad: true, isRailway: true, isWaterway: true, mutiple: false, order: 37, scale: false, onlyCostScale: null, amount: false, workDay: true },
};
-export const taskList = {
+export const taskList = {
0: { serviceID: 15, code: 'C4-1', name: '工程造价日常顾问', basicParam: '服务月份数', required: true, unit: '万元/月', conversion: 10000, maxPrice: 0.5, minPrice: 0.3, defPrice: 0.4, desc: '' },
1: { serviceID: 15, code: 'C4-2', name: '工程造价专项顾问', basicParam: '服务项目的造价金额', required: true, unit: '%', conversion: 0.01, maxPrice: null, minPrice: null, defPrice: 0.01, desc: '适用于涉及造价费用类的顾问' },
2: { serviceID: 16, code: 'C5-1', name: '组织与调研工作', basicParam: '调研次数', required: true, unit: '万元/次', conversion: 10000, maxPrice: 2, minPrice: 1, defPrice: 1.5, desc: '' },
@@ -128,7 +133,7 @@ export const taskList = {
38: { serviceID: 19, code: 'C8-5', name: 'Q>100条', basicParam: '价格信息数量', required: true, unit: '元/条', conversion: 1, maxPrice: null, minPrice: null, defPrice: 100, desc: '' },
};
-export const expertList = {
+export const expertList = {
0: { code: 'C9-1-1', name: '技术员及其他', maxPrice: 800, minPrice: 600, defPrice: 700, manageCoe: 2.3 },
1: { code: 'C9-1-2', name: '助理工程师', maxPrice: 1000, minPrice: 800, defPrice: 900, manageCoe: 2.3 },
2: { code: 'C9-1-3', name: '中级工程师或二级造价工程师', maxPrice: 1500, minPrice: 1000, defPrice: 1250, manageCoe: 2.2 },
@@ -170,28 +175,206 @@ let areaScaleCal = [
-export const GENERIC_MAJOR_CODE = 'E1'
-export const isMajorCodeInIndustryScope = (majorCode, industryCode, includeGeneric = true) => {
- const code = String(majorCode || '').trim()
- const industry = String(industryCode || '').trim()
- if (!code || !industry) return false
- if (code === industry || code.startsWith(`${industry}-`)) return true
- if (!includeGeneric) return false
- if (industry === GENERIC_MAJOR_CODE) return code === GENERIC_MAJOR_CODE || code.startsWith(`${GENERIC_MAJOR_CODE}-`)
- return code === GENERIC_MAJOR_CODE || code.startsWith(`${GENERIC_MAJOR_CODE}-`)
+export const industryTypeList = [
+ {id:'0', name: '公路工程', type: 'isRoad' },
+ {id:'1', name: '铁路工程', type: 'isRailway' },
+ {id:'2', name: '水运工程', type: 'isWaterway' }
+] as const
+
+export type IndustryType = (typeof industryTypeList)[number]['type']
+type DictItem = Record
+type DictEntry = { id: string; rawId: string; item: DictItem }
+type DictByIdMap = Record
+type BasicFeeFromScaleResult = {
+ basic: number
+ optional: number
+ basicFormula: string
+ optionalFormula: string
}
-export const isMajorIndustrySelectable = (item) =>
- Boolean(item?.code && !String(item.code).includes('-') && !item?.hideInIndustrySelector)
+const industryTypeById = new Map(
+ industryTypeList.flatMap(item => {
+ return [[String(item.id).trim(), item.type]]
+
+ })
+)
+/**
+ * 根据行业的id获取对应专业字段(isRoad/isRailway/isWaterway)。
+ * @returns 对应行业字段;未命中时返回 null
+ */
+export const getIndustryTypeValue = (industryId: unknown): IndustryType | null =>
+ industryTypeById.get(String(industryId || '').trim()) || null
+
+/**
+ * 判断字典项是否在指定行业下可用。
+ * @returns 是否可用
+ */
+export const isIndustryEnabledByType = (
+ item: DictItem | undefined,
+ typeValue: IndustryType | null
+): boolean => Boolean(typeValue && item?.[typeValue] === true)
+
+// 判断是否为“通用项”(三种行业都适用)。
+const isGenericIndustryItem = (item: Record | undefined) =>
+ Boolean(item?.isRoad && item?.isRailway && item?.isWaterway)
+
+/**
+ * 判断专业ID(majorList 的 key)是否属于指定行业范围(可选是否包含通用项)。
+ * @returns 是否属于行业范围
+ */
+export const isMajorIdInIndustryScope = (
+ majorId: unknown,
+ industryCode: unknown,
+ includeGeneric = true
+): boolean => {
+ const industryType = getIndustryTypeValue(industryCode)
+ if (!majorId || !industryType) return false
+ const id = String(majorId).trim()
+ const majorItem = getMajorDictEntries().find(entry => String(entry.id).trim() === id)?.item
+ if (isIndustryEnabledByType(majorItem, industryType)) return true
+
+ if (!includeGeneric) return false
+ return isGenericIndustryItem(majorItem)
+}
+
+const isPlainObject = (value: unknown): value is Record =>
+ Boolean(value) && typeof value === 'object' && !Array.isArray(value)
+
+const hasCodeLike = (value: Record) =>
+ typeof value.code === 'string'
+
+const isDictLeafNode = (value: unknown): value is Record =>
+ isPlainObject(value) && hasCodeLike(value) && typeof value.name === 'string'
+
+// 递归提取字典树中的叶子节点(具有 code + name 的业务项)。
+const collectDictLeafEntries = (
+ source: Record,
+ prefix = ''
+): Array<{ rawId: string; item: Record }> => {
+ const result: Array<{ rawId: string; item: Record }> = []
+ if (!isPlainObject(source)) return result
+
+ for (const [key, value] of Object.entries(source)) {
+ const rawId = prefix ? `${prefix}.${key}` : String(key)
+ if (isDictLeafNode(value)) {
+ result.push({ rawId, item: value })
+ continue
+ }
+ if (isPlainObject(value)) {
+ result.push(...collectDictLeafEntries(value, rawId))
+ }
+ }
+ return result
+}
+
+// 计算排序权重:优先用 order,再退化为 rawId 的数值。
+const getItemSortValue = (item: Record, rawId: string) => {
+ const order = Number(item?.order)
+ if (Number.isFinite(order)) return order
+ const rawIdNum = Number(rawId)
+ if (Number.isFinite(rawIdNum)) return rawIdNum
+ return Number.POSITIVE_INFINITY
+}
+
+// 按权重与编码排序,保证下拉展示顺序稳定。
+const sortDictEntries = (
+ entries: Array<{ id: string; rawId: string; item: Record }>
+) =>
+ entries.sort((a, b) => {
+ const orderDiff =
+ getItemSortValue(a.item, a.rawId) - getItemSortValue(b.item, b.rawId)
+ if (orderDiff !== 0) return orderDiff
+ const codeA = String(a.item?.code || a.id)
+ const codeB = String(b.item?.code || b.id)
+ return codeA.localeCompare(codeB)
+ })
+
+// 将原始字典对象转换为扁平 entries 列表。
+const buildDictEntries = (source: Record) =>
+ sortDictEntries(
+ collectDictLeafEntries(source).map(({ rawId, item }) => {
+ return {
+ id: rawId,
+ rawId,
+ item
+ }
+ })
+ )
+
+/**
+ * 获取专业字典的扁平化列表。
+ * @returns 专业字典条目列表
+ */
+export const getMajorDictEntries = (): DictEntry[] => buildDictEntries(majorList as Record)
+
+/**
+ * 获取服务字典的扁平化列表。
+ * @returns 服务字典条目列表
+ */
+export const getServiceDictEntries = (): DictEntry[] => buildDictEntries(serviceList as Record)
+
+/**
+ * 判断字典项是否属于某行业(基于 isRoad/isRailway/isWaterway)。
+ * @returns 是否属于该行业
+ */
+export const isDictItemInIndustryScope = (item: DictItem | undefined, industryCode: unknown): boolean =>
+ isIndustryEnabledByType(item, getIndustryTypeValue(industryCode))
+
+/**
+ * 构建“专业ID -> 专业项”映射。
+ * @returns 专业项映射表
+ */
+export const getMajorDictById = (): DictByIdMap =>
+ Object.fromEntries(getMajorDictEntries().map(entry => [entry.id, entry.item]))
+
+/**
+ * 构建“服务ID -> 服务项”映射。
+ * @returns 服务项映射表
+ */
+export const getServiceDictById = (): DictByIdMap =>
+ Object.fromEntries(getServiceDictEntries().map(entry => [entry.id, entry.item]))
+
+/**
+ * 构建“专业编码(code) -> 专业ID”别名映射。
+ * @returns 编码到专业ID的别名映射
+ */
+export const getMajorIdAliasMap = (): Map =>
+ new Map(
+ getMajorDictEntries().flatMap(entry => {
+ const code = String(entry.item?.code || '')
+ return code ? [[code, entry.id]] : []
+ })
+ )
+
+/**
+ * 按 ID 或 code 获取专业项(优先 ID,找不到时按 code 别名回查)。
+ * @returns 匹配的专业项;未命中时返回 undefined
+ */
+export const getMajorDictItemById = (id: string | number): DictItem | undefined => {
+ const key = String(id)
+ const dict = getMajorDictById() as DictByIdMap
+ if (dict[key]) return dict[key]
+ const alias = getMajorIdAliasMap().get(key)
+ return alias ? dict[alias] : undefined
+}
+
+/**
+ * 按 ID 获取服务项。
+ * @returns 匹配的服务项;未命中时返回 undefined
+ */
+export const getServiceDictItemById = (id: string | number): DictItem | undefined => {
+ const key = String(id)
+ const dict = getServiceDictById() as DictByIdMap
+ return dict[key]
+}
-
-const infoServiceScaleCal: Array<{ staLine: number; endLine: number | null; staPrice: number; rate: number }> = []
-
+// 判断数值是否命中分段区间:(staLine, endLine]。
const inRange = (sv: number, staLine: number, endLine: number | null) =>
staLine < sv && (endLine == null || sv <= endLine)
+// 按分段参数计算基础费用。
const calcScaleFee = (params: {
staPrice: number
sv: number
@@ -210,8 +393,10 @@ const calcScaleFee = (params: {
)
}
+// 将费率转成千分数文本(例如 0.012 -> 12‰)。
const scaleRatePermillage = (rate: number) => roundTo(toDecimal(rate).mul(1000), 2)
+// 生成“基础费用”计算公式字符串,供前端展示。
const buildScaleFormula = (params: {
staPrice: number
sv: number
@@ -232,7 +417,14 @@ const buildScaleFormula = (params: {
-export function getBasicFeeFromScale(scaleValue: unknown, scaleType: 'cost' | 'area' | 'amount') {
+/**
+ * 根据规模值与规模类型,计算基础费用及对应公式(返回基础/附加两部分)。
+ * @returns 基础费用计算结果;输入非法或未命中区间时返回 null
+ */
+export function getBasicFeeFromScale(
+ scaleValue: unknown,
+ scaleType: 'cost' | 'area'
+): BasicFeeFromScaleResult | null {
const sv = Number(scaleValue)
if (!Number.isFinite(sv) || sv <= 0) return null
@@ -304,326 +496,15 @@ export function getBasicFeeFromScale(scaleValue: unknown, scaleType: 'cost' | 'a
}
}
- const targetRange = infoServiceScaleCal.find(f => inRange(sv, f.staLine, f.endLine))
- if (!targetRange) return null
- return {
- basic: calcScaleFee({
- staPrice: targetRange.staPrice,
- sv,
- staLine: targetRange.staLine,
- rate: targetRange.rate
- }),
- optional: 0,
- basicFormula: buildScaleFormula({
- staPrice: targetRange.staPrice,
- sv,
- staLine: targetRange.staLine,
- rate: targetRange.rate
- }),
- optionalFormula: ''
- }
+ return null
}
-//demo
-let data1 = {
- name: 'test001',
- writer: '张三',// 编制人
- reviewer: '李四',// 复核人
- date: '2021-09-24',// 编制日期
- industry: 0,// 0为公路工程,1为铁路工程,2为水运工程
- fee: 10000,
- scaleCost: 100000,// scale的cost的合计数
- scale: [// 规模信息
- {
- major: 0,
- cost: 100000,
- area: 200,
- },
- {
- major: 1,
- cost: 100000,
- area: 200,
- },
- ],
- serviceCoes: [// 项目咨询分类系数
- {
- serviceid: 0,
- coe: 1.1,
- remark: '',// 用户输入的说明
- },
- {
- serviceid: 1,
- coe: 1.2,
- remark: '',// 用户输入的说明
- },
- ],
- majorCoes: [// 项目工程专业系数
- {
- majorid: 0,
- coe: 1.1,
- remark: '',// 用户输入的说明
- },
- {
- majorid: 1,
- coe: 1.2,
- remark: '',// 用户输入的说明
- },
- ],
- contracts: [// 合同段信息
- {
- name: 'A合同段',
- serviceFee: 100000,
- addtionalFee: 0,
- reserveFee: 0,
- fee: 10000,
- scale: [
- {
- major: 0,
- cost: 100000,
- area: 200,
- },
- {
- major: 1,
- cost: 100000,
- area: 200,
- },
- ],
- serviceCoes: [// 合同段咨询分类系数
- {
- serviceid: 0,
- coe: 1.1,
- remark: '',// 用户输入的说明
- },
- {
- serviceid: 1,
- coe: 1.2,
- remark: '',// 用户输入的说明
- },
- ],
- majorCoes: [// 合同段工程专业系数
- {
- majorid: 0,
- coe: 1.1,
- remark: '',// 用户输入的说明
- },
- {
- majorid: 1,
- coe: 1.2,
- remark: '',// 用户输入的说明
- },
- ],
- services: [
- {
- id: 0,
- fee: 100000,
- process: 0,// 工作环节,0为编制,1为审核
- method1: { // 投资规模法
- cost: 100000,
- basicFee: 200,
- basicFee_basic: 200,
- basicFee_optional: 0,
- fee: 250000,
- det: [
- {
- major: 0,
- cost: 100000,
- basicFee: 200,
- basicFormula: '856,000+(1,000,000,000-500,000,000)×1‰',
- basicFee_basic: 200,
- optionalFormula: '171,200+(1,000,000,000-500,000,000)×0.2‰',
- basicFee_optional: 0,
- serviceCoe: 1.1,
- majorCoe: 1.2,
- processCoe: 1,// 工作环节系数(编审系数)
- proportion: 0.5,// 工作占比
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- method2: { // 用地规模法
- area: 1200,
- basicFee: 200,
- basicFee_basic: 200,
- basicFee_optional: 0,
- fee: 250000,
- det: [
- {
- major: 0,
- area: 1200,
- basicFee: 200,
- basicFormula: '106,000+(1,200-1,000)×60',
- basicFee_basic: 200,
- optionalFormula: '21,200+(1,200-1,000)×12',
- basicFee_optional: 0,
- serviceCoe: 1.1,
- majorCoe: 1.2,
- processCoe: 1,// 工作环节系数(编审系数)
- proportion: 0.5,// 工作占比
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- method3: { // 工作量法
- basicFee: 200,
- fee: 250000,
- det: [
- {
- task: 0,
- price: 100000,
- amount: 10,
- basicFee: 200,
- serviceCoe: 1.1,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- task: 1,
- price: 100000,
- amount: 10,
- basicFee: 200,
- serviceCoe: 1.1,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- method4: { // 工时法
- person_num: 10,
- work_day: 10,
- fee: 250000,
- det: [
- {
- expert: 0,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- expert: 1,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- },
- ],
- addtional: [// 附加工作费
- {
- type: 0,// 0为费率计取,1为工时法,2为数量单价
- coe: 0.03,
- fee: 10000,
- },
- {
- type: 1,// 0为费率计取,1为工时法,2为数量单价
- fee: 10000,
- det: [
- {
- expert: 0,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- expert: 1,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- {
- type: 2,// 0为费率计取,1为工时法,2为数量单价
- fee: 10000,
- det: [
- {
- name: '×××项',
- unit: '项',
- amount: 10,
- price: 100000,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- name: '×××项',
- unit: '项',
- amount: 10,
- price: 100000,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- }
- ],
- reserve: [// 预留费
- {
- type: 0,// 0为费率计取,1为工时法,2为数量单价
- coe: 0.03,
- fee: 10000,
- },
- {
- type: 1,// 0为费率计取,1为工时法,2为数量单价
- fee: 10000,
- det: [
- {
- expert: 0,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- expert: 1,
- price: 100000,
- person_num: 10,
- work_day: 3,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- },
- {
- type: 2,// 0为费率计取,1为工时法,2为数量单价
- fee: 10000,
- det: [
- {
- name: '×××项',
- unit: '项',
- amount: 10,
- price: 100000,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- {
- name: '×××项',
- unit: '项',
- amount: 10,
- price: 100000,
- fee: 100000,
- remark: '',// 用户输入的说明
- },
- ],
- }
- ],
- },
- ],
-};
-
-
-
-export async function exportFile(fileName, data) {
+/**
+ * 导出入口:生成 Excel 并触发保存(优先使用 File System Access API)。
+ * @returns 导出流程完成后的 Promise
+ */
+export async function exportFile(fileName: string, data: any): Promise {
console.log(data)
if (window.showSaveFilePicker) {
const handle = await window.showSaveFilePicker({
@@ -669,478 +550,497 @@ export async function exportFile(fileName, data) {
+// 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。
async function generateTemplate(data) {
try {
- // 获取模板
- let templateExcel = 'template20260226001test010';
- let templateUrl = `https://oa.zwgczx.com/myExcelTemplate/${templateExcel}.xlsx`;
- let buf = await (await fetch(templateUrl)).arrayBuffer();
- let workbook = new ExcelJS.Workbook();
- await workbook.xlsx.load(buf);
+ // 获取模板
+ let templateExcel = 'template20260226001test010';
+ let templateUrl = `https://oa.zwgczx.com/myExcelTemplate/${templateExcel}.xlsx`;
+ let buf = await (await fetch(templateUrl)).arrayBuffer();
+ let workbook = new ExcelJS.Workbook();
+ await workbook.xlsx.load(buf);
- // 生成表格
- let fm_sheet = workbook.getWorksheet('封面');
- let ml_sheet = workbook.getWorksheet('目录');
- let yz01_sheet = workbook.getWorksheet('预总01表');
- let f01_sheet = workbook.getWorksheet('辅01表');
+ // 生成表格
+ let fm_sheet = workbook.getWorksheet('封面');
+ let ml_sheet = workbook.getWorksheet('目录');
+ let yz01_sheet = workbook.getWorksheet('预总01表');
+ let f01_sheet = workbook.getWorksheet('辅01表');
- let now = new Date();
- let year = now.getFullYear();
- let month = now.getMonth() + 1;
- let day = now.getDate();
+ let now = new Date();
+ let year = now.getFullYear();
+ let month = now.getMonth() + 1;
+ let day = now.getDate();
- fm_sheet.getRow(2).getCell(1).value = data.name;
- fm_sheet.getRow(11).getCell(4).value = `${year} 年 ${month} 月 ${day} 日`;
+ fm_sheet.getRow(2).getCell(1).value = data.name;
+ fm_sheet.getRow(11).getCell(4).value = `${year} 年 ${month} 月 ${day} 日`;
- let yz01Mod = (data.contracts.length + 1) % 4;
- let yz01Num = (data.contracts.length + 1 - yz01Mod) / 4;
- switch (yz01Mod) {
- case 0:
- yz01_sheet.spliceColumns(8, 15);
- break;
- case 1:
- yz01_sheet.spliceColumns(8, 11);
- break;
- case 2:
- yz01_sheet.spliceColumns(19, 4);
- yz01_sheet.spliceColumns(8, 6);
- break;
- case 3:
- yz01_sheet.spliceColumns(14, 9);
- break;
- }
- if (yz01Num == 0) {
- yz01_sheet.spliceColumns(1, 7);
- } else {
- if (yz01Num > 1) {
- for (let i = 0; i < yz01Num - 1; i++) {
- insertAndCopyColumn(7 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7], yz01_sheet);
+ let yz01Mod = (data.contracts.length + 1) % 4;
+ let yz01Num = (data.contracts.length + 1 - yz01Mod) / 4;
+ switch (yz01Mod) {
+ case 0:
+ yz01_sheet.spliceColumns(8, 15);
+ break;
+ case 1:
+ yz01_sheet.spliceColumns(8, 11);
+ break;
+ case 2:
+ yz01_sheet.spliceColumns(19, 4);
+ yz01_sheet.spliceColumns(8, 6);
+ break;
+ case 3:
+ yz01_sheet.spliceColumns(14, 9);
+ break;
+ }
+ if (yz01Num == 0) {
+ yz01_sheet.spliceColumns(1, 7);
+ } else {
+ if (yz01Num > 1) {
+ for (let i = 0; i < yz01Num - 1; i++) {
+ insertAndCopyColumn(7 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7], yz01_sheet);
+ }
}
}
- }
- let f01Mod = (data.contracts.length) % 3;
- let f01Num = (data.contracts.length - f01Mod) / 3;
- switch (f01Mod) {
- case 0:
- f01_sheet.spliceColumns(11, 14);
- break;
- case 1:
- f01_sheet.spliceColumns(11, 8);
- break;
- case 2:
- f01_sheet.spliceColumns(19, 6);
- break;
- }
- if (f01Num == 0) {
- f01_sheet.spliceColumns(1, 10);
- } else {
- if (f01Num > 1) {
- for (let i = 0; i < f01Num - 1; i++) {
- insertAndCopyColumn(10 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], f01_sheet);
+ let f01Mod = (data.contracts.length) % 3;
+ let f01Num = (data.contracts.length - f01Mod) / 3;
+ switch (f01Mod) {
+ case 0:
+ f01_sheet.spliceColumns(11, 14);
+ break;
+ case 1:
+ f01_sheet.spliceColumns(11, 8);
+ break;
+ case 2:
+ f01_sheet.spliceColumns(19, 6);
+ break;
+ }
+ if (f01Num == 0) {
+ f01_sheet.spliceColumns(1, 10);
+ } else {
+ if (f01Num > 1) {
+ for (let i = 0; i < f01Num - 1; i++) {
+ insertAndCopyColumn(10 * (i + 1) + 1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], f01_sheet);
+ }
+ }
+ for (let i = 0; i < f01Num; i++) {
+ f01_sheet.mergeCells(1, i * 10 + 1, 2, i * 10 + 1);
+ f01_sheet.mergeCells(1, i * 10 + 2, 2, i * 10 + 2);
+ f01_sheet.mergeCells(1, i * 10 + 3, 2, i * 10 + 3);
+ f01_sheet.mergeCells(1, i * 10 + 10, 2, i * 10 + 10);
+ f01_sheet.mergeCells(1, i * 10 + 4, 1, i * 10 + 5);
+ f01_sheet.getRow(1).getCell(i * 10 + 4).value = data.contracts[i * 3].name;
+ f01_sheet.mergeCells(1, i * 10 + 6, 1, i * 10 + 7);
+ f01_sheet.getRow(1).getCell(i * 10 + 6).value = data.contracts[i * 3 + 1].name;
+ f01_sheet.mergeCells(1, i * 10 + 8, 1, i * 10 + 9);
+ f01_sheet.getRow(1).getCell(i * 10 + 8).value = data.contracts[i * 3 + 2].name;
}
}
- for (let i = 0; i < f01Num; i++) {
- f01_sheet.mergeCells(1, i * 10 + 1, 2, i * 10 + 1);
- f01_sheet.mergeCells(1, i * 10 + 2, 2, i * 10 + 2);
- f01_sheet.mergeCells(1, i * 10 + 3, 2, i * 10 + 3);
- f01_sheet.mergeCells(1, i * 10 + 10, 2, i * 10 + 10);
- f01_sheet.mergeCells(1, i * 10 + 4, 1, i * 10 + 5);
- f01_sheet.getRow(1).getCell(i * 10 + 4).value = data.contracts[i * 3].name;
- f01_sheet.mergeCells(1, i * 10 + 6, 1, i * 10 + 7);
- f01_sheet.getRow(1).getCell(i * 10 + 6).value = data.contracts[i * 3 + 1].name;
- f01_sheet.mergeCells(1, i * 10 + 8, 1, i * 10 + 9);
- f01_sheet.getRow(1).getCell(i * 10 + 8).value = data.contracts[i * 3 + 2].name;
- }
- }
- if (f01Mod > 0) {
- f01_sheet.mergeCells(1, f01Num * 10 + 1, 2, f01Num * 10 + 1);
- f01_sheet.mergeCells(1, f01Num * 10 + 2, 2, f01Num * 10 + 2);
- f01_sheet.mergeCells(1, f01Num * 10 + 3, 2, f01Num * 10 + 3);
- f01_sheet.mergeCells(1, f01Num * 10 + 2 * f01Mod + 4, 2, f01Num * 10 + 2 * f01Mod + 4);
- f01_sheet.mergeCells(1, f01Num * 10 + 4, 1, f01Num * 10 + 5);
- f01_sheet.getRow(1).getCell(f01Num * 10 + 4).value = data.contracts[f01Num * 3].name;
- if (f01Mod == 2) {
- f01_sheet.mergeCells(1, f01Num * 10 + 6, 1, f01Num * 10 + 7);
- f01_sheet.getRow(1).getCell(f01Num * 10 + 6).value = data.contracts[f01Num * 3 + 1].name;
- }
- }
-
- let ml_slotRow = 13;
- let ml_number = 1;
- let allServices = [];
- data.contracts.forEach((ci, index) => {
- ci.method1 = [];
- ci.method2 = [];
- ci.method3 = [];
- ci.method4 = [];
- ci.services.forEach(si => {
- if (si.method1) {
- ci.method1.push(si.id);
+ if (f01Mod > 0) {
+ f01_sheet.mergeCells(1, f01Num * 10 + 1, 2, f01Num * 10 + 1);
+ f01_sheet.mergeCells(1, f01Num * 10 + 2, 2, f01Num * 10 + 2);
+ f01_sheet.mergeCells(1, f01Num * 10 + 3, 2, f01Num * 10 + 3);
+ f01_sheet.mergeCells(1, f01Num * 10 + 2 * f01Mod + 4, 2, f01Num * 10 + 2 * f01Mod + 4);
+ f01_sheet.mergeCells(1, f01Num * 10 + 4, 1, f01Num * 10 + 5);
+ f01_sheet.getRow(1).getCell(f01Num * 10 + 4).value = data.contracts[f01Num * 3].name;
+ if (f01Mod == 2) {
+ f01_sheet.mergeCells(1, f01Num * 10 + 6, 1, f01Num * 10 + 7);
+ f01_sheet.getRow(1).getCell(f01Num * 10 + 6).value = data.contracts[f01Num * 3 + 1].name;
}
- if (si.method2) {
- ci.method2.push(si.id);
- }
- if (si.method3) ci.method3.push(si.id);
- if (si.method4) ci.method4.push(si.id);
- });
-
- let ml_sourceRows = [ml_sheet.getRow(6)];
- let sheet_1 = copyWorksheet(workbook, '预i-1表', `预${index + 1}-1表`);
- sheet_1.headerFooter.oddHeader = sheet_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-1 表/g, `预 ${index + 1}-1 表`).replace(/第i合同/g, ci.name);
- sheet_1.getRow(1).getCell(4).value = sheet_1.getRow(1).getCell(4).value.replace(/第i合同/g, ci.name);
- let sheet_2;
- let sheet_2_1;
- let sheet_2_2;
- let sheet_3;
- let sheet_4;
- let sheet_4_1;
- if (ci.method1.length || ci.method2.length) {
- ml_sourceRows.push(ml_sheet.getRow(7));
- sheet_2 = copyWorksheet(workbook, '预i-2表', `预${index + 1}-2表`);
- sheet_2.headerFooter.oddHeader = sheet_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2 表/g, `预 ${index + 1}-2 表`).replace(/第i合同/g, ci.name);
- if (ci.method1.length) {
- ml_sourceRows.push(ml_sheet.getRow(8));
- sheet_2_1 = copyWorksheet(workbook, '预i-2-1表', `预${index + 1}-2-1表`);
- sheet_2_1.headerFooter.oddHeader = sheet_2_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-1 表/g, `预 ${index + 1}-2-1 表`).replace(/第i合同/g, ci.name);
- }
- if (ci.method2.length) {
- ml_sourceRows.push(ml_sheet.getRow(9));
- sheet_2_2 = copyWorksheet(workbook, '预i-2-2表', `预${index + 1}-2-2表`);
- sheet_2_2.headerFooter.oddHeader = sheet_2_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-2 表/g, `预 ${index + 1}-2-2 表`).replace(/第i合同/g, ci.name);
- }
- }
- if (ci.method3.length) {
- ml_sourceRows.push(ml_sheet.getRow(10));
- sheet_3 = copyWorksheet(workbook, '预i-3表', `预${index + 1}-3表`);
- sheet_3.headerFooter.oddHeader = sheet_3.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-3 表/g, `预 ${index + 1}-3 表`).replace(/第i合同/g, ci.name);
- }
- if (ci.method4.length) {
- ml_sourceRows.push(ml_sheet.getRow(11));
- ml_sourceRows.push(ml_sheet.getRow(12));
- sheet_4 = copyWorksheet(workbook, '预i-4表', `预${index + 1}-4表`);
- sheet_4.headerFooter.oddHeader = sheet_4.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4 表/g, `预 ${index + 1}-4 表`).replace(/第i合同/g, ci.name);
- sheet_4_1 = copyWorksheet(workbook, '预i-4-1表', `预${index + 1}-4-1表`);
- sheet_4_1.headerFooter.oddHeader = sheet_4_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4-1 表/g, `预 ${index + 1}-4-1 表`).replace(/第i合同/g, ci.name);
- let sumObj = ci.method4.reduce((a, b) => {
- const m4 = ci.services.find(f => f.id == b)?.method4;
- return {
- person_num: addNumbers(a.person_num, toFiniteNumber(m4?.person_num)),
- work_day: addNumbers(a.work_day, toFiniteNumber(m4?.work_day)),
- fee: addNumbers(a.fee, toFiniteNumber(m4?.fee))
- };
- }, { person_num: 0, work_day: 0, fee: 0 });
- sheet_4.getRow(3).getCell(4).value = sumObj.person_num;
- sheet_4.getRow(3).getCell(5).value = sumObj.work_day;
- sheet_4.getRow(3).getCell(6).value = sumObj.fee;
- sheet_4_1.getRow(4).getCell(4).value = '/';
- sheet_4_1.getRow(4).getCell(5).value = '/';
- sheet_4_1.getRow(4).getCell(6).value = '/';
- sheet_4_1.getRow(4).getCell(7).value = sumObj.person_num;
- sheet_4_1.getRow(4).getCell(8).value = sumObj.work_day;
- sheet_4_1.getRow(4).getCell(9).value = sumObj.fee
}
- cusInsertRowFunc(ml_slotRow, ml_sourceRows, ml_sheet, () => ml_slotRow++, (targetCell, sourceCell, colNumber) => {
- if (colNumber == 1) {
- targetCell.value = ml_number++;
- } else if (colNumber == 2) {
- targetCell.value = sourceCell.value.replaceAll('第i合同', ci.name);
- } else if (colNumber == 3) {
- targetCell.value = sourceCell.value.replaceAll('i', index + 1);
- } else {
- targetCell.value = sourceCell.value;
- }
- });
-
- let num_2 = 1;
- let num_2_1 = 1;
- let num_2_2 = 1;
- let num_3 = 1;
- let num_4 = 1;
- let num_4_1 = 1;
- ci.services.forEach((sobj, sindex) => {
- let allServicesX = allServices.find(s => s.id == sobj.id);
- if (allServicesX) {
- allServicesX.contracts[index] = sobj.fee;
- } else {
- allServices.push({
- id: sobj.id,
- contracts: {
- [index]: sobj.fee,
- },
- });
- }
- let serviceX = serviceList[sobj.id];
- cusInsertRowFunc(4 + sindex, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
- targetRow.getCell(1).value = sindex + 1;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = sobj.method1 ? sobj.method1.fee : '';
- targetRow.getCell(5).value = sobj.method2 ? sobj.method2.fee : '';
- targetRow.getCell(6).value = sobj.method3 ? sobj.method3.fee : '';
- targetRow.getCell(7).value = sobj.method4 ? sobj.method4.fee : '';
- targetRow.getCell(8).value = sobj.fee;
+ let ml_slotRow = 13;
+ let ml_number = 1;
+ let allServices = [];
+ data.contracts.forEach((ci, index) => {
+ ci.method1 = [];
+ ci.method2 = [];
+ ci.method3 = [];
+ ci.method4 = [];
+ ci.services.forEach(si => {
+ if (si.method1) {
+ ci.method1.push(si.id);
+ }
+ if (si.method2) {
+ ci.method2.push(si.id);
+ }
+ if (si.method3) ci.method3.push(si.id);
+ if (si.method4) ci.method4.push(si.id);
});
- if (sobj.method1 || sobj.method2) {
- let det1 = sobj.method1 ? sobj.method1.det.map(m => m.major) : [];
- let det2 = sobj.method2 ? sobj.method2.det.map(m => m.major) : [];
- let allDet = [...(new Set([...det1, ...det2]))].sort((a, b) => a - b).map(m => {
- return {
- major: m,
- mth1: det1.includes(m) ? sobj.method1.det[det1.indexOf(m)] : null,
- mth2: det2.includes(m) ? sobj.method2.det[det2.indexOf(m)] : null,
- };
- });
- cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
- targetRow.getCell(1).value = num_2++;
+ let ml_sourceRows = [ml_sheet.getRow(6)];
+ let sheet_1 = copyWorksheet(workbook, '预i-1表', `预${index + 1}-1表`);
+ sheet_1.headerFooter.oddHeader = sheet_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-1 表/g, `预 ${index + 1}-1 表`).replace(/第i合同/g, ci.name);
+ sheet_1.getRow(1).getCell(4).value = sheet_1.getRow(1).getCell(4).value.replace(/第i合同/g, ci.name);
+ let sheet_2;
+ let sheet_2_1;
+ let sheet_2_2;
+ let sheet_3;
+ let sheet_4;
+ let sheet_4_1;
+ if (ci.method1.length || ci.method2.length) {
+ ml_sourceRows.push(ml_sheet.getRow(7));
+ sheet_2 = copyWorksheet(workbook, '预i-2表', `预${index + 1}-2表`);
+ sheet_2.headerFooter.oddHeader = sheet_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2 表/g, `预 ${index + 1}-2 表`).replace(/第i合同/g, ci.name);
+ if (ci.method1.length) {
+ ml_sourceRows.push(ml_sheet.getRow(8));
+ sheet_2_1 = copyWorksheet(workbook, '预i-2-1表', `预${index + 1}-2-1表`);
+ sheet_2_1.headerFooter.oddHeader = sheet_2_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-1 表/g, `预 ${index + 1}-2-1 表`).replace(/第i合同/g, ci.name);
+ }
+ if (ci.method2.length) {
+ ml_sourceRows.push(ml_sheet.getRow(9));
+ sheet_2_2 = copyWorksheet(workbook, '预i-2-2表', `预${index + 1}-2-2表`);
+ sheet_2_2.headerFooter.oddHeader = sheet_2_2.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-2-2 表/g, `预 ${index + 1}-2-2 表`).replace(/第i合同/g, ci.name);
+ }
+ }
+ if (ci.method3.length) {
+ ml_sourceRows.push(ml_sheet.getRow(10));
+ sheet_3 = copyWorksheet(workbook, '预i-3表', `预${index + 1}-3表`);
+ sheet_3.headerFooter.oddHeader = sheet_3.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-3 表/g, `预 ${index + 1}-3 表`).replace(/第i合同/g, ci.name);
+ }
+ if (ci.method4.length) {
+ ml_sourceRows.push(ml_sheet.getRow(11));
+ ml_sourceRows.push(ml_sheet.getRow(12));
+ sheet_4 = copyWorksheet(workbook, '预i-4表', `预${index + 1}-4表`);
+ sheet_4.headerFooter.oddHeader = sheet_4.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4 表/g, `预 ${index + 1}-4 表`).replace(/第i合同/g, ci.name);
+ sheet_4_1 = copyWorksheet(workbook, '预i-4-1表', `预${index + 1}-4-1表`);
+ sheet_4_1.headerFooter.oddHeader = sheet_4_1.headerFooter.oddHeader.replace(/×××/g, ci.name).replace(/预 i-4-1 表/g, `预 ${index + 1}-4-1 表`).replace(/第i合同/g, ci.name);
+ let sumObj = ci.method4.reduce((a, b) => {
+ const m4 = ci.services.find(f => f.id == b)?.method4;
+ return {
+ person_num: addNumbers(a.person_num, toFiniteNumber(m4?.person_num)),
+ work_day: addNumbers(a.work_day, toFiniteNumber(m4?.work_day)),
+ fee: addNumbers(a.fee, toFiniteNumber(m4?.fee))
+ };
+ }, { person_num: 0, work_day: 0, fee: 0 });
+ sheet_4.getRow(3).getCell(4).value = sumObj.person_num;
+ sheet_4.getRow(3).getCell(5).value = sumObj.work_day;
+ sheet_4.getRow(3).getCell(6).value = sumObj.fee;
+ sheet_4_1.getRow(4).getCell(4).value = '/';
+ sheet_4_1.getRow(4).getCell(5).value = '/';
+ sheet_4_1.getRow(4).getCell(6).value = '/';
+ sheet_4_1.getRow(4).getCell(7).value = sumObj.person_num;
+ sheet_4_1.getRow(4).getCell(8).value = sumObj.work_day;
+ sheet_4_1.getRow(4).getCell(9).value = sumObj.fee
+ }
+
+ cusInsertRowFunc(ml_slotRow, ml_sourceRows, ml_sheet, () => ml_slotRow++, (targetCell, sourceCell, colNumber) => {
+ if (colNumber == 1) {
+ targetCell.value = ml_number++;
+ } else if (colNumber == 2) {
+ targetCell.value = sourceCell.value.replaceAll('第i合同', ci.name);
+ } else if (colNumber == 3) {
+ targetCell.value = sourceCell.value.replaceAll('i', index + 1);
+ } else {
+ targetCell.value = sourceCell.value;
+ }
+ });
+
+ let num_2 = 1;
+ let num_2_1 = 1;
+ let num_2_2 = 1;
+ let num_3 = 1;
+ let num_4 = 1;
+ let num_4_1 = 1;
+ ci.services.forEach((sobj, sindex) => {
+ let allServicesX = allServices.find(s => s.id == sobj.id);
+ if (allServicesX) {
+ allServicesX.contracts[index] = sobj.fee;
+ } else {
+ allServices.push({
+ id: sobj.id,
+ contracts: {
+ [index]: sobj.fee,
+ },
+ });
+ }
+ let serviceX = getServiceDictItemById(sobj.id) || { code: '', name: '' };
+ cusInsertRowFunc(4 + sindex, [sheet_1.getRow(3)], sheet_1, (targetRow) => {
+ targetRow.getCell(1).value = sindex + 1;
targetRow.getCell(2).value = serviceX.code;
targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = '/';
- targetRow.getCell(5).value = '/';
- if (sobj.method1) {
- targetRow.getCell(6).value = sobj.method1.basicFee;
- targetRow.getCell(7).value = sobj.method1.fee;
- cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
- targetRow.getCell(1).value = num_2_1++;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = sobj.method1.cost;
- targetRow.getCell(5).value = '/';
- targetRow.getCell(6).value = sobj.method1.basicFee_basic;
- targetRow.getCell(7).value = '/';
- targetRow.getCell(8).value = sobj.method1.basicFee_optional;
- targetRow.getCell(9).value = sobj.method1.basicFee;
- });
- }
- if (sobj.method2) {
- targetRow.getCell(8).value = sobj.method2.basicFee;
- targetRow.getCell(9).value = sobj.method2.fee;
- cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
- targetRow.getCell(1).value = num_2_2++;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = sobj.method2.area;
- targetRow.getCell(5).value = '/';
- targetRow.getCell(6).value = sobj.method2.basicFee_basic;
- targetRow.getCell(7).value = '/';
- targetRow.getCell(8).value = sobj.method2.basicFee_optional;
- targetRow.getCell(9).value = sobj.method2.basicFee;
- });
- }
+ targetRow.getCell(4).value = sobj.method1 ? sobj.method1.fee : '';
+ targetRow.getCell(5).value = sobj.method2 ? sobj.method2.fee : '';
+ targetRow.getCell(6).value = sobj.method3 ? sobj.method3.fee : '';
+ targetRow.getCell(7).value = sobj.method4 ? sobj.method4.fee : '';
+ targetRow.getCell(8).value = sobj.fee;
});
- allDet.forEach((m, mindex) => {
- let majorX = majorList[m.major];
+ if (sobj.method1 || sobj.method2) {
+ let det1 = sobj.method1 ? sobj.method1.det.map(m => m.major) : [];
+ let det2 = sobj.method2 ? sobj.method2.det.map(m => m.major) : [];
+ const majorOrderMap = new Map(getMajorDictEntries().map((entry, idx) => [entry.id, idx]));
+ let allDet = [...(new Set([...det1, ...det2]))].sort((a, b) => {
+ const aId = String(a);
+ const bId = String(b);
+ const ao = majorOrderMap.get(aId) ?? majorOrderMap.get(getMajorIdAliasMap().get(aId) || '');
+ const bo = majorOrderMap.get(bId) ?? majorOrderMap.get(getMajorIdAliasMap().get(bId) || '');
+ if (ao != null && bo != null) return ao - bo;
+ if (ao != null) return -1;
+ if (bo != null) return 1;
+ return aId.localeCompare(bId);
+ }).map(m => {
+ return {
+ major: m,
+ mth1: det1.includes(m) ? sobj.method1.det[det1.indexOf(m)] : null,
+ mth2: det2.includes(m) ? sobj.method2.det[det2.indexOf(m)] : null,
+ };
+ });
+
cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
targetRow.getCell(1).value = num_2++;
- targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
- targetRow.getCell(3).value = majorX.name;
- if (m.mth1) {
- targetRow.getCell(4).value = m.mth1.serviceCoe;
- targetRow.getCell(5).value = m.mth1.majorCoe;
- targetRow.getCell(6).value = m.mth1.basicFee;
- targetRow.getCell(7).value = m.mth1.fee;
- targetRow.getCell(8).value = 0;
- targetRow.getCell(9).value = 0;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = '/';
+ targetRow.getCell(5).value = '/';
+ if (sobj.method1) {
+ targetRow.getCell(6).value = sobj.method1.basicFee;
+ targetRow.getCell(7).value = sobj.method1.fee;
cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
targetRow.getCell(1).value = num_2_1++;
- targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
- targetRow.getCell(3).value = majorX.name;
- targetRow.getCell(4).value = m.mth1.cost;
- targetRow.getCell(5).value = m.mth1.basicFormula;
- targetRow.getCell(6).value = m.mth1.basicFee_basic;
- targetRow.getCell(7).value = m.mth1.optionalFormula;
- targetRow.getCell(8).value = m.mth1.basicFee_optional;
- targetRow.getCell(9).value = m.mth1.basicFee;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = sobj.method1.cost;
+ targetRow.getCell(5).value = '/';
+ targetRow.getCell(6).value = sobj.method1.basicFee_basic;
+ targetRow.getCell(7).value = '/';
+ targetRow.getCell(8).value = sobj.method1.basicFee_optional;
+ targetRow.getCell(9).value = sobj.method1.basicFee;
});
- } else {
- targetRow.getCell(4).value = m.mth2.serviceCoe;
- targetRow.getCell(5).value = m.mth2.majorCoe;
- targetRow.getCell(6).value = 0;
- targetRow.getCell(7).value = 0;
- targetRow.getCell(8).value = m.mth2.basicFee;
- targetRow.getCell(9).value = m.mth2.fee;
+ }
+ if (sobj.method2) {
+ targetRow.getCell(8).value = sobj.method2.basicFee;
+ targetRow.getCell(9).value = sobj.method2.fee;
cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
targetRow.getCell(1).value = num_2_2++;
- targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
- targetRow.getCell(3).value = majorX.name;
- targetRow.getCell(4).value = m.mth2.area;
- targetRow.getCell(5).value = m.mth2.basicFormula;
- targetRow.getCell(6).value = m.mth2.basicFee_basic;
- targetRow.getCell(7).value = m.mth2.optionalFormula;
- targetRow.getCell(8).value = m.mth2.basicFee_optional;
- targetRow.getCell(9).value = m.mth2.basicFee;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = sobj.method2.area;
+ targetRow.getCell(5).value = '/';
+ targetRow.getCell(6).value = sobj.method2.basicFee_basic;
+ targetRow.getCell(7).value = '/';
+ targetRow.getCell(8).value = sobj.method2.basicFee_optional;
+ targetRow.getCell(9).value = sobj.method2.basicFee;
});
}
});
- });
- }
- if (sobj.method3) {
- cusInsertRowFunc(3 + num_3, [sheet_3.getRow(3)], sheet_3, (targetRow) => {
- targetRow.getCell(1).value = num_3++;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = '/';
- targetRow.getCell(5).value = '/';
- targetRow.getCell(6).value = '/';
- targetRow.getCell(7).value = sobj.method3.basicFee;
- targetRow.getCell(8).value = '/';
- targetRow.getCell(9).value = sobj.method3.fee;
- });
- sobj.method3.det.forEach((tobj, tindex) => {
- const taskX = taskList[tobj.task];
+ allDet.forEach((m, mindex) => {
+ let majorX = getMajorDictItemById(m.major) || { name: '' };
+ cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
+ targetRow.getCell(1).value = num_2++;
+ targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
+ targetRow.getCell(3).value = majorX.name;
+ if (m.mth1) {
+ targetRow.getCell(4).value = m.mth1.serviceCoe;
+ targetRow.getCell(5).value = m.mth1.majorCoe;
+ targetRow.getCell(6).value = m.mth1.basicFee;
+ targetRow.getCell(7).value = m.mth1.fee;
+ targetRow.getCell(8).value = 0;
+ targetRow.getCell(9).value = 0;
+ cusInsertRowFunc(4 + num_2_1, [sheet_2_1.getRow(4)], sheet_2_1, (targetRow) => {
+ targetRow.getCell(1).value = num_2_1++;
+ targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
+ targetRow.getCell(3).value = majorX.name;
+ targetRow.getCell(4).value = m.mth1.cost;
+ targetRow.getCell(5).value = m.mth1.basicFormula;
+ targetRow.getCell(6).value = m.mth1.basicFee_basic;
+ targetRow.getCell(7).value = m.mth1.optionalFormula;
+ targetRow.getCell(8).value = m.mth1.basicFee_optional;
+ targetRow.getCell(9).value = m.mth1.basicFee;
+ });
+ } else {
+ targetRow.getCell(4).value = m.mth2.serviceCoe;
+ targetRow.getCell(5).value = m.mth2.majorCoe;
+ targetRow.getCell(6).value = 0;
+ targetRow.getCell(7).value = 0;
+ targetRow.getCell(8).value = m.mth2.basicFee;
+ targetRow.getCell(9).value = m.mth2.fee;
+ cusInsertRowFunc(4 + num_2_2, [sheet_2_2.getRow(4)], sheet_2_2, (targetRow) => {
+ targetRow.getCell(1).value = num_2_2++;
+ targetRow.getCell(2).value = serviceX.code + '-' + (mindex + 1);
+ targetRow.getCell(3).value = majorX.name;
+ targetRow.getCell(4).value = m.mth2.area;
+ targetRow.getCell(5).value = m.mth2.basicFormula;
+ targetRow.getCell(6).value = m.mth2.basicFee_basic;
+ targetRow.getCell(7).value = m.mth2.optionalFormula;
+ targetRow.getCell(8).value = m.mth2.basicFee_optional;
+ targetRow.getCell(9).value = m.mth2.basicFee;
+ });
+ }
+ });
+ });
+ }
+ if (sobj.method3) {
cusInsertRowFunc(3 + num_3, [sheet_3.getRow(3)], sheet_3, (targetRow) => {
targetRow.getCell(1).value = num_3++;
- targetRow.getCell(2).value = taskX.code;
- targetRow.getCell(3).value = taskX.name + (taskX.desc ? `(${taskX.desc})` : '');
- targetRow.getCell(4).value = taskX.basicParam;
- targetRow.getCell(5).value = tobj.price;
- targetRow.getCell(6).value = tobj.amount;
- targetRow.getCell(7).value = tobj.basicFee;
- targetRow.getCell(8).value = tobj.serviceCoe;
- targetRow.getCell(9).value = tobj.fee;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = '/';
+ targetRow.getCell(5).value = '/';
+ targetRow.getCell(6).value = '/';
+ targetRow.getCell(7).value = sobj.method3.basicFee;
+ targetRow.getCell(8).value = '/';
+ targetRow.getCell(9).value = sobj.method3.fee;
+ });
+ sobj.method3.det.forEach((tobj, tindex) => {
+ const taskX = taskList[tobj.task];
+ cusInsertRowFunc(3 + num_3, [sheet_3.getRow(3)], sheet_3, (targetRow) => {
+ targetRow.getCell(1).value = num_3++;
+ targetRow.getCell(2).value = taskX.code;
+ targetRow.getCell(3).value = taskX.name + (taskX.desc ? `(${taskX.desc})` : '');
+ targetRow.getCell(4).value = taskX.basicParam;
+ targetRow.getCell(5).value = tobj.price;
+ targetRow.getCell(6).value = tobj.amount;
+ targetRow.getCell(7).value = tobj.basicFee;
+ targetRow.getCell(8).value = tobj.serviceCoe;
+ targetRow.getCell(9).value = tobj.fee;
+ });
+ });
+ }
+ if (sobj.method4) {
+ cusInsertRowFunc(4 + num_4, [sheet_4.getRow(4)], sheet_4, (targetRow) => {
+ targetRow.getCell(1).value = num_4++;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = sobj.method4.person_num;
+ targetRow.getCell(5).value = sobj.method4.work_day;
+ targetRow.getCell(6).value = sobj.method4.fee;
});
- });
- }
- if (sobj.method4) {
- cusInsertRowFunc(4 + num_4, [sheet_4.getRow(4)], sheet_4, (targetRow) => {
- targetRow.getCell(1).value = num_4++;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = sobj.method4.person_num;
- targetRow.getCell(5).value = sobj.method4.work_day;
- targetRow.getCell(6).value = sobj.method4.fee;
- });
- cusInsertRowFunc(5 + num_4_1, [sheet_4_1.getRow(5)], sheet_4_1, (targetRow) => {
- targetRow.getCell(1).value = num_4_1++;
- targetRow.getCell(2).value = serviceX.code;
- targetRow.getCell(3).value = serviceX.name;
- targetRow.getCell(4).value = '/';
- targetRow.getCell(5).value = '/';
- targetRow.getCell(6).value = '/';
- targetRow.getCell(7).value = sobj.method4.person_num;
- targetRow.getCell(8).value = sobj.method4.work_day;
- targetRow.getCell(9).value = sobj.method4.fee;
- });
- sobj.method4.det.forEach((eobj, eindex) => {
- const expertX = expertList[eobj.expert];
cusInsertRowFunc(5 + num_4_1, [sheet_4_1.getRow(5)], sheet_4_1, (targetRow) => {
targetRow.getCell(1).value = num_4_1++;
- targetRow.getCell(2).value = expertX.code;
- targetRow.getCell(3).value = expertX.name;
- targetRow.getCell(4).value = `${expertX.minPrice}~${expertX.maxPrice}`;
- targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}~${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
- targetRow.getCell(6).value = eobj.price;
- targetRow.getCell(7).value = eobj.person_num;
- targetRow.getCell(8).value = eobj.work_day;
- targetRow.getCell(9).value = eobj.fee;
- targetRow.getCell(10).value = eobj.remark;
+ targetRow.getCell(2).value = serviceX.code;
+ targetRow.getCell(3).value = serviceX.name;
+ targetRow.getCell(4).value = '/';
+ targetRow.getCell(5).value = '/';
+ targetRow.getCell(6).value = '/';
+ targetRow.getCell(7).value = sobj.method4.person_num;
+ targetRow.getCell(8).value = sobj.method4.work_day;
+ targetRow.getCell(9).value = sobj.method4.fee;
+ });
+ sobj.method4.det.forEach((eobj, eindex) => {
+ const expertX = expertList[eobj.expert];
+ cusInsertRowFunc(5 + num_4_1, [sheet_4_1.getRow(5)], sheet_4_1, (targetRow) => {
+ targetRow.getCell(1).value = num_4_1++;
+ targetRow.getCell(2).value = expertX.code;
+ targetRow.getCell(3).value = expertX.name;
+ targetRow.getCell(4).value = `${expertX.minPrice}~${expertX.maxPrice}`;
+ targetRow.getCell(5).value = `${roundTo(toDecimal(toFiniteNumber(expertX.minPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}~${roundTo(toDecimal(toFiniteNumber(expertX.maxPrice)).mul(toFiniteNumber(expertX.manageCoe)), 0)}`;
+ targetRow.getCell(6).value = eobj.price;
+ targetRow.getCell(7).value = eobj.person_num;
+ targetRow.getCell(8).value = eobj.work_day;
+ targetRow.getCell(9).value = eobj.fee;
+ targetRow.getCell(10).value = eobj.remark;
+ });
});
- });
- }
- });
- });
-
- allServices.sort((a, b) => a.id - b.id);
- allServices.forEach((s, sindex) => {
- const serviceX = serviceList[s.id];
- cusInsertRowFunc(3 + sindex, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
- let siSum = 0;
- for (let i = 0; i < yz01Num; i++) {
- targetRow.getCell(i * 7 + 1).value = sindex + 1;
- targetRow.getCell(i * 7 + 2).value = serviceX.code;
- targetRow.getCell(i * 7 + 3).value = serviceX.name;
- targetRow.getCell(i * 7 + 4).value = s.contracts[i * 4];
- targetRow.getCell(i * 7 + 5).value = s.contracts[i * 4 + 1];
- targetRow.getCell(i * 7 + 6).value = s.contracts[i * 4 + 2];
- siSum = addNumbers(
- siSum,
- toFiniteNumber(s.contracts[i * 4]),
- toFiniteNumber(s.contracts[i * 4 + 1]),
- toFiniteNumber(s.contracts[i * 4 + 2])
- );
- if (i == yz01Num - 1 && yz01Mod == 0) {
- targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
- } else {
- targetRow.getCell(i * 7 + 7).value = s.contracts[i * 4 + 3];
- siSum = addNumbers(siSum, toFiniteNumber(s.contracts[i * 4 + 3]));
}
- }
- if (yz01Mod) {
- targetRow.getCell(yz01Num * 7 + 1).value = sindex + 1;
- targetRow.getCell(yz01Num * 7 + 2).value = serviceX.code;
- targetRow.getCell(yz01Num * 7 + 3).value = serviceX.name;
- if (yz01Mod == 1) {
- targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
- } else if (yz01Mod == 2) {
- targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
- siSum = addNumbers(siSum, toFiniteNumber(s.contracts[yz01Num * 4]));
- targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
- } else {
- targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
- targetRow.getCell(yz01Num * 7 + 5).value = s.contracts[yz01Num * 4 + 1];
+ });
+ });
+
+ const serviceOrderMap = new Map(getServiceDictEntries().map((entry, index) => [entry.id, index]));
+ allServices.sort((a, b) => {
+ const ao = serviceOrderMap.get(String(a.id));
+ const bo = serviceOrderMap.get(String(b.id));
+ if (ao != null && bo != null) return ao - bo;
+ if (ao != null) return -1;
+ if (bo != null) return 1;
+ return String(a.id).localeCompare(String(b.id));
+ });
+ allServices.forEach((s, sindex) => {
+ const serviceX = getServiceDictItemById(s.id) || { code: '', name: '' };
+ cusInsertRowFunc(3 + sindex, [yz01_sheet.getRow(2)], yz01_sheet, (targetRow) => {
+ let siSum = 0;
+ for (let i = 0; i < yz01Num; i++) {
+ targetRow.getCell(i * 7 + 1).value = sindex + 1;
+ targetRow.getCell(i * 7 + 2).value = serviceX.code;
+ targetRow.getCell(i * 7 + 3).value = serviceX.name;
+ targetRow.getCell(i * 7 + 4).value = s.contracts[i * 4];
+ targetRow.getCell(i * 7 + 5).value = s.contracts[i * 4 + 1];
+ targetRow.getCell(i * 7 + 6).value = s.contracts[i * 4 + 2];
siSum = addNumbers(
siSum,
- toFiniteNumber(s.contracts[yz01Num * 4]),
- toFiniteNumber(s.contracts[yz01Num * 4 + 1])
+ toFiniteNumber(s.contracts[i * 4]),
+ toFiniteNumber(s.contracts[i * 4 + 1]),
+ toFiniteNumber(s.contracts[i * 4 + 2])
);
- targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
+ if (i == yz01Num - 1 && yz01Mod == 0) {
+ targetRow.getCell(i * 7 + 7).value = numberFormatter(siSum, 2);
+ } else {
+ targetRow.getCell(i * 7 + 7).value = s.contracts[i * 4 + 3];
+ siSum = addNumbers(siSum, toFiniteNumber(s.contracts[i * 4 + 3]));
+ }
+ }
+ if (yz01Mod) {
+ targetRow.getCell(yz01Num * 7 + 1).value = sindex + 1;
+ targetRow.getCell(yz01Num * 7 + 2).value = serviceX.code;
+ targetRow.getCell(yz01Num * 7 + 3).value = serviceX.name;
+ if (yz01Mod == 1) {
+ targetRow.getCell(yz01Num * 7 + 4).value = numberFormatter(siSum, 2);
+ } else if (yz01Mod == 2) {
+ targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
+ siSum = addNumbers(siSum, toFiniteNumber(s.contracts[yz01Num * 4]));
+ targetRow.getCell(yz01Num * 7 + 5).value = numberFormatter(siSum, 2);
+ } else {
+ targetRow.getCell(yz01Num * 7 + 4).value = s.contracts[yz01Num * 4];
+ targetRow.getCell(yz01Num * 7 + 5).value = s.contracts[yz01Num * 4 + 1];
+ siSum = addNumbers(
+ siSum,
+ toFiniteNumber(s.contracts[yz01Num * 4]),
+ toFiniteNumber(s.contracts[yz01Num * 4 + 1])
+ );
+ targetRow.getCell(yz01Num * 7 + 6).value = numberFormatter(siSum, 2);
+ }
+ }
+ });
+ });
+
+ ml_sheet.spliceRows(6, 6);
+ ml_sheet.spliceRows(6, 1);
+
+ // 合并说明
+ if (yz01Num) {
+ for (let i = 0; i < yz01Num; i++) {
+ yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4].name}预算\n(元)`;
+ yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 1].name}预算\n(元)`;
+ yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 2].name}预算\n(元)`;
+ if (i == yz01Num - 1 && yz01Mod == 0) {
+ yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `预算小计\n(元)`;
+ } else {
+ yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 3].name}预算\n(元)`;
+ }
+ yz01_sheet.mergeCells(6, i * 7 + 2, 6, i * 7 + 7);
+ }
+ }
+ if (yz01Mod) {
+ for (let i = 0; i < yz01Mod; i++) {
+ if (i == yz01Mod - 1) {
+ yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `预算小计\n(元)`;
+ } else {
+ yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `${data.contracts[yz01Num * 4 + i].name}预算\n(元)`;
}
}
+ yz01_sheet.mergeCells(6, yz01Num * 7 + 2, 6, yz01Num * 7 + 3 + yz01Mod);
+ }
+ ml_sheet.mergeCells(ml_slotRow - 7, 1, ml_slotRow - 7, 4);
+
+ workbook.removeWorksheet('预i-1表');
+ workbook.removeWorksheet('预i-2表');
+ workbook.removeWorksheet('预i-2-1表');
+ workbook.removeWorksheet('预i-2-2表');
+ workbook.removeWorksheet('预i-3表');
+ workbook.removeWorksheet('预i-4表');
+ workbook.removeWorksheet('预i-4-1表');
+ workbook.getWorksheet('辅01表').orderNo = ml_number + 2 + 10;
+ workbook.getWorksheet('辅02表').orderNo = ml_number + 3 + 10;
+ workbook.getWorksheet('辅03表').orderNo = ml_number + 4 + 10;
+
+ workbook._worksheets.forEach(sheet => {
+ if (sheet) {
+ if (sheet.headerFooter.oddHeader) sheet.headerFooter.oddHeader = sheet.headerFooter.oddHeader.replace(/&([CLR])&/g, '&$1&"宋体"&');
+ if (sheet.headerFooter.oddFooter) sheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter.replace(/&([CLR])&/g, '&$1&"宋体"&');
+ }
});
- });
- ml_sheet.spliceRows(6, 6);
- ml_sheet.spliceRows(6, 1);
+ window.workbook = workbook;
- // 合并说明
- if (yz01Num) {
- for (let i = 0; i < yz01Num; i++) {
- yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4].name}预算\n(元)`;
- yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 1].name}预算\n(元)`;
- yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 2].name}预算\n(元)`;
- if (i == yz01Num - 1 && yz01Mod == 0) {
- yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `预算小计\n(元)`;
- } else {
- yz01_sheet.getRow(1).getCell(i * 7 + 4).value = `${data.contracts[i * 4 + 3].name}预算\n(元)`;
- }
- yz01_sheet.mergeCells(6, i * 7 + 2, 6, i * 7 + 7);
- }
- }
- if (yz01Mod) {
- for (let i = 0; i < yz01Mod; i++) {
- if (i == yz01Mod - 1) {
- yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `预算小计\n(元)`;
- } else {
- yz01_sheet.getRow(1).getCell(yz01Num * 7 + 4 + i).value = `${data.contracts[yz01Num * 4 + i].name}预算\n(元)`;
- }
- }
- yz01_sheet.mergeCells(6, yz01Num * 7 + 2, 6, yz01Num * 7 + 3 + yz01Mod);
- }
- ml_sheet.mergeCells(ml_slotRow - 7, 1, ml_slotRow - 7, 4);
-
- workbook.removeWorksheet('预i-1表');
- workbook.removeWorksheet('预i-2表');
- workbook.removeWorksheet('预i-2-1表');
- workbook.removeWorksheet('预i-2-2表');
- workbook.removeWorksheet('预i-3表');
- workbook.removeWorksheet('预i-4表');
- workbook.removeWorksheet('预i-4-1表');
- workbook.getWorksheet('辅01表').orderNo = ml_number + 2 + 10;
- workbook.getWorksheet('辅02表').orderNo = ml_number + 3 + 10;
- workbook.getWorksheet('辅03表').orderNo = ml_number + 4 + 10;
-
- workbook._worksheets.forEach(sheet => {
- if (sheet) {
- if (sheet.headerFooter.oddHeader) sheet.headerFooter.oddHeader = sheet.headerFooter.oddHeader.replace(/&([CLR])&/g, '&$1&"宋体"&');
- if (sheet.headerFooter.oddFooter) sheet.headerFooter.oddFooter = sheet.headerFooter.oddFooter.replace(/&([CLR])&/g, '&$1&"宋体"&');
- }
- });
-
- window.workbook = workbook;
-
- return workbook;
+ return workbook;
} catch (error) {
console.log(error)
throw error
@@ -1148,6 +1048,7 @@ async function generateTemplate(data) {
}
+// 在指定位置插入行,并按模板行复制样式,可选回调填充值。
function cusInsertRowFunc(insertRowNum, sourceRows, worksheet, RowFun, cellFun) {
// 插入行
let newRows = [];
@@ -1179,6 +1080,7 @@ function cusInsertRowFunc(insertRowNum, sourceRows, worksheet, RowFun, cellFun)
}
}
+// 复制整张工作表(含页眉页脚、合并单元格、列宽样式与单元格值)。
function copyWorksheet(workbook, sourceName, targetName) {
const source = workbook.getWorksheet(sourceName);
if (!source) throw new Error("Source sheet not found");
@@ -1228,6 +1130,7 @@ function copyWorksheet(workbook, sourceName, targetName) {
return target;
}
+// 在指定列位置批量插入新列,并从给定来源列复制样式与内容。
function insertAndCopyColumn(insertAt, cols, ws) {
let insertAti = insertAt;
cols.forEach((col, index) => {
@@ -1244,6 +1147,7 @@ function insertAndCopyColumn(insertAt, cols, ws) {
});
}
+// 复制单列:列级属性 + 该列所有单元格值和样式。
function copyColumn(toCol, fromCol, ws) {
const srcCol = ws.getColumn(fromCol);
const dstCol = ws.getColumn(toCol);
@@ -1261,6 +1165,7 @@ function copyColumn(toCol, fromCol, ws) {
});
}
+// 深拷贝单元格值,避免对象类型(公式/富文本)引用同一实例。
function cloneCellValue(value) {
if (value == null) return value;