Files
F9Web/单据设计与打印调用帮助文档.md
T

16 KiB
Raw Blame History

单据设计与打印调用帮助文档

适用系统:F9 智慧缫丝系统(MES) 本文档说明两类能力:

  1. 单据页(表单录入界面):全屏单据风格的新增/编辑界面,如何设计与接入
  2. 打印(单据套打):打印模板如何设计、前端如何调用打印

一、总体架构

webVue3 MES 前端,端口 5173
 ├─ 列表页  CrudPage.vue(通用列表 + 操作列按钮)
 │    ├─ "新增/编辑" → BILL_MAP 路由 → 全屏单据页(BillPage.vue 或专用 xxx-bill.vue
 │    ├─ "打印"      → PRINT_MAP 模板名 → 后端查模板 id → window.open(openprint 打印页)
 │    └─ "审批"      → WORKFLOW_MAP 发起流程
 │
 ├─ 单据页
 │    ├─ web/src/components/BillPage.vue        (配置驱动通用单据页,路由 /bill/:table/:id?
 │    └─ web/src/views/<模块>/<xxx>-bill.vue    (专用全屏单据页,原料 4 表为代表)
 │
server.NET F9MES 后端,端口 5136
 ├─ /api/print/templates      模板 CRUD(存 Common_PrintTemplate / RepCenter_ReportDesign
 ├─ /api/print/data/{表}/{id} 打印数据(关联名称 + 枚举文本自动补全)
 └─ 通用 CRUD /api/xxx          单据页表单数据来源(meta 驱动)

openprint(打印渲染服务,端口 5227)
 ├─ 打印设计器(图形化设计模板,可导 JSON)
 ├─ 打印页 PrintPage.vue:解析 URL 参数 → 拉模板+数据 → 渲染 → 调系统打印对话框
 └─ 静默打印 createHeadless.tsiframe + srcdoc + window.print()

二、设计单据页(表单录入界面)

系统提供两种单据页方式,推荐优先用「配置驱动通用单据页」,只有字段交互非常特殊的表才写专用 vue。

方式 A:配置驱动通用单据页(推荐,全系统 42 张表已接入)

只需改两个配置文件,无需新建任何 vue 文件

第 1 步:web/src/config/bill-configs.js 添加布局配置

Process_Trial 为例:

export const BILL_CONFIGS = {
  Process_Trial: {
    title: '样茧试缫单',        // 页面标题(单据名称)
    codeField: 'billNo',         // 单号字段(自动生成编号、顶部展示)
    statusField: 'status',       // 状态字段(可选,自动在顶部显示状态标签)
    groups: [
      { title: '试缫信息', fields: ['billNo', 'trialType', 'zhuangkouId', 'processZhuangkouId', 'trialDate', 'trialMan'] },
      { title: '试缫数据', fields: [{ name: 'dataJson', span: 16 }] }  // 长文本字段占 2/3 行
    ]
  },
  // ... 其他表
}

字段 span 规则(每行 12 栅格,即 24 宽):

span 占宽 适用
8(默认) 1/3 行(每行 3 列) 常规字段
16 2/3 行 较长的文本字段
24 整行 备注、JSON 明细等长内容

字段名统一用 camelCase(与后端 meta/data 接口一致)。

第 2 步:web/src/config/table-map.jsBILL_MAP 加一行

export const BILL_MAP = {
  'RawMaterial/outstock': '/rawmaterial/outstock-bill',  // 专用单据页(方式 B
  'Process/trial': '/bill/Process_Trial',                 // 通用单据页:/bill/<表名>
  // ...
}

路由 /bill/:table/:id? 已在 web/src/router/index.js 注册,无需再配。

接入后自动获得的能力:

  • 列表页「新增」→ 打开 /bill/<表名>(无 id),「编辑」→ 打开 /bill/<表名>/<id>
  • 表单字段控件类型由后端 meta 自动判断(输入框/数字/日期/下拉/多行文本)
  • 关联字段(*Id)自动拉取 refs 数据源生成下拉框
  • statusField 自动在顶部显示状态标签;codeField 新增时自动 gencode
  • 保存逻辑通用:剔除系统字段 + 空值;新增成功后自动 router.replace 到带 id 路径

方式 B:专用全屏单据页(原料 4 表做法)

适用:字段有特殊联动、复杂校验、作废/审批等定制按钮的核心业务单据

参考文件:web/src/views/rawmaterial/outstock-bill.vue(最早先例)、zhuangkou-bill.vueinstock-bill.vueinspect-bill.vue

接入步骤:

  1. 新建 web/src/views/<模块>/<xxx>-bill.vue,按下方布局规范编写
  2. table-map.jsBILL_MAP 注册:'RawMaterial/outstock': '/rawmaterial/outstock-bill'
  3. router/index.js 注册静态路由(meta: { hidden: true } 不进菜单):
    {
      path: '/rawmaterial/outstock-bill/:id?',       // :id 可选:无 id 新增,带 id 编辑
      name: 'RawMaterialOutstockBill',
      component: () => import('@/views/rawmaterial/outstock-bill.vue'),
      meta: { title: '原料出库单', hidden: true }
    }
    
  4. 页面内处理:无 id 为新增(可用 gencode 生成单号),带 id 时加载数据走 crudUpdate

单据页布局规范(A、B 通用,全系统统一)

  • 全页 max-width: 1360px; padding: 18px 24px,横向充分扩展
  • 深色顶部工具条:返回按钮 + 页面标题 + 单据编号 + 状态标签(右上可有打印/作废等操作按钮)
  • 按语义分组为多张卡片,每行 3 列(el-col :span="8"),栅格 gutter: 36
  • 卡片 body padding: 30px 36px;表单项 margin-bottom: 28px;输入控件内边距加大
  • 底部居中操作栏(取消 / 保存)
  • 基础/简单表(warehouse/stock/team/machine 等台账类)不接单据页,用通用宽弹窗(CrudPage.vue 已按字段数自适应:≤6 字段 720px / ≤12 字段 1000px / >12 字段 1240px,全局类 .crud-dialog

三、设计打印模板

3.1 模板库与接口

模板存储在后端,表 Common_PrintTemplate(展示实体 RepCenter_ReportDesign),接口:

接口 说明
GET /api/print/templates 列表({ items, total },不含 content
GET /api/print/templates/{id} 详情(含 content,即模板 JSON 字符串)
POST /api/print/templates 创建({ name, content }
PUT /api/print/templates/{id} 全量更新
DELETE /api/print/templates/{id} 删除

鉴权复用 F9 JWTAuthorization: Bearer <token>)。注意:这些接口返回裸 JSON(非 ApiResult 信封)。

3.2 模板 JSON 结构

content 是一个 JSON 字符串,完整示例见 server/scripts/print-template-outstock.json(三等分原料出库单,纸张 210×99mm):

{
  "name": "三等分原料出库单",
  "content": {
    "version": "1.0",
    "document": {
      "type": "report",
      "page": {
        "width": 210, "height": 99, "unit": "mm",
        "orientation": "portrait",
        "margin": { "top": 8, "bottom": 8, "left": 8, "right": 8 }
      },
      "sections": [
        {
          "type": "body",
          "components": [
            {
              "id": "txt-title",
              "type": "text",
              "left": 0, "top": 0, "width": 194, "height": 10,
              "value": "原料出库单",
              "style": { "fontSize": 15, "fontWeight": "bold", "textAlign": "center" }
            }
          ]
        }
      ]
    }
  }
}

3.3 组件(components)类型

type 说明 关键属性
text 文本/字段值 value(固定文本)或 binding(绑定字段);stylefontSize/fontWeight/textAlign);format(格式化)
line 横线(分隔线/签字线) left/top/width/heightstroke(颜色,如 #333333

所有组件用毫米定位left / top / width / height

3.4 字段绑定与格式化

  • binding 格式:表名.字段名PascalCase),例如:
    • "RawMaterial_OutStock.BillNo" —— 出库单号
    • "RawMaterial_OutStock.OutWeight" —— 出库重量
    • "RawMaterial_OutStock.OutDate" —— 出库日期
  • 小数格式化:"format": { "kind": "decimal", "digits": 2 }
  • 日期格式化:"format": { "kind": "date", "pattern": "YYYY-MM-DD" }
  • 模板绑定字段与数据的大小写兼容:数据接口返回 camelCase + PascalCase 双别名,模板统一用 PascalCase 即可
  • 关联名称 / 枚举文本由后端自动补全(无需在模板里写关联逻辑):
    • *Id 字段 → 自动注入 {Xxx}Code / {Xxx}Name,如 ZhuangkouIdZhuangkouCodeOutOrgIdOutOrgName
    • 枚举字段 → 自动注入 {Prop}Text,如 OutTypeOutTypeText(领料/退货/报损)
    • 支持的关系见 PrintApiController.cs 中的 RefMapEnumMaps,需要新关系时在此扩展

3.5 创建模板的两种方式

方式 1:打印设计器(图形化,推荐日常使用)

打开 openprint 应用 http://localhost:5227,进入设计器拖拽组件、绑定字段,设计完成后保存到模板库(走 /api/print/templates)。

方式 2:JSON 手写 + 种子脚本(适合快速造模板)

cd server/scripts
node seed-print-template.mjs        # 读取 print-template-xxx.json,登录→查重→POST 创建

脚本内容:登录获取 token → GET /templates 查同名是否已存在 → 不存在则 POST /templates 创建。用 node 执行可避免 PowerShell 中文编码问题。


四、如何调用打印

4.1 列表页「打印」按钮(PRINT_MAP 配置)

通用列表页(CrudPage.vue)自动按配置显示「打印」按钮,只需在 web/src/config/table-map.js 加配置:

export const PRINT_MAP = {
  'RawMaterial/outstock': { templateName: '三等分原料出库单', title: '原料出库单' }
}

templateName 必须与模板库里模板的 name 完全一致。配置后列表操作列自动出现「打印」按钮。

点击后的执行流程CrudPage.vueonPrint):

  1. GET /print/templates 拉模板列表,按 templateName 找到模板 id
  2. 找不到 → 提示「未找到打印模板,请先在打印设计器创建」
  3. 找到 → window.open(buildPrintUrl({ template: id, table: 表名, row: 行id, token })) 打开 openprint 打印页

4.2 专用单据页内「打印」按钮

在专用单据页(如 outstock-bill.vue)里自行实现 onPrint(参考 305-324 行):

import { buildPrintUrl } from '@/config/print'
import { useUserStore } from '@/stores/user'

const PRINT_TEMPLATE = '三等分原料出库单'   // 模板名

async function onPrint() {
  const res = await request.get('/print/templates')
  const items = res.items || []
  const tpl = items.find((t) => t.name === PRINT_TEMPLATE)
  if (!tpl) return ElMessage.warning(`未找到打印模板「${PRINT_TEMPLATE}」`)
  const url = buildPrintUrl({
    template: tpl.id,
    table: TABLE,                 // 如 'RawMaterial_OutStock'
    row: billId.value,            // 当前单据 id
    token: useUserStore().token
  })
  window.open(url, '_blank')
}

4.3 buildPrintUrl 参数(web/src/config/print.js

参数 必填 说明
template 模板 id(数字)
table 条件必填 数据表名(如 RawMaterial_OutStock);改用 data 直传时可不填
row 条件必填 数据行 id;改用 data 直传时可不填
token 建议 后端 JWTopenprint 打印页取模板/数据时用 Bearer 鉴权)
api 后端地址,默认 PRINT_API_BASEenv VITE_PRINT_API_BASE,默认 http://localhost:5136
data URL-safe Base64 的 JSON 数据(直传数据,跳过接口拉取)

4.4 openprint 打印页执行流程(openprint/src/print/PrintPage.vue

  1. 解析 URL 参数:template(或 tpl)、tablerow(或 id)、tokenapidata
  2. 取数据:有 data 参数则 base64 解码直用;否则 GET {api}/api/print/data/{table}/{row}(带 Bearer
  3. 数据归一化:顶层 { 表名: 行 } 结构,行内补 PascalCase 别名
  4. createHeadless({ repository })buildRequest(templateId, data)headless.print()
    • 渲染出 HTML(页面尺寸、字体就绪)
    • 创建瞬态 iframeop-silent-print-frame 类:屏幕下隐藏、@media print 显示在左上角)
    • 调系统打印对话框,完成/取消后移除 iframe
  5. 页面提供「重新打印」「关闭」按钮

打印页可直接用浏览器访问调试

http://localhost:5227/?print=1&template=2&table=RawMaterial_OutStock&row=2&token=<token>&api=http://localhost:5136

4.5 打印数据接口

GET /api/print/data/{table}/{id}(见 PrintApiController.cs):

  • 返回 { 表名: { ...行数据 } } 结构(裸 JSON
  • 自动注入关联 {Xxx}Code / {Xxx}Name 与枚举 {Xxx}Text
  • 未配置新表也能直接打:模板绑定的是「表名.字段名」,数据接口按表通用返回

五、常见问题排查

现象 排查方向
列表页没有「打印」按钮 PRINT_MAP 是否配置了该菜单 component
点击打印提示「未找到打印模板」 模板库里是否已创建同名模板(GET /api/print/templates 确认 name 完全一致)
打印预览空白 ① openprint 服务(5227)是否在线;② 浏览器打印对话框对屏幕外 iframe 兼容性问题——已通过 @media print + top:0 !important 修复,需刷新页面/强刷缓存;③ 模板 content 是否为空
打印出来是 A4 而不是三等分 模板 page.width/height 是否 210×99mm;打印对话框纸张方向/纸型是否选对
打印字段为空白 binding表名.字段名 是否 PascalCase 正确;② 数据接口 /api/print/data/{表}/{id} 是否返回该字段;③ 关联/枚举字段确认注入名(XxxName / XxxText
单据页保存后跳转不对 确认 BILL_MAP value 是 /bill/<表名>(通用)还是专用路由;专用路由须在 router/index.js 注册

六、关键文件清单

文件 作用
web/src/config/bill-configs.js 通用单据页布局配置(title/codeField/statusField/groups
web/src/config/table-map.js BILL_MAP(单据页接入)、PRINT_MAP(打印按钮)、WORKFLOW_MAP(审批按钮)
web/src/components/BillPage.vue 配置驱动通用单据页(路由 /bill/:table/:id?
web/src/views/rawmaterial/outstock-bill.vue 专用全屏单据页参考实现
web/src/config/print.js buildPrintUrl() 构造 openprint 打印页 URL
web/src/components/CrudPage.vue 通用列表页(操作列 新增/编辑/打印/审批 逻辑)
web/src/router/index.js 静态路由(专用单据页、/bill/:table/:id?
server/src/F9MES.Api/Controllers/PrintApiController.cs 模板/数据接口 + 关联/枚举补全映射
server/src/F9MES.Application/Print/PrintService.cs 打印服务(模板存取)
server/scripts/print-template-outstock.json 模板 JSON 参考示例
server/scripts/seed-print-template.mjs 模板种子脚本(登录→查重→创建)
server/scripts/verify-print-visual.mjs 打印页视觉回归验证脚本(Playwright 截图)
openprint/src/print/PrintPage.vue openprint 打印页(URL 参数 → 模板+数据 → 打印)
openprint/src/core/headless/createHeadless.ts 静默打印引擎(iframe 渲染 + 系统打印对话框)

七、新增一个"可打印单据"的最小步骤清单

  1. 后端实体 + 通用 CRUD(已有则跳过)
  2. 表单单据页bill-configs.js 加配置 + BILL_MAP 加一行(两步走)
  3. 打印模板:设计器(或 JSON 种子脚本)创建模板,记录模板名
  4. 打印按钮PRINT_MAP 加一行 '模块/页面': { templateName: '<模板名>', title: '<单据标题>' }
  5. 验证:列表点「打印」→ 弹出 openprint 打印页 → 预览正常 → 打印