From bec166eb55d3b3218d42e7990edbe10d1ee3a14a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E7=90=A6?= Date: Thu, 22 Jan 2026 15:37:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- pnpm-lock.yaml | 61 +-- src/components/spreadsheet/Cell.vue | 29 ++ src/components/spreadsheet/ContextMenu.vue | 37 ++ src/components/spreadsheet/FormulaBar.vue | 17 + src/components/spreadsheet/GridEditor.vue | 446 ++++++++++++++++++ .../spreadsheet/SelectionOverlay.vue | 26 + src/components/spreadsheet/StatusBar.vue | 16 + src/components/spreadsheet/Toolbar.vue | 94 ++++ src/layouts/Header.vue | 55 ++- src/stores/spreadsheet.js | 242 ++++++++++ src/utils/db.js | 46 ++ src/views/index/index.vue | 68 ++- vite.config.js | 2 +- 14 files changed, 1060 insertions(+), 81 deletions(-) create mode 100644 src/components/spreadsheet/Cell.vue create mode 100644 src/components/spreadsheet/ContextMenu.vue create mode 100644 src/components/spreadsheet/FormulaBar.vue create mode 100644 src/components/spreadsheet/GridEditor.vue create mode 100644 src/components/spreadsheet/SelectionOverlay.vue create mode 100644 src/components/spreadsheet/StatusBar.vue create mode 100644 src/components/spreadsheet/Toolbar.vue create mode 100644 src/stores/spreadsheet.js create mode 100644 src/utils/db.js diff --git a/index.html b/index.html index 8388c4b..ef68414 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Vite + Vue + 奶酪文档
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 061cee9..45e372f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@ant-design/icons-vue': specifier: ^7.0.1 version: 7.0.1(vue@3.5.13) + '@fortawesome/fontawesome-free': + specifier: ^6.7.2 + version: 6.7.2 '@tailwindcss/vite': specifier: ^4.1.4 version: 4.1.4(vite@6.3.3(jiti@2.4.2)(lightningcss@1.29.2)(sass@1.88.0)) @@ -23,9 +26,6 @@ importers: ant-design-vue: specifier: ^4.1.1 version: 4.2.6(vue@3.5.13) - aos: - specifier: ^2.3.4 - version: 2.3.4 axios: specifier: ^1.6.2 version: 1.9.0 @@ -263,6 +263,10 @@ packages: cpu: [x64] os: [win32] + '@fortawesome/fontawesome-free@6.7.2': + resolution: {integrity: sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==} + engines: {node: '>=6'} + '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} @@ -295,42 +299,36 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -388,67 +386,56 @@ packages: resolution: {integrity: sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.40.0': resolution: {integrity: sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.40.0': resolution: {integrity: sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.40.0': resolution: {integrity: sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loongarch64-gnu@4.40.0': resolution: {integrity: sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-powerpc64le-gnu@4.40.0': resolution: {integrity: sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.40.0': resolution: {integrity: sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.40.0': resolution: {integrity: sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.40.0': resolution: {integrity: sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.40.0': resolution: {integrity: sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.40.0': resolution: {integrity: sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.40.0': resolution: {integrity: sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==} @@ -506,28 +493,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.4': resolution: {integrity: sha512-X3As2xhtgPTY/m5edUtddmZ8rCruvBvtxYLMw9OsZdH01L2gS2icsHRwxdU0dMItNfVmrBezueXZCHxVeeb7Aw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.4': resolution: {integrity: sha512-2VG4DqhGaDSmYIu6C4ua2vSLXnJsb/C9liej7TuSO04NK+JJJgJucDUgmX6sn7Gw3Cs5ZJ9ZLrnI0QRDOjLfNQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.4': resolution: {integrity: sha512-v+mxVgH2kmur/X5Mdrz9m7TsoVjbdYQT0b4Z+dr+I4RvreCNXyCFELZL/DO0M1RsidZTrm6O1eMnV6zlgEzTMQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.4': resolution: {integrity: sha512-2TLe9ir+9esCf6Wm+lLWTMbgklIjiF0pbmDnwmhR9MksVOq+e8aP3TSsXySnBDDvTTVd/vKu1aNttEGj3P6l8Q==} @@ -632,9 +615,6 @@ packages: peerDependencies: vue: '>=3.2.0' - aos@2.3.4: - resolution: {integrity: sha512-zh/ahtR2yME4I51z8IttIt4lC1Nw0ktsFtmeDzID1m9naJnWXhCoARaCgNOGXb5CLy3zm+wqmRAEgMYB5E2HUw==} - array-tree-filter@2.1.0: resolution: {integrity: sha512-4ROwICNlNw/Hqa9v+rk5h22KjmzB1JGTMVKP2AKJBOCgb0yL0ASf0+YvCcLNNwquOHNX48jkeZIJ3a+oOQqKcw==} @@ -659,9 +639,6 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} - classlist-polyfill@1.2.0: - resolution: {integrity: sha512-GzIjNdcEtH4ieA2S8NmrSxv7DfEV5fmixQeyTmqmRmRJPGpRBaSnA2a0VrCjyT8iW8JjEdMbKzDotAJf+ajgaQ==} - combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -851,28 +828,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.29.2: resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.29.2: resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.29.2: resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.29.2: resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} @@ -893,12 +866,6 @@ packages: lodash-es@4.17.21: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} - lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -1229,6 +1196,8 @@ snapshots: '@esbuild/win32-x64@0.25.3': optional: true + '@fortawesome/fontawesome-free@6.7.2': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@parcel/watcher-android-arm64@2.5.1': @@ -1547,12 +1516,6 @@ snapshots: vue-types: 3.0.2(vue@3.5.13) warning: 4.0.3 - aos@2.3.4: - dependencies: - classlist-polyfill: 1.2.0 - lodash.debounce: 4.0.8 - lodash.throttle: 4.1.1 - array-tree-filter@2.1.0: {} async-validator@4.2.5: {} @@ -1581,8 +1544,6 @@ snapshots: dependencies: readdirp: 4.1.2 - classlist-polyfill@1.2.0: {} - combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -1787,10 +1748,6 @@ snapshots: lodash-es@4.17.21: {} - lodash.debounce@4.0.8: {} - - lodash.throttle@4.1.1: {} - lodash@4.17.21: {} loose-envify@1.4.0: diff --git a/src/components/spreadsheet/Cell.vue b/src/components/spreadsheet/Cell.vue new file mode 100644 index 0000000..bd5147e --- /dev/null +++ b/src/components/spreadsheet/Cell.vue @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/src/components/spreadsheet/ContextMenu.vue b/src/components/spreadsheet/ContextMenu.vue new file mode 100644 index 0000000..a6c6c73 --- /dev/null +++ b/src/components/spreadsheet/ContextMenu.vue @@ -0,0 +1,37 @@ + + + \ No newline at end of file diff --git a/src/components/spreadsheet/FormulaBar.vue b/src/components/spreadsheet/FormulaBar.vue new file mode 100644 index 0000000..05428b9 --- /dev/null +++ b/src/components/spreadsheet/FormulaBar.vue @@ -0,0 +1,17 @@ + + + \ No newline at end of file diff --git a/src/components/spreadsheet/GridEditor.vue b/src/components/spreadsheet/GridEditor.vue new file mode 100644 index 0000000..11fb4be --- /dev/null +++ b/src/components/spreadsheet/GridEditor.vue @@ -0,0 +1,446 @@ + + + + + \ No newline at end of file diff --git a/src/components/spreadsheet/SelectionOverlay.vue b/src/components/spreadsheet/SelectionOverlay.vue new file mode 100644 index 0000000..cc7894c --- /dev/null +++ b/src/components/spreadsheet/SelectionOverlay.vue @@ -0,0 +1,26 @@ + + + \ No newline at end of file diff --git a/src/components/spreadsheet/StatusBar.vue b/src/components/spreadsheet/StatusBar.vue new file mode 100644 index 0000000..b8ac331 --- /dev/null +++ b/src/components/spreadsheet/StatusBar.vue @@ -0,0 +1,16 @@ + + + \ No newline at end of file diff --git a/src/components/spreadsheet/Toolbar.vue b/src/components/spreadsheet/Toolbar.vue new file mode 100644 index 0000000..fa72670 --- /dev/null +++ b/src/components/spreadsheet/Toolbar.vue @@ -0,0 +1,94 @@ + + + \ No newline at end of file diff --git a/src/layouts/Header.vue b/src/layouts/Header.vue index 5dfaf00..c84056a 100644 --- a/src/layouts/Header.vue +++ b/src/layouts/Header.vue @@ -2,31 +2,38 @@ - + \ No newline at end of file diff --git a/src/stores/spreadsheet.js b/src/stores/spreadsheet.js new file mode 100644 index 0000000..d5bf688 --- /dev/null +++ b/src/stores/spreadsheet.js @@ -0,0 +1,242 @@ +import { defineStore } from 'pinia' +import { ref, computed, reactive, watch } from 'vue' +import { db } from '@/utils/db' + +export const useSpreadsheetStore = defineStore('spreadsheet', () => { + // --- State --- + // 初始化 100 行 26 列 (默认空) + const rows = ref(Array.from({ length: 100 }, (_, i) => ({ index: i, height: 25 }))) + const cols = ref(Array.from({ length: 26 }, (_, i) => ({ label: String.fromCharCode(65 + i), width: 100 }))) + + // 核心数据:Map<"r_c", CellJSON> + const cells = reactive({}) + + const selection = reactive({ start: { r: 1, c: 1 }, end: { r: 1, c: 1 } }) + const isEditing = ref(false) + const editingValue = ref('') + const loading = ref(true) // 加载状态 + + // --- Helpers --- + const getKey = (r, c) => `${r}_${c}` + + // --- Persistence --- + const SHEET_KEY = 'default_sheet_v1' + + // 加载数据 + const loadData = async () => { + loading.value = true + try { + const data = await db.get(SHEET_KEY) + if (data) { + // 恢复数据 + Object.assign(cells, data.cells) + if (data.rows) rows.value = data.rows + if (data.cols) cols.value = data.cols + } + } catch (e) { + console.error('Failed to load DB', e) + } finally { + loading.value = false + } + } + + // 自动保存 (防抖建议在生产环境加,这里直接监听) + watch([cells, rows, cols], () => { + if (!loading.value) { + db.set(SHEET_KEY, { + cells, + rows: rows.value, + cols: cols.value + }) + } + }, { deep: true }) + + // --- Getters --- + const minR = computed(() => Math.min(selection.start.r, selection.end.r)) + const maxR = computed(() => Math.max(selection.start.r, selection.end.r)) + const minC = computed(() => Math.min(selection.start.c, selection.end.c)) + const maxC = computed(() => Math.max(selection.start.c, selection.end.c)) + + const totalWidth = computed(() => cols.value.reduce((acc, c) => acc + c.width, 0)) + const totalHeight = computed(() => rows.value.reduce((acc, r) => acc + r.height, 0)) + + const getCell = (r, c) => { + return cells[getKey(r, c)] || { value: '', type: 'text', style: {} } + } + + const currentStyle = computed(() => { + const cell = getCell(selection.start.r, selection.start.c) + return cell.style || {} + }) + + const activeCellFormula = computed(() => { + if (isEditing.value) return editingValue.value + const cell = getCell(selection.start.r, selection.start.c) + return String(cell.value || '') + }) + + // 计算选区覆盖层位置 + const layoutInfo = computed(() => { + const getRowTop = (r) => rows.value.slice(0, r).reduce((sum, row) => sum + row.height, 0) + const getColLeft = (c) => cols.value.slice(0, c).reduce((sum, col) => sum + col.width, 0) + + const top = getRowTop(minR.value) + const left = getColLeft(minC.value) + + let height = 0 + for(let i=minR.value; i<=maxR.value; i++) height += rows.value[i].height + + let width = 0 + for(let i=minC.value; i<=maxC.value; i++) width += cols.value[i].width + + return { top, left, width, height } + }) + + const editorStyle = computed(() => { + const startR = selection.start.r + const startC = selection.start.c + const lInfo = layoutInfo.value // 复用计算结果的起始位置,但不包含宽高累加 + + // 重新计算起始点的绝对位置(为了精确性建议封装通用方法,这里简化复用) + let top = 0; for(let i=0; i { + setCell(r, c, { style: { [prop]: !current } }) + }) + } + + function setStyle(prop, value) { + forEachSelectedCell((r, c) => { + setCell(r, c, { style: { [prop]: value } }) + }) + } + + function insertRow(index, count = 1) { + // 实际项目中需要处理数据的下移(修改 cells 的 key),这里仅演示 UI 层的插入 + for(let k=0; k r.index = i) + // TODO: 实现 Data Shift 逻辑 + } + + function insertCol(index, count = 1) { + for(let k=0; k { + col.label = i < 26 ? String.fromCharCode(65 + i) : 'A' + String.fromCharCode(65 + (i%26)) + }) + // TODO: 实现 Data Shift 逻辑 + } + + function deleteRow(index) { + rows.value.splice(index, 1) + rows.value.forEach((r, i) => r.index = i) + // TODO: 实现 Data Shift 逻辑 + } + + function autoFill(targetEndR, targetEndC) { + const srcR = selection.start.r + const srcC = selection.start.c + const template = getCell(srcR, srcC) + + for(let r=minR.value; r<=targetEndR; r++) { + for(let c=minC.value; c<=targetEndC; c++) { + if (r <= maxR.value && c <= maxC.value) continue + // 深度拷贝数据 + const newData = JSON.parse(JSON.stringify(template)) + setCell(r, c, newData) + } + } + selection.end = { r: targetEndR, c: targetEndC } + } + + function isColSelected(i) { return i >= minC.value && i <= maxC.value } + function isRowSelected(i) { return i >= minR.value && i <= maxR.value } + + return { + rows, cols, cells, selection, isEditing, editingValue, loading, + minR, maxR, minC, maxC, totalWidth, totalHeight, + getCell, currentStyle, activeCellFormula, layoutInfo, editorStyle, + isColSelected, isRowSelected, + loadData, + select, extendSelection, selectAll, + enableEditing, finishEditing, updateActiveCell, setCell, + toggleStyle, setStyle, + insertRow, insertCol, deleteRow, autoFill, forEachSelectedCell + } +}) \ No newline at end of file diff --git a/src/utils/db.js b/src/utils/db.js new file mode 100644 index 0000000..9475527 --- /dev/null +++ b/src/utils/db.js @@ -0,0 +1,46 @@ +// 简单的 IndexedDB 封装 +const DB_NAME = 'SpreadsheetDB' +const STORE_NAME = 'sheets' +const DB_VERSION = 1 + +const openDB = () => { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, DB_VERSION) + + request.onupgradeneeded = (event) => { + const db = event.target.result + if (!db.objectStoreNames.contains(STORE_NAME)) { + db.createObjectStore(STORE_NAME) + } + } + + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(request.error) + }) +} + +export const db = { + async get(key) { + const db = await openDB() + return new Promise((resolve, reject) => { + const transaction = db.transaction(STORE_NAME, 'readonly') + const store = transaction.objectStore(STORE_NAME) + const request = store.get(key) + request.onsuccess = () => resolve(request.result) + request.onerror = () => reject(request.error) + }) + }, + + async set(key, value) { + const db = await openDB() + return new Promise((resolve, reject) => { + const transaction = db.transaction(STORE_NAME, 'readwrite') + const store = transaction.objectStore(STORE_NAME) + // 使用 JSON.parse(JSON.stringify()) 去除 Proxy 对象,确保存储的是纯数据 + const rawValue = JSON.parse(JSON.stringify(value)) + const request = store.put(rawValue, key) + request.onsuccess = () => resolve() + request.onerror = () => reject(request.error) + }) + } +} \ No newline at end of file diff --git a/src/views/index/index.vue b/src/views/index/index.vue index 38bc235..af55a5c 100644 --- a/src/views/index/index.vue +++ b/src/views/index/index.vue @@ -1,11 +1,73 @@ + \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index df31b24..0f0a592 100644 --- a/vite.config.js +++ b/vite.config.js @@ -15,7 +15,7 @@ export default defineConfig({ } }, server: { - port: 8888, + port: 8806, proxy: { '/api': { // target: 'http://127.0.0.1:18007/api/',