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 @@
+
+
+
+
+
+
{{ data.value }}
+
+
\ 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 @@
+
+
+
+
+
+ 复制 Ctrl+C
+
+
+ 粘贴 Ctrl+V
+
+
+
+
+
+
+ 插入图片...
+
+
+
+
在上方插入 1 行
+
在下方插入 1 行
+
+
删除所在行
+
清除内容
+
+
\ 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 @@
+
+
+
+
+
+
+
Sheet1
+
+
Sheet2
+
+
+
就绪
+
+
\ 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 @@
-
-
-
-
-
My App
-
+
+
+
+
+
+
+
My App
+
-
-
- 首页
- About
- Services
- Contact
-
+
+
+ 首页
+ 关于
+ 服务
+
-
-
-
- Personal Center
-
-
-
-
+
+
+
+ 个人中心
+
+
+
+
+
+
+
+
+
+
+
-
+
\ 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 @@
-你好
+
+
+
+
+
+
store.updateActiveCell(val)"
+ />
+
+
+
+
+
+
+
+
\ 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/',