fix someone

This commit is contained in:
zwgczx committed 2026-03-07 11:47:07 +08:00
1 parent c482faacbf
commit ad4e9cdee0
17 files changed
+1084 -311

No files matched your search

@@ -55,10 +55,14 @@ interface DetailRow {
benchmarkBudget: number | null
benchmarkBudgetBasic: number | null
benchmarkBudgetOptional: number | null
benchmarkBudgetBasicChecked: boolean
benchmarkBudgetOptionalChecked: boolean
basicFormula: string
optionalFormula: string
consultCategoryFactor: number | null
majorFactor: number | null
workStageFactor: number | null
workRatio: number | null
budgetFee: number | null
budgetFeeBasic: number | null
budgetFeeOptional: number | null
@@ -80,6 +84,8 @@ const props = defineProps<{
}>()
const DB_KEY = computed(() => `tzGMF-${props.contractId}-${props.serviceId}`)
const HT_DB_KEY = computed(() => `ht-info-v3-${props.contractId}`)
const HT_CONSULT_FACTOR_KEY = computed(() => `ht-consult-category-factor-v1-${props.contractId}`)
const HT_MAJOR_FACTOR_KEY = computed(() => `ht-major-factor-v1-${props.contractId}`)
const BASE_INFO_KEY = 'xm-base-info-v1'
const activeIndustryCode = ref('')
const PRICING_CLEAR_SKIP_PREFIX = 'pricing-clear-skip:'
@@ -97,8 +103,8 @@ const getDefaultMajorFactorById = (id: string): number | null => majorFactorMap.
const loadFactorDefaults = async () => {
const [consultMap, majorMap] = await Promise.all([
loadConsultCategoryFactorMap(),
loadMajorFactorMap()
loadConsultCategoryFactorMap(HT_CONSULT_FACTOR_KEY.value),
loadMajorFactorMap(HT_MAJOR_FACTOR_KEY.value)
])
consultCategoryFactorMap.value = consultMap
majorFactorMap.value = majorMap
@@ -216,10 +222,14 @@ const buildDefaultRows = (): DetailRow[] => {
benchmarkBudget: null,
benchmarkBudgetBasic: null,
benchmarkBudgetOptional: null,
benchmarkBudgetBasicChecked: true,
benchmarkBudgetOptionalChecked: true,
basicFormula: '',
optionalFormula: '',
consultCategoryFactor: null,
majorFactor: null,
workStageFactor: 1,
workRatio: 100,
budgetFee: null,
budgetFeeBasic: null,
budgetFeeOptional: null,
@@ -239,10 +249,14 @@ type SourceRow = Pick<DetailRow, 'id'> &
| 'benchmarkBudget'
| 'benchmarkBudgetBasic'
| 'benchmarkBudgetOptional'
| 'benchmarkBudgetBasicChecked'
| 'benchmarkBudgetOptionalChecked'
| 'basicFormula'
| 'optionalFormula'
| 'consultCategoryFactor'
| 'majorFactor'
| 'workStageFactor'
| 'workRatio'
| 'budgetFee'
| 'budgetFeeBasic'
| 'budgetFeeOptional'
@@ -277,6 +291,10 @@ const mergeWithDictRows = (
benchmarkBudget: typeof fromDb.benchmarkBudget === 'number' ? fromDb.benchmarkBudget : null,
benchmarkBudgetBasic: typeof fromDb.benchmarkBudgetBasic === 'number' ? fromDb.benchmarkBudgetBasic : null,
benchmarkBudgetOptional: typeof fromDb.benchmarkBudgetOptional === 'number' ? fromDb.benchmarkBudgetOptional : null,
benchmarkBudgetBasicChecked:
typeof fromDb.benchmarkBudgetBasicChecked === 'boolean' ? fromDb.benchmarkBudgetBasicChecked : true,
benchmarkBudgetOptionalChecked:
typeof fromDb.benchmarkBudgetOptionalChecked === 'boolean' ? fromDb.benchmarkBudgetOptionalChecked : true,
basicFormula: typeof fromDb.basicFormula === 'string' ? fromDb.basicFormula : '',
optionalFormula: typeof fromDb.optionalFormula === 'string' ? fromDb.optionalFormula : '',
consultCategoryFactor:
@@ -295,6 +313,8 @@ const mergeWithDictRows = (
: hasMajorFactor
? null
: getDefaultMajorFactorById(row.id),
workStageFactor: typeof fromDb.workStageFactor === 'number' ? fromDb.workStageFactor : row.workStageFactor,
workRatio: typeof fromDb.workRatio === 'number' ? fromDb.workRatio : row.workRatio,
budgetFee: typeof fromDb.budgetFee === 'number' ? fromDb.budgetFee : null,
budgetFeeBasic: typeof fromDb.budgetFeeBasic === 'number' ? fromDb.budgetFeeBasic : null,
budgetFeeOptional: typeof fromDb.budgetFeeOptional === 'number' ? fromDb.budgetFeeOptional : null,
@@ -335,10 +355,46 @@ const formatReadonlyMoney = (params: any) => {
return formatThousands(roundTo(params.value, 2))
}
type BudgetCheckField = 'benchmarkBudgetBasicChecked' | 'benchmarkBudgetOptionalChecked'
const createBudgetCellRendererWithCheck = (checkField: BudgetCheckField) => (params: any) => {
const valueText = formatReadonlyMoney(params)
const hasValue = params.value != null && params.value !== ''
if (params.node?.group || params.node?.rowPinned || !params.data || !hasValue) {
return valueText
}
const wrapper = document.createElement('div')
wrapper.style.display = 'flex'
wrapper.style.alignItems = 'center'
wrapper.style.justifyContent = 'flex-end'
wrapper.style.gap = '6px'
wrapper.style.width = '100%'
const checkbox = document.createElement('input')
checkbox.type = 'checkbox'
checkbox.className = 'cursor-pointer'
checkbox.checked = params.data[checkField] !== false
checkbox.addEventListener('click', event => event.stopPropagation())
checkbox.addEventListener('change', () => {
params.data[checkField] = checkbox.checked
handleCellValueChanged()
})
const valueSpan = document.createElement('span')
valueSpan.textContent = valueText
wrapper.append(checkbox, valueSpan)
return wrapper
}
const getBenchmarkBudgetSplitByAmount = (row?: Pick<DetailRow, 'amount'>) =>
getBenchmarkBudgetSplitByScale(row?.amount, 'cost')
const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
const getBudgetFee = (
row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio'>
) => {
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
if (!benchmarkBudgetSplit) return null
@@ -346,19 +402,25 @@ const getBudgetFee = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultC
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
majorFactor: row?.majorFactor,
consultCategoryFactor: row?.consultCategoryFactor
consultCategoryFactor: row?.consultCategoryFactor,
workStageFactor: row?.workStageFactor,
workRatio: row?.workRatio
})
return splitBudgetFee ? splitBudgetFee.total : null
}
const getBudgetFeeSplit = (row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor'>) => {
const getBudgetFeeSplit = (
row?: Pick<DetailRow, 'amount' | 'majorFactor' | 'consultCategoryFactor' | 'workStageFactor' | 'workRatio'>
) => {
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
if (!benchmarkBudgetSplit) return null
return getScaleBudgetFeeSplit({
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
majorFactor: row?.majorFactor,
consultCategoryFactor: row?.consultCategoryFactor
consultCategoryFactor: row?.consultCategoryFactor,
workStageFactor: row?.workStageFactor,
workRatio: row?.workRatio
})
}
@@ -367,7 +429,7 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
headerName: '造价金额(万元)',
field: 'amount',
headerClass: 'ag-right-aligned-header',
minWidth: 100,
minWidth: 90,
flex: 2,
editable: params => !params.node?.group && !params.node?.rowPinned && Boolean(params.data?.hasCost),
@@ -383,37 +445,6 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableMoney
},
{
headerName: '咨询分类系数',
field: 'consultCategoryFactor',
width: 80,
minWidth: 70,
maxWidth: 90,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatConsultCategoryFactor
},
{
headerName: '专业系数',
field: 'majorFactor',
width: 80,
minWidth: 70,
maxWidth: 90,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatMajorFactor
},
{
headerName: '基准预算(元)',
marryChildren: true,
@@ -431,6 +462,7 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
params.node?.rowPinned
? params.data?.benchmarkBudgetBasic ?? null
: getBenchmarkBudgetSplitByAmount(params.data)?.basic ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetBasicChecked'),
valueFormatter: formatReadonlyMoney
},
{
@@ -446,6 +478,22 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
params.node?.rowPinned
? params.data?.benchmarkBudgetOptional ?? null
: getBenchmarkBudgetSplitByAmount(params.data)?.optional ?? null,
cellRenderer: createBudgetCellRendererWithCheck('benchmarkBudgetOptionalChecked'),
valueFormatter: formatReadonlyMoney
},
{
headerName: '小计',
field: 'benchmarkBudget',
colId: 'benchmarkBudgetTotal',
headerClass: 'ag-right-aligned-header',
minWidth: 100,
flex: 1,
cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params =>
params.node?.rowPinned
? params.data?.benchmarkBudget ?? null
: getBenchmarkBudgetSplitByAmount(params.data)?.total ?? null,
valueFormatter: formatReadonlyMoney
}
]
@@ -455,41 +503,71 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
marryChildren: true,
children: [
{
headerName: '基本工作',
field: 'budgetFeeBasic',
colId: 'budgetFeeBasic',
headerClass: 'ag-right-aligned-header',
minWidth: 120,
headerName: '咨询分类系数',
field: 'consultCategoryFactor',
colId: 'consultCategoryFactor',
minWidth: 80,
flex: 1,
cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params =>
params.node?.rowPinned
? params.data?.budgetFeeBasic ?? null
: getBudgetFeeSplit(params.data)?.basic ?? null,
valueFormatter: formatReadonlyMoney
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatConsultCategoryFactor
},
{
headerName: '可选工作',
field: 'budgetFeeOptional',
colId: 'budgetFeeOptional',
headerClass: 'ag-right-aligned-header',
minWidth: 120,
headerName: '专业系数',
field: 'majorFactor',
colId: 'majorFactor',
minWidth: 80,
flex: 1,
cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
valueGetter: params =>
params.node?.rowPinned
? params.data?.budgetFeeOptional ?? null
: getBudgetFeeSplit(params.data)?.optional ?? null,
valueFormatter: formatReadonlyMoney
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatMajorFactor
},
{
headerName: '工作环节系数(编审系数)',
field: 'workStageFactor',
colId: 'workStageFactor',
minWidth: 80,
flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableNumber
},
{
headerName: '工作占比(%)',
field: 'workRatio',
colId: 'workRatio',
minWidth: 80,
flex: 1,
editable: params => !params.node?.group && !params.node?.rowPinned,
cellClass: params => (!params.node?.group && !params.node?.rowPinned ? 'editable-cell-line' : ''),
cellClassRules: {
'editable-cell-empty': params =>
!params.node?.group && !params.node?.rowPinned && (params.value == null || params.value === '')
},
valueParser: params => parseNumberOrNull(params.newValue, { precision: 2 }),
valueFormatter: formatEditableNumber
},
{
headerName: '合计',
field: 'budgetFee',
colId: 'budgetFeeTotal',
headerClass: 'ag-right-aligned-header',
minWidth: 130,
minWidth: 120,
flex: 1,
cellClass: 'ag-right-aligned-cell',
aggFunc: decimalAggSum,
@@ -501,7 +579,7 @@ const columnDefs: Array<ColDef<DetailRow> | ColGroupDef<DetailRow>> = [
{
headerName: '说明',
field: 'remark',
minWidth: 180,
minWidth: 100,
flex: 1.2,
cellEditor: 'agLargeTextCellEditor',
wrapText: true,
@@ -570,10 +648,14 @@ const pinnedTopRowData = computed(() => [
benchmarkBudget: totalBenchmarkBudget.value,
benchmarkBudgetBasic: totalBenchmarkBudgetBasic.value,
benchmarkBudgetOptional: totalBenchmarkBudgetOptional.value,
benchmarkBudgetBasicChecked: true,
benchmarkBudgetOptionalChecked: true,
basicFormula: '',
optionalFormula: '',
consultCategoryFactor: null,
majorFactor: null,
workStageFactor: null,
workRatio: null,
budgetFee: totalBudgetFee.value,
budgetFeeBasic: totalBudgetFeeBasic.value,
budgetFeeOptional: totalBudgetFeeOptional.value,
@@ -582,30 +664,35 @@ const pinnedTopRowData = computed(() => [
}
])
const buildPersistDetailRows = () =>
detailRows.value.map(row => {
const syncComputedValuesToDetailRows = () => {
for (const row of detailRows.value) {
const benchmarkBudgetSplit = getBenchmarkBudgetSplitByAmount(row)
const budgetFeeSplit = benchmarkBudgetSplit
? getScaleBudgetFeeSplit({
benchmarkBudgetBasic: benchmarkBudgetSplit.basic,
benchmarkBudgetOptional: benchmarkBudgetSplit.optional,
majorFactor: row.majorFactor,
consultCategoryFactor: row.consultCategoryFactor
consultCategoryFactor: row.consultCategoryFactor,
workStageFactor: row.workStageFactor,
workRatio: row.workRatio
})
: null
return {
...row,
benchmarkBudget: benchmarkBudgetSplit?.total ?? null,
benchmarkBudgetBasic: benchmarkBudgetSplit?.basic ?? null,
benchmarkBudgetOptional: benchmarkBudgetSplit?.optional ?? null,
basicFormula: benchmarkBudgetSplit?.basicFormula ?? '',
optionalFormula: benchmarkBudgetSplit?.optionalFormula ?? '',
budgetFee: budgetFeeSplit?.total ?? null,
budgetFeeBasic: budgetFeeSplit?.basic ?? null,
budgetFeeOptional: budgetFeeSplit?.optional ?? null
}
})
row.benchmarkBudget = benchmarkBudgetSplit?.total ?? null
row.benchmarkBudgetBasic = benchmarkBudgetSplit?.basic ?? null
row.benchmarkBudgetOptional = benchmarkBudgetSplit?.optional ?? null
row.basicFormula = benchmarkBudgetSplit?.basicFormula ?? ''
row.optionalFormula = benchmarkBudgetSplit?.optionalFormula ?? ''
row.budgetFee = budgetFeeSplit?.total ?? null
row.budgetFeeBasic = budgetFeeSplit?.basic ?? null
row.budgetFeeOptional = budgetFeeSplit?.optional ?? null
}
}
const buildPersistDetailRows = () => {
syncComputedValuesToDetailRows()
return detailRows.value.map(row => ({ ...row }))
}
const saveToIndexedDB = async () => {
if (shouldSkipPersist()) return
@@ -637,30 +724,35 @@ const loadFromIndexedDB = async () => {
await ensureFactorDefaultsLoaded()
if (shouldForceDefaultLoad()) {
const applyContractDefaultRows = async () => {
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
detailRows.value = htData?.detailRows
? mergeWithDictRows(htData.detailRows, { includeAmount: false, includeFactorValues: false })
: buildDefaultRows()
const hasContractRows = Array.isArray(htData?.detailRows) && htData.detailRows.length > 0
detailRows.value = hasContractRows
? mergeWithDictRows(htData!.detailRows, { includeFactorValues: true })
: buildDefaultRows().map(row => ({
...row,
consultCategoryFactor: getDefaultConsultCategoryFactor(),
majorFactor: getDefaultMajorFactorById(row.id)
}))
syncComputedValuesToDetailRows()
}
if (shouldForceDefaultLoad()) {
await applyContractDefaultRows()
return
}
const data = await localforage.getItem<XmInfoState>(DB_KEY.value)
if (data) {
detailRows.value = mergeWithDictRows(data.detailRows)
syncComputedValuesToDetailRows()
return
}
const htData = await localforage.getItem<{ detailRows: SourceRow[] }>(HT_DB_KEY.value)
if (htData?.detailRows) {
detailRows.value = mergeWithDictRows(htData.detailRows, { includeAmount: false, includeFactorValues: false })
return
}
detailRows.value = buildDefaultRows()
await applyContractDefaultRows()
} catch (error) {
console.error('loadFromIndexedDB failed:', error)
detailRows.value = buildDefaultRows()
syncComputedValuesToDetailRows()
}
}
@@ -687,6 +779,15 @@ const importContractData = async () => {
}
}
const clearAllData = async () => {
try {
detailRows.value = buildDefaultRows()
await saveToIndexedDB()
} catch (error) {
console.error('clearAllData failed:', error)
}
}
watch(
() => pricingPaneReloadStore.getReloadVersion(props.contractId, props.serviceId),
(nextVersion, prevVersion) => {
@@ -702,10 +803,11 @@ let persistTimer: ReturnType<typeof setTimeout> | null = null
let gridPersistTimer: ReturnType<typeof setTimeout> | null = null
const handleCellValueChanged = () => {
syncComputedValuesToDetailRows()
if (gridPersistTimer) clearTimeout(gridPersistTimer)
gridPersistTimer = setTimeout(() => {
void saveToIndexedDB()
}, 1000)
}, 300)
}
onMounted(async () => {
@@ -748,28 +850,52 @@ const processCellFromClipboard = (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>
<AlertDialogRoot>
<AlertDialogTrigger as-child>
<Button type="button" variant="outline" size="sm">使用默认数据</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认覆盖当前明细</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将使用合同默认数据覆盖当前投资规模明细,是否继续?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button @click="importContractData">确认覆盖</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<div class="flex items-center gap-2">
<AlertDialogRoot>
<AlertDialogTrigger as-child>
<Button type="button" variant="outline" size="sm">清空</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认清空当前明细</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将清空当前投资规模明细,是否继续?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button variant="destructive" @click="clearAllData">确认清空</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
<AlertDialogRoot>
<AlertDialogTrigger as-child>
<Button type="button" variant="outline" size="sm">使用默认数据</Button>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="fixed inset-0 z-50 bg-black/45" />
<AlertDialogContent class="fixed left-1/2 top-1/2 z-50 w-[92vw] max-w-md -translate-x-1/2 -translate-y-1/2 rounded-lg border bg-background p-5 shadow-xl">
<AlertDialogTitle class="text-base font-semibold">确认覆盖当前明细</AlertDialogTitle>
<AlertDialogDescription class="mt-2 text-sm text-muted-foreground">
将使用合同默认数据覆盖当前投资规模明细,是否继续?
</AlertDialogDescription>
<div class="mt-4 flex items-center justify-end gap-2">
<AlertDialogCancel as-child>
<Button variant="outline">取消</Button>
</AlertDialogCancel>
<AlertDialogAction as-child>
<Button @click="importContractData">确认覆盖</Button>
</AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
</div>
</div>
<div class="ag-theme-quartz h-full min-h-0 w-full flex-1">
@@ -784,5 +910,3 @@ const processCellFromClipboard = (params: any) => {
</div>
</div>
</template>