@l1yp/file-viewer 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -6
- package/dist/assets/step.worker-CfDTYOf_.js.map +1 -0
- package/dist/components/CertViewer.js +1 -1
- package/dist/components/CertViewer.js.map +1 -1
- package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxNavigationPane.js +1 -1
- package/dist/components/DocxNavigationPane.js.map +1 -1
- package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/DocxViewer.js +1 -1
- package/dist/components/DocxViewer.js.map +1 -1
- package/dist/components/DocxViewer.vue.d.ts +1 -1
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +80 -78
- package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/EplViewer.js +1 -1
- package/dist/components/EplViewer.js.map +1 -1
- package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerBody.js +1 -1
- package/dist/components/FileViewerBody.js.map +1 -1
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +265 -257
- package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/FileViewerPanel.js +1 -1
- package/dist/components/FileViewerPanel.js.map +1 -1
- package/dist/components/FileViewerPanel.vue.d.ts +6 -1
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +42 -40
- package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/HarViewer.js +1 -1
- package/dist/components/HarViewer.js.map +1 -1
- package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/JksViewer.js +1 -1
- package/dist/components/JksViewer.js.map +1 -1
- package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ModelViewModes.js +9 -0
- package/dist/components/ModelViewModes.js.map +1 -0
- package/dist/components/ModelViewModes.vue.d.ts +11 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js +49 -0
- package/dist/components/ModelViewModes.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/ModelViewer.js +1 -1
- package/dist/components/ModelViewer.js.map +1 -1
- package/dist/components/ModelViewer.vue.d.ts +2 -1
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +183 -96
- package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfNavigationPane.js +1 -1
- package/dist/components/PdfNavigationPane.js.map +1 -1
- package/dist/components/PdfNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PdfViewer.js +1 -1
- package/dist/components/PdfViewer.js.map +1 -1
- package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PptxNavigationPane.js +1 -1
- package/dist/components/PptxNavigationPane.js.map +1 -1
- package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/PsdViewer.js +1 -1
- package/dist/components/PsdViewer.js.map +1 -1
- package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/SqliteViewer.js +1 -1
- package/dist/components/SqliteViewer.js.map +1 -1
- package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/ViewerLoadError.js +9 -0
- package/dist/components/ViewerLoadError.js.map +1 -0
- package/dist/components/ViewerLoadError.vue.d.ts +17 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js +58 -0
- package/dist/components/ViewerLoadError.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxChart.js +1 -1
- package/dist/components/XlsxChart.js.map +1 -1
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +9 -9
- package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/XlsxImageLayer.js +9 -0
- package/dist/components/XlsxImageLayer.js.map +1 -0
- package/dist/components/XlsxImageLayer.vue.d.ts +9 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js +46 -0
- package/dist/components/XlsxImageLayer.vue_vue_type_script_setup_true_lang.js.map +1 -0
- package/dist/components/XlsxViewer.js +1 -1
- package/dist/components/XlsxViewer.js.map +1 -1
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +230 -216
- package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/epl/EplResource.js +1 -1
- package/dist/components/epl/EplResource.js.map +1 -1
- package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -1
- package/dist/components/optionalViewer.d.ts +3 -2
- package/dist/components/optionalViewer.js +6 -3
- package/dist/components/optionalViewer.js.map +1 -1
- package/dist/composables/useFileViewer.d.ts +11 -3
- package/dist/composables/useFileViewer.js.map +1 -1
- package/dist/composables/useTeleportTheme.d.ts +7 -0
- package/dist/composables/useTeleportTheme.js +42 -0
- package/dist/composables/useTeleportTheme.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +29 -28
- package/dist/lib/docxTheme.d.ts +6 -0
- package/dist/lib/docxTheme.js +64 -0
- package/dist/lib/docxTheme.js.map +1 -0
- package/dist/lib/epl/code/structures.js +82 -0
- package/dist/lib/epl/code/structures.js.map +1 -0
- package/dist/lib/epl/code/variable.js +16 -0
- package/dist/lib/epl/code/variable.js.map +1 -0
- package/dist/lib/epl/eplDocument.js +28 -22
- package/dist/lib/epl/eplDocument.js.map +1 -1
- package/dist/lib/epl/index.d.ts +2 -1
- package/dist/lib/epl/index.js.map +1 -1
- package/dist/lib/epl/sections/code.js +37 -0
- package/dist/lib/epl/sections/code.js.map +1 -0
- package/dist/lib/epl/sections/esystemInfo.js +29 -0
- package/dist/lib/epl/sections/esystemInfo.js.map +1 -0
- package/dist/lib/epl/sections/losable.js +33 -0
- package/dist/lib/epl/sections/losable.js.map +1 -0
- package/dist/lib/epl/sections/projectConfig.js +39 -0
- package/dist/lib/epl/sections/projectConfig.js.map +1 -0
- package/dist/lib/epl/sections/resource.js +84 -0
- package/dist/lib/epl/sections/resource.js.map +1 -0
- package/dist/lib/fileType.d.ts +3 -1
- package/dist/lib/fileType.js +150 -137
- package/dist/lib/fileType.js.map +1 -1
- package/dist/lib/modelAppearance.d.ts +7 -0
- package/dist/lib/modelAppearance.js +41 -0
- package/dist/lib/modelAppearance.js.map +1 -0
- package/dist/lib/step.d.ts +10 -0
- package/dist/lib/step.js +35 -0
- package/dist/lib/step.js.map +1 -0
- package/dist/lib/step.worker.d.ts +1 -0
- package/dist/lib/step.worker.js +18 -0
- package/dist/lib/step.worker.js.map +1 -0
- package/dist/lib/stepObject.d.ts +4 -0
- package/dist/lib/stepObject.js +52 -0
- package/dist/lib/stepObject.js.map +1 -0
- package/dist/lib/stepTypes.d.ts +60 -0
- package/dist/lib/viewerKind.js +10 -5
- package/dist/lib/viewerKind.js.map +1 -1
- package/dist/lib/xlsx.d.ts +1 -1
- package/dist/lib/xlsx.js +106 -89
- package/dist/lib/xlsx.js.map +1 -1
- package/dist/lib/xlsxCharts.js +71 -119
- package/dist/lib/xlsxCharts.js.map +1 -1
- package/dist/lib/xlsxImages.d.ts +43 -0
- package/dist/lib/xlsxImages.js +114 -0
- package/dist/lib/xlsxImages.js.map +1 -0
- package/dist/lib/xlsxParts.d.ts +17 -0
- package/dist/lib/xlsxParts.js +86 -0
- package/dist/lib/xlsxParts.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +8 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SqliteViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/SqliteViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport type { Database } from 'sql.js'\nimport {\n type CellValue,\n type Column,\n type QueryResult,\n type TableInfo,\n execSql,\n initSqlEngine,\n listTables,\n loadDatabase,\n readPage,\n} from '@/lib/sqlite'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer SQLite (`.sqlite`/`.db`/`.sqlite3`) reader. Fetches the database bytes and opens them with sql.js\n * (WASM, lazily loaded — see lib/sqlite.ts), then browses it read-only: a table/view picker, a paginated data\n * grid (with PK/FK/IDX column badges), and a small SQL console for ad-hoc queries.\n *\n * Everything runs in-browser; sql.js holds the whole file in WASM memory, so very large databases are gated\n * behind a confirm before loading. The DB handle is closed on reload/unmount to free that memory. BLOB cells\n * render as a `[BLOB nB]` placeholder (never decoded into the grid).\n */\nconst props = defineProps<{\n /** Download URL of the database bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n\nlet db: Database | null = null\nconst tables = ref<TableInfo[]>([])\nconst selectedName = ref('')\n\ntype Mode = 'table' | 'sql'\nconst mode = ref<Mode>('table')\n\nconst PAGE_SIZE = 100\nconst page = ref(0)\nconst pageRows = ref<CellValue[][]>([])\n\nconst sqlText = ref('')\nconst queryResult = ref<QueryResult | null>(null)\n\n// sql.js loads the entire file into WASM memory; gate big databases behind a confirm.\nconst SIZE_LIMIT = 60 * 1024 * 1024\n\nconst selectedTable = computed(\n () => tables.value.find((t) => t.name === selectedName.value) ?? null,\n)\nconst totalPages = computed(() => {\n const t = selectedTable.value\n if (!t || t.rowCount <= 0) return 1\n return Math.max(1, Math.ceil(t.rowCount / PAGE_SIZE))\n})\n\nfunction closeDb(): void {\n try {\n db?.close()\n } catch {\n /* already closed — ignore */\n }\n db = null\n}\n\n/** Reload the current page of the selected table from the DB. */\nfunction loadPage(): void {\n if (!db || !selectedTable.value) {\n pageRows.value = []\n return\n }\n try {\n pageRows.value = readPage(db, selectedName.value, PAGE_SIZE, page.value * PAGE_SIZE)\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n pageRows.value = []\n }\n}\n\nfunction selectTable(name: string): void {\n selectedName.value = name\n page.value = 0\n mode.value = 'table'\n loadPage()\n}\n\nfunction prevPage(): void {\n if (page.value > 0) {\n page.value--\n loadPage()\n }\n}\nfunction nextPage(): void {\n if (page.value < totalPages.value - 1) {\n page.value++\n loadPage()\n }\n}\n\nfunction runSql(): void {\n if (!db) return\n const sql = sqlText.value.trim()\n if (!sql) return\n queryResult.value = execSql(db, sql)\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n closeDb()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const SQL = await initSqlEngine()\n db = loadDatabase(SQL, new Uint8Array(buf))\n tables.value = listTables(db)\n if (tables.value.length) {\n selectedName.value = tables.value[0].name\n page.value = 0\n loadPage()\n } else {\n selectedName.value = ''\n }\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n closeDb()\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(closeDb)\n\n/* ===== grid rendering: shared between table view and query results ===== */\n\n// Column descriptors for the active grid. Table mode uses the rich Column (with badges); SQL mode\n// only knows column names, so it maps them to bare descriptors.\nconst gridColumns = computed<Column[]>(() => {\n if (mode.value === 'sql') {\n return (queryResult.value?.columns ?? []).map((name) => ({ name, type: '' }))\n }\n return selectedTable.value?.columns ?? []\n})\nconst gridRows = computed<CellValue[][]>(() =>\n mode.value === 'sql' ? (queryResult.value?.rows ?? []) : pageRows.value,\n)\n\nfunction colBadge(c: Column): string {\n if (c.pk) return 'PK'\n if (c.fk) return 'FK'\n if (c.idx) return 'IDX'\n return ''\n}\nfunction cellText(v: CellValue): string {\n return v == null ? '' : String(v)\n}\n</script>\n\n<template>\n <div class=\"sq\">\n <p v-if=\"status === 'loading'\" class=\"sq__msg\">正在加载数据库…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"sq__msg sq__gate\">\n <p>该数据库较大({{ formatBytes(props.size) }}),将整体载入浏览器内存。是否继续?</p>\n <button type=\"button\" class=\"sq__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"sq__msg sq__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"sq__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready'\">\n <!-- toolbar: table picker + mode toggle -->\n <div class=\"sq__bar\">\n <select\n v-if=\"tables.length\"\n class=\"sq__select\"\n :value=\"selectedName\"\n @change=\"selectTable(($event.target as HTMLSelectElement).value)\"\n >\n <option v-for=\"t in tables\" :key=\"t.name\" :value=\"t.name\">\n {{ t.kind === 'view' ? '⊞ ' : '' }}{{ t.name }} ({{ t.rowCount }})\n </option>\n </select>\n <span v-else class=\"sq__notables\">(无用户表)</span>\n <div class=\"sq__modes\">\n <button\n type=\"button\"\n class=\"sq__mode\"\n :class=\"{ 'sq__mode--on': mode === 'table' }\"\n :disabled=\"!tables.length\"\n @click=\"mode = 'table'\"\n >\n 数据\n </button>\n <button\n type=\"button\"\n class=\"sq__mode\"\n :class=\"{ 'sq__mode--on': mode === 'sql' }\"\n @click=\"mode = 'sql'\"\n >\n SQL\n </button>\n </div>\n </div>\n\n <!-- SQL console -->\n <div v-if=\"mode === 'sql'\" class=\"sq__sqlbar\">\n <textarea\n v-model=\"sqlText\"\n class=\"sq__sqlinput\"\n rows=\"3\"\n placeholder=\"输入 SQL(如 SELECT * FROM … LIMIT 100),Ctrl+Enter 执行\"\n @keydown.ctrl.enter.prevent=\"runSql\"\n @keydown.meta.enter.prevent=\"runSql\"\n />\n <div class=\"sq__sqlrun\">\n <button type=\"button\" class=\"sq__btn\" @click=\"runSql\">执行</button>\n <span v-if=\"queryResult && !queryResult.error\" class=\"sq__stat\">\n {{ queryResult.rowCount }} 行 · {{ queryResult.elapsedMs.toFixed(1) }} ms\n </span>\n <span v-else-if=\"queryResult?.error\" class=\"sq__err\">{{ queryResult.error }}</span>\n </div>\n </div>\n\n <!-- data grid (table rows or query results) -->\n <div class=\"sq__gridwrap\">\n <table v-if=\"gridColumns.length\" class=\"sq__grid\">\n <thead>\n <tr>\n <th class=\"sq__rownum\">#</th>\n <th v-for=\"(c, ci) in gridColumns\" :key=\"ci\" class=\"sq__th\">\n <span class=\"sq__colname\">{{ c.name }}</span>\n <span\n v-if=\"colBadge(c)\"\n class=\"sq__badge\"\n :class=\"`sq__badge--${colBadge(c).toLowerCase()}`\"\n >{{ colBadge(c) }}</span\n >\n <span v-if=\"c.type\" class=\"sq__coltype\">{{ c.type }}</span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, ri) in gridRows\" :key=\"ri\">\n <td class=\"sq__rownum\">\n {{ mode === 'table' ? page * PAGE_SIZE + ri + 1 : ri + 1 }}\n </td>\n <td\n v-for=\"(v, ci) in row\"\n :key=\"ci\"\n class=\"sq__td\"\n :class=\"{ 'sq__td--null': v == null }\"\n >\n {{ v == null ? 'NULL' : cellText(v) }}\n </td>\n </tr>\n <tr v-if=\"!gridRows.length\">\n <td class=\"sq__emptycell\" :colspan=\"gridColumns.length + 1\">\n {{ mode === 'sql' ? '(无结果)' : '(空表)' }}\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"sq__msg\">\n {{ mode === 'sql' ? '执行一条 SQL 查看结果。' : '该表无列。' }}\n </p>\n </div>\n\n <!-- pagination (table mode only) -->\n <div v-if=\"mode === 'table' && selectedTable\" class=\"sq__pager\">\n <button type=\"button\" class=\"sq__pgbtn\" :disabled=\"page === 0\" @click=\"prevPage\">‹</button>\n <span class=\"sq__pginfo\">\n 第 {{ page + 1 }} / {{ totalPages }} 页 · 共 {{ selectedTable.rowCount }} 行\n </span>\n <button\n type=\"button\"\n class=\"sq__pgbtn\"\n :disabled=\"page >= totalPages - 1\"\n @click=\"nextPage\"\n >\n ›\n </button>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.sq {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.sq__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.sq__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.sq__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.sq__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.sq__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* toolbar */\n.sq__bar {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.sq__select {\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 10px;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n}\n.sq__select:focus {\n outline: none;\n border-color: var(--accent);\n}\n.sq__notables {\n flex: 1 1 auto;\n font-size: 12.5px;\n color: var(--faint);\n}\n.sq__modes {\n display: inline-flex;\n flex-shrink: 0;\n gap: 2px;\n padding: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.sq__mode {\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: transparent;\n border: none;\n border-radius: 6px;\n padding: 4px 12px;\n cursor: pointer;\n}\n.sq__mode:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.sq__mode--on {\n color: var(--accent);\n background: var(--surface);\n box-shadow: var(--shadow);\n}\n\n/* SQL console */\n.sq__sqlbar {\n flex-shrink: 0;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.sq__sqlinput {\n width: 100%;\n resize: vertical;\n padding: 8px 10px;\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.5;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border);\n border-radius: 7px;\n}\n.sq__sqlinput:focus {\n outline: none;\n border-color: var(--accent);\n}\n.sq__sqlrun {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-top: 8px;\n}\n.sq__stat {\n font-size: 11.5px;\n color: var(--faint);\n}\n.sq__err {\n font-size: 11.5px;\n color: var(--danger);\n word-break: break-word;\n}\n\n/* data grid */\n.sq__gridwrap {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n.sq__grid {\n border-collapse: collapse;\n width: max-content;\n min-width: 100%;\n font-size: 12px;\n}\n.sq__grid th,\n.sq__grid td {\n border-right: 1px solid var(--border);\n border-bottom: 1px solid var(--border);\n padding: 5px 10px;\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n max-width: 360px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sq__th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: var(--surface-2);\n white-space: nowrap;\n}\n.sq__colname {\n font-weight: 600;\n color: var(--text-strong);\n}\n.sq__badge {\n margin-left: 5px;\n padding: 0 4px;\n border-radius: 3px;\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.03em;\n vertical-align: middle;\n}\n.sq__badge--pk {\n color: #b08900;\n background: rgba(240, 200, 0, 0.16);\n}\n.sq__badge--fk {\n color: #1c7ed6;\n background: rgba(28, 126, 214, 0.14);\n}\n.sq__badge--idx {\n color: #2f9e44;\n background: rgba(47, 158, 68, 0.14);\n}\n.sq__coltype {\n margin-left: 5px;\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--faint);\n}\n.sq__rownum {\n position: sticky;\n left: 0;\n z-index: 1;\n width: 1%;\n text-align: right;\n color: var(--faint);\n background: var(--surface-2);\n font-family: var(--font-mono);\n font-size: 11px;\n}\n.sq__td {\n color: var(--text);\n font-family: var(--font-mono);\n font-size: 11.5px;\n}\n.sq__td--null {\n color: var(--faint);\n font-style: italic;\n}\n.sq__emptycell {\n padding: 16px;\n text-align: center;\n color: var(--faint);\n font-size: 12px;\n}\n\n/* pagination */\n.sq__pager {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 14px;\n padding: 8px 12px;\n border-top: 1px solid var(--border);\n}\n.sq__pgbtn {\n width: 30px;\n height: 26px;\n font-size: 16px;\n line-height: 1;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n}\n.sq__pgbtn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.sq__pgbtn:not(:disabled):hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.sq__pginfo {\n font-size: 11.5px;\n color: var(--muted);\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SqliteViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/SqliteViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport type { Database } from 'sql.js'\nimport {\n type CellValue,\n type Column,\n type QueryResult,\n type TableInfo,\n execSql,\n initSqlEngine,\n listTables,\n loadDatabase,\n readPage,\n} from '@/lib/sqlite'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * Side-viewer SQLite (`.sqlite`/`.db`/`.sqlite3`) reader. Fetches the database bytes and opens them with sql.js\n * (WASM, lazily loaded — see lib/sqlite.ts), then browses it read-only: a table/view picker, a paginated data\n * grid (with PK/FK/IDX column badges), and a small SQL console for ad-hoc queries.\n *\n * Everything runs in-browser; sql.js holds the whole file in WASM memory, so very large databases are gated\n * behind a confirm before loading. The DB handle is closed on reload/unmount to free that memory. BLOB cells\n * render as a `[BLOB nB]` placeholder (never decoded into the grid).\n */\nconst props = defineProps<{\n /** Download URL of the database bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'gate' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n\nlet db: Database | null = null\nconst tables = ref<TableInfo[]>([])\nconst selectedName = ref('')\n\ntype Mode = 'table' | 'sql'\nconst mode = ref<Mode>('table')\n\nconst PAGE_SIZE = 100\nconst page = ref(0)\nconst pageRows = ref<CellValue[][]>([])\n\nconst sqlText = ref('')\nconst queryResult = ref<QueryResult | null>(null)\n\n// sql.js loads the entire file into WASM memory; gate big databases behind a confirm.\nconst SIZE_LIMIT = 60 * 1024 * 1024\n\nconst selectedTable = computed(\n () => tables.value.find((t) => t.name === selectedName.value) ?? null,\n)\nconst totalPages = computed(() => {\n const t = selectedTable.value\n if (!t || t.rowCount <= 0) return 1\n return Math.max(1, Math.ceil(t.rowCount / PAGE_SIZE))\n})\n\nfunction closeDb(): void {\n try {\n db?.close()\n } catch {\n /* already closed — ignore */\n }\n db = null\n}\n\n/** Reload the current page of the selected table from the DB. */\nfunction loadPage(): void {\n if (!db || !selectedTable.value) {\n pageRows.value = []\n return\n }\n try {\n pageRows.value = readPage(db, selectedName.value, PAGE_SIZE, page.value * PAGE_SIZE)\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n pageRows.value = []\n }\n}\n\nfunction selectTable(name: string): void {\n selectedName.value = name\n page.value = 0\n mode.value = 'table'\n loadPage()\n}\n\nfunction prevPage(): void {\n if (page.value > 0) {\n page.value--\n loadPage()\n }\n}\nfunction nextPage(): void {\n if (page.value < totalPages.value - 1) {\n page.value++\n loadPage()\n }\n}\n\nfunction runSql(): void {\n if (!db) return\n const sql = sqlText.value.trim()\n if (!sql) return\n queryResult.value = execSql(db, sql)\n}\n\nasync function doLoad(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n closeDb()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const SQL = await initSqlEngine()\n db = loadDatabase(SQL, new Uint8Array(buf))\n tables.value = listTables(db)\n if (tables.value.length) {\n selectedName.value = tables.value[0].name\n page.value = 0\n loadPage()\n } else {\n selectedName.value = ''\n }\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n closeDb()\n }\n}\n\nfunction load(): void {\n if (props.size && props.size > SIZE_LIMIT) {\n status.value = 'gate'\n return\n }\n void doLoad()\n}\n\nwatch(() => props.url, load, { immediate: true })\nonBeforeUnmount(closeDb)\n\n/* ===== grid rendering: shared between table view and query results ===== */\n\n// Column descriptors for the active grid. Table mode uses the rich Column (with badges); SQL mode\n// only knows column names, so it maps them to bare descriptors.\nconst gridColumns = computed<Column[]>(() => {\n if (mode.value === 'sql') {\n return (queryResult.value?.columns ?? []).map((name) => ({ name, type: '' }))\n }\n return selectedTable.value?.columns ?? []\n})\nconst gridRows = computed<CellValue[][]>(() =>\n mode.value === 'sql' ? (queryResult.value?.rows ?? []) : pageRows.value,\n)\n\nfunction colBadge(c: Column): string {\n if (c.pk) return 'PK'\n if (c.fk) return 'FK'\n if (c.idx) return 'IDX'\n return ''\n}\nfunction cellText(v: CellValue): string {\n return v == null ? '' : String(v)\n}\n</script>\n\n<template>\n <div class=\"sq\">\n <p v-if=\"status === 'loading'\" class=\"sq__msg\">正在加载数据库…</p>\n\n <div v-else-if=\"status === 'gate'\" class=\"sq__msg sq__gate\">\n <p>该数据库较大({{ formatBytes(props.size) }}),将整体载入浏览器内存。是否继续?</p>\n <button type=\"button\" class=\"sq__btn\" @click=\"doLoad\">加载并预览</button>\n </div>\n\n <div v-else-if=\"status === 'error'\" class=\"sq__msg sq__msg--error\">\n <p>加载失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"sq__btn\" @click=\"doLoad\">重试</button>\n </div>\n\n <template v-else-if=\"status === 'ready'\">\n <!-- toolbar: table picker + mode toggle -->\n <div class=\"sq__bar\">\n <select\n v-if=\"tables.length\"\n class=\"sq__select\"\n :value=\"selectedName\"\n @change=\"selectTable(($event.target as HTMLSelectElement).value)\"\n >\n <option v-for=\"t in tables\" :key=\"t.name\" :value=\"t.name\">\n {{ t.kind === 'view' ? '⊞ ' : '' }}{{ t.name }} ({{ t.rowCount }})\n </option>\n </select>\n <span v-else class=\"sq__notables\">(无用户表)</span>\n <div class=\"sq__modes\">\n <button\n type=\"button\"\n class=\"sq__mode\"\n :class=\"{ 'sq__mode--on': mode === 'table' }\"\n :disabled=\"!tables.length\"\n @click=\"mode = 'table'\"\n >\n 数据\n </button>\n <button\n type=\"button\"\n class=\"sq__mode\"\n :class=\"{ 'sq__mode--on': mode === 'sql' }\"\n @click=\"mode = 'sql'\"\n >\n SQL\n </button>\n </div>\n </div>\n\n <!-- SQL console -->\n <div v-if=\"mode === 'sql'\" class=\"sq__sqlbar\">\n <textarea\n v-model=\"sqlText\"\n class=\"sq__sqlinput\"\n rows=\"3\"\n placeholder=\"输入 SQL(如 SELECT * FROM … LIMIT 100),Ctrl+Enter 执行\"\n @keydown.ctrl.enter.prevent=\"runSql\"\n @keydown.meta.enter.prevent=\"runSql\"\n />\n <div class=\"sq__sqlrun\">\n <button type=\"button\" class=\"sq__btn\" @click=\"runSql\">执行</button>\n <span v-if=\"queryResult && !queryResult.error\" class=\"sq__stat\">\n {{ queryResult.rowCount }} 行 · {{ queryResult.elapsedMs.toFixed(1) }} ms\n </span>\n <span v-else-if=\"queryResult?.error\" class=\"sq__err\">{{ queryResult.error }}</span>\n </div>\n </div>\n\n <!-- data grid (table rows or query results) -->\n <div class=\"sq__gridwrap\">\n <table v-if=\"gridColumns.length\" class=\"sq__grid\">\n <thead>\n <tr>\n <th class=\"sq__rownum\">#</th>\n <th v-for=\"(c, ci) in gridColumns\" :key=\"ci\" class=\"sq__th\">\n <span class=\"sq__colname\">{{ c.name }}</span>\n <span\n v-if=\"colBadge(c)\"\n class=\"sq__badge\"\n :class=\"`sq__badge--${colBadge(c).toLowerCase()}`\"\n >{{ colBadge(c) }}</span\n >\n <span v-if=\"c.type\" class=\"sq__coltype\">{{ c.type }}</span>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, ri) in gridRows\" :key=\"ri\">\n <td class=\"sq__rownum\">\n {{ mode === 'table' ? page * PAGE_SIZE + ri + 1 : ri + 1 }}\n </td>\n <td\n v-for=\"(v, ci) in row\"\n :key=\"ci\"\n class=\"sq__td\"\n :class=\"{ 'sq__td--null': v == null }\"\n >\n {{ v == null ? 'NULL' : cellText(v) }}\n </td>\n </tr>\n <tr v-if=\"!gridRows.length\">\n <td class=\"sq__emptycell\" :colspan=\"gridColumns.length + 1\">\n {{ mode === 'sql' ? '(无结果)' : '(空表)' }}\n </td>\n </tr>\n </tbody>\n </table>\n <p v-else class=\"sq__msg\">\n {{ mode === 'sql' ? '执行一条 SQL 查看结果。' : '该表无列。' }}\n </p>\n </div>\n\n <!-- pagination (table mode only) -->\n <div v-if=\"mode === 'table' && selectedTable\" class=\"sq__pager\">\n <button type=\"button\" class=\"sq__pgbtn\" :disabled=\"page === 0\" @click=\"prevPage\">‹</button>\n <span class=\"sq__pginfo\">\n 第 {{ page + 1 }} / {{ totalPages }} 页 · 共 {{ selectedTable.rowCount }} 行\n </span>\n <button\n type=\"button\"\n class=\"sq__pgbtn\"\n :disabled=\"page >= totalPages - 1\"\n @click=\"nextPage\"\n >\n ›\n </button>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.sq {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.sq__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.sq__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.sq__gate {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 12px;\n}\n.sq__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.sq__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* toolbar */\n.sq__bar {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.sq__select {\n flex: 1 1 auto;\n min-width: 0;\n padding: 6px 10px;\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n}\n.sq__select:focus {\n outline: none;\n border-color: var(--accent);\n}\n.sq__notables {\n flex: 1 1 auto;\n font-size: 12.5px;\n color: var(--faint);\n}\n.sq__modes {\n display: inline-flex;\n flex-shrink: 0;\n gap: 2px;\n padding: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.sq__mode {\n font-family: inherit;\n font-size: 12px;\n color: var(--muted);\n background: transparent;\n border: none;\n border-radius: 6px;\n padding: 4px 12px;\n cursor: pointer;\n}\n.sq__mode:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.sq__mode--on {\n color: var(--accent);\n background: var(--surface);\n box-shadow: var(--shadow);\n}\n\n/* SQL console */\n.sq__sqlbar {\n flex-shrink: 0;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n}\n.sq__sqlinput {\n width: 100%;\n resize: vertical;\n padding: 8px 10px;\n font-family: var(--font-mono);\n font-size: 12px;\n line-height: 1.5;\n color: var(--text);\n background: var(--code-bg);\n border: 1px solid var(--border);\n border-radius: 7px;\n}\n.sq__sqlinput:focus {\n outline: none;\n border-color: var(--accent);\n}\n.sq__sqlrun {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-top: 8px;\n}\n.sq__stat {\n font-size: 11.5px;\n color: var(--faint);\n}\n.sq__err {\n font-size: 11.5px;\n color: var(--danger);\n word-break: break-word;\n}\n\n/* data grid */\n.sq__gridwrap {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n}\n.sq__grid {\n border-collapse: collapse;\n width: max-content;\n min-width: 100%;\n font-size: 12px;\n}\n.sq__grid th,\n.sq__grid td {\n border-right: 1px solid var(--border);\n border-bottom: 1px solid var(--border);\n padding: 5px 10px;\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n max-width: 360px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.sq__th {\n position: sticky;\n top: 0;\n z-index: 1;\n background: var(--surface-2);\n white-space: nowrap;\n}\n.sq__colname {\n font-weight: 600;\n color: var(--text-strong);\n}\n.sq__badge {\n margin-left: 5px;\n padding: 0 4px;\n border-radius: 3px;\n font-size: 9.5px;\n font-weight: 700;\n letter-spacing: 0.03em;\n vertical-align: middle;\n}\n.sq__badge--pk {\n color: var(--ansi-yellow);\n background: rgba(240, 200, 0, 0.16);\n}\n.sq__badge--fk {\n color: var(--hl-attr);\n background: rgba(28, 126, 214, 0.14);\n}\n.sq__badge--idx {\n color: var(--hl-name);\n background: rgba(47, 158, 68, 0.14);\n}\n.sq__coltype {\n margin-left: 5px;\n font-family: var(--font-mono);\n font-size: 10px;\n color: var(--faint);\n}\n.sq__rownum {\n position: sticky;\n left: 0;\n z-index: 1;\n width: 1%;\n text-align: right;\n color: var(--faint);\n background: var(--surface-2);\n font-family: var(--font-mono);\n font-size: 11px;\n}\n.sq__td {\n color: var(--text);\n font-family: var(--font-mono);\n font-size: 11.5px;\n}\n.sq__td--null {\n color: var(--faint);\n font-style: italic;\n}\n.sq__emptycell {\n padding: 16px;\n text-align: center;\n color: var(--faint);\n font-size: 12px;\n}\n\n/* pagination */\n.sq__pager {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 14px;\n padding: 8px 12px;\n border-top: 1px solid var(--border);\n}\n.sq__pgbtn {\n width: 30px;\n height: 26px;\n font-size: 16px;\n line-height: 1;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n cursor: pointer;\n}\n.sq__pgbtn:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.sq__pgbtn:not(:disabled):hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n.sq__pginfo {\n font-size: 11.5px;\n color: var(--muted);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
2
|
+
import t from "./ViewerLoadError.vue_vue_type_script_setup_true_lang.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
//#region src/components/ViewerLoadError.vue
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-41c3d550"]]);
|
|
6
|
+
//#endregion
|
|
7
|
+
export { n as default };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=ViewerLoadError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewerLoadError.js","names":[],"sources":["../../src/components/ViewerLoadError.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport MissingDependency from './MissingDependency.vue'\n\nconst props = withDefaults(\n defineProps<{\n error?: unknown\n pkg: string[]\n label: string\n downloadUrl?: string\n downloadName?: string\n retryLabel?: string\n }>(),\n { retryLabel: '刷新页面' },\n)\nconst emit = defineEmits<{ retry: [] }>()\nconst message = computed(() =>\n props.error instanceof Error ? props.error.message : String(props.error ?? ''),\n)\nconst missingPackage = computed(() => {\n // A failed chunk fetch (including Vite's stale-dependency 504) says nothing about installed packages.\n // Only an explicit module-resolution error naming one of this viewer's dependencies is evidence.\n const match = message.value.match(\n /(?:cannot find (?:module|package)|failed to resolve (?:module specifier|import)|could not resolve)\\s+['\"]([^'\"]+)['\"]/i,\n )\n return !!match && props.pkg.some((pkg) => match[1] === pkg || match[1].startsWith(`${pkg}/`))\n})\n</script>\n\n<template>\n <MissingDependency\n v-if=\"missingPackage\"\n :pkg=\"pkg\"\n :label=\"label\"\n :download-url=\"downloadUrl\"\n :download-name=\"downloadName\"\n />\n <div v-else class=\"viewer-error\" role=\"alert\">\n <p class=\"viewer-error__title\">{{ label }}预览暂时无法加载</p>\n <p>预览资源加载失败。如果页面刚更新,请刷新页面后重试。</p>\n <button type=\"button\" class=\"viewer-error__retry\" @click=\"emit('retry')\">\n {{ retryLabel }}\n </button>\n <a v-if=\"downloadUrl\" :href=\"downloadUrl\" :download=\"downloadName\">下载原文件</a>\n <details v-if=\"message\" class=\"viewer-error__detail\">\n <summary>查看错误详情</summary>\n <pre>{{ message }}</pre>\n </details>\n </div>\n</template>\n\n<style scoped>\n.viewer-error {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px;\n color: var(--muted);\n font-size: 13px;\n text-align: center;\n}\n.viewer-error p {\n margin: 0;\n}\n.viewer-error__title {\n color: var(--text-strong);\n font-weight: 600;\n}\n.viewer-error__retry {\n padding: 6px 12px;\n border: 1px solid var(--border-2);\n border-radius: 6px;\n background: var(--surface);\n color: var(--text);\n font: inherit;\n cursor: pointer;\n}\n.viewer-error__retry:hover {\n border-color: var(--accent);\n}\n.viewer-error a {\n color: var(--muted);\n}\n.viewer-error__detail {\n max-width: 100%;\n text-align: left;\n}\n.viewer-error__detail summary {\n cursor: pointer;\n}\n.viewer-error__detail pre {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: 12px;\n}\n</style>\n"],"mappings":""}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
type __VLS_Props = {
|
|
2
|
+
error?: unknown;
|
|
3
|
+
pkg: string[];
|
|
4
|
+
label: string;
|
|
5
|
+
downloadUrl?: string;
|
|
6
|
+
downloadName?: string;
|
|
7
|
+
retryLabel?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
10
|
+
retry: () => any;
|
|
11
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
12
|
+
onRetry?: (() => any) | undefined;
|
|
13
|
+
}>, {
|
|
14
|
+
retryLabel: string;
|
|
15
|
+
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
16
|
+
declare const _default: typeof __VLS_export;
|
|
17
|
+
export default _default;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import e from "./MissingDependency.js";
|
|
2
|
+
import { computed as t, createBlock as n, createCommentVNode as r, createElementBlock as i, createElementVNode as a, defineComponent as o, openBlock as s, toDisplayString as c } from "vue";
|
|
3
|
+
//#region src/components/ViewerLoadError.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var l = {
|
|
5
|
+
key: 1,
|
|
6
|
+
class: "viewer-error",
|
|
7
|
+
role: "alert"
|
|
8
|
+
}, u = { class: "viewer-error__title" }, d = ["href", "download"], f = {
|
|
9
|
+
key: 1,
|
|
10
|
+
class: "viewer-error__detail"
|
|
11
|
+
}, p = /*@__PURE__*/ o({
|
|
12
|
+
__name: "ViewerLoadError",
|
|
13
|
+
props: {
|
|
14
|
+
error: {},
|
|
15
|
+
pkg: {},
|
|
16
|
+
label: {},
|
|
17
|
+
downloadUrl: {},
|
|
18
|
+
downloadName: {},
|
|
19
|
+
retryLabel: { default: "刷新页面" }
|
|
20
|
+
},
|
|
21
|
+
emits: ["retry"],
|
|
22
|
+
setup(o, { emit: p }) {
|
|
23
|
+
let m = o, h = p, g = t(() => m.error instanceof Error ? m.error.message : String(m.error ?? "")), _ = t(() => {
|
|
24
|
+
let e = g.value.match(/(?:cannot find (?:module|package)|failed to resolve (?:module specifier|import)|could not resolve)\s+['"]([^'"]+)['"]/i);
|
|
25
|
+
return !!e && m.pkg.some((t) => e[1] === t || e[1].startsWith(`${t}/`));
|
|
26
|
+
});
|
|
27
|
+
return (t, p) => _.value ? (s(), n(e, {
|
|
28
|
+
key: 0,
|
|
29
|
+
pkg: o.pkg,
|
|
30
|
+
label: o.label,
|
|
31
|
+
"download-url": o.downloadUrl,
|
|
32
|
+
"download-name": o.downloadName
|
|
33
|
+
}, null, 8, [
|
|
34
|
+
"pkg",
|
|
35
|
+
"label",
|
|
36
|
+
"download-url",
|
|
37
|
+
"download-name"
|
|
38
|
+
])) : (s(), i("div", l, [
|
|
39
|
+
a("p", u, c(o.label) + "预览暂时无法加载", 1),
|
|
40
|
+
p[2] ||= a("p", null, "预览资源加载失败。如果页面刚更新,请刷新页面后重试。", -1),
|
|
41
|
+
a("button", {
|
|
42
|
+
type: "button",
|
|
43
|
+
class: "viewer-error__retry",
|
|
44
|
+
onClick: p[0] ||= (e) => h("retry")
|
|
45
|
+
}, c(o.retryLabel), 1),
|
|
46
|
+
o.downloadUrl ? (s(), i("a", {
|
|
47
|
+
key: 0,
|
|
48
|
+
href: o.downloadUrl,
|
|
49
|
+
download: o.downloadName
|
|
50
|
+
}, "下载原文件", 8, d)) : r("", !0),
|
|
51
|
+
g.value ? (s(), i("details", f, [p[1] ||= a("summary", null, "查看错误详情", -1), a("pre", null, c(g.value), 1)])) : r("", !0)
|
|
52
|
+
]));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
//#endregion
|
|
56
|
+
export { p as default };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=ViewerLoadError.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewerLoadError.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/ViewerLoadError.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport MissingDependency from './MissingDependency.vue'\n\nconst props = withDefaults(\n defineProps<{\n error?: unknown\n pkg: string[]\n label: string\n downloadUrl?: string\n downloadName?: string\n retryLabel?: string\n }>(),\n { retryLabel: '刷新页面' },\n)\nconst emit = defineEmits<{ retry: [] }>()\nconst message = computed(() =>\n props.error instanceof Error ? props.error.message : String(props.error ?? ''),\n)\nconst missingPackage = computed(() => {\n // A failed chunk fetch (including Vite's stale-dependency 504) says nothing about installed packages.\n // Only an explicit module-resolution error naming one of this viewer's dependencies is evidence.\n const match = message.value.match(\n /(?:cannot find (?:module|package)|failed to resolve (?:module specifier|import)|could not resolve)\\s+['\"]([^'\"]+)['\"]/i,\n )\n return !!match && props.pkg.some((pkg) => match[1] === pkg || match[1].startsWith(`${pkg}/`))\n})\n</script>\n\n<template>\n <MissingDependency\n v-if=\"missingPackage\"\n :pkg=\"pkg\"\n :label=\"label\"\n :download-url=\"downloadUrl\"\n :download-name=\"downloadName\"\n />\n <div v-else class=\"viewer-error\" role=\"alert\">\n <p class=\"viewer-error__title\">{{ label }}预览暂时无法加载</p>\n <p>预览资源加载失败。如果页面刚更新,请刷新页面后重试。</p>\n <button type=\"button\" class=\"viewer-error__retry\" @click=\"emit('retry')\">\n {{ retryLabel }}\n </button>\n <a v-if=\"downloadUrl\" :href=\"downloadUrl\" :download=\"downloadName\">下载原文件</a>\n <details v-if=\"message\" class=\"viewer-error__detail\">\n <summary>查看错误详情</summary>\n <pre>{{ message }}</pre>\n </details>\n </div>\n</template>\n\n<style scoped>\n.viewer-error {\n display: flex;\n flex: 1 1 auto;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 12px;\n padding: 32px;\n color: var(--muted);\n font-size: 13px;\n text-align: center;\n}\n.viewer-error p {\n margin: 0;\n}\n.viewer-error__title {\n color: var(--text-strong);\n font-weight: 600;\n}\n.viewer-error__retry {\n padding: 6px 12px;\n border: 1px solid var(--border-2);\n border-radius: 6px;\n background: var(--surface);\n color: var(--text);\n font: inherit;\n cursor: pointer;\n}\n.viewer-error__retry:hover {\n border-color: var(--accent);\n}\n.viewer-error a {\n color: var(--muted);\n}\n.viewer-error__detail {\n max-width: 100%;\n text-align: left;\n}\n.viewer-error__detail summary {\n cursor: pointer;\n}\n.viewer-error__detail pre {\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: 12px;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;EAIA,IAAM,IAAQ,GAWR,IAAO,GACP,IAAU,QACd,EAAM,iBAAiB,QAAQ,EAAM,MAAM,UAAU,OAAO,EAAM,SAAS,EAAE,CAC/E,GACM,IAAiB,QAAe;GAGpC,IAAM,IAAQ,EAAQ,MAAM,MAC1B,wHACF;GACA,OAAO,CAAC,CAAC,KAAS,EAAM,IAAI,MAAM,MAAQ,EAAM,OAAO,KAAO,EAAM,EAAE,CAAC,WAAW,GAAG,EAAI,EAAE,CAAC;EAC9F,CAAC;mBAKS,EAAA,SAAA,EAAA,GADR,EAME,GAAA;;GAJC,KAAK,EAAA;GACL,OAAO,EAAA;GACP,gBAAc,EAAA;GACd,iBAAe,EAAA;;;;;;cAElB,EAWM,OAXN,GAWM;GAVJ,EAAsD,KAAtD,GAAsD,EAApB,EAAA,KAAK,IAAG,YAAQ,CAAA;YAClD,EAAiC,KAAA,MAA9B,8BAA0B,EAAA;GAC7B,EAES,UAAA;IAFD,MAAK;IAAS,OAAM;IAAuB,SAAK,AAAA,EAAA,QAAA,MAAE,EAAI,OAAA;QACzD,EAAA,UAAU,GAAA,CAAA;GAEN,EAAA,eAAA,EAAA,GAAT,EAA4E,KAAA;;IAArD,MAAM,EAAA;IAAc,UAAU,EAAA;MAAc,SAAK,GAAA,CAAA,KAAA,EAAA,IAAA,EAAA;GACzD,EAAA,SAAA,EAAA,GAAf,EAGU,WAHV,GAGU,CAAA,AAAA,EAAA,OAFR,EAAyB,WAAA,MAAhB,UAAM,EAAA,GACf,EAAwB,OAAA,MAAA,EAAhB,EAAA,KAAO,GAAA,CAAA,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA"}
|
|
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
|
2
2
|
import t from "./XlsxChart.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/components/XlsxChart.vue
|
|
5
|
-
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-284e60ab"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { n as default };
|
|
8
8
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"XlsxChart.js","names":[],"sources":["../../src/components/XlsxChart.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { XlsxChart } from '@/lib/xlsxCharts'\n\n/**\n * Render one parsed workbook chart ({@link XlsxChart}) as inline SVG — no charting dependency, in keeping with\n * the hand-rolled renderers elsewhere (markdown, binobj). Covers the kinds these spreadsheets actually use:\n * line/area/scatter as polylines, bar/barH as grouped bars, pie as slices. Geometry is derived from the chart's\n * cached series so the picture matches what Excel last drew. The host ({@link XlsxViewer}) positions us over the\n * grid at the drawing anchor; here we just fill the given pixel box.\n */\nconst props = defineProps<{ chart: XlsxChart }>()\n\n// A small palette for series that don't carry an explicit colour (Office-ish accents).\nconst PALETTE = [\n '#4472C4',\n '#ED7D31',\n '#A5A5A5',\n '#FFC000',\n '#5B9BD5',\n '#70AD47',\n '#264478',\n '#9E480E',\n]\n\nconst W = computed(() => Math.max(220, props.chart.anchor.wPx))\nconst H = computed(() => Math.max(140, props.chart.anchor.hPx))\n\nconst hasLegend = computed(() => props.chart.series.length > 1)\nconst titleH = computed(() => (props.chart.title ? 18 : 4))\nconst legendH = computed(() => (hasLegend.value ? 16 : 0))\n\n// Plot rectangle inside the axes/labels.\nconst PAD_L = 38\nconst PAD_R = 8\nconst PAD_B = 26\nconst plot = computed(() => {\n const top = titleH.value + 4\n return {\n x: PAD_L,\n y: top,\n w: Math.max(10, W.value - PAD_L - PAD_R),\n h: Math.max(10, H.value - top - PAD_B - legendH.value),\n }\n})\n\nfunction seriesColor(i: number): string {\n return props.chart.series[i]?.color || PALETTE[i % PALETTE.length]\n}\n\n// Value-axis bounds: explicit from the workbook, else a \"nice\" range around the data.\nconst bounds = computed(() => {\n const c = props.chart\n const vals = c.series.flatMap((s) => s.values.filter((v): v is number => v != null))\n let lo = c.yMin\n let hi = c.yMax\n if (lo == null || hi == null) {\n let dmin = vals.length ? Math.min(...vals) : 0\n let dmax = vals.length ? Math.max(...vals) : 1\n if (c.type === 'bar' || c.type === 'barH' || c.type === 'area') {\n if (dmin > 0) dmin = 0\n if (dmax < 0) dmax = 0\n }\n if (dmin === dmax) {\n dmin -= 1\n dmax += 1\n }\n const pad = (dmax - dmin) * 0.08\n if (lo == null) lo = dmin - pad\n if (hi == null) hi = dmax + pad\n }\n if (hi <= lo) hi = lo + 1\n return { lo, hi }\n})\n\n// Y ticks (4 intervals) for gridlines + axis labels.\nconst yTicks = computed(() => {\n const { lo, hi } = bounds.value\n const n = 4\n const out: { v: number; y: number }[] = []\n for (let i = 0; i <= n; i++) {\n const v = lo + ((hi - lo) * i) / n\n out.push({ v, y: plot.value.y + plot.value.h - (plot.value.h * i) / n })\n }\n return out\n})\n\nfunction fmtNum(v: number): string {\n const a = Math.abs(v)\n if (a >= 1000) return Math.round(v).toLocaleString('en-US')\n if (a >= 1) return (Math.round(v * 100) / 100).toString()\n return (Math.round(v * 1000) / 1000).toString()\n}\n\nconst yToPx = (v: number): number => {\n const { lo, hi } = bounds.value\n const p = plot.value\n return p.y + p.h - ((v - lo) / (hi - lo)) * p.h\n}\n\nconst cats = computed(() => props.chart.categories)\n// X position of category i. Line/scatter sit on the points; bars/area centre within a band.\nconst xCenter = (i: number): number => {\n const p = plot.value\n const n = Math.max(1, cats.value.length)\n return n === 1 ? p.x + p.w / 2 : p.x + (p.w * i) / (n - 1)\n}\nconst bandX = (i: number): number =>\n plot.value.x + (plot.value.w * i) / Math.max(1, cats.value.length)\nconst bandW = computed(() => plot.value.w / Math.max(1, cats.value.length))\n\n// Polyline points for a line/area/scatter series (skips gaps).\nfunction linePoints(si: number): string {\n const s = props.chart.series[si]\n return s.values\n .map((v, i) => (v == null ? null : `${xCenter(i).toFixed(1)},${yToPx(v).toFixed(1)}`))\n .filter(Boolean)\n .join(' ')\n}\nfunction areaPoints(si: number): string {\n const pts = linePoints(si)\n if (!pts) return ''\n const p = plot.value\n const first = pts.split(' ')[0].split(',')[0]\n const last = pts.split(' ').at(-1)!.split(',')[0]\n const baseY = yToPx(Math.max(bounds.value.lo, 0)).toFixed(1)\n return `${first},${baseY} ${pts} ${last},${baseY}`\n}\n\ninterface BarRect {\n x: number\n y: number\n w: number\n h: number\n fill: string\n}\nconst bars = computed<BarRect[]>(() => {\n const c = props.chart\n if (c.type !== 'bar' && c.type !== 'barH') return []\n const ns = c.series.length\n const out: BarRect[] = []\n const gap = bandW.value * 0.18\n const slot = (bandW.value - gap) / Math.max(1, ns)\n const zeroY = yToPx(Math.max(bounds.value.lo, 0))\n for (let i = 0; i < cats.value.length; i++) {\n for (let si = 0; si < ns; si++) {\n const v = c.series[si].values[i]\n if (v == null) continue\n const x = bandX(i) + gap / 2 + slot * si\n const y = yToPx(v)\n out.push({\n x,\n y: Math.min(y, zeroY),\n w: Math.max(1, slot - 1),\n h: Math.max(1, Math.abs(zeroY - y)),\n fill: seriesColor(si),\n })\n }\n }\n return out\n})\n\n// Pie slices from the first series (absolute values).\ninterface PieSlice {\n d: string\n fill: string\n label: string\n}\nconst pie = computed<PieSlice[]>(() => {\n const c = props.chart\n if (c.type !== 'pie') return []\n const s = c.series[0]\n if (!s) return []\n const vals = s.values.map((v) => (v == null ? 0 : Math.abs(v)))\n const total = vals.reduce((a, b) => a + b, 0)\n if (total <= 0) return []\n const p = plot.value\n const cx = p.x + p.w / 2\n const cy = p.y + p.h / 2\n const r = Math.max(8, Math.min(p.w, p.h) / 2 - 2)\n let a0 = -Math.PI / 2\n const out: PieSlice[] = []\n for (let i = 0; i < vals.length; i++) {\n if (vals[i] <= 0) continue\n const a1 = a0 + (vals[i] / total) * Math.PI * 2\n const large = a1 - a0 > Math.PI ? 1 : 0\n const x0 = cx + r * Math.cos(a0)\n const y0 = cy + r * Math.sin(a0)\n const x1 = cx + r * Math.cos(a1)\n const y1 = cy + r * Math.sin(a1)\n out.push({\n d: `M${cx.toFixed(1)},${cy.toFixed(1)} L${x0.toFixed(1)},${y0.toFixed(1)} A${r.toFixed(1)},${r.toFixed(1)} 0 ${large} 1 ${x1.toFixed(1)},${y1.toFixed(1)} Z`,\n fill: PALETTE[i % PALETTE.length],\n label: cats.value[i] ?? '',\n })\n }\n return out\n})\n\n// Show every Nth category label so they don't overlap on a narrow chart.\nconst labelStep = computed(() => {\n const maxLabels = Math.max(2, Math.floor(plot.value.w / 34))\n return Math.max(1, Math.ceil(cats.value.length / maxLabels))\n})\nconst isLineLike = computed(() => ['line', 'area', 'scatter'].includes(props.chart.type))\n</script>\n\n<template>\n <svg\n class=\"xc\"\n :width=\"W\"\n :height=\"H\"\n :viewBox=\"`0 0 ${W} ${H}`\"\n role=\"img\"\n :aria-label=\"chart.title || '图表'\"\n >\n <rect class=\"xc__bg\" :width=\"W\" :height=\"H\" rx=\"3\" />\n <text v-if=\"chart.title\" class=\"xc__title\" :x=\"W / 2\" :y=\"13\" text-anchor=\"middle\">\n {{ chart.title }}\n </text>\n\n <!-- y gridlines + labels (skipped for pie) -->\n <template v-if=\"chart.type !== 'pie' && chart.type !== 'unknown'\">\n <g class=\"xc__grid\">\n <line\n v-for=\"(t, i) in yTicks\"\n :key=\"i\"\n :x1=\"plot.x\"\n :x2=\"plot.x + plot.w\"\n :y1=\"t.y\"\n :y2=\"t.y\"\n />\n </g>\n <g class=\"xc__ylabels\">\n <text v-for=\"(t, i) in yTicks\" :key=\"i\" :x=\"plot.x - 4\" :y=\"t.y + 3\" text-anchor=\"end\">\n {{ fmtNum(t.v) }}\n </text>\n </g>\n <!-- x category labels -->\n <g class=\"xc__xlabels\">\n <text\n v-for=\"(c, i) in cats\"\n v-show=\"i % labelStep === 0\"\n :key=\"i\"\n :x=\"isLineLike ? xCenter(i) : bandX(i) + bandW / 2\"\n :y=\"plot.y + plot.h + 12\"\n text-anchor=\"middle\"\n >\n {{ c }}\n </text>\n </g>\n </template>\n\n <!-- area fills (under the lines) -->\n <template v-if=\"chart.type === 'area'\">\n <polygon\n v-for=\"(s, si) in chart.series\"\n :key=\"'a' + si\"\n :points=\"areaPoints(si)\"\n :fill=\"seriesColor(si)\"\n fill-opacity=\"0.25\"\n stroke=\"none\"\n />\n </template>\n\n <!-- line / scatter -->\n <template v-if=\"isLineLike\">\n <polyline\n v-for=\"(s, si) in chart.series\"\n :key=\"'l' + si\"\n :points=\"linePoints(si)\"\n fill=\"none\"\n :stroke=\"seriesColor(si)\"\n stroke-width=\"1.6\"\n stroke-linejoin=\"round\"\n />\n <template v-for=\"(s, si) in chart.series\" :key=\"'m' + si\">\n <circle\n v-for=\"(v, i) in s.values\"\n v-show=\"v != null\"\n :key=\"i\"\n :cx=\"xCenter(i)\"\n :cy=\"v != null ? yToPx(v) : 0\"\n r=\"1.8\"\n :fill=\"seriesColor(si)\"\n />\n </template>\n </template>\n\n <!-- bars -->\n <template v-if=\"chart.type === 'bar' || chart.type === 'barH'\">\n <rect\n v-for=\"(b, i) in bars\"\n :key=\"i\"\n :x=\"b.x\"\n :y=\"b.y\"\n :width=\"b.w\"\n :height=\"b.h\"\n :fill=\"b.fill\"\n />\n </template>\n\n <!-- pie -->\n <template v-if=\"chart.type === 'pie'\">\n <path\n v-for=\"(s, i) in pie\"\n :key=\"i\"\n :d=\"s.d\"\n :fill=\"s.fill\"\n stroke=\"#fff\"\n stroke-width=\"0.6\"\n />\n </template>\n\n <text\n v-if=\"chart.type === 'unknown'\"\n class=\"xc__note\"\n :x=\"W / 2\"\n :y=\"H / 2\"\n text-anchor=\"middle\"\n >\n (暂不支持的图表类型)\n </text>\n\n <!-- legend -->\n <g v-if=\"hasLegend\" class=\"xc__legend\">\n <g\n v-for=\"(s, si) in chart.series\"\n :key=\"si\"\n :transform=\"`translate(${plot.x + si * 84}, ${H - 4})`\"\n >\n <rect :width=\"9\" :height=\"9\" :y=\"-9\" :fill=\"seriesColor(si)\" rx=\"1.5\" />\n <text :x=\"12\" :y=\"-1\">{{ s.name }}</text>\n </g>\n </g>\n </svg>\n</template>\n\n<style scoped>\n.xc {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n}\n.xc__bg {\n fill: #fff;\n stroke: #d9dce1;\n stroke-width: 1;\n}\n.xc__title {\n font-size: 11px;\n font-weight: 600;\n fill: #1a1a1a;\n}\n.xc__grid line {\n stroke: #e6e8eb;\n stroke-width: 1;\n}\n.xc__ylabels text {\n font-size: 8px;\n fill: #6b7280;\n}\n.xc__xlabels text {\n font-size: 8px;\n fill: #6b7280;\n}\n.xc__legend text {\n font-size: 9px;\n fill: #374151;\n}\n.xc__note {\n font-size: 10px;\n fill: #9aa0a6;\n}\n</style>\n"],"mappings":""}
|
|
1
|
+
{"version":3,"file":"XlsxChart.js","names":[],"sources":["../../src/components/XlsxChart.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { XlsxChart } from '@/lib/xlsxCharts'\n\n/**\n * Render one parsed workbook chart ({@link XlsxChart}) as inline SVG — no charting dependency, in keeping with\n * the hand-rolled renderers elsewhere (markdown, binobj). Covers the kinds these spreadsheets actually use:\n * line/area/scatter as polylines, bar/barH as grouped bars, pie as slices. Geometry is derived from the chart's\n * cached series so the picture matches what Excel last drew. The host ({@link XlsxViewer}) positions us over the\n * grid at the drawing anchor; here we just fill the given pixel box.\n */\nconst props = defineProps<{ chart: XlsxChart }>()\n\n// Theme-aware defaults; series with authored colors still use those colors verbatim.\nconst PALETTE = [\n 'var(--hl-attr)',\n 'var(--hl-built)',\n 'var(--hl-cmt)',\n 'var(--ansi-yellow)',\n 'var(--ansi-cyan)',\n 'var(--hl-name)',\n 'var(--hl-fn)',\n 'var(--ansi-red)',\n]\n\nconst W = computed(() => Math.max(220, props.chart.anchor.wPx))\nconst H = computed(() => Math.max(140, props.chart.anchor.hPx))\n\nconst hasLegend = computed(() => props.chart.series.length > 1)\nconst titleH = computed(() => (props.chart.title ? 18 : 4))\nconst legendH = computed(() => (hasLegend.value ? 16 : 0))\n\n// Plot rectangle inside the axes/labels.\nconst PAD_L = 38\nconst PAD_R = 8\nconst PAD_B = 26\nconst plot = computed(() => {\n const top = titleH.value + 4\n return {\n x: PAD_L,\n y: top,\n w: Math.max(10, W.value - PAD_L - PAD_R),\n h: Math.max(10, H.value - top - PAD_B - legendH.value),\n }\n})\n\nfunction seriesColor(i: number): string {\n return props.chart.series[i]?.color || PALETTE[i % PALETTE.length]\n}\n\n// Value-axis bounds: explicit from the workbook, else a \"nice\" range around the data.\nconst bounds = computed(() => {\n const c = props.chart\n const vals = c.series.flatMap((s) => s.values.filter((v): v is number => v != null))\n let lo = c.yMin\n let hi = c.yMax\n if (lo == null || hi == null) {\n let dmin = vals.length ? Math.min(...vals) : 0\n let dmax = vals.length ? Math.max(...vals) : 1\n if (c.type === 'bar' || c.type === 'barH' || c.type === 'area') {\n if (dmin > 0) dmin = 0\n if (dmax < 0) dmax = 0\n }\n if (dmin === dmax) {\n dmin -= 1\n dmax += 1\n }\n const pad = (dmax - dmin) * 0.08\n if (lo == null) lo = dmin - pad\n if (hi == null) hi = dmax + pad\n }\n if (hi <= lo) hi = lo + 1\n return { lo, hi }\n})\n\n// Y ticks (4 intervals) for gridlines + axis labels.\nconst yTicks = computed(() => {\n const { lo, hi } = bounds.value\n const n = 4\n const out: { v: number; y: number }[] = []\n for (let i = 0; i <= n; i++) {\n const v = lo + ((hi - lo) * i) / n\n out.push({ v, y: plot.value.y + plot.value.h - (plot.value.h * i) / n })\n }\n return out\n})\n\nfunction fmtNum(v: number): string {\n const a = Math.abs(v)\n if (a >= 1000) return Math.round(v).toLocaleString('en-US')\n if (a >= 1) return (Math.round(v * 100) / 100).toString()\n return (Math.round(v * 1000) / 1000).toString()\n}\n\nconst yToPx = (v: number): number => {\n const { lo, hi } = bounds.value\n const p = plot.value\n return p.y + p.h - ((v - lo) / (hi - lo)) * p.h\n}\n\nconst cats = computed(() => props.chart.categories)\n// X position of category i. Line/scatter sit on the points; bars/area centre within a band.\nconst xCenter = (i: number): number => {\n const p = plot.value\n const n = Math.max(1, cats.value.length)\n return n === 1 ? p.x + p.w / 2 : p.x + (p.w * i) / (n - 1)\n}\nconst bandX = (i: number): number =>\n plot.value.x + (plot.value.w * i) / Math.max(1, cats.value.length)\nconst bandW = computed(() => plot.value.w / Math.max(1, cats.value.length))\n\n// Polyline points for a line/area/scatter series (skips gaps).\nfunction linePoints(si: number): string {\n const s = props.chart.series[si]\n return s.values\n .map((v, i) => (v == null ? null : `${xCenter(i).toFixed(1)},${yToPx(v).toFixed(1)}`))\n .filter(Boolean)\n .join(' ')\n}\nfunction areaPoints(si: number): string {\n const pts = linePoints(si)\n if (!pts) return ''\n const p = plot.value\n const first = pts.split(' ')[0].split(',')[0]\n const last = pts.split(' ').at(-1)!.split(',')[0]\n const baseY = yToPx(Math.max(bounds.value.lo, 0)).toFixed(1)\n return `${first},${baseY} ${pts} ${last},${baseY}`\n}\n\ninterface BarRect {\n x: number\n y: number\n w: number\n h: number\n fill: string\n}\nconst bars = computed<BarRect[]>(() => {\n const c = props.chart\n if (c.type !== 'bar' && c.type !== 'barH') return []\n const ns = c.series.length\n const out: BarRect[] = []\n const gap = bandW.value * 0.18\n const slot = (bandW.value - gap) / Math.max(1, ns)\n const zeroY = yToPx(Math.max(bounds.value.lo, 0))\n for (let i = 0; i < cats.value.length; i++) {\n for (let si = 0; si < ns; si++) {\n const v = c.series[si].values[i]\n if (v == null) continue\n const x = bandX(i) + gap / 2 + slot * si\n const y = yToPx(v)\n out.push({\n x,\n y: Math.min(y, zeroY),\n w: Math.max(1, slot - 1),\n h: Math.max(1, Math.abs(zeroY - y)),\n fill: seriesColor(si),\n })\n }\n }\n return out\n})\n\n// Pie slices from the first series (absolute values).\ninterface PieSlice {\n d: string\n fill: string\n label: string\n}\nconst pie = computed<PieSlice[]>(() => {\n const c = props.chart\n if (c.type !== 'pie') return []\n const s = c.series[0]\n if (!s) return []\n const vals = s.values.map((v) => (v == null ? 0 : Math.abs(v)))\n const total = vals.reduce((a, b) => a + b, 0)\n if (total <= 0) return []\n const p = plot.value\n const cx = p.x + p.w / 2\n const cy = p.y + p.h / 2\n const r = Math.max(8, Math.min(p.w, p.h) / 2 - 2)\n let a0 = -Math.PI / 2\n const out: PieSlice[] = []\n for (let i = 0; i < vals.length; i++) {\n if (vals[i] <= 0) continue\n const a1 = a0 + (vals[i] / total) * Math.PI * 2\n const large = a1 - a0 > Math.PI ? 1 : 0\n const x0 = cx + r * Math.cos(a0)\n const y0 = cy + r * Math.sin(a0)\n const x1 = cx + r * Math.cos(a1)\n const y1 = cy + r * Math.sin(a1)\n out.push({\n d: `M${cx.toFixed(1)},${cy.toFixed(1)} L${x0.toFixed(1)},${y0.toFixed(1)} A${r.toFixed(1)},${r.toFixed(1)} 0 ${large} 1 ${x1.toFixed(1)},${y1.toFixed(1)} Z`,\n fill: PALETTE[i % PALETTE.length],\n label: cats.value[i] ?? '',\n })\n }\n return out\n})\n\n// Show every Nth category label so they don't overlap on a narrow chart.\nconst labelStep = computed(() => {\n const maxLabels = Math.max(2, Math.floor(plot.value.w / 34))\n return Math.max(1, Math.ceil(cats.value.length / maxLabels))\n})\nconst isLineLike = computed(() => ['line', 'area', 'scatter'].includes(props.chart.type))\n</script>\n\n<template>\n <svg\n class=\"xc\"\n :width=\"W\"\n :height=\"H\"\n :viewBox=\"`0 0 ${W} ${H}`\"\n role=\"img\"\n :aria-label=\"chart.title || '图表'\"\n >\n <rect class=\"xc__bg\" :width=\"W\" :height=\"H\" rx=\"3\" />\n <text v-if=\"chart.title\" class=\"xc__title\" :x=\"W / 2\" :y=\"13\" text-anchor=\"middle\">\n {{ chart.title }}\n </text>\n\n <!-- y gridlines + labels (skipped for pie) -->\n <template v-if=\"chart.type !== 'pie' && chart.type !== 'unknown'\">\n <g class=\"xc__grid\">\n <line\n v-for=\"(t, i) in yTicks\"\n :key=\"i\"\n :x1=\"plot.x\"\n :x2=\"plot.x + plot.w\"\n :y1=\"t.y\"\n :y2=\"t.y\"\n />\n </g>\n <g class=\"xc__ylabels\">\n <text v-for=\"(t, i) in yTicks\" :key=\"i\" :x=\"plot.x - 4\" :y=\"t.y + 3\" text-anchor=\"end\">\n {{ fmtNum(t.v) }}\n </text>\n </g>\n <!-- x category labels -->\n <g class=\"xc__xlabels\">\n <text\n v-for=\"(c, i) in cats\"\n v-show=\"i % labelStep === 0\"\n :key=\"i\"\n :x=\"isLineLike ? xCenter(i) : bandX(i) + bandW / 2\"\n :y=\"plot.y + plot.h + 12\"\n text-anchor=\"middle\"\n >\n {{ c }}\n </text>\n </g>\n </template>\n\n <!-- area fills (under the lines) -->\n <template v-if=\"chart.type === 'area'\">\n <polygon\n v-for=\"(s, si) in chart.series\"\n :key=\"'a' + si\"\n :points=\"areaPoints(si)\"\n :fill=\"seriesColor(si)\"\n fill-opacity=\"0.25\"\n stroke=\"none\"\n />\n </template>\n\n <!-- line / scatter -->\n <template v-if=\"isLineLike\">\n <polyline\n v-for=\"(s, si) in chart.series\"\n :key=\"'l' + si\"\n :points=\"linePoints(si)\"\n fill=\"none\"\n :stroke=\"seriesColor(si)\"\n stroke-width=\"1.6\"\n stroke-linejoin=\"round\"\n />\n <template v-for=\"(s, si) in chart.series\" :key=\"'m' + si\">\n <circle\n v-for=\"(v, i) in s.values\"\n v-show=\"v != null\"\n :key=\"i\"\n :cx=\"xCenter(i)\"\n :cy=\"v != null ? yToPx(v) : 0\"\n r=\"1.8\"\n :fill=\"seriesColor(si)\"\n />\n </template>\n </template>\n\n <!-- bars -->\n <template v-if=\"chart.type === 'bar' || chart.type === 'barH'\">\n <rect\n v-for=\"(b, i) in bars\"\n :key=\"i\"\n :x=\"b.x\"\n :y=\"b.y\"\n :width=\"b.w\"\n :height=\"b.h\"\n :fill=\"b.fill\"\n />\n </template>\n\n <!-- pie -->\n <template v-if=\"chart.type === 'pie'\">\n <path\n v-for=\"(s, i) in pie\"\n :key=\"i\"\n :d=\"s.d\"\n :fill=\"s.fill\"\n stroke=\"var(--surface)\"\n stroke-width=\"0.6\"\n />\n </template>\n\n <text\n v-if=\"chart.type === 'unknown'\"\n class=\"xc__note\"\n :x=\"W / 2\"\n :y=\"H / 2\"\n text-anchor=\"middle\"\n >\n (暂不支持的图表类型)\n </text>\n\n <!-- legend -->\n <g v-if=\"hasLegend\" class=\"xc__legend\">\n <g\n v-for=\"(s, si) in chart.series\"\n :key=\"si\"\n :transform=\"`translate(${plot.x + si * 84}, ${H - 4})`\"\n >\n <rect :width=\"9\" :height=\"9\" :y=\"-9\" :fill=\"seriesColor(si)\" rx=\"1.5\" />\n <text :x=\"12\" :y=\"-1\">{{ s.name }}</text>\n </g>\n </g>\n </svg>\n</template>\n\n<style scoped>\n.xc {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n}\n.xc__bg {\n fill: var(--surface);\n stroke: var(--border-2);\n stroke-width: 1;\n}\n.xc__title {\n font-size: 11px;\n font-weight: 600;\n fill: var(--text-strong);\n}\n.xc__grid line {\n stroke: var(--border);\n stroke-width: 1;\n}\n.xc__ylabels text {\n font-size: 8px;\n fill: var(--muted);\n}\n.xc__xlabels text {\n font-size: 8px;\n fill: var(--muted);\n}\n.xc__legend text {\n font-size: 9px;\n fill: var(--text);\n}\n.xc__note {\n font-size: 10px;\n fill: var(--muted);\n}\n</style>\n"],"mappings":""}
|
|
@@ -31,14 +31,14 @@ var d = [
|
|
|
31
31
|
props: { chart: {} },
|
|
32
32
|
setup(a) {
|
|
33
33
|
let M = a, N = [
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
34
|
+
"var(--hl-attr)",
|
|
35
|
+
"var(--hl-built)",
|
|
36
|
+
"var(--hl-cmt)",
|
|
37
|
+
"var(--ansi-yellow)",
|
|
38
|
+
"var(--ansi-cyan)",
|
|
39
|
+
"var(--hl-name)",
|
|
40
|
+
"var(--hl-fn)",
|
|
41
|
+
"var(--ansi-red)"
|
|
42
42
|
], P = t(() => Math.max(220, M.chart.anchor.wPx)), F = t(() => Math.max(140, M.chart.anchor.hPx)), I = t(() => M.chart.series.length > 1), L = t(() => M.chart.title ? 18 : 4), R = t(() => I.value ? 16 : 0), z = t(() => {
|
|
43
43
|
let e = L.value + 4;
|
|
44
44
|
return {
|
|
@@ -213,7 +213,7 @@ var d = [
|
|
|
213
213
|
key: t,
|
|
214
214
|
d: e.d,
|
|
215
215
|
fill: e.fill,
|
|
216
|
-
stroke: "
|
|
216
|
+
stroke: "var(--surface)",
|
|
217
217
|
"stroke-width": "0.6"
|
|
218
218
|
}, null, 8, w))), 128)) : n("", !0),
|
|
219
219
|
a.chart.type === "unknown" ? (o(), r("text", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"XlsxChart.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/XlsxChart.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { XlsxChart } from '@/lib/xlsxCharts'\n\n/**\n * Render one parsed workbook chart ({@link XlsxChart}) as inline SVG — no charting dependency, in keeping with\n * the hand-rolled renderers elsewhere (markdown, binobj). Covers the kinds these spreadsheets actually use:\n * line/area/scatter as polylines, bar/barH as grouped bars, pie as slices. Geometry is derived from the chart's\n * cached series so the picture matches what Excel last drew. The host ({@link XlsxViewer}) positions us over the\n * grid at the drawing anchor; here we just fill the given pixel box.\n */\nconst props = defineProps<{ chart: XlsxChart }>()\n\n// A small palette for series that don't carry an explicit colour (Office-ish accents).\nconst PALETTE = [\n '#4472C4',\n '#ED7D31',\n '#A5A5A5',\n '#FFC000',\n '#5B9BD5',\n '#70AD47',\n '#264478',\n '#9E480E',\n]\n\nconst W = computed(() => Math.max(220, props.chart.anchor.wPx))\nconst H = computed(() => Math.max(140, props.chart.anchor.hPx))\n\nconst hasLegend = computed(() => props.chart.series.length > 1)\nconst titleH = computed(() => (props.chart.title ? 18 : 4))\nconst legendH = computed(() => (hasLegend.value ? 16 : 0))\n\n// Plot rectangle inside the axes/labels.\nconst PAD_L = 38\nconst PAD_R = 8\nconst PAD_B = 26\nconst plot = computed(() => {\n const top = titleH.value + 4\n return {\n x: PAD_L,\n y: top,\n w: Math.max(10, W.value - PAD_L - PAD_R),\n h: Math.max(10, H.value - top - PAD_B - legendH.value),\n }\n})\n\nfunction seriesColor(i: number): string {\n return props.chart.series[i]?.color || PALETTE[i % PALETTE.length]\n}\n\n// Value-axis bounds: explicit from the workbook, else a \"nice\" range around the data.\nconst bounds = computed(() => {\n const c = props.chart\n const vals = c.series.flatMap((s) => s.values.filter((v): v is number => v != null))\n let lo = c.yMin\n let hi = c.yMax\n if (lo == null || hi == null) {\n let dmin = vals.length ? Math.min(...vals) : 0\n let dmax = vals.length ? Math.max(...vals) : 1\n if (c.type === 'bar' || c.type === 'barH' || c.type === 'area') {\n if (dmin > 0) dmin = 0\n if (dmax < 0) dmax = 0\n }\n if (dmin === dmax) {\n dmin -= 1\n dmax += 1\n }\n const pad = (dmax - dmin) * 0.08\n if (lo == null) lo = dmin - pad\n if (hi == null) hi = dmax + pad\n }\n if (hi <= lo) hi = lo + 1\n return { lo, hi }\n})\n\n// Y ticks (4 intervals) for gridlines + axis labels.\nconst yTicks = computed(() => {\n const { lo, hi } = bounds.value\n const n = 4\n const out: { v: number; y: number }[] = []\n for (let i = 0; i <= n; i++) {\n const v = lo + ((hi - lo) * i) / n\n out.push({ v, y: plot.value.y + plot.value.h - (plot.value.h * i) / n })\n }\n return out\n})\n\nfunction fmtNum(v: number): string {\n const a = Math.abs(v)\n if (a >= 1000) return Math.round(v).toLocaleString('en-US')\n if (a >= 1) return (Math.round(v * 100) / 100).toString()\n return (Math.round(v * 1000) / 1000).toString()\n}\n\nconst yToPx = (v: number): number => {\n const { lo, hi } = bounds.value\n const p = plot.value\n return p.y + p.h - ((v - lo) / (hi - lo)) * p.h\n}\n\nconst cats = computed(() => props.chart.categories)\n// X position of category i. Line/scatter sit on the points; bars/area centre within a band.\nconst xCenter = (i: number): number => {\n const p = plot.value\n const n = Math.max(1, cats.value.length)\n return n === 1 ? p.x + p.w / 2 : p.x + (p.w * i) / (n - 1)\n}\nconst bandX = (i: number): number =>\n plot.value.x + (plot.value.w * i) / Math.max(1, cats.value.length)\nconst bandW = computed(() => plot.value.w / Math.max(1, cats.value.length))\n\n// Polyline points for a line/area/scatter series (skips gaps).\nfunction linePoints(si: number): string {\n const s = props.chart.series[si]\n return s.values\n .map((v, i) => (v == null ? null : `${xCenter(i).toFixed(1)},${yToPx(v).toFixed(1)}`))\n .filter(Boolean)\n .join(' ')\n}\nfunction areaPoints(si: number): string {\n const pts = linePoints(si)\n if (!pts) return ''\n const p = plot.value\n const first = pts.split(' ')[0].split(',')[0]\n const last = pts.split(' ').at(-1)!.split(',')[0]\n const baseY = yToPx(Math.max(bounds.value.lo, 0)).toFixed(1)\n return `${first},${baseY} ${pts} ${last},${baseY}`\n}\n\ninterface BarRect {\n x: number\n y: number\n w: number\n h: number\n fill: string\n}\nconst bars = computed<BarRect[]>(() => {\n const c = props.chart\n if (c.type !== 'bar' && c.type !== 'barH') return []\n const ns = c.series.length\n const out: BarRect[] = []\n const gap = bandW.value * 0.18\n const slot = (bandW.value - gap) / Math.max(1, ns)\n const zeroY = yToPx(Math.max(bounds.value.lo, 0))\n for (let i = 0; i < cats.value.length; i++) {\n for (let si = 0; si < ns; si++) {\n const v = c.series[si].values[i]\n if (v == null) continue\n const x = bandX(i) + gap / 2 + slot * si\n const y = yToPx(v)\n out.push({\n x,\n y: Math.min(y, zeroY),\n w: Math.max(1, slot - 1),\n h: Math.max(1, Math.abs(zeroY - y)),\n fill: seriesColor(si),\n })\n }\n }\n return out\n})\n\n// Pie slices from the first series (absolute values).\ninterface PieSlice {\n d: string\n fill: string\n label: string\n}\nconst pie = computed<PieSlice[]>(() => {\n const c = props.chart\n if (c.type !== 'pie') return []\n const s = c.series[0]\n if (!s) return []\n const vals = s.values.map((v) => (v == null ? 0 : Math.abs(v)))\n const total = vals.reduce((a, b) => a + b, 0)\n if (total <= 0) return []\n const p = plot.value\n const cx = p.x + p.w / 2\n const cy = p.y + p.h / 2\n const r = Math.max(8, Math.min(p.w, p.h) / 2 - 2)\n let a0 = -Math.PI / 2\n const out: PieSlice[] = []\n for (let i = 0; i < vals.length; i++) {\n if (vals[i] <= 0) continue\n const a1 = a0 + (vals[i] / total) * Math.PI * 2\n const large = a1 - a0 > Math.PI ? 1 : 0\n const x0 = cx + r * Math.cos(a0)\n const y0 = cy + r * Math.sin(a0)\n const x1 = cx + r * Math.cos(a1)\n const y1 = cy + r * Math.sin(a1)\n out.push({\n d: `M${cx.toFixed(1)},${cy.toFixed(1)} L${x0.toFixed(1)},${y0.toFixed(1)} A${r.toFixed(1)},${r.toFixed(1)} 0 ${large} 1 ${x1.toFixed(1)},${y1.toFixed(1)} Z`,\n fill: PALETTE[i % PALETTE.length],\n label: cats.value[i] ?? '',\n })\n }\n return out\n})\n\n// Show every Nth category label so they don't overlap on a narrow chart.\nconst labelStep = computed(() => {\n const maxLabels = Math.max(2, Math.floor(plot.value.w / 34))\n return Math.max(1, Math.ceil(cats.value.length / maxLabels))\n})\nconst isLineLike = computed(() => ['line', 'area', 'scatter'].includes(props.chart.type))\n</script>\n\n<template>\n <svg\n class=\"xc\"\n :width=\"W\"\n :height=\"H\"\n :viewBox=\"`0 0 ${W} ${H}`\"\n role=\"img\"\n :aria-label=\"chart.title || '图表'\"\n >\n <rect class=\"xc__bg\" :width=\"W\" :height=\"H\" rx=\"3\" />\n <text v-if=\"chart.title\" class=\"xc__title\" :x=\"W / 2\" :y=\"13\" text-anchor=\"middle\">\n {{ chart.title }}\n </text>\n\n <!-- y gridlines + labels (skipped for pie) -->\n <template v-if=\"chart.type !== 'pie' && chart.type !== 'unknown'\">\n <g class=\"xc__grid\">\n <line\n v-for=\"(t, i) in yTicks\"\n :key=\"i\"\n :x1=\"plot.x\"\n :x2=\"plot.x + plot.w\"\n :y1=\"t.y\"\n :y2=\"t.y\"\n />\n </g>\n <g class=\"xc__ylabels\">\n <text v-for=\"(t, i) in yTicks\" :key=\"i\" :x=\"plot.x - 4\" :y=\"t.y + 3\" text-anchor=\"end\">\n {{ fmtNum(t.v) }}\n </text>\n </g>\n <!-- x category labels -->\n <g class=\"xc__xlabels\">\n <text\n v-for=\"(c, i) in cats\"\n v-show=\"i % labelStep === 0\"\n :key=\"i\"\n :x=\"isLineLike ? xCenter(i) : bandX(i) + bandW / 2\"\n :y=\"plot.y + plot.h + 12\"\n text-anchor=\"middle\"\n >\n {{ c }}\n </text>\n </g>\n </template>\n\n <!-- area fills (under the lines) -->\n <template v-if=\"chart.type === 'area'\">\n <polygon\n v-for=\"(s, si) in chart.series\"\n :key=\"'a' + si\"\n :points=\"areaPoints(si)\"\n :fill=\"seriesColor(si)\"\n fill-opacity=\"0.25\"\n stroke=\"none\"\n />\n </template>\n\n <!-- line / scatter -->\n <template v-if=\"isLineLike\">\n <polyline\n v-for=\"(s, si) in chart.series\"\n :key=\"'l' + si\"\n :points=\"linePoints(si)\"\n fill=\"none\"\n :stroke=\"seriesColor(si)\"\n stroke-width=\"1.6\"\n stroke-linejoin=\"round\"\n />\n <template v-for=\"(s, si) in chart.series\" :key=\"'m' + si\">\n <circle\n v-for=\"(v, i) in s.values\"\n v-show=\"v != null\"\n :key=\"i\"\n :cx=\"xCenter(i)\"\n :cy=\"v != null ? yToPx(v) : 0\"\n r=\"1.8\"\n :fill=\"seriesColor(si)\"\n />\n </template>\n </template>\n\n <!-- bars -->\n <template v-if=\"chart.type === 'bar' || chart.type === 'barH'\">\n <rect\n v-for=\"(b, i) in bars\"\n :key=\"i\"\n :x=\"b.x\"\n :y=\"b.y\"\n :width=\"b.w\"\n :height=\"b.h\"\n :fill=\"b.fill\"\n />\n </template>\n\n <!-- pie -->\n <template v-if=\"chart.type === 'pie'\">\n <path\n v-for=\"(s, i) in pie\"\n :key=\"i\"\n :d=\"s.d\"\n :fill=\"s.fill\"\n stroke=\"#fff\"\n stroke-width=\"0.6\"\n />\n </template>\n\n <text\n v-if=\"chart.type === 'unknown'\"\n class=\"xc__note\"\n :x=\"W / 2\"\n :y=\"H / 2\"\n text-anchor=\"middle\"\n >\n (暂不支持的图表类型)\n </text>\n\n <!-- legend -->\n <g v-if=\"hasLegend\" class=\"xc__legend\">\n <g\n v-for=\"(s, si) in chart.series\"\n :key=\"si\"\n :transform=\"`translate(${plot.x + si * 84}, ${H - 4})`\"\n >\n <rect :width=\"9\" :height=\"9\" :y=\"-9\" :fill=\"seriesColor(si)\" rx=\"1.5\" />\n <text :x=\"12\" :y=\"-1\">{{ s.name }}</text>\n </g>\n </g>\n </svg>\n</template>\n\n<style scoped>\n.xc {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n}\n.xc__bg {\n fill: #fff;\n stroke: #d9dce1;\n stroke-width: 1;\n}\n.xc__title {\n font-size: 11px;\n font-weight: 600;\n fill: #1a1a1a;\n}\n.xc__grid line {\n stroke: #e6e8eb;\n stroke-width: 1;\n}\n.xc__ylabels text {\n font-size: 8px;\n fill: #6b7280;\n}\n.xc__xlabels text {\n font-size: 8px;\n fill: #6b7280;\n}\n.xc__legend text {\n font-size: 9px;\n fill: #374151;\n}\n.xc__note {\n font-size: 10px;\n fill: #9aa0a6;\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"XlsxChart.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/XlsxChart.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { XlsxChart } from '@/lib/xlsxCharts'\n\n/**\n * Render one parsed workbook chart ({@link XlsxChart}) as inline SVG — no charting dependency, in keeping with\n * the hand-rolled renderers elsewhere (markdown, binobj). Covers the kinds these spreadsheets actually use:\n * line/area/scatter as polylines, bar/barH as grouped bars, pie as slices. Geometry is derived from the chart's\n * cached series so the picture matches what Excel last drew. The host ({@link XlsxViewer}) positions us over the\n * grid at the drawing anchor; here we just fill the given pixel box.\n */\nconst props = defineProps<{ chart: XlsxChart }>()\n\n// Theme-aware defaults; series with authored colors still use those colors verbatim.\nconst PALETTE = [\n 'var(--hl-attr)',\n 'var(--hl-built)',\n 'var(--hl-cmt)',\n 'var(--ansi-yellow)',\n 'var(--ansi-cyan)',\n 'var(--hl-name)',\n 'var(--hl-fn)',\n 'var(--ansi-red)',\n]\n\nconst W = computed(() => Math.max(220, props.chart.anchor.wPx))\nconst H = computed(() => Math.max(140, props.chart.anchor.hPx))\n\nconst hasLegend = computed(() => props.chart.series.length > 1)\nconst titleH = computed(() => (props.chart.title ? 18 : 4))\nconst legendH = computed(() => (hasLegend.value ? 16 : 0))\n\n// Plot rectangle inside the axes/labels.\nconst PAD_L = 38\nconst PAD_R = 8\nconst PAD_B = 26\nconst plot = computed(() => {\n const top = titleH.value + 4\n return {\n x: PAD_L,\n y: top,\n w: Math.max(10, W.value - PAD_L - PAD_R),\n h: Math.max(10, H.value - top - PAD_B - legendH.value),\n }\n})\n\nfunction seriesColor(i: number): string {\n return props.chart.series[i]?.color || PALETTE[i % PALETTE.length]\n}\n\n// Value-axis bounds: explicit from the workbook, else a \"nice\" range around the data.\nconst bounds = computed(() => {\n const c = props.chart\n const vals = c.series.flatMap((s) => s.values.filter((v): v is number => v != null))\n let lo = c.yMin\n let hi = c.yMax\n if (lo == null || hi == null) {\n let dmin = vals.length ? Math.min(...vals) : 0\n let dmax = vals.length ? Math.max(...vals) : 1\n if (c.type === 'bar' || c.type === 'barH' || c.type === 'area') {\n if (dmin > 0) dmin = 0\n if (dmax < 0) dmax = 0\n }\n if (dmin === dmax) {\n dmin -= 1\n dmax += 1\n }\n const pad = (dmax - dmin) * 0.08\n if (lo == null) lo = dmin - pad\n if (hi == null) hi = dmax + pad\n }\n if (hi <= lo) hi = lo + 1\n return { lo, hi }\n})\n\n// Y ticks (4 intervals) for gridlines + axis labels.\nconst yTicks = computed(() => {\n const { lo, hi } = bounds.value\n const n = 4\n const out: { v: number; y: number }[] = []\n for (let i = 0; i <= n; i++) {\n const v = lo + ((hi - lo) * i) / n\n out.push({ v, y: plot.value.y + plot.value.h - (plot.value.h * i) / n })\n }\n return out\n})\n\nfunction fmtNum(v: number): string {\n const a = Math.abs(v)\n if (a >= 1000) return Math.round(v).toLocaleString('en-US')\n if (a >= 1) return (Math.round(v * 100) / 100).toString()\n return (Math.round(v * 1000) / 1000).toString()\n}\n\nconst yToPx = (v: number): number => {\n const { lo, hi } = bounds.value\n const p = plot.value\n return p.y + p.h - ((v - lo) / (hi - lo)) * p.h\n}\n\nconst cats = computed(() => props.chart.categories)\n// X position of category i. Line/scatter sit on the points; bars/area centre within a band.\nconst xCenter = (i: number): number => {\n const p = plot.value\n const n = Math.max(1, cats.value.length)\n return n === 1 ? p.x + p.w / 2 : p.x + (p.w * i) / (n - 1)\n}\nconst bandX = (i: number): number =>\n plot.value.x + (plot.value.w * i) / Math.max(1, cats.value.length)\nconst bandW = computed(() => plot.value.w / Math.max(1, cats.value.length))\n\n// Polyline points for a line/area/scatter series (skips gaps).\nfunction linePoints(si: number): string {\n const s = props.chart.series[si]\n return s.values\n .map((v, i) => (v == null ? null : `${xCenter(i).toFixed(1)},${yToPx(v).toFixed(1)}`))\n .filter(Boolean)\n .join(' ')\n}\nfunction areaPoints(si: number): string {\n const pts = linePoints(si)\n if (!pts) return ''\n const p = plot.value\n const first = pts.split(' ')[0].split(',')[0]\n const last = pts.split(' ').at(-1)!.split(',')[0]\n const baseY = yToPx(Math.max(bounds.value.lo, 0)).toFixed(1)\n return `${first},${baseY} ${pts} ${last},${baseY}`\n}\n\ninterface BarRect {\n x: number\n y: number\n w: number\n h: number\n fill: string\n}\nconst bars = computed<BarRect[]>(() => {\n const c = props.chart\n if (c.type !== 'bar' && c.type !== 'barH') return []\n const ns = c.series.length\n const out: BarRect[] = []\n const gap = bandW.value * 0.18\n const slot = (bandW.value - gap) / Math.max(1, ns)\n const zeroY = yToPx(Math.max(bounds.value.lo, 0))\n for (let i = 0; i < cats.value.length; i++) {\n for (let si = 0; si < ns; si++) {\n const v = c.series[si].values[i]\n if (v == null) continue\n const x = bandX(i) + gap / 2 + slot * si\n const y = yToPx(v)\n out.push({\n x,\n y: Math.min(y, zeroY),\n w: Math.max(1, slot - 1),\n h: Math.max(1, Math.abs(zeroY - y)),\n fill: seriesColor(si),\n })\n }\n }\n return out\n})\n\n// Pie slices from the first series (absolute values).\ninterface PieSlice {\n d: string\n fill: string\n label: string\n}\nconst pie = computed<PieSlice[]>(() => {\n const c = props.chart\n if (c.type !== 'pie') return []\n const s = c.series[0]\n if (!s) return []\n const vals = s.values.map((v) => (v == null ? 0 : Math.abs(v)))\n const total = vals.reduce((a, b) => a + b, 0)\n if (total <= 0) return []\n const p = plot.value\n const cx = p.x + p.w / 2\n const cy = p.y + p.h / 2\n const r = Math.max(8, Math.min(p.w, p.h) / 2 - 2)\n let a0 = -Math.PI / 2\n const out: PieSlice[] = []\n for (let i = 0; i < vals.length; i++) {\n if (vals[i] <= 0) continue\n const a1 = a0 + (vals[i] / total) * Math.PI * 2\n const large = a1 - a0 > Math.PI ? 1 : 0\n const x0 = cx + r * Math.cos(a0)\n const y0 = cy + r * Math.sin(a0)\n const x1 = cx + r * Math.cos(a1)\n const y1 = cy + r * Math.sin(a1)\n out.push({\n d: `M${cx.toFixed(1)},${cy.toFixed(1)} L${x0.toFixed(1)},${y0.toFixed(1)} A${r.toFixed(1)},${r.toFixed(1)} 0 ${large} 1 ${x1.toFixed(1)},${y1.toFixed(1)} Z`,\n fill: PALETTE[i % PALETTE.length],\n label: cats.value[i] ?? '',\n })\n }\n return out\n})\n\n// Show every Nth category label so they don't overlap on a narrow chart.\nconst labelStep = computed(() => {\n const maxLabels = Math.max(2, Math.floor(plot.value.w / 34))\n return Math.max(1, Math.ceil(cats.value.length / maxLabels))\n})\nconst isLineLike = computed(() => ['line', 'area', 'scatter'].includes(props.chart.type))\n</script>\n\n<template>\n <svg\n class=\"xc\"\n :width=\"W\"\n :height=\"H\"\n :viewBox=\"`0 0 ${W} ${H}`\"\n role=\"img\"\n :aria-label=\"chart.title || '图表'\"\n >\n <rect class=\"xc__bg\" :width=\"W\" :height=\"H\" rx=\"3\" />\n <text v-if=\"chart.title\" class=\"xc__title\" :x=\"W / 2\" :y=\"13\" text-anchor=\"middle\">\n {{ chart.title }}\n </text>\n\n <!-- y gridlines + labels (skipped for pie) -->\n <template v-if=\"chart.type !== 'pie' && chart.type !== 'unknown'\">\n <g class=\"xc__grid\">\n <line\n v-for=\"(t, i) in yTicks\"\n :key=\"i\"\n :x1=\"plot.x\"\n :x2=\"plot.x + plot.w\"\n :y1=\"t.y\"\n :y2=\"t.y\"\n />\n </g>\n <g class=\"xc__ylabels\">\n <text v-for=\"(t, i) in yTicks\" :key=\"i\" :x=\"plot.x - 4\" :y=\"t.y + 3\" text-anchor=\"end\">\n {{ fmtNum(t.v) }}\n </text>\n </g>\n <!-- x category labels -->\n <g class=\"xc__xlabels\">\n <text\n v-for=\"(c, i) in cats\"\n v-show=\"i % labelStep === 0\"\n :key=\"i\"\n :x=\"isLineLike ? xCenter(i) : bandX(i) + bandW / 2\"\n :y=\"plot.y + plot.h + 12\"\n text-anchor=\"middle\"\n >\n {{ c }}\n </text>\n </g>\n </template>\n\n <!-- area fills (under the lines) -->\n <template v-if=\"chart.type === 'area'\">\n <polygon\n v-for=\"(s, si) in chart.series\"\n :key=\"'a' + si\"\n :points=\"areaPoints(si)\"\n :fill=\"seriesColor(si)\"\n fill-opacity=\"0.25\"\n stroke=\"none\"\n />\n </template>\n\n <!-- line / scatter -->\n <template v-if=\"isLineLike\">\n <polyline\n v-for=\"(s, si) in chart.series\"\n :key=\"'l' + si\"\n :points=\"linePoints(si)\"\n fill=\"none\"\n :stroke=\"seriesColor(si)\"\n stroke-width=\"1.6\"\n stroke-linejoin=\"round\"\n />\n <template v-for=\"(s, si) in chart.series\" :key=\"'m' + si\">\n <circle\n v-for=\"(v, i) in s.values\"\n v-show=\"v != null\"\n :key=\"i\"\n :cx=\"xCenter(i)\"\n :cy=\"v != null ? yToPx(v) : 0\"\n r=\"1.8\"\n :fill=\"seriesColor(si)\"\n />\n </template>\n </template>\n\n <!-- bars -->\n <template v-if=\"chart.type === 'bar' || chart.type === 'barH'\">\n <rect\n v-for=\"(b, i) in bars\"\n :key=\"i\"\n :x=\"b.x\"\n :y=\"b.y\"\n :width=\"b.w\"\n :height=\"b.h\"\n :fill=\"b.fill\"\n />\n </template>\n\n <!-- pie -->\n <template v-if=\"chart.type === 'pie'\">\n <path\n v-for=\"(s, i) in pie\"\n :key=\"i\"\n :d=\"s.d\"\n :fill=\"s.fill\"\n stroke=\"var(--surface)\"\n stroke-width=\"0.6\"\n />\n </template>\n\n <text\n v-if=\"chart.type === 'unknown'\"\n class=\"xc__note\"\n :x=\"W / 2\"\n :y=\"H / 2\"\n text-anchor=\"middle\"\n >\n (暂不支持的图表类型)\n </text>\n\n <!-- legend -->\n <g v-if=\"hasLegend\" class=\"xc__legend\">\n <g\n v-for=\"(s, si) in chart.series\"\n :key=\"si\"\n :transform=\"`translate(${plot.x + si * 84}, ${H - 4})`\"\n >\n <rect :width=\"9\" :height=\"9\" :y=\"-9\" :fill=\"seriesColor(si)\" rx=\"1.5\" />\n <text :x=\"12\" :y=\"-1\">{{ s.name }}</text>\n </g>\n </g>\n </svg>\n</template>\n\n<style scoped>\n.xc {\n display: block;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;\n}\n.xc__bg {\n fill: var(--surface);\n stroke: var(--border-2);\n stroke-width: 1;\n}\n.xc__title {\n font-size: 11px;\n font-weight: 600;\n fill: var(--text-strong);\n}\n.xc__grid line {\n stroke: var(--border);\n stroke-width: 1;\n}\n.xc__ylabels text {\n font-size: 8px;\n fill: var(--muted);\n}\n.xc__xlabels text {\n font-size: 8px;\n fill: var(--muted);\n}\n.xc__legend text {\n font-size: 9px;\n fill: var(--text);\n}\n.xc__note {\n font-size: 10px;\n fill: var(--muted);\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
2
|
+
import t from "./XlsxImageLayer.vue_vue_type_script_setup_true_lang.js";
|
|
3
|
+
/* empty css */
|
|
4
|
+
//#region src/components/XlsxImageLayer.vue
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-459058bc"]]);
|
|
6
|
+
//#endregion
|
|
7
|
+
export { n as default };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=XlsxImageLayer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XlsxImageLayer.js","names":[],"sources":["../../src/components/XlsxImageLayer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, shallowRef, watch } from 'vue'\nimport { xlsxImageBox, type XlsxImage } from '@/lib/xlsxImages'\n\nconst props = defineProps<{ images: XlsxImage[]; columnEdges: number[]; rowEdges: number[] }>()\nconst sources = shallowRef(new Map<Blob, string>())\nfunction release(): void {\n for (const url of sources.value.values()) URL.revokeObjectURL(url)\n sources.value = new Map()\n}\nwatch(\n () => props.images,\n (images) => {\n release()\n const next = new Map<Blob, string>()\n for (const image of images) {\n if (!next.has(image.blob)) next.set(image.blob, URL.createObjectURL(image.blob))\n }\n sources.value = next\n },\n { immediate: true },\n)\nonBeforeUnmount(release)\nconst positioned = computed(() =>\n props.images\n .map((image) => ({\n image,\n box: xlsxImageBox(image.anchor, props.columnEdges, props.rowEdges),\n url: sources.value.get(image.blob),\n }))\n .filter(({ box }) => box.width > 0 && box.height > 0),\n)\n</script>\n\n<template>\n <div class=\"xlsx-images\">\n <img\n v-for=\"item in positioned\"\n :key=\"item.image.id\"\n class=\"xlsx-images__image\"\n :src=\"item.url\"\n :alt=\"item.image.alt\"\n :style=\"{\n left: `${item.box.left}px`,\n top: `${item.box.top}px`,\n width: `${item.box.width}px`,\n height: `${item.box.height}px`,\n }\"\n draggable=\"false\"\n />\n </div>\n</template>\n\n<style scoped>\n.xlsx-images {\n position: absolute;\n inset: 0;\n z-index: 1;\n pointer-events: none;\n}\n.xlsx-images__image {\n position: absolute;\n display: block;\n max-width: none;\n object-fit: fill;\n}\n</style>\n"],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type XlsxImage } from '@/lib/xlsxImages';
|
|
2
|
+
type __VLS_Props = {
|
|
3
|
+
images: XlsxImage[];
|
|
4
|
+
columnEdges: number[];
|
|
5
|
+
rowEdges: number[];
|
|
6
|
+
};
|
|
7
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
8
|
+
declare const _default: typeof __VLS_export;
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { xlsxImageBox as e } from "../lib/xlsxImages.js";
|
|
2
|
+
import { Fragment as t, computed as n, createElementBlock as r, defineComponent as i, normalizeStyle as a, onBeforeUnmount as o, openBlock as s, renderList as c, shallowRef as l, watch as u } from "vue";
|
|
3
|
+
//#region src/components/XlsxImageLayer.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var d = { class: "xlsx-images" }, f = ["src", "alt"], p = /*@__PURE__*/ i({
|
|
5
|
+
__name: "XlsxImageLayer",
|
|
6
|
+
props: {
|
|
7
|
+
images: {},
|
|
8
|
+
columnEdges: {},
|
|
9
|
+
rowEdges: {}
|
|
10
|
+
},
|
|
11
|
+
setup(i) {
|
|
12
|
+
let p = i, m = l(/* @__PURE__ */ new Map());
|
|
13
|
+
function h() {
|
|
14
|
+
for (let e of m.value.values()) URL.revokeObjectURL(e);
|
|
15
|
+
m.value = /* @__PURE__ */ new Map();
|
|
16
|
+
}
|
|
17
|
+
u(() => p.images, (e) => {
|
|
18
|
+
h();
|
|
19
|
+
let t = /* @__PURE__ */ new Map();
|
|
20
|
+
for (let n of e) t.has(n.blob) || t.set(n.blob, URL.createObjectURL(n.blob));
|
|
21
|
+
m.value = t;
|
|
22
|
+
}, { immediate: !0 }), o(h);
|
|
23
|
+
let g = n(() => p.images.map((t) => ({
|
|
24
|
+
image: t,
|
|
25
|
+
box: e(t.anchor, p.columnEdges, p.rowEdges),
|
|
26
|
+
url: m.value.get(t.blob)
|
|
27
|
+
})).filter(({ box: e }) => e.width > 0 && e.height > 0));
|
|
28
|
+
return (e, n) => (s(), r("div", d, [(s(!0), r(t, null, c(g.value, (e) => (s(), r("img", {
|
|
29
|
+
key: e.image.id,
|
|
30
|
+
class: "xlsx-images__image",
|
|
31
|
+
src: e.url,
|
|
32
|
+
alt: e.image.alt,
|
|
33
|
+
style: a({
|
|
34
|
+
left: `${e.box.left}px`,
|
|
35
|
+
top: `${e.box.top}px`,
|
|
36
|
+
width: `${e.box.width}px`,
|
|
37
|
+
height: `${e.box.height}px`
|
|
38
|
+
}),
|
|
39
|
+
draggable: "false"
|
|
40
|
+
}, null, 12, f))), 128))]));
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
//#endregion
|
|
44
|
+
export { p as default };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=XlsxImageLayer.vue_vue_type_script_setup_true_lang.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"XlsxImageLayer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/XlsxImageLayer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, shallowRef, watch } from 'vue'\nimport { xlsxImageBox, type XlsxImage } from '@/lib/xlsxImages'\n\nconst props = defineProps<{ images: XlsxImage[]; columnEdges: number[]; rowEdges: number[] }>()\nconst sources = shallowRef(new Map<Blob, string>())\nfunction release(): void {\n for (const url of sources.value.values()) URL.revokeObjectURL(url)\n sources.value = new Map()\n}\nwatch(\n () => props.images,\n (images) => {\n release()\n const next = new Map<Blob, string>()\n for (const image of images) {\n if (!next.has(image.blob)) next.set(image.blob, URL.createObjectURL(image.blob))\n }\n sources.value = next\n },\n { immediate: true },\n)\nonBeforeUnmount(release)\nconst positioned = computed(() =>\n props.images\n .map((image) => ({\n image,\n box: xlsxImageBox(image.anchor, props.columnEdges, props.rowEdges),\n url: sources.value.get(image.blob),\n }))\n .filter(({ box }) => box.width > 0 && box.height > 0),\n)\n</script>\n\n<template>\n <div class=\"xlsx-images\">\n <img\n v-for=\"item in positioned\"\n :key=\"item.image.id\"\n class=\"xlsx-images__image\"\n :src=\"item.url\"\n :alt=\"item.image.alt\"\n :style=\"{\n left: `${item.box.left}px`,\n top: `${item.box.top}px`,\n width: `${item.box.width}px`,\n height: `${item.box.height}px`,\n }\"\n draggable=\"false\"\n />\n </div>\n</template>\n\n<style scoped>\n.xlsx-images {\n position: absolute;\n inset: 0;\n z-index: 1;\n pointer-events: none;\n}\n.xlsx-images__image {\n position: absolute;\n display: block;\n max-width: none;\n object-fit: fill;\n}\n</style>\n"],"mappings":";;;;;;;;;;;EAIA,IAAM,IAAQ,GACR,IAAU,kBAAW,IAAI,IAAkB,CAAC;EAClD,SAAS,IAAgB;GACvB,KAAK,IAAM,KAAO,EAAQ,MAAM,OAAO,GAAG,IAAI,gBAAgB,CAAG;GACjE,EAAQ,wBAAQ,IAAI,IAAI;EAC1B;EAaA,AAZA,QACQ,EAAM,SACX,MAAW;GACV,EAAQ;GACR,IAAM,oBAAO,IAAI,IAAkB;GACnC,KAAK,IAAM,KAAS,GAClB,AAAK,EAAK,IAAI,EAAM,IAAI,KAAG,EAAK,IAAI,EAAM,MAAM,IAAI,gBAAgB,EAAM,IAAI,CAAC;GAEjF,EAAQ,QAAQ;EAClB,GACA,EAAE,WAAW,GAAK,CACpB,GACA,EAAgB,CAAO;EACvB,IAAM,IAAa,QACjB,EAAM,OACH,KAAK,OAAW;GACf;GACA,KAAK,EAAa,EAAM,QAAQ,EAAM,aAAa,EAAM,QAAQ;GACjE,KAAK,EAAQ,MAAM,IAAI,EAAM,IAAI;EACnC,EAAE,CAAA,CACD,QAAQ,EAAE,aAAU,EAAI,QAAQ,KAAK,EAAI,SAAS,CAAC,CACxD;yBAIE,EAeM,OAfN,GAeM,EAAA,EAAA,EAAA,GAdJ,EAaE,GAAA,MAAA,EAZe,EAAA,QAAR,YADT,EAaE,OAAA;GAXC,KAAK,EAAK,MAAM;GACjB,OAAM;GACL,KAAK,EAAK;GACV,KAAK,EAAK,MAAM;GAChB,OAAK,EAAA;aAAqB,EAAK,IAAI,KAAI;YAAsB,EAAK,IAAI,IAAG;cAAwB,EAAK,IAAI,MAAK;eAAyB,EAAK,IAAI,OAAM;;GAMxJ,WAAU"}
|
|
@@ -2,7 +2,7 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
|
|
|
2
2
|
import t from "./XlsxViewer.vue_vue_type_script_setup_true_lang.js";
|
|
3
3
|
/* empty css */
|
|
4
4
|
//#region src/components/XlsxViewer.vue
|
|
5
|
-
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-
|
|
5
|
+
var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-3a2a25a5"]]);
|
|
6
6
|
//#endregion
|
|
7
7
|
export { n as default };
|
|
8
8
|
|