Initial commit: Receipt Printer design docs and examples
This commit is contained in:
commit
1c4187cb29
125
PROJECT.md
Normal file
125
PROJECT.md
Normal file
@ -0,0 +1,125 @@
|
||||
# Receipt Printer 项目
|
||||
|
||||
## 项目概述
|
||||
一个基于 WiFi ESC/POS 协议的 80mm 小票打印系统,支持:
|
||||
- 可视化模板配置(YAML + 实时预览)
|
||||
- REST API 调用打印
|
||||
- 自动从模板提取数据 Schema
|
||||
- 多种排版元素(文本、表格、图片、条码等)
|
||||
|
||||
## 技术栈
|
||||
- **Runtime**: Bun
|
||||
- **Web 框架**: Hono
|
||||
- **模板引擎**: Mustache
|
||||
- **配置格式**: YAML
|
||||
|
||||
## 项目结构
|
||||
```
|
||||
receipt-printer/
|
||||
├── README.md # 项目说明
|
||||
├── package.json # 依赖配置
|
||||
├── docs/
|
||||
│ ├── design.md # 系统设计文档
|
||||
│ └── api.md # API 规范
|
||||
├── templates/examples/ # 示例模板
|
||||
│ ├── daily-todo.yaml # 每日待办
|
||||
│ ├── food-order-simple.yaml # 餐饮订单
|
||||
│ ├── fancy-receipt.yaml # 精致账单
|
||||
│ ├── ticket-list.yaml # Ticket 列表
|
||||
│ └── long-text.yaml # 长文阅读
|
||||
└── src/ # 源码目录(待开发)
|
||||
```
|
||||
|
||||
## 核心设计决策
|
||||
|
||||
### 1. 模板配置格式
|
||||
使用 YAML 而非 JSON:
|
||||
- 支持注释
|
||||
- 多行字符串更友好
|
||||
- 编辑器支持语法高亮
|
||||
|
||||
### 2. Schema 自动生成
|
||||
- 从模板中的 `{{变量}}` 自动提取
|
||||
- 无需手动维护数据结构
|
||||
- API 可 introspect 获取数据要求
|
||||
|
||||
### 3. 块类型系统
|
||||
| 类型 | 用途 |
|
||||
|------|------|
|
||||
| text | 普通文本(支持样式) |
|
||||
| row | 多列行布局 |
|
||||
| table | 多列表格 |
|
||||
| list | 循环渲染数组 |
|
||||
| divider | 分隔线 |
|
||||
| image | 图片 |
|
||||
| barcode | 条码/二维码 |
|
||||
| space | 空行 |
|
||||
|
||||
### 4. 样式支持
|
||||
- 对齐:left/center/right
|
||||
- 字体大小:small/normal/large/xlarge
|
||||
- 文本样式:bold/italic/underline
|
||||
- 间距:lineHeight/marginTop/marginBottom
|
||||
|
||||
## API 设计概览
|
||||
|
||||
```
|
||||
POST /api/print/:templateId # 打印
|
||||
GET /api/templates # 列出模板
|
||||
GET /api/templates/:id # 获取模板
|
||||
POST /api/templates # 创建模板
|
||||
PUT /api/templates/:id # 更新模板
|
||||
DELETE /api/templates/:id # 删除模板
|
||||
GET /api/templates/:id/schema # 获取数据 Schema
|
||||
GET /api/jobs/:id # 查询任务状态
|
||||
GET /api/printer/status # 打印机状态
|
||||
POST /api/printer/test # 打印测试页
|
||||
```
|
||||
|
||||
## 下一步开发计划
|
||||
|
||||
1. **核心引擎**
|
||||
- YAML 模板解析
|
||||
- Mustache 数据绑定
|
||||
- ESC/POS 指令生成
|
||||
|
||||
2. **打印驱动**
|
||||
- WiFi 打印机连接
|
||||
- 指令发送队列
|
||||
- 状态轮询
|
||||
|
||||
3. **Web 服务**
|
||||
- Hono HTTP 服务
|
||||
- REST API 实现
|
||||
- 静态文件服务
|
||||
|
||||
4. **配置界面**
|
||||
- YAML 编辑器(CodeMirror)
|
||||
- 实时预览(HTML 模拟)
|
||||
- 数据模拟器
|
||||
|
||||
5. **示例与文档**
|
||||
- 更多模板示例
|
||||
- API 使用示例
|
||||
- 部署指南
|
||||
|
||||
## 示例模板使用
|
||||
|
||||
```bash
|
||||
# 打印每日待办
|
||||
curl -X POST http://localhost:3000/api/print/daily-todo \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"data": {
|
||||
"date": "2025-02-12",
|
||||
"tasks": [
|
||||
{"status": "☐", "title": "修复 Bug #123"},
|
||||
{"status": "☑", "title": "代码审查"}
|
||||
],
|
||||
"completedCount": 1
|
||||
}
|
||||
}'
|
||||
|
||||
# 获取模板 Schema
|
||||
curl http://localhost:3000/api/print/daily-todo/schema
|
||||
```
|
||||
67
README.md
Normal file
67
README.md
Normal file
@ -0,0 +1,67 @@
|
||||
# Receipt Printer - 80mm 小票打印系统
|
||||
|
||||
基于 WiFi ESC/POS 的 80mm 小票打印系统,支持可视化模板配置和 REST API 调用。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- 🎨 **可视化模板配置** - Web 界面实时编辑和预览
|
||||
- 📝 **YAML/JSON 模板** - 声明式排版配置
|
||||
- 🔌 **REST API** - 通过 HTTP 调用打印
|
||||
- 📐 **自动 Schema 提取** - 从模板变量自动生成数据契约
|
||||
- 🖨️ **WiFi ESC/POS** - 支持主流 80mm 热敏打印机
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **后端**: Bun + Hono (轻量高性能)
|
||||
- **模板引擎**: 自研 YAML → ESC/POS 转换器
|
||||
- **前端**: 原生 HTML/JS (轻量化)
|
||||
- **打印机协议**: ESC/POS over TCP
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
├── docs/ # 设计文档
|
||||
├── templates/ # 模板文件
|
||||
├── src/ # 源码
|
||||
│ ├── server.ts # HTTP 服务
|
||||
│ ├── printer.ts # 打印机驱动
|
||||
│ ├── template.ts # 模板引擎
|
||||
│ └── static/ # Web 界面
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
```bash
|
||||
# 安装依赖
|
||||
bun install
|
||||
|
||||
# 配置打印机
|
||||
bun run config
|
||||
|
||||
# 启动服务
|
||||
bun run start
|
||||
|
||||
# 打开配置页面
|
||||
open http://localhost:3000
|
||||
```
|
||||
|
||||
## API 示例
|
||||
|
||||
```bash
|
||||
# 打印模板
|
||||
curl -X POST http://localhost:3000/api/print/daily-todo \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"data": {
|
||||
"date": "2025-02-12",
|
||||
"tasks": [
|
||||
{"status": "☐", "title": "修复 Bug #123"}
|
||||
]
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
425
docs/api.md
Normal file
425
docs/api.md
Normal file
@ -0,0 +1,425 @@
|
||||
# API 规范
|
||||
|
||||
## 基础信息
|
||||
|
||||
```
|
||||
Base URL: http://localhost:3000/api
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
## 端点列表
|
||||
|
||||
### 1. 打印
|
||||
|
||||
#### 执行打印
|
||||
|
||||
```
|
||||
POST /print/:templateId
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"date": "2025-02-12",
|
||||
"tasks": [
|
||||
{"status": "☐", "title": "修复 Bug #123"},
|
||||
{"status": "☑", "title": "代码审查"}
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"copies": 1,
|
||||
"cutPaper": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**参数说明:**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `data` | object | 是 | 模板数据,结构由模板 Schema 定义 |
|
||||
| `options.copies` | number | 否 | 打印份数,默认 1 |
|
||||
| `options.cutPaper` | boolean | 否 | 是否切纸,默认 true |
|
||||
|
||||
**成功响应 (200):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"jobId": "job_abc123",
|
||||
"status": "queued",
|
||||
"estimatedTime": "5s"
|
||||
}
|
||||
```
|
||||
|
||||
**错误响应 (400/404/500):**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "数据验证失败",
|
||||
"details": [
|
||||
"tasks: 必填字段缺失"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**错误码说明:**
|
||||
|
||||
| 错误码 | HTTP 状态 | 说明 |
|
||||
|--------|-----------|------|
|
||||
| `TEMPLATE_NOT_FOUND` | 404 | 模板不存在 |
|
||||
| `VALIDATION_ERROR` | 400 | 数据验证失败 |
|
||||
| `PRINTER_OFFLINE` | 503 | 打印机离线 |
|
||||
| `TEMPLATE_RENDER_ERROR` | 500 | 模板渲染失败 |
|
||||
| `PRINT_FAILED` | 500 | 打印失败 |
|
||||
|
||||
---
|
||||
|
||||
### 2. 模板管理
|
||||
|
||||
#### 列出所有模板
|
||||
|
||||
```
|
||||
GET /templates
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"templates": [
|
||||
{
|
||||
"id": "daily-todo",
|
||||
"name": "每日待办",
|
||||
"description": "打印今日待办事项列表",
|
||||
"updatedAt": "2025-02-12T07:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 获取单个模板
|
||||
|
||||
```
|
||||
GET /templates/:id
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"template": {
|
||||
"id": "daily-todo",
|
||||
"name": "每日待办",
|
||||
"config": { /* 完整模板配置 */ }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 创建模板
|
||||
|
||||
```
|
||||
POST /templates
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "food-order",
|
||||
"name": "餐饮订单",
|
||||
"description": "打印餐饮订单小票",
|
||||
"config": {
|
||||
"name": "餐饮订单",
|
||||
"width": "80mm",
|
||||
"blocks": [ /* 块定义 */ ]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"template": {
|
||||
"id": "food-order",
|
||||
"name": "餐饮订单",
|
||||
"createdAt": "2025-02-12T07:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新模板
|
||||
|
||||
```
|
||||
PUT /templates/:id
|
||||
```
|
||||
|
||||
**请求体:**(同创建,可部分更新)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "新的名称",
|
||||
"config": { /* 新配置 */ }
|
||||
}
|
||||
```
|
||||
|
||||
#### 删除模板
|
||||
|
||||
```
|
||||
DELETE /templates/:id
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "模板已删除"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 模板 Schema
|
||||
|
||||
#### 获取模板数据 Schema
|
||||
|
||||
```
|
||||
GET /templates/:id/schema
|
||||
```
|
||||
|
||||
用于获取模板所需的数据结构和示例。
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"templateId": "food-order",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["orderId", "items"],
|
||||
"properties": {
|
||||
"orderType": {
|
||||
"type": "string",
|
||||
"description": "订单类型"
|
||||
},
|
||||
"orderId": {
|
||||
"type": "string",
|
||||
"description": "订单编号"
|
||||
},
|
||||
"items": {
|
||||
"type": "array",
|
||||
"description": "商品列表",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"quantity": { "type": "number" },
|
||||
"price": { "type": "number" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
"orderType": "外带",
|
||||
"orderId": "35205-1",
|
||||
"items": [
|
||||
{ "name": "双层吉士堡", "quantity": 1, "price": 22 }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 打印任务
|
||||
|
||||
#### 查询任务状态
|
||||
|
||||
```
|
||||
GET /jobs/:jobId
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"job": {
|
||||
"id": "job_abc123",
|
||||
"templateId": "daily-todo",
|
||||
"status": "completed",
|
||||
"createdAt": "2025-02-12T07:00:00Z",
|
||||
"startedAt": "2025-02-12T07:00:01Z",
|
||||
"completedAt": "2025-02-12T07:00:05Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**状态值:**
|
||||
- `queued` - 排队中
|
||||
- `printing` - 打印中
|
||||
- `completed` - 完成
|
||||
- `failed` - 失败
|
||||
- `cancelled` - 已取消
|
||||
|
||||
#### 列出最近任务
|
||||
|
||||
```
|
||||
GET /jobs?limit=10&status=completed
|
||||
```
|
||||
|
||||
**查询参数:**
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `limit` | number | 返回数量,默认 10 |
|
||||
| `status` | string | 按状态筛选 |
|
||||
| `templateId` | string | 按模板筛选 |
|
||||
|
||||
#### 取消任务
|
||||
|
||||
```
|
||||
DELETE /jobs/:jobId
|
||||
```
|
||||
|
||||
仅可取消 `queued` 状态的任务。
|
||||
|
||||
---
|
||||
|
||||
### 5. 打印机管理
|
||||
|
||||
#### 获取打印机状态
|
||||
|
||||
```
|
||||
GET /printer/status
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"printer": {
|
||||
"name": "XP-80C",
|
||||
"ip": "192.168.1.100",
|
||||
"port": 9100,
|
||||
"status": "online",
|
||||
"paperStatus": "ok",
|
||||
"coverStatus": "closed",
|
||||
"queueLength": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**状态值:**
|
||||
- `online` - 在线
|
||||
- `offline` - 离线
|
||||
- `error` - 错误(查看 `errorCode`)
|
||||
|
||||
#### 打印测试页
|
||||
|
||||
```
|
||||
POST /printer/test
|
||||
```
|
||||
|
||||
打印一张测试页,用于检查连接和打印质量。
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"jobId": "job_test_123"
|
||||
}
|
||||
```
|
||||
|
||||
#### 更新打印机配置
|
||||
|
||||
```
|
||||
POST /printer/config
|
||||
```
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"ip": "192.168.1.100",
|
||||
"port": 9100,
|
||||
"width": 80
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 认证(可选)
|
||||
|
||||
如需简单保护,在配置文件中启用 API Key:
|
||||
|
||||
```yaml
|
||||
security:
|
||||
enabled: true
|
||||
apiKey: "your-secret-key"
|
||||
```
|
||||
|
||||
启用后,所有请求需携带 Header:
|
||||
|
||||
```
|
||||
X-API-Key: your-secret-key
|
||||
```
|
||||
|
||||
未提供或错误的 Key 返回:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "UNAUTHORIZED",
|
||||
"message": "无效的 API Key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## WebSocket 实时状态(可选)
|
||||
|
||||
如需实时推送打印状态,可连接 WebSocket:
|
||||
|
||||
```
|
||||
ws://localhost:3000/ws
|
||||
```
|
||||
|
||||
**事件类型:**
|
||||
|
||||
```json
|
||||
// 任务状态更新
|
||||
{
|
||||
"type": "jobUpdate",
|
||||
"data": {
|
||||
"jobId": "job_abc123",
|
||||
"status": "printing"
|
||||
}
|
||||
}
|
||||
|
||||
// 打印机状态更新
|
||||
{
|
||||
"type": "printerUpdate",
|
||||
"data": {
|
||||
"status": "online",
|
||||
"paperStatus": "low"
|
||||
}
|
||||
}
|
||||
```
|
||||
389
docs/design.md
Normal file
389
docs/design.md
Normal file
@ -0,0 +1,389 @@
|
||||
# 系统设计文档
|
||||
|
||||
## 1. 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 小票打印系统 (本地服务) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │
|
||||
│ │ Web 配置页 │◄──►│ 模板引擎 │◄──►│ 打印机 │ │
|
||||
│ │ (Vue/React) │ │ (渲染/ESC-POS)│ │ (WiFi ESC)│ │
|
||||
│ └──────────────┘ └──────────────┘ └───────────┘ │
|
||||
│ ▲ │
|
||||
│ │ REST API │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ 外部调用端 │ ← 脚本、IFTTT、快捷指令等 │
|
||||
│ │ (curl/HTTP) │ │
|
||||
│ └──────────────┘ │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 数据流
|
||||
|
||||
1. **配置阶段**: Web 页创建/编辑模板 → 保存为 YAML 配置
|
||||
2. **调用阶段**: POST 数据到 `/api/print/:templateId` → 模板引擎渲染 → 生成 ESC/POS 指令 → 发送到打印机
|
||||
|
||||
## 2. 模板系统
|
||||
|
||||
### 2.1 模板结构
|
||||
|
||||
```yaml
|
||||
# 模板元信息
|
||||
name: "每日待办"
|
||||
id: "daily-todo"
|
||||
width: 80mm
|
||||
description: "打印今日待办事项列表"
|
||||
|
||||
# 页面设置
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
# 默认样式
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.0
|
||||
marginBottom: 0
|
||||
|
||||
# 内容块
|
||||
blocks:
|
||||
# 文本块
|
||||
- type: text
|
||||
content: "📋 {{date}} 待办"
|
||||
align: center
|
||||
fontSize: large
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 列表循环
|
||||
- type: list
|
||||
data: "{{tasks}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: "[{{status}}] {{title}}"
|
||||
fontSize: normal
|
||||
marginBottom: 0
|
||||
|
||||
# 多列行
|
||||
- type: row
|
||||
marginBottom: 1
|
||||
columns:
|
||||
- content: "{{leftText}}"
|
||||
align: left
|
||||
width: 50%
|
||||
bold: true
|
||||
- content: "{{rightText}}"
|
||||
align: right
|
||||
width: 50%
|
||||
|
||||
# 表格
|
||||
- type: table
|
||||
marginBottom: 1
|
||||
columns:
|
||||
- header: "商品"
|
||||
align: left
|
||||
width: 50%
|
||||
- header: "数量"
|
||||
align: center
|
||||
width: 25%
|
||||
- header: "金额"
|
||||
align: right
|
||||
width: 25%
|
||||
data: "{{items}}"
|
||||
|
||||
# 图片
|
||||
- type: image
|
||||
src: "{{logoUrl}}"
|
||||
align: center
|
||||
maxWidth: 200
|
||||
|
||||
# 条码
|
||||
- type: barcode
|
||||
format: "CODE128" # CODE128 | QR | EAN13
|
||||
data: "{{orderId}}"
|
||||
align: center
|
||||
height: 64
|
||||
|
||||
# 空行
|
||||
- type: space
|
||||
lines: 2
|
||||
```
|
||||
|
||||
### 2.2 支持的块类型
|
||||
|
||||
| 类型 | 用途 | 关键属性 |
|
||||
|------|------|---------|
|
||||
| `text` | 普通文本 | `content`, `align`, `fontSize`, `bold`, `italic`, `underline`, `lineHeight`, `marginTop`, `marginBottom` |
|
||||
| `list` | 循环渲染数组 | `data`, `itemTemplate` |
|
||||
| `table` | 多列表格 | `columns`, `data` |
|
||||
| `row` | 多列行布局 | `columns` |
|
||||
| `divider` | 分隔线 | `char` |
|
||||
| `image` | 图片/Logo | `src`, `align`, `maxWidth` |
|
||||
| `barcode` | 条码/二维码 | `format`, `data`, `height` |
|
||||
| `space` | 空行 | `lines` |
|
||||
|
||||
### 2.3 样式属性
|
||||
|
||||
```yaml
|
||||
# 对齐
|
||||
align: left | center | right
|
||||
|
||||
# 字体大小
|
||||
fontSize: small | normal | large | xlarge
|
||||
|
||||
# 文本样式
|
||||
bold: true | false
|
||||
italic: true | false # 打印机支持时
|
||||
underline: true | false
|
||||
|
||||
# 间距
|
||||
lineHeight: 1.0 # 行高倍数
|
||||
marginTop: 0 # 上方间距(行数)
|
||||
marginBottom: 0 # 下方间距(行数)
|
||||
|
||||
# 列宽(仅 row/table)
|
||||
width: 50% | 20 # 百分比或字符数
|
||||
```
|
||||
|
||||
### 2.4 数据绑定
|
||||
|
||||
使用 Mustache 语法 `{{variable}}`:
|
||||
|
||||
```yaml
|
||||
content: "订单编号: {{orderId}}"
|
||||
data: "{{items}}" # 列表数据源
|
||||
```
|
||||
|
||||
支持嵌套访问:
|
||||
```yaml
|
||||
content: "{{customer.name}} - {{customer.phone}}"
|
||||
```
|
||||
|
||||
### 2.5 Schema 自动生成
|
||||
|
||||
系统从模板的 `{{变量}}` 自动提取数据结构:
|
||||
|
||||
```yaml
|
||||
# 模板示例
|
||||
blocks:
|
||||
- content: "{{orderType}} - {{orderId}}"
|
||||
- data: "{{items}}"
|
||||
```
|
||||
|
||||
自动生成:
|
||||
```json
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"orderType": { "type": "string" },
|
||||
"orderId": { "type": "string" },
|
||||
"items": { "type": "array" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
通过 `GET /api/templates/:id/schema` 获取 Schema 和示例数据。
|
||||
|
||||
## 3. Web 配置页
|
||||
|
||||
### 3.1 核心功能
|
||||
|
||||
1. **模板编辑器**
|
||||
- 左侧:YAML 代码编辑(语法高亮 + 错误提示)
|
||||
- 右侧:实时预览(HTML 模拟小票效果)
|
||||
|
||||
2. **块类型选择器**
|
||||
- 快捷插入按钮(文本、表格、图片、分隔线等)
|
||||
- 点击后插入模板代码片段
|
||||
|
||||
3. **数据模拟器**
|
||||
- 基于 Schema 生成表单
|
||||
- 输入模拟数据测试渲染
|
||||
- 保存测试数据集
|
||||
|
||||
4. **打印机设置**
|
||||
- IP 地址配置
|
||||
- 打印测试页
|
||||
- 打印队列状态
|
||||
|
||||
### 3.2 界面布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ [Logo] 小票打印机 [打印机状态 ●] │
|
||||
├────────────┬────────────────────────┬───────────────────┤
|
||||
│ │ │ │
|
||||
│ 模板列表 │ YAML 编辑器 │ 实时预览 │
|
||||
│ ─────── │ │ │
|
||||
│ daily-todo│ │ ┌─────────┐ │
|
||||
│ food-order│ - type: text │ │ 📋 待办 │ │
|
||||
│ ticket │ content: "..." │ │ ======= │ │
|
||||
│ │ │ │ [☐] ... │ │
|
||||
│ [+ 新建] │ │ └─────────┘ │
|
||||
│ │ │ │
|
||||
├────────────┴────────────────────────┴───────────────────┤
|
||||
│ 块类型: [文本] [表格] [图片] [分隔线] [条码] [空行] │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 4. 设计参考
|
||||
|
||||
### 4.1 餐饮小票(麦当劳风格)
|
||||
|
||||
```yaml
|
||||
blocks:
|
||||
# 顶部双栏
|
||||
- type: row
|
||||
columns:
|
||||
- content: "外带"
|
||||
align: left
|
||||
- content: "订单编号: {{orderId}}"
|
||||
align: right
|
||||
- type: divider
|
||||
char: "-"
|
||||
|
||||
# 商品列表(带缩进备注)
|
||||
- type: list
|
||||
data: "{{items}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: "{{quantity}} {{name}}"
|
||||
- type: list
|
||||
data: "{{notes}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: " {{.}}"
|
||||
fontSize: small
|
||||
- type: divider
|
||||
char: "-"
|
||||
|
||||
# 取餐号(超大)
|
||||
- type: text
|
||||
content: "取餐柜取餐"
|
||||
align: center
|
||||
marginBottom: 0
|
||||
- type: text
|
||||
content: "{{pickupNumber}}"
|
||||
align: center
|
||||
fontSize: xlarge
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 条码
|
||||
- type: barcode
|
||||
format: "CODE128"
|
||||
data: "{{pickupNumber}}"
|
||||
align: center
|
||||
|
||||
# 时间戳
|
||||
- type: row
|
||||
columns:
|
||||
- content: "MOBILE"
|
||||
align: left
|
||||
fontSize: small
|
||||
- content: "{{timestamp}}"
|
||||
align: right
|
||||
fontSize: small
|
||||
```
|
||||
|
||||
### 4.2 精致小票(铁板烧风格)
|
||||
|
||||
```yaml
|
||||
blocks:
|
||||
# Logo 图片
|
||||
- type: image
|
||||
src: "{{logoUrl}}"
|
||||
align: center
|
||||
maxWidth: 150
|
||||
marginBottom: 0
|
||||
|
||||
# 英文小字
|
||||
- type: text
|
||||
content: "THE SOURCE OF THIS DESIGN MATERIAL IS TEPPANYAKI DESIGN"
|
||||
align: center
|
||||
fontSize: small
|
||||
|
||||
# 中文标题
|
||||
- type: text
|
||||
content: "铁板烧设计"
|
||||
align: center
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 装饰分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 表头
|
||||
- type: row
|
||||
columns:
|
||||
- content: "商品名称"
|
||||
align: left
|
||||
bold: true
|
||||
width: 50%
|
||||
- content: "数量"
|
||||
align: center
|
||||
bold: true
|
||||
width: 25%
|
||||
- content: "金额"
|
||||
align: right
|
||||
bold: true
|
||||
width: 25%
|
||||
|
||||
# 表格数据
|
||||
- type: table
|
||||
columns:
|
||||
- align: left
|
||||
width: 50%
|
||||
- align: center
|
||||
width: 25%
|
||||
- align: right
|
||||
width: 25%
|
||||
data: "{{items}}"
|
||||
|
||||
# 汇总
|
||||
- type: divider
|
||||
char: "="
|
||||
- type: row
|
||||
columns:
|
||||
- content: "小计"
|
||||
align: left
|
||||
- content: "¥{{subtotal}}"
|
||||
align: right
|
||||
```
|
||||
|
||||
## 5. 技术决策
|
||||
|
||||
### 5.1 为什么选择 YAML?
|
||||
|
||||
- 比 JSON 更易手写(支持注释、多行字符串)
|
||||
- 比纯代码更声明式(专注"是什么"而非"怎么做")
|
||||
- 主流编辑器支持语法高亮和验证
|
||||
|
||||
### 5.2 为什么自动生成 Schema?
|
||||
|
||||
- 降低用户负担(无需学习 JSON Schema)
|
||||
- 保持数据和模板同步(一处修改,处处生效)
|
||||
- 自动生成文档和表单
|
||||
|
||||
### 5.3 为什么用 HTML 预览?
|
||||
|
||||
- 无需连接真实打印机即可调试
|
||||
- 跨平台、易实现
|
||||
- 样式可精确还原(CSS)
|
||||
|
||||
## 6. 待决策事项
|
||||
|
||||
- [ ] 是否支持模板继承/复用?
|
||||
- [ ] 是否支持条件渲染(if/else)?
|
||||
- [ ] 是否支持自定义字体?
|
||||
- [ ] 是否支持打印历史记录?
|
||||
- [ ] 是否支持多打印机管理?
|
||||
30
package.json
Normal file
30
package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "receipt-printer",
|
||||
"version": "0.1.0",
|
||||
"description": "80mm 小票打印系统 - 支持可视化模板配置和 REST API",
|
||||
"main": "src/server.ts",
|
||||
"scripts": {
|
||||
"start": "bun run src/server.ts",
|
||||
"dev": "bun run --watch src/server.ts",
|
||||
"build": "bun build src/server.ts --outdir dist",
|
||||
"test": "bun test"
|
||||
},
|
||||
"dependencies": {
|
||||
"hono": "^4.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"mustache": "^4.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"bun-types": "latest"
|
||||
},
|
||||
"keywords": [
|
||||
"receipt",
|
||||
"printer",
|
||||
"esc-pos",
|
||||
"thermal-printer",
|
||||
"80mm"
|
||||
],
|
||||
"license": "MIT"
|
||||
}
|
||||
58
templates/examples/daily-todo.yaml
Normal file
58
templates/examples/daily-todo.yaml
Normal file
@ -0,0 +1,58 @@
|
||||
# 每日待办模板
|
||||
|
||||
name: "每日待办"
|
||||
id: "daily-todo"
|
||||
width: 80mm
|
||||
description: "打印今日待办事项列表"
|
||||
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.0
|
||||
marginBottom: 0
|
||||
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
blocks:
|
||||
# 标题
|
||||
- type: text
|
||||
content: "📋 {{date}} 待办"
|
||||
align: center
|
||||
fontSize: large
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 待办列表
|
||||
- type: list
|
||||
data: "{{tasks}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: "[{{status}}] {{title}}"
|
||||
fontSize: normal
|
||||
marginBottom: 0
|
||||
|
||||
# 空行
|
||||
- type: space
|
||||
lines: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "-"
|
||||
marginBottom: 1
|
||||
|
||||
# 统计
|
||||
- type: text
|
||||
content: "共 {{tasks.length}} 项待办"
|
||||
align: right
|
||||
fontSize: small
|
||||
|
||||
- type: text
|
||||
content: "已完成 {{completedCount}} / {{tasks.length}}"
|
||||
align: right
|
||||
fontSize: small
|
||||
190
templates/examples/fancy-receipt.yaml
Normal file
190
templates/examples/fancy-receipt.yaml
Normal file
@ -0,0 +1,190 @@
|
||||
# 精致小票模板(铁板烧风格)
|
||||
|
||||
name: "精致账单"
|
||||
id: "fancy-receipt"
|
||||
width: 80mm
|
||||
description: "带有 Logo 和详细商品信息的精致账单"
|
||||
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.0
|
||||
marginBottom: 0
|
||||
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
blocks:
|
||||
# Logo 图片
|
||||
- type: image
|
||||
src: "{{logoUrl}}"
|
||||
align: center
|
||||
maxWidth: 150
|
||||
marginBottom: 0
|
||||
|
||||
# 英文标语
|
||||
- type: text
|
||||
content: "{{taglineEn}}"
|
||||
align: center
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
|
||||
# 中文店名
|
||||
- type: text
|
||||
content: "{{shopName}}"
|
||||
align: center
|
||||
fontSize: large
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 装饰分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 菜单标题
|
||||
- type: text
|
||||
content: "{{menuTitle}}"
|
||||
align: center
|
||||
fontSize: normal
|
||||
bold: true
|
||||
marginBottom: 0
|
||||
|
||||
# 时间
|
||||
- type: text
|
||||
content: "{{datetime}}"
|
||||
align: center
|
||||
fontSize: small
|
||||
marginBottom: 1
|
||||
|
||||
# 表头
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "商品名称"
|
||||
align: left
|
||||
bold: true
|
||||
width: 50%
|
||||
- content: "数量"
|
||||
align: center
|
||||
bold: true
|
||||
width: 25%
|
||||
- content: "金额"
|
||||
align: right
|
||||
bold: true
|
||||
width: 25%
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 0
|
||||
|
||||
# 商品表格
|
||||
- type: table
|
||||
data: "{{items}}"
|
||||
columns:
|
||||
- align: left
|
||||
width: 50%
|
||||
- align: center
|
||||
width: 25%
|
||||
- align: right
|
||||
width: 25%
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 0
|
||||
|
||||
# 汇总行
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "小计"
|
||||
align: left
|
||||
width: 50%
|
||||
- content: "{{totalQuantity}}"
|
||||
align: center
|
||||
width: 25%
|
||||
- content: "¥{{subtotal}}"
|
||||
align: right
|
||||
width: 25%
|
||||
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "税收({{taxRate}})"
|
||||
align: left
|
||||
width: 50%
|
||||
- content: ""
|
||||
align: center
|
||||
width: 25%
|
||||
- content: "¥{{tax}}"
|
||||
align: right
|
||||
width: 25%
|
||||
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "账单总额"
|
||||
align: left
|
||||
bold: true
|
||||
width: 50%
|
||||
- content: ""
|
||||
align: center
|
||||
width: 25%
|
||||
- content: "¥{{total}}"
|
||||
align: right
|
||||
bold: true
|
||||
width: 25%
|
||||
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "折扣"
|
||||
align: left
|
||||
width: 50%
|
||||
- content: ""
|
||||
align: center
|
||||
width: 25%
|
||||
- content: "{{discount}}"
|
||||
align: right
|
||||
width: 25%
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 签名区
|
||||
- type: text
|
||||
content: "签名"
|
||||
align: left
|
||||
marginBottom: 1
|
||||
|
||||
# 装饰
|
||||
- type: text
|
||||
content: "{{signature}}"
|
||||
align: center
|
||||
fontSize: large
|
||||
italic: true
|
||||
marginBottom: 1
|
||||
|
||||
# 底部分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 0
|
||||
|
||||
# 版权信息
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "★"
|
||||
align: left
|
||||
width: 10%
|
||||
- content: "{{copyright}}"
|
||||
align: center
|
||||
fontSize: small
|
||||
width: 80%
|
||||
- content: "★"
|
||||
align: right
|
||||
width: 10%
|
||||
88
templates/examples/food-order-simple.yaml
Normal file
88
templates/examples/food-order-simple.yaml
Normal file
@ -0,0 +1,88 @@
|
||||
# 餐饮订单模板(麦当劳风格)
|
||||
|
||||
name: "餐饮订单"
|
||||
id: "food-order-simple"
|
||||
width: 80mm
|
||||
description: "外带餐饮订单小票"
|
||||
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.0
|
||||
marginBottom: 0
|
||||
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
blocks:
|
||||
# 顶部信息
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "{{orderType}}"
|
||||
align: left
|
||||
fontSize: normal
|
||||
- content: "订单编号: {{orderId}}"
|
||||
align: right
|
||||
fontSize: normal
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "-"
|
||||
marginBottom: 1
|
||||
|
||||
# 商品列表(带备注)
|
||||
- type: list
|
||||
data: "{{items}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: "{{quantity}} {{name}}"
|
||||
marginBottom: 0
|
||||
- type: list
|
||||
data: "{{notes}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: " 不要 {{.}}"
|
||||
fontSize: small
|
||||
- type: divider
|
||||
char: "-"
|
||||
marginBottom: 0
|
||||
|
||||
# 取餐信息
|
||||
- type: text
|
||||
content: "取餐柜取餐"
|
||||
align: center
|
||||
fontSize: normal
|
||||
marginTop: 1
|
||||
marginBottom: 0
|
||||
|
||||
- type: text
|
||||
content: "{{pickupNumber}}"
|
||||
align: center
|
||||
fontSize: xlarge
|
||||
bold: true
|
||||
marginBottom: 1
|
||||
|
||||
# 条码
|
||||
- type: barcode
|
||||
format: "CODE128"
|
||||
data: "{{pickupNumber}}"
|
||||
align: center
|
||||
height: 64
|
||||
marginBottom: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "-"
|
||||
marginBottom: 0
|
||||
|
||||
# 底部信息
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "MOBILE"
|
||||
align: left
|
||||
fontSize: small
|
||||
- content: "{{timestamp}}"
|
||||
align: right
|
||||
fontSize: small
|
||||
75
templates/examples/long-text.yaml
Normal file
75
templates/examples/long-text.yaml
Normal file
@ -0,0 +1,75 @@
|
||||
# 长文阅读模板
|
||||
|
||||
name: "长文阅读"
|
||||
id: "long-text"
|
||||
width: 80mm
|
||||
description: "打印长篇文章或阅读材料"
|
||||
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.2
|
||||
marginBottom: 0
|
||||
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
blocks:
|
||||
# 标题
|
||||
- type: text
|
||||
content: "{{title}}"
|
||||
align: center
|
||||
fontSize: xlarge
|
||||
bold: true
|
||||
marginBottom: 0
|
||||
|
||||
# 作者
|
||||
- type: text
|
||||
content: "作者: {{author}}"
|
||||
align: center
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
|
||||
# 日期
|
||||
- type: text
|
||||
content: "{{publishDate}}"
|
||||
align: center
|
||||
fontSize: small
|
||||
marginBottom: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 正文段落
|
||||
- type: list
|
||||
data: "{{paragraphs}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: "{{content}}"
|
||||
align: left
|
||||
lineHeight: 1.3
|
||||
marginBottom: 1
|
||||
|
||||
# 空行
|
||||
- type: space
|
||||
lines: 2
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# 阅读统计
|
||||
- type: text
|
||||
content: "共 {{wordCount}} 字 | {{paragraphs.length}} 段落"
|
||||
align: right
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
|
||||
- type: text
|
||||
content: "打印于 {{printDate}}"
|
||||
align: right
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
107
templates/examples/ticket-list.yaml
Normal file
107
templates/examples/ticket-list.yaml
Normal file
@ -0,0 +1,107 @@
|
||||
# Ticket/Issue 列表模板
|
||||
|
||||
name: "Ticket 列表"
|
||||
id: "ticket-list"
|
||||
width: 80mm
|
||||
description: "打印 GitHub/Jira 等 Ticket 或 Issue 列表"
|
||||
|
||||
defaults:
|
||||
fontSize: normal
|
||||
lineHeight: 1.0
|
||||
marginBottom: 0
|
||||
|
||||
page:
|
||||
marginTop: 2
|
||||
marginBottom: 3
|
||||
|
||||
blocks:
|
||||
# 标题
|
||||
- type: text
|
||||
content: "🎫 {{project}} Tickets"
|
||||
align: center
|
||||
fontSize: large
|
||||
bold: true
|
||||
marginBottom: 0
|
||||
|
||||
# 副标题
|
||||
- type: text
|
||||
content: "{{filter}} - 共 {{tickets.length}} 条"
|
||||
align: center
|
||||
fontSize: small
|
||||
marginBottom: 1
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 1
|
||||
|
||||
# Ticket 列表
|
||||
- type: list
|
||||
data: "{{tickets}}"
|
||||
itemTemplate:
|
||||
# Ticket 编号和状态
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "#{{id}}"
|
||||
align: left
|
||||
bold: true
|
||||
width: 30%
|
||||
- content: "[{{status}}]"
|
||||
align: center
|
||||
width: 35%
|
||||
- content: "{{priority}}"
|
||||
align: right
|
||||
width: 35%
|
||||
|
||||
# 标题
|
||||
- type: text
|
||||
content: "{{title}}"
|
||||
align: left
|
||||
marginBottom: 0
|
||||
|
||||
# 标签
|
||||
- type: text
|
||||
content: "标签: {{labels.join ', '}}"
|
||||
align: left
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
|
||||
# 负责人和时间
|
||||
- type: row
|
||||
marginBottom: 0
|
||||
columns:
|
||||
- content: "👤 {{assignee}}"
|
||||
align: left
|
||||
fontSize: small
|
||||
width: 50%
|
||||
- content: "📅 {{dueDate}}"
|
||||
align: right
|
||||
fontSize: small
|
||||
width: 50%
|
||||
|
||||
# 分隔线
|
||||
- type: divider
|
||||
char: "-"
|
||||
marginBottom: 1
|
||||
|
||||
# 底部统计
|
||||
- type: divider
|
||||
char: "="
|
||||
marginBottom: 0
|
||||
|
||||
- type: text
|
||||
content: "按状态统计:"
|
||||
align: left
|
||||
fontSize: small
|
||||
bold: true
|
||||
marginBottom: 0
|
||||
|
||||
- type: list
|
||||
data: "{{statusStats}}"
|
||||
itemTemplate:
|
||||
- type: text
|
||||
content: " {{status}}: {{count}}"
|
||||
align: left
|
||||
fontSize: small
|
||||
marginBottom: 0
|
||||
Loading…
x
Reference in New Issue
Block a user