This commit is contained in:
wintsa 2026-03-12 12:02:05 +08:00
parent 398fca9265
commit f4f6e5c618
6 changed files with 207 additions and 71 deletions

View File

@ -119,6 +119,7 @@ const buildDefaultRows = (): DetailRow[] => {
const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => { const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => {
const dbValueMap = new Map<string, DetailRow>() const dbValueMap = new Map<string, DetailRow>()
for (const row of rowsFromDb || []) { for (const row of rowsFromDb || []) {
if (row?.isGroupRow === true) continue
const rowId = String(row.id) const rowId = String(row.id)
dbValueMap.set(rowId, row) dbValueMap.set(rowId, row)
const aliasId = majorIdAliasMap.get(rowId) const aliasId = majorIdAliasMap.get(rowId)
@ -140,6 +141,45 @@ const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] =>
}) })
} }
const buildGroupRows = (rows: DetailRow[]): DetailRow[] => {
const rowById = new Map(rows.map(row => [String(row.id || ''), row] as const))
const groupRows: DetailRow[] = []
for (const group of detailDict.value) {
let amountTotal = 0
let hasAmount = false
let landAreaTotal = 0
let hasLandArea = false
for (const child of group.children) {
const leaf = rowById.get(String(child.id || ''))
const amount = leaf?.amount
if (typeof amount === 'number' && Number.isFinite(amount)) {
amountTotal += amount
hasAmount = true
}
const landArea = leaf?.landArea
if (typeof landArea === 'number' && Number.isFinite(landArea)) {
landAreaTotal += landArea
hasLandArea = true
}
}
groupRows.push({
id: group.id,
groupCode: group.code,
groupName: group.name,
majorCode: group.code,
majorName: group.name,
hasCost: true,
hasArea: true,
amount: hasAmount ? roundTo(amountTotal, 3) : null,
landArea: hasLandArea ? roundTo(landAreaTotal, 3) : null,
path: [`${group.code} ${group.name}`],
hide: false,
isGroupRow: true
})
}
return groupRows
}
const applyPinnedTotalAmount = ( const applyPinnedTotalAmount = (
api: GridApi<DetailRow> | null | undefined, api: GridApi<DetailRow> | null | undefined,
@ -239,6 +279,7 @@ interface DetailRow {
landArea: number | null landArea: number | null
path: string[] path: string[]
hide?: boolean hide?: boolean
isGroupRow?: boolean
} }
interface XmBaseInfoState { interface XmBaseInfoState {
@ -398,11 +439,13 @@ const pinnedTopRowData = ref<DetailRow[]>([
const saveToIndexedDB = async () => { const saveToIndexedDB = async () => {
try { try {
const payload: GridPersistState = { const leafRows = detailRows.value.map(row => ({
detailRows: detailRows.value.map(row => ({
...JSON.parse(JSON.stringify(row)), ...JSON.parse(JSON.stringify(row)),
hide: Boolean(row.hide) hide: Boolean(row.hide),
isGroupRow: false
})) }))
const payload: GridPersistState = {
detailRows: [...leafRows, ...buildGroupRows(leafRows)]
} }
payload.roughCalcEnabled = roughCalcEnabled.value payload.roughCalcEnabled = roughCalcEnabled.value
payload.totalAmount = pinnedTopRowData.value[0].amount payload.totalAmount = pinnedTopRowData.value[0].amount

View File

@ -2,7 +2,7 @@
import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { computed, defineComponent, h, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import type { ComponentPublicInstance, PropType } from 'vue' import type { ComponentPublicInstance, PropType } from 'vue'
import { AgGridVue } from 'ag-grid-vue3' import { AgGridVue } from 'ag-grid-vue3'
import type { ColDef, GridOptions, ICellRendererParams, IHeaderParams } from 'ag-grid-community' import type { ColDef, GridOptions, ICellRendererParams } from 'ag-grid-community'
import { myTheme, gridOptions } from '@/lib/diyAgGridOptions' import { myTheme, gridOptions } from '@/lib/diyAgGridOptions'
import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale' import { AG_GRID_LOCALE_CN } from '@ag-grid-community/locale'
import { addNumbers } from '@/lib/decimal' import { addNumbers } from '@/lib/decimal'
@ -14,7 +14,7 @@ import {
getPricingMethodTotalsForServices, getPricingMethodTotalsForServices,
type PricingMethodTotals type PricingMethodTotals
} from '@/lib/pricingMethodTotals' } from '@/lib/pricingMethodTotals'
import { Pencil, Eraser, Trash2, CircleHelp } from 'lucide-vue-next' import { Pencil, Eraser, Trash2 } from 'lucide-vue-next'
import { import {
AlertDialogAction, AlertDialogAction,
AlertDialogCancel, AlertDialogCancel,
@ -554,46 +554,38 @@ const ProcessCellRenderer = defineComponent({
return () => { return () => {
const row = props.params.data const row = props.params.data
if (!row || isFixedRow(row)) return null if (!row || isFixedRow(row)) return null
const checked = row.process === 1 const processValue = row.process === 1 ? 1 : 0
const onToggle = (event: Event) => { const onSelect = (event: Event, value: 0 | 1) => {
event.stopPropagation() event.stopPropagation()
const target = event.target as HTMLInputElement | null void props.params.context?.onSetProcess?.(row.id, value)
void props.params.context?.onToggleProcess?.(row.id, Boolean(target?.checked))
} }
return h('div', { class: 'flex items-center justify-center w-full' }, [ const radioName = `process-${row.id}`
return h('div', { class: 'flex items-center justify-center gap-4 w-full text-sm' }, [
h('label', { class: 'inline-flex items-center gap-1.5 cursor-pointer' }, [
h('input', { h('input', {
type: 'checkbox', type: 'radio',
checked, name: radioName,
class: 'cursor-pointer', checked: processValue === 0,
onChange: onToggle class: 'cursor-pointer h-4 w-4',
}) onClick: (event: Event) => event.stopPropagation(),
]) onChange: (event: Event) => onSelect(event, 0)
} }),
} h('span', '编制')
}) ]),
h('label', { class: 'inline-flex items-center gap-1.5 cursor-pointer' }, [
const ProcessHeaderRenderer = defineComponent({ h('input', {
name: 'ProcessHeaderRenderer', type: 'radio',
props: { name: radioName,
params: { checked: processValue === 1,
type: Object as PropType<IHeaderParams>, class: 'cursor-pointer h-4 w-4',
required: true onClick: (event: Event) => event.stopPropagation(),
} onChange: (event: Event) => onSelect(event, 1)
}, }),
setup(props) { h('span', '审核')
const tooltipText = '默认为编制,勾选为审核'
props.params.setTooltip?.(tooltipText, () => true)
return () =>
h('div', { class: 'flex items-center justify-center gap-1 w-full' }, [
h('span', props.params.displayName || '工作环节'),
h('span', { class: 'inline-flex items-center pointer-events-auto' }, [
h(CircleHelp, {
size: 20,
class: 'text-muted-foreground pointer-events-none'
})
]) ])
]) ])
} }
}
}) })
const columnDefs: ColDef<DetailRow>[] = [ const columnDefs: ColDef<DetailRow>[] = [
@ -624,10 +616,10 @@ const columnDefs: ColDef<DetailRow>[] = [
{ {
headerName: '工作环节', headerName: '工作环节',
field: 'process', field: 'process',
headerClass: 'ag-center-header', headerClass: 'ag-center-header zxfw-process-header',
minWidth: 90, minWidth: 170,
maxWidth: 110, maxWidth: 220,
flex: 1, flex: 1.2,
editable: false, editable: false,
sortable: false, sortable: false,
filter: false, filter: false,
@ -641,7 +633,6 @@ const columnDefs: ColDef<DetailRow>[] = [
if (!params.data || isFixedRow(params.data)) return null if (!params.data || isFixedRow(params.data)) return null
return params.data.process === 1 ? 1 : 0 return params.data.process === 1 ? 1 : 0
}, },
headerComponent: ProcessHeaderRenderer,
cellRenderer: ProcessCellRenderer cellRenderer: ProcessCellRenderer
}, },
{ {
@ -754,15 +745,17 @@ const detailGridOptions: GridOptions<DetailRow> = {
treeData: false, treeData: false,
getDataPath: undefined, getDataPath: undefined,
context: { context: {
onToggleProcess: async (rowId: string, checked: boolean) => { onSetProcess: async (rowId: string, value: 0 | 1) => {
const currentState = getCurrentContractState() const currentState = getCurrentContractState()
let changed = false let changed = false
const nextRows = currentState.detailRows.map(row => { const nextRows = currentState.detailRows.map(row => {
if (isFixedRow(row) || String(row.id) !== String(rowId)) return row if (isFixedRow(row) || String(row.id) !== String(rowId)) return row
const nextValue = value === 1 ? 1 : 0
if ((row.process === 1 ? 1 : 0) === nextValue) return row
changed = true changed = true
return { return {
...row, ...row,
process: checked ? 1 : 0 process: nextValue
} }
}) })
if (!changed) return if (!changed) return
@ -1208,3 +1201,13 @@ onBeforeUnmount(() => {
</div> </div>
</TooltipProvider> </TooltipProvider>
</template> </template>
<style scoped>
:deep(.zxfw-process-header .ag-header-cell-label) {
justify-content: center;
}
:deep(.zxfw-process-header .ag-header-cell-text) {
text-align: center;
}
</style>

View File

@ -20,6 +20,11 @@ import {
AlertDialogRoot, AlertDialogRoot,
AlertDialogTitle, AlertDialogTitle,
AlertDialogTrigger, AlertDialogTrigger,
ToastDescription,
ToastProvider,
ToastRoot,
ToastTitle,
ToastViewport,
} from 'reka-ui' } from 'reka-ui'
import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive' import { decodeZwArchive, encodeZwArchive, ZW_FILE_EXTENSION } from '@/lib/zwArchive'
import { addNumbers, roundTo } from '@/lib/decimal' import { addNumbers, roundTo } from '@/lib/decimal'
@ -62,6 +67,7 @@ interface ScaleRowLike {
id: string id: string
amount: number | null amount: number | null
landArea: number | null landArea: number | null
isGroupRow?: boolean
} }
interface XmInfoStorageLike extends XmInfoLike { interface XmInfoStorageLike extends XmInfoLike {
@ -457,6 +463,10 @@ const pendingImportPayload = shallowRef<DataPackage | null>(null)
const pendingImportFileName = ref('') const pendingImportFileName = ref('')
const userGuideOpen = ref(false) const userGuideOpen = ref(false)
const userGuideStepIndex = ref(0) const userGuideStepIndex = ref(0)
const reportExportToastOpen = ref(false)
const reportExportProgress = ref(0)
const reportExportStatus = ref<'running' | 'success' | 'error'>('running')
const reportExportText = ref('')
const tabItemElMap = new Map<string, HTMLElement>() const tabItemElMap = new Map<string, HTMLElement>()
const tabTitleElMap = new Map<string, HTMLElement>() const tabTitleElMap = new Map<string, HTMLElement>()
const tabPanelElMap = new Map<string, HTMLElement>() const tabPanelElMap = new Map<string, HTMLElement>()
@ -468,6 +478,7 @@ const isTabDragging = ref(false)
const tabTitleOverflowMap = ref<Record<string, boolean>>({}) const tabTitleOverflowMap = ref<Record<string, boolean>>({})
let tabStripViewportEl: HTMLElement | null = null let tabStripViewportEl: HTMLElement | null = null
let tabTitleOverflowRafId: number | null = null let tabTitleOverflowRafId: number | null = null
let reportExportToastTimer: ReturnType<typeof setTimeout> | null = null
const tabsModel = computed({ const tabsModel = computed({
get: () => tabStore.tabs, get: () => tabStore.tabs,
@ -502,6 +513,31 @@ const closeMenus = () => {
dataMenuOpen.value = false dataMenuOpen.value = false
} }
const clearReportExportToastTimer = () => {
if (!reportExportToastTimer) return
clearTimeout(reportExportToastTimer)
reportExportToastTimer = null
}
const showReportExportProgress = (progress: number, text: string) => {
clearReportExportToastTimer()
reportExportStatus.value = 'running'
reportExportProgress.value = Math.max(0, Math.min(100, progress))
reportExportText.value = text
reportExportToastOpen.value = true
}
const finishReportExportProgress = (success: boolean, text: string) => {
clearReportExportToastTimer()
reportExportStatus.value = success ? 'success' : 'error'
reportExportProgress.value = 100
reportExportText.value = text
reportExportToastOpen.value = true
reportExportToastTimer = setTimeout(() => {
reportExportToastOpen.value = false
}, success ? 1200 : 1800)
}
const markGuideCompleted = () => { const markGuideCompleted = () => {
try { try {
localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1') localStorage.setItem(USER_GUIDE_COMPLETED_KEY, '1')
@ -1016,6 +1052,16 @@ const toExportScaleRows = (rows: ScaleRowLike[] | undefined): ExportScaleRow[] =
.filter((item): item is ExportScaleRow => Boolean(item)) .filter((item): item is ExportScaleRow => Boolean(item))
} }
const sumLeafScaleCost = (rows: ScaleRowLike[] | undefined) => {
if (!Array.isArray(rows)) return 0
return sumNumbers(
rows.map(row => {
if (row?.isGroupRow === true) return null
return toFiniteNumber(row?.amount)
})
)
}
const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | null => { const buildMethod1 = (rows: ScaleMethodRowLike[] | undefined): ExportMethod1 | null => {
@ -1365,9 +1411,8 @@ const buildExportReportPayload = async (): Promise<ExportReportPayload> => {
const projectInfo = projectInfoRaw || {} const projectInfo = projectInfoRaw || {}
const projectScaleSource = projectScaleRaw || {} const projectScaleSource = projectScaleRaw || {}
const projectScale = projectScaleSource.roughCalcEnabled ? [] : toExportScaleRows(projectScaleSource.detailRows) const projectScale = projectScaleSource.roughCalcEnabled ? [] : toExportScaleRows(projectScaleSource.detailRows)
const projectScaleCost = toFiniteNumber(projectScaleSource.totalAmount) ?? sumNumbers(projectScale.map(item => item.cost)) const projectScaleCost = toFiniteNumber(projectScaleSource.totalAmount) ?? sumLeafScaleCost(projectScaleSource.detailRows)
projectScale.push({ projectScale.push({
major: -1, cost: projectScaleCost, major: -1, cost: projectScaleCost,
area: null area: null
@ -1572,14 +1617,17 @@ const exportData = async () => {
const exportReport = async () => { const exportReport = async () => {
try { try {
showReportExportProgress(10, '正在准备报表导出...')
const now = new Date() const now = new Date()
showReportExportProgress(40, '正在汇总报表数据...')
const payload = await buildExportReportPayload() const payload = await buildExportReportPayload()
showReportExportProgress(80, '正在生成并写出报表文件...')
const fileName = `${sanitizeFileNamePart(payload.name)}-报表-${formatExportTimestamp(now)}` const fileName = `${sanitizeFileNamePart(payload.name)}-报表-${formatExportTimestamp(now)}`
console.log(payload)
await exportFile(fileName, payload) await exportFile(fileName, payload)
finishReportExportProgress(true, '报表导出完成')
} catch (error) { } catch (error) {
console.error('export report failed:', error) console.error('export report failed:', error)
// window.alert('') finishReportExportProgress(false, '报表导出失败,请重试')
} finally { } finally {
dataMenuOpen.value = false dataMenuOpen.value = false
} }
@ -1710,6 +1758,7 @@ onMounted(() => {
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
clearReportExportToastTimer()
window.removeEventListener('mousedown', handleGlobalMouseDown) window.removeEventListener('mousedown', handleGlobalMouseDown)
window.removeEventListener('keydown', handleGlobalKeyDown) window.removeEventListener('keydown', handleGlobalKeyDown)
window.removeEventListener('resize', scheduleUpdateTabTitleOverflow) window.removeEventListener('resize', scheduleUpdateTabTitleOverflow)
@ -1761,6 +1810,7 @@ watch(
</script> </script>
<template> <template>
<ToastProvider>
<TooltipProvider> <TooltipProvider>
<div class="flex flex-col w-full h-screen bg-background overflow-hidden"> <div class="flex flex-col w-full h-screen bg-background overflow-hidden">
<div class="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-2 border-b bg-muted/30 px-2 pt-1 h-15 flex-none"> <div class="grid grid-cols-[minmax(0,1fr)_auto] items-start gap-2 border-b bg-muted/30 px-2 pt-1 h-15 flex-none">
@ -1966,8 +2016,30 @@ watch(
</div> </div>
</div> </div>
</div> </div>
<ToastRoot
:open="reportExportToastOpen"
:duration="0"
class="pointer-events-auto rounded-xl border border-border bg-card px-4 py-3 text-foreground shadow-lg"
>
<ToastTitle class="text-sm font-semibold text-foreground">
{{ reportExportStatus === 'running' ? '导出报表' : (reportExportStatus === 'success' ? '导出成功' : '导出失败') }}
</ToastTitle>
<ToastDescription class="mt-1 text-xs text-muted-foreground">{{ reportExportText }}</ToastDescription>
<div class="mt-2 h-1.5 w-full overflow-hidden rounded-full bg-muted">
<div
class="h-full transition-all duration-300"
:class="reportExportStatus === 'error'
? 'bg-red-500'
: (reportExportStatus === 'success' ? 'bg-foreground/70' : 'bg-foreground')"
:style="{ width: `${reportExportProgress}%` }"
/>
</div>
</ToastRoot>
<ToastViewport class="fixed bottom-5 right-5 z-[85] flex w-[380px] max-w-[92vw] flex-col gap-2 outline-none" />
</div> </div>
</TooltipProvider> </TooltipProvider>
</ToastProvider>
</template> </template>
<style scoped> <style scoped>

View File

@ -30,5 +30,14 @@ export const sumByNumber = <T>(list: T[], pick: (item: T) => MaybeNumber) => {
return total.toNumber() return total.toNumber()
} }
export const decimalAggSum = (params: { values?: unknown[] }) => export const decimalAggSum = (params: { values?: unknown[] }) => {
sumFiniteValues(params.values || []) const values = params.values || []
let hasFinite = false
for (const value of values) {
if (!isFiniteNumber(value)) continue
hasFinite = true
break
}
if (!hasFinite) return null
return sumFiniteValues(values)
}

View File

@ -164,6 +164,12 @@ const toStoredDetailRowsState = <TRow = unknown>(state: { detailRows?: TRow[] }
const hasOwn = (obj: unknown, key: string) => const hasOwn = (obj: unknown, key: string) =>
Object.prototype.hasOwnProperty.call(obj || {}, key) Object.prototype.hasOwnProperty.call(obj || {}, key)
const isGroupScaleRow = (row: unknown) =>
Boolean(row && typeof row === 'object' && (row as Record<string, unknown>).isGroupRow === true)
const stripGroupScaleRows = <TRow>(rows: TRow[] | undefined): TRow[] =>
(rows || []).filter(row => !isGroupScaleRow(row))
const getRowNumberOrFallback = ( const getRowNumberOrFallback = (
row: Record<string, unknown> | undefined, row: Record<string, unknown> | undefined,
key: string, key: string,
@ -306,8 +312,9 @@ const mergeScaleRows = (
consultCategoryFactorMap?: Map<string, number | null>, consultCategoryFactorMap?: Map<string, number | null>,
majorFactorMap?: Map<string, number | null> majorFactorMap?: Map<string, number | null>
): ScaleRow[] => { ): ScaleRow[] => {
const dbValueMap = toRowMap(rowsFromDb) const sourceRows = stripGroupScaleRows(rowsFromDb)
for (const row of rowsFromDb || []) { const dbValueMap = toRowMap(sourceRows)
for (const row of sourceRows) {
const rowId = String(row.id) const rowId = String(row.id)
const nextId = majorIdAliasMap.get(rowId) const nextId = majorIdAliasMap.get(rowId)
if (nextId && !dbValueMap.has(nextId)) { if (nextId && !dbValueMap.has(nextId)) {
@ -370,7 +377,7 @@ const getOnlyCostScaleBudgetFee = (
industryId?: string | null industryId?: string | null
) => { ) => {
const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId) const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId)
const sourceRows = rowsFromDb || [] const sourceRows = stripGroupScaleRows(rowsFromDb)
const defaultConsultCategoryFactor = const defaultConsultCategoryFactor =
consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId) consultCategoryFactorMap?.get(String(serviceId)) ?? getDefaultConsultCategoryFactor(serviceId)
const defaultMajorFactor = const defaultMajorFactor =
@ -426,14 +433,15 @@ const buildOnlyCostScaleDetailRows = (
majorFactorMap?: Map<string, number | null>, majorFactorMap?: Map<string, number | null>,
industryId?: string | null industryId?: string | null
) => { ) => {
const totalAmount = sumByNumberNullable(rowsFromDb || [], row => const sourceRows = stripGroupScaleRows(rowsFromDb)
const totalAmount = sumByNumberNullable(sourceRows, row =>
typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null typeof row?.amount === 'number' && Number.isFinite(row.amount) ? row.amount : null
) )
const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId) const industryMajorEntry = getIndustryMajorEntryByIndustryId(industryId)
const onlyCostRowId = industryMajorEntry?.id || ONLY_COST_SCALE_ROW_ID const onlyCostRowId = industryMajorEntry?.id || ONLY_COST_SCALE_ROW_ID
const onlyRow = const onlyRow =
(rowsFromDb || []).find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) || sourceRows.find(row => String(row?.id || '') === ONLY_COST_SCALE_ROW_ID) ||
(rowsFromDb || []).find(row => String(row?.id || '') === onlyCostRowId) sourceRows.find(row => String(row?.id || '') === onlyCostRowId)
const consultCategoryFactor = getRowNumberOrFallback( const consultCategoryFactor = getRowNumberOrFallback(
onlyRow, onlyRow,
'consultCategoryFactor', 'consultCategoryFactor',
@ -598,7 +606,7 @@ const resolveScaleRows = (
if (htData?.detailRows != null) { if (htData?.detailRows != null) {
return mergeScaleRows( return mergeScaleRows(
serviceId, serviceId,
htData.detailRows as any, stripGroupScaleRows(htData.detailRows as any),
consultCategoryFactorMap, consultCategoryFactorMap,
majorFactorMap majorFactorMap
) )

View File

@ -2,7 +2,6 @@
import { addNumbers, roundTo, toDecimal } from '@/lib/decimal' import { addNumbers, roundTo, toDecimal } from '@/lib/decimal'
import { formatThousands } from '@/lib/numberFormat' import { formatThousands } from '@/lib/numberFormat'
import ExcelJS from "ExcelJS"; import ExcelJS from "ExcelJS";
import { number } from 'motion-v/es';
// 统一数字千分位格式化,默认保留 2 位小数。 // 统一数字千分位格式化,默认保留 2 位小数。
const numberFormatter = (value: unknown, fractionDigits = 2) => const numberFormatter = (value: unknown, fractionDigits = 2) =>
formatThousands(value, fractionDigits) formatThousands(value, fractionDigits)
@ -552,7 +551,6 @@ export async function exportFile(fileName: string, data: any): Promise<void> {
// 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。 // 按模板生成最终工作簿:填充封面、目录、各分表及汇总数据。
async function generateTemplate(data) { async function generateTemplate(data) {
// const downTextTmp = { richText: [{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '常规' }, { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: '下标' }] }; // const downTextTmp = { richText: [{ font: { charset: 134, color: { theme: 1 }, italic: true, name: '宋体', size: 10 }, text: '常规' }, { font: { charset: 134, color: { theme: 1 }, italic: true, name: 'Calibri', size: 10, vertAlign: 'subscript' }, text: '下标' }] };
console.log(data)
try { try {
// 获取模板 // 获取模板
let templateExcel = 'template20260226001test010'; let templateExcel = 'template20260226001test010';
@ -724,7 +722,12 @@ async function generateTemplate(data) {
sheet_5.getRow(3).getCell(3).value = '/'; sheet_5.getRow(3).getCell(3).value = '/';
sheet_5.getRow(3).getCell(4).value = '/'; sheet_5.getRow(3).getCell(4).value = '/';
sheet_5.getRow(3).getCell(5).value = '/'; sheet_5.getRow(3).getCell(5).value = '/';
sheet_5.getRow(3).getCell(6).value = numberFormatter((ci.method5.addtional?.reduce((a, b) => a + b.m5.fee, 0) || 0) + (ci.method5.reserve?.fee || 0), 2); const method5AdditionalFee = ci.method5.addtional?.reduce((a, b) =>
addNumbers(a, toFiniteNumber(b?.m5?.fee)), 0) || 0;
sheet_5.getRow(3).getCell(6).value = numberFormatter(
addNumbers(method5AdditionalFee, toFiniteNumber(ci.method5.reserve?.fee)),
2
);
} }
// 更新目录的第三部分 // 更新目录的第三部分
@ -772,22 +775,22 @@ async function generateTemplate(data) {
targetRow.getCell(3).value = serviceX.name; targetRow.getCell(3).value = serviceX.name;
if (sobj.method1) { if (sobj.method1) {
targetRow.getCell(4).value = numberFormatter(sobj.method1.fee, 2); targetRow.getCell(4).value = numberFormatter(sobj.method1.fee, 2);
m1Sum += sobj.method1.fee; m1Sum = addNumbers(m1Sum, toFiniteNumber(sobj.method1.fee));
} }
if (sobj.method2) { if (sobj.method2) {
targetRow.getCell(5).value = numberFormatter(sobj.method2.fee, 2); targetRow.getCell(5).value = numberFormatter(sobj.method2.fee, 2);
m2Sum += sobj.method2.fee; m2Sum = addNumbers(m2Sum, toFiniteNumber(sobj.method2.fee));
} }
if (sobj.method3) { if (sobj.method3) {
targetRow.getCell(6).value = numberFormatter(sobj.method3.fee, 2); targetRow.getCell(6).value = numberFormatter(sobj.method3.fee, 2);
m3Sum += sobj.method3.fee; m3Sum = addNumbers(m3Sum, toFiniteNumber(sobj.method3.fee));
} }
if (sobj.method4) { if (sobj.method4) {
targetRow.getCell(7).value = numberFormatter(sobj.method4.fee, 2); targetRow.getCell(7).value = numberFormatter(sobj.method4.fee, 2);
m4Sum += sobj.method4.fee; m4Sum = addNumbers(m4Sum, toFiniteNumber(sobj.method4.fee));
} }
targetRow.getCell(8).value = numberFormatter(sobj.fee, 2); targetRow.getCell(8).value = numberFormatter(sobj.fee, 2);
serviceSum += sobj.fee; serviceSum = addNumbers(serviceSum, toFiniteNumber(sobj.fee));
}); });
if (sobj.method1 || sobj.method2) { if (sobj.method1 || sobj.method2) {
cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => { cusInsertRowFunc(4 + num_2, [sheet_2.getRow(4)], sheet_2, (targetRow) => {
@ -1800,8 +1803,6 @@ async function generateTemplate(data) {
descSheet.spliceRows(descRowNum, 5); descSheet.spliceRows(descRowNum, 5);
descRowNum++; descRowNum++;
} }
console.log(descRowNum);
return workbook; return workbook;
} catch (error) { } catch (error) {
console.log(error) console.log(error)