From 5734cfa5340e4bfe5778aa925235d2e8bea264e4 Mon Sep 17 00:00:00 2001 From: wintsa <770775984@qq.com> Date: Wed, 25 Feb 2026 14:26:40 +0800 Subject: [PATCH] fix more --- index.html | 4 +- public/favicon.ico | Bin 0 -> 9662 bytes src/components/ui/button/index.ts | 2 +- src/components/views/ContractDetailView.vue | 60 +++ src/components/views/Ht.vue | 284 +++++++++++-- src/components/views/Xm.vue | 2 +- src/components/views/htInfo.vue | 428 ++++++++++++++++++++ src/components/views/xmInfo.vue | 70 +--- src/components/views/zxFw.vue | 377 +++++++++++++++++ src/layout/tab.vue | 395 ++++++++++++++++-- src/layout/typeLine.vue | 2 +- src/pinia/tab.ts | 94 ++++- 12 files changed, 1584 insertions(+), 134 deletions(-) create mode 100644 public/favicon.ico create mode 100644 src/components/views/ContractDetailView.vue create mode 100644 src/components/views/htInfo.vue create mode 100644 src/components/views/zxFw.vue diff --git a/index.html b/index.html index 70ad674..973ca0d 100644 --- a/index.html +++ b/index.html @@ -2,9 +2,9 @@
- + -x}Y~4 zcdCDHSDXD5cuVs2g;=yK#J#e;{OHY5fq~P?jPY|@Vq;?qmoBKzRf64wo6cMbGv^ce z#dnm3?ce>A*PY_*t6#!&-3gip@hjnsTCuoxG5p1wzAbSrxVyVI9y`+L?f+0^m)(S) z^PR90pR{aFi27V#81AMndrAFV{B}pa!}BRVxax1ErKOf`Zm!p0Y#E2l*pMMZ*oqY^ zL7(xOVXSFmjLXb9~>Sm ?zV2;% }{v-%o-pC?~){z&@|j+q>xFTY!S{q=u6w>c8` zXO4sOHr8LOKYi+g!q?ZgM(%se<6A8?H8pKcSjBEf!`>@lmpFuH_3HGx$o}TP+xl<6 zVTxEkZ5{vJf^)&kwvz2AxE7e4oSbKhANM6cKfkm2uQmmF*w9(PaZ@aiKQp~`&0l{0 z>)^Y^H<11>#9zvHf_<9v7C0<^*VFIX`}RI}cw2VyN3;d-Kk&dvw(a#dTp#z==sAZt z?-9Saj^X=4N^I5kfz18iXtTfgh7W$rqjiTkB0O~0zW$=ka`NQKr%Ypj9D+AnQc8?2 z5ARX(vp@-Vk_p3AY|Gl~_;cw!`@`S(__@mO1QYFlWS^A&?2wX@G9T~IavR2s8Pj>_ z(4jGJZfy3LI8x#e*&pktI)BfA8z#z1t4((i_e8#jz?u*-6c-m;jU79-MUFo-H1sco z@5!m!^NtWNih(nn&5NI|x#Kq?jr&{W;e8!;fzE39-4Zwq=T3UE^iGBE6MG3ip3BRZ zFP~52FPVQS9lurM9U0GqDUYZ--zjPxL-qGk#s_PU;O|jW9#M53kYUKk$Oyx?CE5V* z$;RWyk6RJ0BYWCLO> Z_-Q%!N? zWi} ;|Xo*e$CtPgTpp zC-vPcyS4s(6emxf6pD(9dj0OV{Mnkl+cq@zTL1f?9{8-Ee$+7X u(ca4s#myp3PWvjyR{HDo|C3y=&Y07R)UNC1pq{@6MNmjGBS z17LI1f0e@}02RyCYlR`G2f(5Pz_wVv9umT(1D61JEb0L;m&3UPz`>}$W$z=(+x9-$ fSiT*Q#bqFuEHI0qo~^pY + + + + + diff --git a/src/components/views/Ht.vue b/src/components/views/Ht.vue index 063360b..d5633eb 100644 --- a/src/components/views/Ht.vue +++ b/src/components/views/Ht.vue @@ -1,70 +1,292 @@ -+-合同段列表
- +- + \ No newline at end of file + diff --git a/src/components/views/Xm.vue b/src/components/views/Xm.vue index 2c6d8f5..450dda1 100644 --- a/src/components/views/Xm.vue +++ b/src/components/views/Xm.vue @@ -16,7 +16,7 @@ const xmView = markRaw(defineAsyncComponent(() => import('@/components/views/xmI const htView = markRaw(defineAsyncComponent(() => import('@/components/views/Ht.vue'))) const xmCategories = [ - { key: 'info', label: '分类信息', component: xmView }, + { key: 'info', label: '基础信息', component: xmView }, { key: 'contract', label: '合同段管理', component: htView } ] diff --git a/src/components/views/htInfo.vue b/src/components/views/htInfo.vue new file mode 100644 index 0000000..1c3b563 --- /dev/null +++ b/src/components/views/htInfo.vue @@ -0,0 +1,428 @@ + + + +- + {{ element.name }} ---+ + ++ + + diff --git a/src/components/views/xmInfo.vue b/src/components/views/xmInfo.vue index 4b93043..8fc0f78 100644 --- a/src/components/views/xmInfo.vue +++ b/src/components/views/xmInfo.vue @@ -2,6 +2,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 'ag-grid-enterprise' import { @@ -62,8 +63,6 @@ interface XmInfoState { detailRows: DetailRow[] } -const DB_NAME = 'jgjs-pricing-db' -const DB_STORE = 'form-state' const DB_KEY = 'xm-info-v3' const DEFAULT_PROJECT_NAME = 'xxx造价咨询服务' @@ -282,41 +281,15 @@ const pinnedTopRowData = computed(() => [ } ]) -const openDB = () => - new Promise++++ +合同规模明细
+导入导出++++ ((resolve, reject) => { - const request = window.indexedDB.open(DB_NAME, 1) - request.onupgradeneeded = () => { - const db = request.result - if (!db.objectStoreNames.contains(DB_STORE)) { - db.createObjectStore(DB_STORE) - } - } - - request.onsuccess = () => resolve(request.result) - request.onerror = () => reject(request.error) - }) const saveToIndexedDB = async () => { try { - const db = await openDB() - const tx = db.transaction(DB_STORE, 'readwrite') - const store = tx.objectStore(DB_STORE) - const payload: XmInfoState = { projectName: projectName.value, - detailRows: detailRows.value + detailRows: JSON.parse(JSON.stringify(detailRows.value)) } - - store.put(payload, DB_KEY) - - await new Promise ((resolve, reject) => { - tx.oncomplete = () => resolve() - tx.onerror = () => reject(tx.error) - tx.onabort = () => reject(tx.error) - }) - - db.close() + await localforage.setItem(DB_KEY, payload) } catch (error) { console.error('saveToIndexedDB failed:', error) } @@ -324,24 +297,8 @@ const saveToIndexedDB = async () => { const loadFromIndexedDB = async () => { try { - const db = await openDB() - const tx = db.transaction(DB_STORE, 'readonly') - const store = tx.objectStore(DB_STORE) - const request = store.get(DB_KEY) - - const data = await new Promise ((resolve, reject) => { - request.onsuccess = () => resolve(request.result as XmInfoState | undefined) - request.onerror = () => reject(request.error) - }) - - await new Promise ((resolve, reject) => { - tx.oncomplete = () => resolve() - tx.onerror = () => reject(tx.error) - tx.onabort = () => reject(tx.error) - }) - - db.close() - + const data = await localforage.getItem (DB_KEY) +console.log(data) if (data) { projectName.value = data.projectName || DEFAULT_PROJECT_NAME detailRows.value = mergeWithDictRows(data.detailRows) @@ -363,24 +320,29 @@ const schedulePersist = () => { }, 250) } -const handleBeforeUnload = () => { - void saveToIndexedDB() -} +// const handleBeforeUnload = () => { +// void saveToIndexedDB() +// } +let gridPersistTimer: ReturnType | null = null const handleCellValueChanged = () => { - schedulePersist() + if (gridPersistTimer) clearTimeout(gridPersistTimer) + gridPersistTimer = setTimeout(() => { + void saveToIndexedDB() + }, 1000) } watch(projectName, schedulePersist) onMounted(async () => { await loadFromIndexedDB() - window.addEventListener('beforeunload', handleBeforeUnload) + // window.addEventListener('beforeunload', handleBeforeUnload) }) onBeforeUnmount(() => { - window.removeEventListener('beforeunload', handleBeforeUnload) + // window.removeEventListener('beforeunload', handleBeforeUnload) if (persistTimer) clearTimeout(persistTimer) + if (gridPersistTimer) clearTimeout(gridPersistTimer) void saveToIndexedDB() }) const processCellForClipboard = (params:any) => { diff --git a/src/components/views/zxFw.vue b/src/components/views/zxFw.vue new file mode 100644 index 0000000..0161f72 --- /dev/null +++ b/src/components/views/zxFw.vue @@ -0,0 +1,377 @@ + + + + ++ + + diff --git a/src/layout/tab.vue b/src/layout/tab.vue index 98a4a8b..21a6c56 100644 --- a/src/layout/tab.vue +++ b/src/layout/tab.vue @@ -1,62 +1,387 @@ - -+ + ++ +++++ +咨询服务
+导入导出++++ - \ No newline at end of file + diff --git a/src/layout/typeLine.vue b/src/layout/typeLine.vue index 89036e6..630c43a 100644 --- a/src/layout/typeLine.vue +++ b/src/layout/typeLine.vue @@ -57,7 +57,7 @@ const activeComponent = computed(() => {- + +-+ +- {{ tab.title }} - -+ {{ tab.title }} + ++ + --- - + ++ ++ ++ ++ 导入/导出 + ++ ++ 导出数据 + ++ 导入数据 + ++ + ++ ++ 重置 + + ++ + +确认重置 ++ 将清空本地缓存(IndexDB / LocalStorage / SessionStorage)并恢复默认页面,确认继续吗? + ++++ +取消 ++ +确认重置 +-+ +-+ ++ 删除所有 + ++ 删除左侧 + ++ 删除右侧 + ++ 删除其他 + +-{{ props.title }}+diff --git a/src/pinia/tab.ts b/src/pinia/tab.ts index 550fc37..cff8792 100644 --- a/src/pinia/tab.ts +++ b/src/pinia/tab.ts @@ -3,15 +3,32 @@ import { defineStore } from 'pinia' import { ref } from 'vue' export const useTabStore = defineStore('tabs', () => { - const tabs = ref([ - { id: 'XmView', title: '项目卡片', componentName: 'XmView' } + interface TabItem> { + id: string; // 标签唯一标识 + title: string; // 标签标题 + componentName: string; // 组件名称 + props?: T; // 传递给组件的 props(可选,泛型适配不同组件) +} + const defaultTabs :TabItem[]= [ + { id: 'XmView', title: '项目卡片', componentName: 'XmView' } + ] + + const tabs = ref([ + ...defaultTabs ]) const activeTabId = ref('XmView') + const ensureActiveValid = () => { + const activeExists = tabs.value.some(t => t.id === activeTabId.value) + if (!activeExists) { + activeTabId.value = tabs.value[0]?.id || 'XmView' + } + } + const openTab = (config: { id: string; title: string; componentName: string; props?: any }) => { - const exists = tabs.value.find(t => t.id === config.id) + const exists = tabs.value.some(t => t.id === config.id) if (!exists) { - tabs.value.push(config) + tabs.value = [...tabs.value, config] } activeTabId.value = config.id } @@ -19,13 +36,72 @@ export const useTabStore = defineStore('tabs', () => { const removeTab = (id: string) => { if (id === 'XmView') return // 首页不可删除 const index = tabs.value.findIndex(t => t.id === id) - if (activeTabId.value === id) { - activeTabId.value = (tabs.value[index - 1]?.id || tabs.value[index + 1]?.id ) as string + if (index < 0) return + const wasActive = activeTabId.value === id + tabs.value = tabs.value.filter(t => t.id !== id) + + if (tabs.value.length === 0) { + tabs.value = [...defaultTabs] + } + + if (wasActive) { + const fallbackIndex = Math.max(0, Math.min(index - 1, tabs.value.length - 1)) + activeTabId.value = tabs.value[fallbackIndex]?.id || 'XmView' + return + } + + const activeStillExists = tabs.value.some(t => t.id === activeTabId.value) + if (!activeStillExists) { + activeTabId.value = tabs.value[0]?.id || 'XmView' } - tabs.value.splice(index, 1) } - return { tabs, activeTabId, openTab, removeTab } + const closeAllTabs = () => { + tabs.value = tabs.value.filter(t => t.id === 'XmView') + if (tabs.value.length === 0) tabs.value = [...defaultTabs] + activeTabId.value = 'XmView' + } + + const closeLeftTabs = (targetId: string) => { + const targetIndex = tabs.value.findIndex(t => t.id === targetId) + if (targetIndex < 0) return + tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index >= targetIndex) + ensureActiveValid() + } + + const closeRightTabs = (targetId: string) => { + const targetIndex = tabs.value.findIndex(t => t.id === targetId) + if (targetIndex < 0) return + tabs.value = tabs.value.filter((tab, index) => tab.id === 'XmView' || index <= targetIndex) + ensureActiveValid() + } + + const closeOtherTabs = (targetId: string) => { + tabs.value = tabs.value.filter(tab => tab.id === 'XmView' || tab.id === targetId) + if (tabs.value.length === 0) tabs.value = [...defaultTabs] + if (targetId === 'XmView') { + activeTabId.value = 'XmView' + return + } + activeTabId.value = tabs.value.some(t => t.id === targetId) ? targetId : 'XmView' + } + + const resetTabs = () => { + tabs.value = [...defaultTabs] + activeTabId.value = 'XmView' + } + + return { + tabs, + activeTabId, + openTab, + removeTab, + closeAllTabs, + closeLeftTabs, + closeRightTabs, + closeOtherTabs, + resetTabs + } }, { // --- 关键配置:开启持久化 --- persist: { @@ -33,4 +109,4 @@ export const useTabStore = defineStore('tabs', () => { storage: localStorage, // 也可以改用 sessionStorage pick: ['tabs', 'activeTabId'], // 指定哪些变量需要持久化 } -}) \ No newline at end of file +})