You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cz-hjjc-budget/docs/付款详情打印页-相关流程详情渲染说明.md

234 lines
13 KiB

2 months ago
# 付款详情打印页「相关流程详情」渲染说明
**页面**: `http://localhost:3000/#/payment/payment-detail-print/:id`(如 `/payment-detail-print/167`
**组件**: `czemc-budget-execution-frontend/src/views/payment/PaymentDetailPrint.vue`
---
## 一、渲染位置与条件
「相关流程详情」是一个**独立区块**,位于付款基本信息、关联内容(合同/事前流程等)**之后**。
```248:262:czemc-budget-execution-frontend/src/views/payment/PaymentDetailPrint.vue
<!-- 相关流程打印模版 -->
<div v-if="renderedPrintTemplates.length > 0" class="flow-print-templates flow-print-templates--page-break">
<h2 class="section-title">相关流程详情</h2>
<div
v-for="(template, idx) in renderedPrintTemplates"
:key="`flow_template_${template.flow_id}_${idx}`"
class="flow-print-template-item"
:class="{ 'flow-print-template-item--page-break': idx > 0 }"
>
<h3 class="flow-template-title">
{{ idx + 1 }}、{{ template.flow_info?.no || `流程ID: ${template.flow_id}` }} - {{ template.flow_info?.title || template.flow_title || '-' }}{{ template.flow_info?.creator_name || '-' }} {{ formatFlowCreatedAt(template.flow_info?.created_at) }}
</h3>
<div class="flow-template-content" v-html="template.html"></div>
</div>
</div>
```
- **显示条件**: `renderedPrintTemplates.length > 0`
- **标题**: `<h2 class="section-title">相关流程详情</h2>`
- **列表**: 遍历 `renderedPrintTemplates`,每条流程一个 `.flow-print-template-item`
---
## 二、单条流程的渲染内容
每条流程渲染两块:
| 部分 | 说明 |
|------|------|
| **标题行** `<h3 class="flow-template-title">` | `序号、流程编号 - 流程标题(创建人 创建时间)`。`flow_info` 缺省时用 `flow_id` / `flow_title` 兜底,创建时间由 `formatFlowCreatedAt` 格式化为 `YYYY-MM-DD HH:mm`。 |
| **正文** `<div class="flow-template-content" v-html="template.html">` | 后端返回的 **HTML 字符串**,直接 `v-html` 插入。来自 OA 流程的**打印模版**`CustomModel.print_format`)渲染结果。 |
每条模板项的数据结构(后端 `renderPrintTemplates` 返回)大致为:
```ts
{
flow_id: number,
flow_title?: string,
flow_info?: {
no: string,
title: string,
creator_name: string | null,
created_at: string | null
},
html: string, // 打印模版渲染出的 HTML
error?: string // 出错时才有
}
```
---
## 三、流程 ID 从哪里来(数据供给)
要渲染「相关流程详情」,必须先**收集流程 ID**,再调接口拿到 `renderedPrintTemplates`
### 3.1 收集流程 ID
- **存储**: `collectedFlowIds``ref<Set<number>>`),在 `loadPaymentDetail` 时会 `clear()` 后重新收集。
- **收集函数**: `collectFlowId(flowId)``flowId` 为 number 时 `collectedFlowIds.value.add(flowId)`
**收集来源**
1. **付款主流程**
-`loadPaymentDetail` 里:若 `payment?.flow_info?.id` 存在,则 `collectFlowId(payment.flow_info.id)`
2. **关联内容里的流程**
以下组件在渲染流程时,会 `emit('collect-flow-id', flowId)`,页面层用 `@collect-flow-id="collectFlowId"` 接收:
- **`ContractInfoCard`**(相关合同信息)
-`embedPlannedExpenditures === true` 时,渲染「关联合同的事前流程」,用 `PlannedExpenditureTemplateReadonly` 展示各事前流程;该组件内部会为每个流程 emit `collect-flow-id`
- 出现场景:`relatedTypeCase === 'null'` 且 `payment?.contract_id`;或 `relatedTypeCase === 'contract'`;或 `relatedTypeCase === 'planned_expenditure'` 时合同区块(`embed-planned-expenditures="false"` 不嵌入事前流程,但合同区块仍挂载)。
- **`PlannedExpenditureInfoCard`**(相关事前流程)
-`relatedTypeCase === 'planned_expenditure'` 时渲染,内部同样是 `PlannedExpenditureTemplateReadonly`,对每个事前流程 emit `collect-flow-id`
3. **`PlannedExpenditureTemplateReadonly` 如何产生 flowId**
- 根据模板配置中的 `oa_custom_model` 字段,从 `flow_bindings``element_values` 里解析出流程 ID。
-`flowItems` 计算属性中,每得到一个 `id` 就会 `emit('collect-flow-id', id)`
-`PlannedExpenditureTemplateReadonly.vue` 约 12701296 行(遍历 config 收集、605626 行(构造 flowItems 时 emit
因此,**流程 ID = 付款主流程 + 合同相关事前流程(若嵌入)+ 当前付款关联的事前流程**,经 `collectFlowId` 汇总到 `collectedFlowIds`
### 3.2 调用接口拿到「相关流程详情」HTML
- **时机**
- `loadPaymentDetail` 在拉完付款、关联事前流程、合同等并 `nextTick` 后,`setTimeout(..., 300)` 调用 `renderPrintTemplates()`
- 另外有 `watch` 监听 `collectedFlowIds.value.size`,变化时也会触发 `renderPrintTemplates`(防抖 500ms
- **请求**:
- `oaFlowAPI.renderPrintTemplates(flowIds)`
- `flowIds = Array.from(collectedFlowIds.value)`,即当前收集到的全部流程 ID。
- **接口**:
- `POST /oa/flow/render-print-templates`body: `{ flow_ids: number[] }`
- 实现: `backend/Modules/Oa/app/Http/Controllers/FlowController.php``renderPrintTemplates`
### 3.3 后端 `renderPrintTemplates` 做了什么
对每个 `flow_id`
1.`Flow::with(['customModel', 'creator', 'creatorDepartment', 'logs'…])` 查流程,取打印用 `customModel`、发起人、部门、审批日志等。
2. `$flow->withData()` 加载流程业务数据。
3. 若流程不存在 / 无 data / 未配置打印模版,则 push 一条带 `error` 的占位项(如「流程不存在」「未配置打印模版」),并有简单 `flow_info``html`
4. 若正常:读取 `customModel.print_format`,用 `createFlowForPrint` 构建打印用 flow 对象,再 `renderPrintTemplate` 渲染出 HTML对 HTML 做 `removeScriptAndStyleTags` 等清理。
5. 返回项包含 `flow_id`、`flow_title`、`flow_info``no`、`title`、`creator_name`、`created_at`)、`html`。
前端把接口返回的数组赋给 `renderedPrintTemplates`,模板里据此渲染「相关流程详情」的标题列表 + 每条 `v-html` 的正文。
---
## 四、打印与分页样式
- **区块整体**:
- `.flow-print-templates--page-break` 使用 `break-before: page` / `page-break-before: always`**从「相关流程详情」整块起新的一页**。
- **每条流程**:
- 第一条(`idx === 0`)无额外分页。
- 从第二条开始(`idx > 0`)加上 `flow-print-template-item--page-break`,同样是 `break-before: page`**每条流程新起一页**。
详见 `PaymentDetailPrint.vue` 约 15791589 行。
---
## 五、流程简要串联
```
loadPaymentDetail
→ paymentAPI.getDetail(id)
→ collectFlowId(payment.flow_info.id) // 付款主流程
→ loadPaymentTemplateElements / loadApprovalFlowDetails
→ loadRelatedPlannedExpenditure
→ nextTick + setTimeout(300) → renderPrintTemplates()
子组件挂载并渲染:
ContractInfoCard / PlannedExpenditureInfoCard
→ PlannedExpenditureTemplateReadonly
→ 从 flow_bindings / element_values 解析流程 ID
→ emit('collect-flow-id', id) → collectFlowId(id)
renderPrintTemplates:
flowIds = Array.from(collectedFlowIds)
→ oaFlowAPI.renderPrintTemplates(flowIds)
→ POST /oa/flow/render-print-templates { flow_ids }
→ 后端 per flow_id 渲染 print_format → HTML
→ renderedPrintTemplates = res.data
模板:
v-if="renderedPrintTemplates.length > 0"
→ 展示「相关流程详情」标题
→ v-for template in renderedPrintTemplates
→ h3: 序号、编号、标题、创建人、时间
→ div v-html="template.html"
```
---
## 六、相关文件
| 用途 | 路径 |
|------|------|
| 页面与「相关流程详情」模板 | `czemc-budget-execution-frontend/src/views/payment/PaymentDetailPrint.vue` |
| 事前流程只读展示 + collect-flow-id | `czemc-budget-execution-frontend/src/components/PlannedExpenditureTemplateReadonly.vue` |
| 合同信息卡片 | `czemc-budget-execution-frontend/src/components/payment-print/ContractInfoCard.vue` |
| 事前流程信息卡片 | `czemc-budget-execution-frontend/src/components/payment-print/PlannedExpenditureInfoCard.vue` |
| 渲染打印模版 API | `oaFlowAPI.renderPrintTemplates``POST /oa/flow/render-print-templates` |
| 后端实现 | `backend/Modules/Oa/app/Http/Controllers/FlowController.php` :: `renderPrintTemplates` |
---
## 七、choice 类型字段在「相关流程详情」中的处理
结合 OA 自定义模型里的 **choice** 类型(含「人员选择」「多选」),说明每个流程的正文 HTML 里,**choice 字段如何被渲染**。
### 7.1 流程正文 HTML 的来源
- 每条流程的 `template.html` 来自后端 **打印模版**`CustomModel.print_format`)。
- 模版里使用 **`<field name="xxx">...</field>`** 占位;后端在 `renderPrintTemplate` 里先跑 Blade再调 **`renderFieldTags`**,把每个 `<field name="...">` 替换成对应字段的渲染结果。
- 字段渲染统一走 **`renderFieldValueByOaRules`**`FlowController.php`),根据 `oa_custom_model_fields``type`、`selection_model` 等规则输出 HTML 或纯文本。
因此,**choice 类型在「相关流程详情」中的表现,完全由后端 `renderFieldValueByOaRules` 的 choice 分支决定**;前端只做 `v-html` 展示,不再解析字段类型。
### 7.2 存值从哪里取
- `renderFieldValueByOaRules` 里:**`$rawValue = $flow->data->{$field->name}`**。
- `$flow->data` 即该流程的业务数据OA 的 `oa_cmt_{custom_model_id}` 等表的一条记录)。
- 亦即:**choice 的存值 = 流程业务表里该字段名对应的列**。
- 与 OA 设计一致:**人员选择** 多为 `id1|id2|...`(竖线分隔),**多选** 也可能为 `|` 分隔或数组,依写入方而定。
### 7.3 choice 的渲染逻辑(后端)
`$field->type === 'choice'``$field->selection_model` 存在时(`FlowController.php` 约 54225445 行):
1. **解析多选 ID**
-`$rawValue` 为**字符串**`preg_split('/[|,]/', $rawValue)`,即按 **`|``,`** 拆成多个 id`trim`、`array_filter` 去空。
- 若为**数组**`$ids = $rawValue`,直接当作 id 数组。
2. **按 id 查展示名**
- `$cls = $field->selection_model`(如 `App\Models\User`)。
- 对每个 `$id``$m = (new $cls())->withTrashed()->find($id)`;若有记录,取 **`$m->name ?? $m->title ?? $id`**,否则用 `$id`
- 人员选择时通常为 User展示的就是**姓名**`name`)或 `title`
3. **拼成 HTML**
- 将上述展示名 `$escapeText(...)` 后放进 `$itemsHtml`,最后 **`return implode('<br/>', $itemsHtml)`**。
- 即:**多个选项竖排,用 `<br/>` 换行**;若仅一个,就一行。
4. **异常与兜底**
- 上述过程包在 `try/catch` 里,出错则退回 `$nl2brSafe($rawValue)`,直接展示原始存值。
**与 select 的区分**`select` 单选,用 `getSelections()` 等做 id→label 映射;**choice 按多选处理**,支持 **`|`、`,` 分隔字符串**与数组,逐 id 查模型再拼接。
### 7.4 小结choice 在「相关流程详情」里)
| 项目 | 说明 |
|------|------|
| **发生位置** | 打印模版 `print_format` 中的 `<field name="...">`,对应 OA 自定义字段 type = `choice`(人员选择 / 多选)。 |
| **数据来源** | `$flow->data->{字段名}`,即流程业务表里该字段的存值。 |
| **存值格式** | 多为 `id1|id2|...``id1,id2,...` 或 id 数组;后端同时支持 **`|`、`,`** 分隔。 |
| **展示逻辑** | 按 `selection_model` 实例化模型,对每个 id `find``name ?? title ?? id`,再 `implode('<br/>', ...)` 输出;多选用换行展示。字符串按 `\|``,` 拆成 id 数组。 |
| **实现位置** | `FlowController::renderFieldValueByOaRules`,约 54225445 行。 |
因此,在「相关流程详情」的每个流程里,**choice 类型字段 = 多选 id → 按 selection_model 解析成姓名/名称 → 用 `<br/>` 竖排展示**与《choice类型字段说明与数据供给》中 OA 的 choice/人员选择/多选语义一致,且**后端已完整支持**,无需前端再参与。
### 7.5 子表单中的 choice
- 打印模版若包含 **relation 子表单**,子表会渲染成 `<table>`;每个单元格同样经 **`renderFieldValueByOaRules`** 生成(传入子表 `CustomModel` 与子表字段 `$sf`)。
- 若子表字段为 **choice**,走的仍是同一套 choice 分支:`$rawValue` 来源于当前上下文的 `$flow->data`;子表场景下通常需从子表行读取对应列,若后端传入的 `$flow->data` 已包含子表行维度,则逻辑一致;否则可能需在实现上区分主表 / 子表取值来源。