完善 README 文档,添加功能特性、使用说明及开发脚本;在转换逻辑中支持嵌套对象构建
This commit is contained in:
117
README.md
117
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
|
```bash
|
||||||
# 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
|
|
||||||
npm run dev
|
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 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 中
|
||||||
|
- 若目标字段包含 `.`,会按路径写入嵌套对象
|
||||||
|
|||||||
@@ -100,6 +100,27 @@ describe('convertData', () => {
|
|||||||
const result = convertData(rows, mappings);
|
const result = convertData(rows, mappings);
|
||||||
expect(result[0]).toEqual({ full_name: 'Alice' });
|
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', () => {
|
describe('date conversion', () => {
|
||||||
|
|||||||
@@ -63,6 +63,31 @@ function formatDate(value: unknown, format?: string): string | number {
|
|||||||
return date.format(format || 'YYYY-MM-DD');
|
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<string, unknown>, path: string, value: unknown): void {
|
||||||
|
if (!path.includes('.')) {
|
||||||
|
obj[path] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = path.split('.');
|
||||||
|
let current: Record<string, unknown> = 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<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
current[keys[keys.length - 1]] = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert raw Excel rows to JSON objects based on mapping configs.
|
* Convert raw Excel rows to JSON objects based on mapping configs.
|
||||||
*/
|
*/
|
||||||
@@ -83,13 +108,13 @@ export function convertData(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEmptyVal) {
|
const finalValue = isEmptyVal
|
||||||
obj[mapping.target] = mapping.defaultValue !== undefined && mapping.defaultValue !== ''
|
? (mapping.defaultValue !== undefined && mapping.defaultValue !== ''
|
||||||
? convertValue(mapping.defaultValue, mapping.type, mapping.format)
|
? convertValue(mapping.defaultValue, mapping.type, mapping.format)
|
||||||
: null;
|
: null)
|
||||||
} else {
|
: convertValue(rawValue, mapping.type, mapping.format);
|
||||||
obj[mapping.target] = convertValue(rawValue, mapping.type, mapping.format);
|
|
||||||
}
|
setNested(obj, mapping.target, finalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
|||||||
Reference in New Issue
Block a user