diff --git a/README.md b/README.md index 2b0b997..2cf9238 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,446 @@ +# Label Designer -# label-designer -> 标签批量设置排版,并生成PDF文档 +标签批量设计与排版工具:可视化编辑标签模板、导入 Excel 数据、预览整页排版并导出 PDF。 + +模板以 JSON 保存,导出文件扩展名为 `.lad`(Label Application Data)。 + +--- + +## 快速开始 + +```bash +npm install +npm run dev # 开发服务器,默认 http://localhost:3000 +npm run build # 生产构建 +npm run preview # 预览构建结果 +``` + +--- + +## 模板文件概览 + +一个模板描述**单张标签**的外观与排版参数。批量数据(Excel 行)在导出阶段关联,**不写入模板 JSON**。 + +### 最小合法结构 + +导入外部模板时,JSON 至少包含: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `width` | number | 标签宽度 (mm) | +| `height` | number | 标签高度 (mm) | +| `elements` | array | 元素列表 | + +### 完整根对象 `LabelTemplate` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `id` | string | 否 | 模板唯一 ID,应用内自动生成 | +| `name` | string | 否 | 模板显示名称 | +| `width` | number | **是** | 标签画布宽度 (mm) | +| `height` | number | **是** | 标签画布高度 (mm) | +| `elements` | `TemplateElement[]` | **是** | 标签上的所有元素 | +| `variableList` | string[] | 否 | 声明的变量名列表(统一管理) | +| `variableDefaults` | Record | 否 | 设计预览用默认值,`变量名 → 文本` | +| `paper` | `PaperConfig` | 否 | 纸张排版;缺省时使用 A4 默认配置 | +| `cutLine` | `CutLineConfig` | 否 | PDF 裁切参考线;缺省时启用灰色虚线 | + +--- + +## 坐标与单位 + +- 所有尺寸、位置、边距、间距:**毫米 (mm)** +- 字号:**磅 (pt)** +- 元素坐标 `(x, y)` 为相对标签画布左上角的距离 +- 旋转 `rotation`:顺时针角度(度),默认 `0` + +--- + +## 变量与占位符 + +### 语法 + +在元素的 `content` 中使用 `{变量名}` 引用数据。导出时由 Excel 列映射或默认值填充。 + +| 占位符 | 说明 | +|--------|------| +| `{变量名}` | 普通变量,如 `{货位编号}`、`{物料名称}` | +| `{#INDEX}` | 内置占位符:当前数据行在导入数据中的 **1-based 序号**(第 1 行 → `1`) | + +变量名规则: + +- 写在花括号内,不含 `}` +- `#INDEX` reserved,不要作为自定义变量名 +- 可在 `variableList` 中预先声明;也会从 `content` 与显示条件中自动汇总 + +### 导出时的变量映射(非模板字段) + +导出 Excel 时,每个变量需关联数据源,映射关系保存在浏览器本地,**不在模板 JSON 内**: + +| 映射值 | 含义 | +|--------|------| +| Excel 列名 | 取该列单元格值 | +| `__use_default__` | 使用模板 `variableDefaults` 中的默认值 | +| `__use_row_index__` | 使用数据序号(与 `{#INDEX}` 相同,1-based) | + +列名与变量名相同时会自动匹配。 + +--- + +## 元素 `TemplateElement` + +每个元素是标签上的一个图层,按数组顺序从下到上绘制。 + +### 通用字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | string | 元素唯一 ID | +| `type` | `'text' \| 'barcode' \| 'qrcode' \| 'image'` | 元素类型 | +| `name` | string | 图层名称(设计器显示用) | +| `x`, `y` | number | 位置 (mm) | +| `width`, `height` | number | 尺寸 (mm) | +| `rotation` | number | 旋转角度,默认 `0` | +| `content` | string | 静态文本或 `{变量}` 组合 | +| `fontFamily` | `'sans' \| 'serif' \| 'mono'` | 字体族 | +| `backgroundColor` | string | 背景色,如 `#ffffff`、`transparent` | +| `backgroundOpacity` | number | 背景不透明度 0–100 | +| `padding` | number | 内边距 (mm),四边统一 | +| `paddingTop/Right/Bottom/Left` | number | 分边内边距,覆盖 `padding` | +| `borderWidth` | number | 边框线宽 (mm),0 为无边框 | +| `borderColor` | string | 边框颜色 | +| `borderWidthTop/Right/Bottom/Left` | number | 分边边框宽度 | +| `borderColorTop/Right/Bottom/Left` | string | 分边边框颜色 | +| `borderRadius` | number | 圆角半径 (mm) | +| `borderRadiusTL/TR/BR/BL` | number | 分角圆角 | +| `borderRadiusCorners` | object | 指定哪些角圆角,如 `{ "tl": true, "tr": true }` | +| `locked` | boolean | 锁定后设计器不可拖动 | + +### 值提取(`text` / `barcode` / `qrcode` / `image` 均有效) + +对 **变量替换完成后的完整 `content` 字符串** 做提取(先替换 `{变量}`,再提取)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `textExtractMode` | `'none' \| 'split' \| 'prefix' \| 'suffix'` | 提取方式;缺省且存在 `textSplitDelimiter` 时视为 `split` | +| `textSplitDelimiter` | string | 分隔符,如 `-` | +| `textSplitIndex` | number | 按分隔符拆分后取第 N 段(**从 1 开始**),段数不足则为空 | +| `textExtractLength` | number | 前 N / 后 N 位时的位数 | + +提取前会对完整内容 `trim()` 去除首尾空格。 + +**示例**:`content: "ABC-{货位编号}"`,货位编号 = `A-1-2-3` +→ 替换后 `ABC-A-1-2-3` → 分隔符 `-`、第 3 段 → 显示 `1` + +### 显示前后缀(仅 `type: "text"`) + +在值提取 **之后** 追加,仅影响显示,**不参与**「内容值」显示条件判断。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `textDisplayPrefix` | string | 前缀字符,如 `¥` | +| `textDisplaySuffix` | string | 后缀字符,如 `元` | + +### 显示控制 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `visibilityMode` | `'always' \| 'conditional'` | 始终显示 / 按条件显示 | +| `visibilityRules` | `VisibilityRule[]` | 条件列表,**全部满足**才显示 | + +#### `VisibilityRule` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `target` | `'variable' \| 'content'` | 条件对象:变量值 或 本元素内容值(提取后、不含前后缀) | +| `variable` | string | `target=variable` 时的变量名 | +| `operator` | 见下表 | 比较运算符 | +| `value` | string | `gt` / `lt` / `eq` / `neq` 时的比较目标 | + +| operator | 含义 | +|----------|------| +| `empty` | 为空 | +| `not_empty` | 不为空 | +| `gt` | 大于(数值优先比较,否则字符串) | +| `lt` | 小于 | +| `eq` | 等于 | +| `neq` | 不等于 | + +--- + +### 文本元素 `type: "text"` + +| 字段 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `fontSize` | number | — | 字号 (pt) | +| `textAlign` | `'left' \| 'center' \| 'right'` | — | 水平对齐 | +| `verticalAlign` | `'top' \| 'middle' \| 'bottom'` | `middle` | 垂直对齐 | +| `wordWrap` | boolean | `true` | 自动换行 | +| `textWritingMode` | `'horizontal' \| 'vertical'` | `horizontal` | 排列方向 | +| `fontWeight` | `'normal' \| 'bold'` | — | 粗体 | +| `fontStyle` | `'normal' \| 'italic'` | `normal` | 斜体 | +| `textUnderline` | boolean | — | 下划线 | +| `textLineThrough` | boolean | — | 删除线 | +| `textScaleX`, `textScaleY` | number | `1` | 水平/垂直拉伸 | +| `letterSpacing` | number | — | 字间距 (mm) | +| `textColor` | string | — | 文字颜色 | +| `textFormat` | `'text' \| 'number' \| 'percent' \| 'currency'` | `text` | 显示格式 | +| `decimalPlaces` | number | `2` | 数值/百分比/货币的小数位 | +| `numberPartDisplay` | `'full' \| 'integer' \| 'fraction'` | `full` | 数值拆分显示(如价格拆成整数与小数两部分) | + +数值格式作用于每个 `{变量}` 替换值;提取与前后缀见上文顺序。 + +--- + +### 条形码 `type: "barcode"` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `barcodeFormat` | `'CODE128' \| 'CODE39' \| 'EAN13' \| 'ITF'` | 码制 | +| `showText` | boolean | 是否在条码下方显示可读文本 | +| `content` | string | 条码数据,通常为 `{变量}` | + +输出模式下内容为空则不渲染该元素。 + +--- + +### 二维码 `type: "qrcode"` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `content` | string | 二维码内容(URL、文本等) | + +二维码元素宽高相等(设计器约束)。内容为空时不渲染。 + +--- + +### 图片 `type: "image"` + +| 字段 | 类型 | 说明 | +|------|------|------| +| `content` | string | 图片 URL、`data:` URI,或 `{变量}` | +| `imageFit` | `'contain' \| 'cover' \| 'fill'` | 缩放方式,默认 `contain` | + +--- + +## 纸张排版 `PaperConfig` + +控制标签如何排列到 PDF 页面上(每模板独立保存)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `type` | `'A4' \| 'A5' \| 'A6' \| 'custom'` | 纸张规格 | +| `width`, `height` | number | 纸张尺寸 (mm);预设规格可省略自定义值 | +| `orientation` | `'portrait' \| 'landscape'` | 纵向 / 横向 | +| `marginTop/Right/Bottom/Left` | number | 页边距 (mm) | +| `columnGap`, `rowGap` | number | 标签横向 / 纵向间距 (mm) | +| `flow` | `'left-right-top-bottom' \| 'top-bottom-left-right'` | 标签填充顺序 | + +**默认 `paper`(未指定时)** + +```json +{ + "type": "A4", + "width": 210, + "height": 297, + "orientation": "portrait", + "marginTop": 10, + "marginRight": 10, + "marginBottom": 10, + "marginLeft": 10, + "columnGap": 2, + "rowGap": 2, + "flow": "left-right-top-bottom" +} +``` + +--- + +## 裁切线 `CutLineConfig` + +PDF 中标签之间的裁切参考线(预览与导出一致)。 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `enabled` | boolean | 是否显示 | +| `style` | `'dashed' \| 'solid'` | 线型 | +| `width` | number | 线宽 (mm),默认 `0.1` | +| `color` | string | 颜色,默认 `#cccccc` | + +--- + +## 完整示例 + +```json +{ + "id": "demo_label_001", + "name": "货位标签示例", + "width": 70, + "height": 40, + "variableList": ["货位编号", "物料名称", "批次号"], + "variableDefaults": { + "货位编号": "A-01-05", + "物料名称": "示例物料", + "批次号": "2026061101" + }, + "paper": { + "type": "A4", + "width": 210, + "height": 297, + "orientation": "portrait", + "marginTop": 10, + "marginRight": 10, + "marginBottom": 10, + "marginLeft": 10, + "columnGap": 2, + "rowGap": 2, + "flow": "left-right-top-bottom" + }, + "cutLine": { + "enabled": true, + "style": "dashed", + "width": 0.1, + "color": "#cccccc" + }, + "elements": [ + { + "id": "el_title", + "type": "text", + "name": "货位前缀", + "x": 2, + "y": 2, + "width": 66, + "height": 8, + "content": "货位:", + "fontSize": 9, + "fontWeight": "bold", + "textAlign": "left", + "fontFamily": "sans", + "barcodeFormat": "CODE128", + "showText": false, + "textColor": "#333333" + }, + { + "id": "el_location", + "type": "text", + "name": "货位段", + "x": 18, + "y": 2, + "width": 20, + "height": 8, + "content": "{货位编号}", + "fontSize": 10, + "fontWeight": "bold", + "textAlign": "left", + "fontFamily": "mono", + "barcodeFormat": "CODE128", + "showText": false, + "textExtractMode": "split", + "textSplitDelimiter": "-", + "textSplitIndex": 2, + "visibilityMode": "conditional", + "visibilityRules": [ + { + "target": "content", + "variable": "", + "operator": "not_empty" + } + ] + }, + { + "id": "el_name", + "type": "text", + "name": "物料名称", + "x": 2, + "y": 12, + "width": 66, + "height": 10, + "content": "{物料名称}", + "fontSize": 8, + "fontWeight": "normal", + "textAlign": "left", + "fontFamily": "sans", + "barcodeFormat": "CODE128", + "showText": false, + "wordWrap": true + }, + { + "id": "el_barcode", + "type": "barcode", + "name": "条码", + "x": 2, + "y": 24, + "width": 40, + "height": 14, + "content": "{货位编号}", + "fontSize": 8, + "textAlign": "center", + "fontWeight": "normal", + "fontFamily": "mono", + "barcodeFormat": "CODE128", + "showText": true, + "textExtractMode": "suffix", + "textExtractLength": 8 + }, + { + "id": "el_qr", + "type": "qrcode", + "name": "二维码", + "x": 48, + "y": 22, + "width": 18, + "height": 18, + "content": "https://example.com/{货位编号}", + "fontSize": 8, + "textAlign": "center", + "fontWeight": "normal", + "fontFamily": "sans", + "barcodeFormat": "CODE128", + "showText": false + } + ] +} +``` + +> 说明:`barcodeFormat` 与 `showText` 在所有元素类型上为历史 schema 要求字段,文本/二维码/图片元素也需填写(常用 `CODE128` + `showText: false`)。 + +--- + +## 内容解析顺序(导出 / 预览) + +对单个元素,最终显示值按以下顺序计算: + +1. 将 `content` 中的 `{变量}` 替换为数据行值(文本元素可对每个变量做数值格式化) +2. 对完整字符串做 **值提取**(trim → 分隔符 / 前 N / 后 N) +3. 文本元素追加 **显示前后缀**(`textDisplayPrefix` / `textDisplaySuffix`) +4. 根据 **显示控制** 决定是否绘制该元素 + +--- + +## 导入 / 导出模板 + +| 操作 | 说明 | +|------|------| +| 应用内导出 | 生成 `.lad` JSON 文件 | +| 应用内导入 | 校验 `width`、`height`、`elements` 后追加到模板库 | +| 本地存储 | 浏览器 `localStorage` 键 `label_templates_list_v2` | + +导入时会执行 `normalizeTemplate`:补全默认 `paper`、`cutLine`,并移除已废弃的数据源字段。 + +--- + +## 导出 PDF 流程(与模板的关系) + +1. 选择模板 → 设计标签(编辑 JSON 中的 `elements` 等) +2. 导入 Excel → 配置 **变量列映射**(非模板部分) +3. 设置纸张排版、数据范围、裁切线(排版参数来自模板 `paper` / `cutLine`) +4. 预览确认 → 导出 PDF + +PDF 每页按 `paper` 网格排列标签,裁切线可选,标签图由 Canvas 渲染为 PNG/JPEG 嵌入 PDF。 + +--- + +## 类型定义源码 + +完整 TypeScript 类型见 [`src/types.ts`](src/types.ts)。 +模板规范化逻辑见 [`src/utils.ts`](src/utils.ts) 中的 `normalizeTemplate`。 diff --git a/src/App.tsx b/src/App.tsx index b7d58c3..92dc7d1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -442,7 +442,6 @@ export default function App() { variableDefaults: parsed.variableDefaults, paper: parsed.paper, cutLine: parsed.cutLine, - showPdfCutLines: parsed.showPdfCutLines, }); const list = [...templates, newTmpl]; saveTemplatesToStorage(list); diff --git a/src/types.ts b/src/types.ts index ec64792..2d287b2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -179,14 +179,6 @@ export interface LabelTemplate { paper?: PaperConfig; /** PDF 裁切参考线配置 */ cutLine?: CutLineConfig; - /** @deprecated 由 cutLine.enabled 替代 */ - showPdfCutLines?: boolean; - /** @deprecated 数据范围仅存在于导出会话,不再写入模板 */ - exportRange?: ExportRangeConfig; - /** @deprecated 仅用于迁移旧数据 */ - dataSource?: ImportData; - /** @deprecated 模板与数据源已解耦,不再写入 */ - dataSourceMeta?: DataSourceMeta; } export type PaperType = 'A4' | 'A5' | 'A6' | 'custom';