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

13 KiB

付款详情打印页「相关流程详情」渲染说明

页面: http://localhost:3000/#/payment/payment-detail-print/:id(如 /payment-detail-print/167
组件: 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 返回)大致为:

{
  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

  • 存储: collectedFlowIdsref<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_bindingselement_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-templatesbody: { flow_ids: number[] }
    • 实现: backend/Modules/Oa/app/Http/Controllers/FlowController.phprenderPrintTemplates

3.3 后端 renderPrintTemplates 做了什么

对每个 flow_id

  1. Flow::with(['customModel', 'creator', 'creatorDepartment', 'logs'…]) 查流程,取打印用 customModel、发起人、部门、审批日志等。
  2. $flow->withData() 加载流程业务数据。
  3. 若流程不存在 / 无 data / 未配置打印模版,则 push 一条带 error 的占位项(如「流程不存在」「未配置打印模版」),并有简单 flow_infohtml
  4. 若正常:读取 customModel.print_format,用 createFlowForPrint 构建打印用 flow 对象,再 renderPrintTemplate 渲染出 HTML对 HTML 做 removeScriptAndStyleTags 等清理。
  5. 返回项包含 flow_idflow_titleflow_infonotitlecreator_namecreated_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.renderPrintTemplatesPOST /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="..."> 替换成对应字段的渲染结果。
  • 字段渲染统一走 renderFieldValueByOaRulesFlowController.php),根据 oa_custom_model_fieldstypeselection_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),即按 |, 拆成多个 idtrimarray_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
展示逻辑 selection_model 实例化模型,对每个 id findname ?? 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 已包含子表行维度,则逻辑一致;否则可能需在实现上区分主表 / 子表取值来源。