From 1609f19b9c0aa526617f1a65f8b193705138f6f8 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Wed, 25 Feb 2026 17:59:10 +0800 Subject: [PATCH] fix more --- index.html | 2 +- src/components/views/ContractDetailView.vue | 15 +- src/components/views/Ht.vue | 51 +- src/components/views/ZxFwView.vue | 54 ++ src/components/views/htInfo.vue | 138 ++-- .../views/pricingView/HourlyPricingPane.vue | 416 ++++++++++ .../InvestmentScalePricingPane.vue | 416 ++++++++++ .../pricingView/LandScalePricingPane.vue | 416 ++++++++++ .../views/pricingView/WorkloadPricingPane.vue | 416 ++++++++++ src/components/views/xmInfo.vue | 122 ++- src/components/views/zxFw.vue | 749 ++++++++++++------ src/layout/tab.vue | 49 +- src/layout/typeLine.vue | 4 +- src/sql.ts | 66 ++ 14 files changed, 2481 insertions(+), 433 deletions(-) create mode 100644 src/components/views/ZxFwView.vue create mode 100644 src/components/views/pricingView/HourlyPricingPane.vue create mode 100644 src/components/views/pricingView/InvestmentScalePricingPane.vue create mode 100644 src/components/views/pricingView/LandScalePricingPane.vue create mode 100644 src/components/views/pricingView/WorkloadPricingPane.vue create mode 100644 src/sql.ts diff --git a/index.html b/index.html index 973ca0d..2bf744d 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + 造价计算工具 diff --git a/src/components/views/ContractDetailView.vue b/src/components/views/ContractDetailView.vue index e0c3abb..70fe112 100644 --- a/src/components/views/ContractDetailView.vue +++ b/src/components/views/ContractDetailView.vue @@ -43,11 +43,16 @@ const htView = markRaw( ); const zxfwView = markRaw( - defineAsyncComponent({ - loader: () => import('@/components/views/zxFw.vue'), - // 可选:加载失败时的兜底组件 - onError: (err) => { - console.error('加载 Ht 组件失败:', err); + defineComponent({ + name: 'ZxFwWithProps', + setup() { + const AsyncZxFw = defineAsyncComponent({ + loader: () => import('@/components/views/zxFw.vue'), + onError: (err) => { + console.error('加载 zxFw 组件失败:', err); + } + }); + return () => h(AsyncZxFw, { contractId: props.contractId }); } }) ); diff --git a/src/components/views/Ht.vue b/src/components/views/Ht.vue index d5633eb..0d0b84d 100644 --- a/src/components/views/Ht.vue +++ b/src/components/views/Ht.vue @@ -6,6 +6,14 @@ import { Card, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { useTabStore } from '@/pinia/tab' import { Edit3, Plus, Trash2, X } from 'lucide-vue-next' +import { + ToastAction, + ToastDescription, + ToastProvider, + ToastRoot, + ToastTitle, + ToastViewport +} from 'reka-ui' interface ContractItem { id: string @@ -25,9 +33,9 @@ const contracts = ref([]) const showCreateModal = ref(false) const contractNameInput = ref('') const editingContractId = ref(null) +const toastOpen = ref(false) +const toastTitle = ref('操作成功') const toastText = ref('') -const showToast = ref(false) -let toastTimer: ReturnType | null = null const modalOffset = ref({ x: 0, y: 0 }) let dragStartX = 0 let dragStartY = 0 @@ -54,12 +62,12 @@ const formatDateTime = (value: string) => { } const notify = (text: string) => { + toastTitle.value = '操作成功' toastText.value = text - showToast.value = true - if (toastTimer) clearTimeout(toastTimer) - toastTimer = setTimeout(() => { - showToast.value = false - }, 1600) + toastOpen.value = false + requestAnimationFrame(() => { + toastOpen.value = true + }) } const saveContracts = async () => { @@ -141,6 +149,7 @@ const createContract = async () => { const deleteContract = async (id: string) => { contracts.value = contracts.value.filter(item => item.id !== id) await saveContracts() + tabStore.removeTab(`contract-${id}`) notify('删除成功') } @@ -185,12 +194,12 @@ onMounted(async () => { onBeforeUnmount(() => { stopDrag() - if (toastTimer) clearTimeout(toastTimer) void saveContracts() }) -
- {{ toastText }} -
-
{
+ +
+ {{ toastTitle }} + {{ toastText }} +
+ + 知道了 + +
+ + diff --git a/src/components/views/ZxFwView.vue b/src/components/views/ZxFwView.vue new file mode 100644 index 0000000..3fd2f3f --- /dev/null +++ b/src/components/views/ZxFwView.vue @@ -0,0 +1,54 @@ + + + diff --git a/src/components/views/htInfo.vue b/src/components/views/htInfo.vue index 1c3b563..8704e90 100644 --- a/src/components/views/htInfo.vue +++ b/src/components/views/htInfo.vue @@ -3,6 +3,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { AgGridVue } from 'ag-grid-vue3' import type { ColDef, GridOptions } from 'ag-grid-community' import localforage from 'localforage' +import { majorList } from '@/sql' import 'ag-grid-enterprise' import { @@ -37,11 +38,13 @@ const myTheme = themeQuartz.withParams({ dataBackgroundColor: "#fefefe" }); interface DictLeaf { + id: string code: string name: string } interface DictGroup { + id: string code: string name: string children: DictLeaf[] @@ -70,66 +73,60 @@ const DB_KEY = computed(() => `ht-info-v3-${props.contractId}`) const detailRows = ref([]) -const detailDict: DictGroup[] = [ - { - code: 'E1', - name: '交通运输工程通用专业', - children: [ - { code: 'E1-1', name: '征地(用海)补偿' }, - { code: 'E1-2', name: '拆迁补偿' }, - { code: 'E1-3', name: '迁改工程' }, - { code: 'E1-4', name: '工程建设其他费' } - ] - }, - { - code: 'E2', - name: '公路工程专业', - children: [ - { code: 'E2-1', name: '临时工程' }, - { code: 'E2-2', name: '路基工程' }, - { code: 'E2-3', name: '路面工程' }, - { code: 'E2-4', name: '桥涵工程' }, - { code: 'E2-5', name: '隧道工程' }, - { code: 'E2-6', name: '交叉工程' }, - { code: 'E2-7', name: '机电工程' }, - { code: 'E2-8', name: '交通安全设施工程' }, - { code: 'E2-9', name: '绿化及环境保护工程' }, - { code: 'E2-10', name: '房建工程' } - ] - }, - { - code: 'E3', - name: '铁路工程专业', - children: [ - { code: 'E3-1', name: '大型临时设施和过渡工程' }, - { code: 'E3-2', name: '路基工程' }, - { code: 'E3-3', name: '桥涵工程' }, - { code: 'E3-4', name: '隧道及明洞工程' }, - { code: 'E3-5', name: '轨道工程' }, - { code: 'E3-6', name: '通信、信号、信息及灾害监测工程' }, - { code: 'E3-7', name: '电力及电力牵引供电工程' }, - { code: 'E3-8', name: '房建工程(房屋建筑及附属工程)' }, - { code: 'E3-9', name: '装饰装修工程' } - ] - }, - { - code: 'E4', - name: '水运工程专业', - children: [ - { code: 'E4-1', name: '临时工程' }, - { code: 'E4-2', name: '土建工程' }, - { code: 'E4-3', name: '机电与金属结构工程' }, - { code: 'E4-4', name: '设备工程' }, - { code: 'E4-5', name: '附属房建工程(房屋建筑及附属工程)' } - ] - } -] +type majorLite = { code: string; name: string } +const serviceEntries = Object.entries(majorList as Record) + .sort((a, b) => Number(a[0]) - Number(b[0])) + .filter((entry): entry is [string, majorLite] => { + const item = entry[1] + return Boolean(item?.code && item?.name) + }) -const codeNameMap = new Map() +const detailDict: DictGroup[] = (() => { + const groupMap = new Map() + const groupOrder: string[] = [] + const codeLookup = new Map(serviceEntries.map(([key, item]) => [item.code, { id: key, code: item.code, name: item.name }])) + + for (const [key, item] of serviceEntries) { + const code = item.code + const isGroup = !code.includes('-') + if (isGroup) { + if (!groupMap.has(code)) groupOrder.push(code) + groupMap.set(code, { + id: key, + code, + name: item.name, + children: [] + }) + continue + } + + const parentCode = code.split('-')[0] + if (!groupMap.has(parentCode)) { + const parent = codeLookup.get(parentCode) + if (!groupOrder.includes(parentCode)) groupOrder.push(parentCode) + groupMap.set(parentCode, { + id: parent?.id || `group-${parentCode}`, + code: parentCode, + name: parent?.name || parentCode, + children: [] + }) + } + + groupMap.get(parentCode)!.children.push({ + id: key, + code, + name: item.name + }) + } + + return groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group)) +})() + +const idLabelMap = new Map() for (const group of detailDict) { - codeNameMap.set(group.code, group.name) + idLabelMap.set(group.id, `${group.code} ${group.name}`) for (const child of group.children) { - codeNameMap.set(child.code, child.name) + idLabelMap.set(child.id, `${child.code} ${child.name}`) } } @@ -138,14 +135,14 @@ const buildDefaultRows = (): DetailRow[] => { for (const group of detailDict) { for (const child of group.children) { rows.push({ - id: `row-${child.code}`, + id: child.id, groupCode: group.code, groupName: group.name, majorCode: child.code, majorName: child.name, amount: null, landArea: null, - path: [group.code, child.code] + path: [group.id, child.id] }) } } @@ -155,11 +152,11 @@ const buildDefaultRows = (): DetailRow[] => { const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => { const dbValueMap = new Map() for (const row of rowsFromDb || []) { - dbValueMap.set(row.majorCode, row) + dbValueMap.set(row.id, row) } return buildDefaultRows().map(row => { - const fromDb = dbValueMap.get(row.majorCode) + const fromDb = dbValueMap.get(row.id) if (!fromDb) return row return { @@ -239,9 +236,8 @@ const autoGroupColumnDef: ColDef = { if (params.node?.rowPinned) { return '总合计' } - const code = String(params.value || '') - const name = codeNameMap.get(code) || '' - return name ? `${code} ${name}` : code + const nodeId = String(params.value || '') + return idLabelMap.get(nodeId) || nodeId } } @@ -289,6 +285,7 @@ const saveToIndexedDB = async () => { const payload = { detailRows: JSON.parse(JSON.stringify(detailRows.value)) } + console.log('Saving to IndexedDB:', payload) await localforage.setItem(DB_KEY.value, payload) } catch (error) { console.error('saveToIndexedDB failed:', error) @@ -311,16 +308,9 @@ const loadFromIndexedDB = async () => { } let persistTimer: ReturnType | null = null -const schedulePersist = () => { - if (persistTimer) clearTimeout(persistTimer) - persistTimer = setTimeout(() => { - void saveToIndexedDB() - }, 250) -} -// const handleBeforeUnload = () => { -// void saveToIndexedDB() -// } + + let gridPersistTimer: ReturnType | null = null const handleCellValueChanged = () => { @@ -333,11 +323,9 @@ const handleCellValueChanged = () => { onMounted(async () => { await loadFromIndexedDB() - // window.addEventListener('beforeunload', handleBeforeUnload) }) onBeforeUnmount(() => { - // window.removeEventListener('beforeunload', handleBeforeUnload) if (persistTimer) clearTimeout(persistTimer) if (gridPersistTimer) clearTimeout(gridPersistTimer) void saveToIndexedDB() diff --git a/src/components/views/pricingView/HourlyPricingPane.vue b/src/components/views/pricingView/HourlyPricingPane.vue new file mode 100644 index 0000000..8704e90 --- /dev/null +++ b/src/components/views/pricingView/HourlyPricingPane.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/src/components/views/pricingView/InvestmentScalePricingPane.vue b/src/components/views/pricingView/InvestmentScalePricingPane.vue new file mode 100644 index 0000000..8704e90 --- /dev/null +++ b/src/components/views/pricingView/InvestmentScalePricingPane.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/src/components/views/pricingView/LandScalePricingPane.vue b/src/components/views/pricingView/LandScalePricingPane.vue new file mode 100644 index 0000000..8704e90 --- /dev/null +++ b/src/components/views/pricingView/LandScalePricingPane.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/src/components/views/pricingView/WorkloadPricingPane.vue b/src/components/views/pricingView/WorkloadPricingPane.vue new file mode 100644 index 0000000..8704e90 --- /dev/null +++ b/src/components/views/pricingView/WorkloadPricingPane.vue @@ -0,0 +1,416 @@ + + + + + diff --git a/src/components/views/xmInfo.vue b/src/components/views/xmInfo.vue index 8fc0f78..e8b4ed8 100644 --- a/src/components/views/xmInfo.vue +++ b/src/components/views/xmInfo.vue @@ -3,6 +3,7 @@ import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { AgGridVue } from 'ag-grid-vue3' import type { ColDef, GridOptions } from 'ag-grid-community' import localforage from 'localforage' +import { majorList } from '@/sql' import 'ag-grid-enterprise' import { @@ -37,11 +38,13 @@ const myTheme = themeQuartz.withParams({ dataBackgroundColor: "#fefefe" }); interface DictLeaf { + id: string code: string name: string } interface DictGroup { + id: string code: string name: string children: DictLeaf[] @@ -68,67 +71,59 @@ const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务' const projectName = ref(DEFAULT_PROJECT_NAME) const detailRows = ref([]) +type MajorLite = { code: string; name: string } +const majorEntries = Object.entries(majorList as Record) + .sort((a, b) => Number(a[0]) - Number(b[0])) + .filter((entry): entry is [string, MajorLite] => { + const item = entry[1] + return Boolean(item?.code && item?.name) + }) -const detailDict: DictGroup[] = [ - { - code: 'E1', - name: '交通运输工程通用专业', - children: [ - { code: 'E1-1', name: '征地(用海)补偿' }, - { code: 'E1-2', name: '拆迁补偿' }, - { code: 'E1-3', name: '迁改工程' }, - { code: 'E1-4', name: '工程建设其他费' } - ] - }, - { - code: 'E2', - name: '公路工程专业', - children: [ - { code: 'E2-1', name: '临时工程' }, - { code: 'E2-2', name: '路基工程' }, - { code: 'E2-3', name: '路面工程' }, - { code: 'E2-4', name: '桥涵工程' }, - { code: 'E2-5', name: '隧道工程' }, - { code: 'E2-6', name: '交叉工程' }, - { code: 'E2-7', name: '机电工程' }, - { code: 'E2-8', name: '交通安全设施工程' }, - { code: 'E2-9', name: '绿化及环境保护工程' }, - { code: 'E2-10', name: '房建工程' } - ] - }, - { - code: 'E3', - name: '铁路工程专业', - children: [ - { code: 'E3-1', name: '大型临时设施和过渡工程' }, - { code: 'E3-2', name: '路基工程' }, - { code: 'E3-3', name: '桥涵工程' }, - { code: 'E3-4', name: '隧道及明洞工程' }, - { code: 'E3-5', name: '轨道工程' }, - { code: 'E3-6', name: '通信、信号、信息及灾害监测工程' }, - { code: 'E3-7', name: '电力及电力牵引供电工程' }, - { code: 'E3-8', name: '房建工程(房屋建筑及附属工程)' }, - { code: 'E3-9', name: '装饰装修工程' } - ] - }, - { - code: 'E4', - name: '水运工程专业', - children: [ - { code: 'E4-1', name: '临时工程' }, - { code: 'E4-2', name: '土建工程' }, - { code: 'E4-3', name: '机电与金属结构工程' }, - { code: 'E4-4', name: '设备工程' }, - { code: 'E4-5', name: '附属房建工程(房屋建筑及附属工程)' } - ] +const detailDict: DictGroup[] = (() => { + const groupMap = new Map() + const groupOrder: string[] = [] + const codeLookup = new Map(majorEntries.map(([key, item]) => [item.code, { id: key, ...item }])) + + for (const [key, item] of majorEntries) { + const isGroup = !item.code.includes('-') + if (isGroup) { + if (!groupMap.has(item.code)) groupOrder.push(item.code) + groupMap.set(item.code, { + id: key, + code: item.code, + name: item.name, + children: [] + }) + continue + } + + const parentCode = item.code.split('-')[0] + if (!groupMap.has(parentCode)) { + const parent = codeLookup.get(parentCode) + if (!groupOrder.includes(parentCode)) groupOrder.push(parentCode) + groupMap.set(parentCode, { + id: parent?.id || `group-${parentCode}`, + code: parentCode, + name: parent?.name || parentCode, + children: [] + }) + } + + groupMap.get(parentCode)!.children.push({ + id: key, + code: item.code, + name: item.name + }) } -] -const codeNameMap = new Map() + return groupOrder.map(code => groupMap.get(code)).filter((group): group is DictGroup => Boolean(group)) +})() + +const idLabelMap = new Map() for (const group of detailDict) { - codeNameMap.set(group.code, group.name) + idLabelMap.set(group.id, `${group.code} ${group.name}`) for (const child of group.children) { - codeNameMap.set(child.code, child.name) + idLabelMap.set(child.id, `${child.code} ${child.name}`) } } @@ -137,14 +132,14 @@ const buildDefaultRows = (): DetailRow[] => { for (const group of detailDict) { for (const child of group.children) { rows.push({ - id: `row-${child.code}`, + id: child.id, groupCode: group.code, groupName: group.name, majorCode: child.code, majorName: child.name, amount: null, landArea: null, - path: [group.code, child.code] + path: [group.id, child.id] }) } } @@ -154,11 +149,11 @@ const buildDefaultRows = (): DetailRow[] => { const mergeWithDictRows = (rowsFromDb: DetailRow[] | undefined): DetailRow[] => { const dbValueMap = new Map() for (const row of rowsFromDb || []) { - dbValueMap.set(row.majorCode, row) + dbValueMap.set(row.id, row) } return buildDefaultRows().map(row => { - const fromDb = dbValueMap.get(row.majorCode) + const fromDb = dbValueMap.get(row.id) if (!fromDb) return row return { @@ -238,9 +233,8 @@ const autoGroupColumnDef: ColDef = { if (params.node?.rowPinned) { return '总合计' } - const code = String(params.value || '') - const name = codeNameMap.get(code) || '' - return name ? `${code} ${name}` : code + const nodeId = String(params.value || '') + return idLabelMap.get(nodeId) || nodeId } } @@ -289,6 +283,7 @@ const saveToIndexedDB = async () => { projectName: projectName.value, detailRows: JSON.parse(JSON.stringify(detailRows.value)) } + console.log(payload) await localforage.setItem(DB_KEY, payload) } catch (error) { console.error('saveToIndexedDB failed:', error) @@ -298,7 +293,6 @@ const saveToIndexedDB = async () => { const loadFromIndexedDB = async () => { try { const data = await localforage.getItem(DB_KEY) -console.log(data) if (data) { projectName.value = data.projectName || DEFAULT_PROJECT_NAME detailRows.value = mergeWithDictRows(data.detailRows) diff --git a/src/components/views/zxFw.vue b/src/components/views/zxFw.vue index 0161f72..a7f5f55 100644 --- a/src/components/views/zxFw.vue +++ b/src/components/views/zxFw.vue @@ -1,239 +1,441 @@ - diff --git a/src/layout/tab.vue b/src/layout/tab.vue index 21a6c56..0e89b55 100644 --- a/src/layout/tab.vue +++ b/src/layout/tab.vue @@ -1,5 +1,6 @@