From c7f97f77c7fea05a50f70b367c850603a2399219 Mon Sep 17 00:00:00 2001 From: lirui Date: Mon, 9 Feb 2026 20:13:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=20README=20=E6=96=87?= =?UTF-8?q?=E6=A1=A3=EF=BC=8C=E6=B7=BB=E5=8A=A0=E5=8A=9F=E8=83=BD=E7=89=B9?= =?UTF-8?q?=E6=80=A7=E3=80=81=E4=BD=BF=E7=94=A8=E8=AF=B4=E6=98=8E=E5=8F=8A?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=84=9A=E6=9C=AC=EF=BC=9B=E5=9C=A8=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E9=80=BB=E8=BE=91=E4=B8=AD=E6=94=AF=E6=8C=81=E5=B5=8C?= =?UTF-8?q?=E5=A5=97=E5=AF=B9=E8=B1=A1=E6=9E=84=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 117 +++++++++++++++++++++++++++++--------- src/lib/converter.spec.ts | 21 +++++++ src/lib/converter.ts | 37 ++++++++++-- 3 files changed, 143 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 2682320..fb56700 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,107 @@ -# sv +# excel2json -Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli). +一个基于 **SvelteKit + TypeScript** 的 Excel 转 JSON 可视化工具。 +支持上传 Excel/CSV、列级映射配置、实时 JSON 预览、模板导入导出与一键复制/下载。 -## Creating a project +## 功能特性 -If you're seeing this, you've probably already done this step. Congrats! +- 支持文件格式:`.xlsx`、`.xls`、`.csv` +- 左侧表格预览 Excel 数据,右侧实时预览转换后的 JSON +- 每列可配置: + - 目标字段名(`target`) + - 数据类型(`string` / `number` / `boolean` / `date`) + - 日期格式(如 `YYYY-MM-DD`、`YYYY-MM-DD HH:mm:ss`、`timestamp`) + - 空值策略(空值时移除字段 / 使用默认值) + - 是否启用该列输出 +- 支持嵌套字段:`user.address.city` 会生成嵌套对象,而不是带点字符串键名 +- 支持映射模板导入/导出(JSON) +- 支持导出 JSON 文件与复制 JSON 到剪贴板 +- 包含单元测试(`vitest`) -```sh -# create a new project -npx sv create my-app +## 技术栈 + +- `Svelte 5` + `SvelteKit 2` +- `TypeScript` +- `Tailwind CSS 4` +- `xlsx`(SheetJS) +- `dayjs` +- `Vitest` + +## 快速开始 + +### 1) 安装依赖 + +```bash +npm install ``` -To recreate this project with the same configuration: +### 2) 启动开发环境 -```sh -# recreate this project -npx sv create --template minimal --types ts --add vitest="usages:unit,component" tailwindcss="plugins:typography,forms" devtools-json mcp="ide:claude-code,vscode,other,gemini,cursor,opencode+setup:remote" --install yarn . -``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```sh +```bash npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open ``` -## Building +默认地址通常为:`http://localhost:5173` -To create a production version of your app: +### 3) 构建与预览 -```sh +```bash npm run build +npm run preview ``` -You can preview the production build with `npm run preview`. +## 使用说明 -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. +1. 上传或拖拽 Excel/CSV 文件 +2. 在左侧点击列头“设置”调整映射规则 +3. 右侧实时查看 JSON 结果 +4. 可导出当前映射为模板,供后续复用 +5. 使用“下载 JSON”或“复制 JSON”输出结果 + +## 映射模板格式 + +模板是一个 JSON 数组,每一项对应一列配置。示例: + +```json +[ + { + "source": "姓名", + "target": "user.name", + "type": "string", + "excludeIfEmpty": false, + "defaultValue": "" + }, + { + "source": "入职日期", + "target": "user.joinDate", + "type": "date", + "format": "YYYY-MM-DD", + "excludeIfEmpty": true + } +] +``` + +## 开发脚本 + +- `npm run dev`:启动开发服务器 +- `npm run build`:生产构建 +- `npm run preview`:预览构建产物 +- `npm run check`:类型与 Svelte 检查 +- `npm run test`:运行测试(单次) +- `npm run test:unit`:运行 Vitest(交互模式) + +## 项目结构(核心) + +- `src/routes/+page.svelte`:主页面与整体交互 +- `src/lib/excel.ts`:Excel/CSV 读取与解析 +- `src/lib/converter.ts`:映射转换核心逻辑 +- `src/lib/types.ts`:类型定义(`MappingConfig`、`RowData` 等) +- `src/lib/components/ExcelTable.svelte`:左侧数据表格 +- `src/lib/components/ColumnConfig.svelte`:列映射配置面板 +- `src/lib/components/JsonPreview.svelte`:右侧 JSON 预览 + +## 注意事项 + +- 日期字段会尽量兼容 Excel 序列日期(数字日期) +- 当 `excludeIfEmpty = true` 时,空值字段不会出现在输出 JSON 中 +- 若目标字段包含 `.`,会按路径写入嵌套对象 diff --git a/src/lib/converter.spec.ts b/src/lib/converter.spec.ts index beff579..b622a53 100644 --- a/src/lib/converter.spec.ts +++ b/src/lib/converter.spec.ts @@ -100,6 +100,27 @@ describe('convertData', () => { const result = convertData(rows, mappings); expect(result[0]).toEqual({ full_name: 'Alice' }); }); + + it('builds nested objects from dot-separated target keys', () => { + const mappings: MappingConfig[] = [ + { source: 'name', target: 'user.name', type: 'string', excludeIfEmpty: false, defaultValue: '', enabled: true }, + { source: 'age', target: 'user.age', type: 'number', excludeIfEmpty: false, defaultValue: '', enabled: true }, + { source: 'active', target: 'meta.active', type: 'boolean', excludeIfEmpty: false, defaultValue: '', enabled: true } + ]; + const result = convertData(rows, mappings); + expect(result[0]).toEqual({ + user: { name: 'Alice', age: 30 }, + meta: { active: true } + }); + }); + + it('builds deeply nested objects', () => { + const mappings: MappingConfig[] = [ + { source: 'name', target: 'a.b.c.d', type: 'string', excludeIfEmpty: false, defaultValue: '', enabled: true } + ]; + const result = convertData(rows, mappings); + expect(result[0]).toEqual({ a: { b: { c: { d: 'Alice' } } } }); + }); }); describe('date conversion', () => { diff --git a/src/lib/converter.ts b/src/lib/converter.ts index 731a673..03c38ab 100644 --- a/src/lib/converter.ts +++ b/src/lib/converter.ts @@ -63,6 +63,31 @@ function formatDate(value: unknown, format?: string): string | number { return date.format(format || 'YYYY-MM-DD'); } +/** + * Set a value at a dot-separated path, creating nested objects as needed. + * e.g. setNested(obj, "user.address.city", "Beijing") + * => obj = { user: { address: { city: "Beijing" } } } + */ +function setNested(obj: Record, path: string, value: unknown): void { + if (!path.includes('.')) { + obj[path] = value; + return; + } + + const keys = path.split('.'); + let current: Record = obj; + + for (let i = 0; i < keys.length - 1; i++) { + const key = keys[i]; + if (current[key] === undefined || current[key] === null || typeof current[key] !== 'object') { + current[key] = {}; + } + current = current[key] as Record; + } + + current[keys[keys.length - 1]] = value; +} + /** * Convert raw Excel rows to JSON objects based on mapping configs. */ @@ -83,13 +108,13 @@ export function convertData( continue; } - if (isEmptyVal) { - obj[mapping.target] = mapping.defaultValue !== undefined && mapping.defaultValue !== '' + const finalValue = isEmptyVal + ? (mapping.defaultValue !== undefined && mapping.defaultValue !== '' ? convertValue(mapping.defaultValue, mapping.type, mapping.format) - : null; - } else { - obj[mapping.target] = convertValue(rawValue, mapping.type, mapping.format); - } + : null) + : convertValue(rawValue, mapping.type, mapping.format); + + setNested(obj, mapping.target, finalValue); } return obj;