` | 后端返回的 **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
>`),在 `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` 约 1270–1296 行(遍历 config 收集)、605–626 行(构造 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` 约 1579–1589 行。
+
+---
+
+## 五、流程简要串联
+
+```
+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`)。
+- 模版里使用 **`...`** 占位;后端在 `renderPrintTemplate` 里先跑 Blade,再调 **`renderFieldTags`**,把每个 `` 替换成对应字段的渲染结果。
+- 字段渲染统一走 **`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` 约 5422–5445 行):
+
+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('
', $itemsHtml)`**。
+ - 即:**多个选项竖排,用 `
` 换行**;若仅一个,就一行。
+
+4. **异常与兜底**
+ - 上述过程包在 `try/catch` 里,出错则退回 `$nl2brSafe($rawValue)`,直接展示原始存值。
+
+**与 select 的区分**:`select` 单选,用 `getSelections()` 等做 id→label 映射;**choice 按多选处理**,支持 **`|`、`,` 分隔字符串**与数组,逐 id 查模型再拼接。
+
+### 7.4 小结(choice 在「相关流程详情」里)
+
+| 项目 | 说明 |
+|------|------|
+| **发生位置** | 打印模版 `print_format` 中的 ``,对应 OA 自定义字段 type = `choice`(人员选择 / 多选)。 |
+| **数据来源** | `$flow->data->{字段名}`,即流程业务表里该字段的存值。 |
+| **存值格式** | 多为 `id1|id2|...` 或 `id1,id2,...` 或 id 数组;后端同时支持 **`|`、`,`** 分隔。 |
+| **展示逻辑** | 按 `selection_model` 实例化模型,对每个 id `find` 取 `name ?? title ?? id`,再 `implode('
', ...)` 输出;多选用换行展示。字符串按 `\|` 或 `,` 拆成 id 数组。 |
+| **实现位置** | `FlowController::renderFieldValueByOaRules`,约 5422–5445 行。 |
+
+因此,在「相关流程详情」的每个流程里,**choice 类型字段 = 多选 id → 按 selection_model 解析成姓名/名称 → 用 `
` 竖排展示**;与《choice类型字段说明与数据供给》中 OA 的 choice/人员选择/多选语义一致,且**后端已完整支持**,无需前端再参与。
+
+### 7.5 子表单中的 choice
+
+- 打印模版若包含 **relation 子表单**,子表会渲染成 ``;每个单元格同样经 **`renderFieldValueByOaRules`** 生成(传入子表 `CustomModel` 与子表字段 `$sf`)。
+- 若子表字段为 **choice**,走的仍是同一套 choice 分支:`$rawValue` 来源于当前上下文的 `$flow->data`;子表场景下通常需从子表行读取对应列,若后端传入的 `$flow->data` 已包含子表行维度,则逻辑一致;否则可能需在实现上区分主表 / 子表取值来源。
diff --git a/src/components/PreApprovalFlowPicker.vue b/src/components/PreApprovalFlowPicker.vue
index a769e0e..ad6f06d 100644
--- a/src/components/PreApprovalFlowPicker.vue
+++ b/src/components/PreApprovalFlowPicker.vue
@@ -460,6 +460,27 @@ const formatDate = (dateString) => {
}
}
+// 将 choice/choices 的值(数组、| 或 , 分隔字符串、单值)解析成 ID 数组
+const parseChoiceValueToIds = (value) => {
+ if (value === null || value === undefined || value === '') return []
+ if (Array.isArray(value)) {
+ return value
+ .map(v => (v === null || v === undefined ? '' : String(v).trim()))
+ .filter(v => v.length > 0)
+ }
+ if (typeof value === 'object') {
+ const id = value.id ?? value.value
+ if (id === null || id === undefined || id === '') return []
+ return [String(id).trim()].filter(v => v.length > 0)
+ }
+ const s = String(value).trim()
+ if (!s) return []
+ return s
+ .split(/[|,]/)
+ .map(v => v.trim())
+ .filter(v => v.length > 0)
+}
+
// 格式化字段值
const formatFieldValue = (value, col, selectionOptionsMap = {}) => {
if (value === null || value === undefined || value === '') {
@@ -481,36 +502,34 @@ const formatFieldValue = (value, col, selectionOptionsMap = {}) => {
// 如果有 selection_model,从 selectionOptionsMap 中查找
if (selectionModel && selectionOptionsMap[selectionModel]) {
const options = selectionOptionsMap[selectionModel]
-
- // 处理多值情况(逗号分隔)
- const valueStr = String(value)
- if (valueStr.includes(',')) {
- // 分割多个值
- const values = valueStr.split(',').map(v => v.trim()).filter(v => v)
- const labels = values.map(val => {
- const option = options.find(opt =>
- opt.id == val ||
- opt.value == val ||
- String(opt.id) === String(val) ||
- String(opt.value) === String(val)
- )
- return option ? (option.label || option.name || val) : val
- })
- return labels.length > 0 ? labels.join(', ') : valueStr
- } else {
- // 单值情况
- const option = options.find(opt =>
- opt.id == value ||
- opt.value == value ||
- String(opt.id) === String(value) ||
- String(opt.value) === String(value)
- )
- if (option) {
- return option.label || option.name || value
- }
+ const option = options.find(opt =>
+ opt.id == value ||
+ opt.value == value ||
+ String(opt.id) === String(value) ||
+ String(opt.value) === String(value)
+ )
+ if (option) {
+ return option.label || option.name || value
}
}
return String(value)
+ case 'choice':
+ case 'choices': {
+ const ids = parseChoiceValueToIds(value)
+ if (ids.length === 0) return '-'
+ if (selectionModel && selectionOptionsMap[selectionModel]) {
+ const options = selectionOptionsMap[selectionModel]
+ const names = ids.map(id => {
+ const opt = options.find(o =>
+ o.id == id || o.value == id ||
+ String(o.id) === String(id) || String(o.value) === String(id)
+ )
+ return opt ? (opt.label || opt.name || id) : id
+ })
+ return names.join('、')
+ }
+ return ids.join('、')
+ }
default:
return String(value)
}
@@ -633,10 +652,11 @@ const loadSubFormFieldsAndOptions = async () => {
return
}
- // 收集需要加载的 relation 字段和 select 字段
+ // 收集需要加载的 relation 字段和 select/choice 字段
const relationFields = listColumns.value.filter(col => col.type === 'relation' && col.sub_custom_model_id)
- const selectFields = listColumns.value.filter(col =>
- (col.type === 'select' || col.type === 'radio') && col.selection_model
+ const selectFields = listColumns.value.filter(col =>
+ (col.type === 'select' || col.type === 'radio' || col.type === 'choice' || col.type === 'choices') &&
+ col.selection_model
)
// 加载 relation 字段的子表单字段定义
@@ -652,9 +672,9 @@ const loadSubFormFieldsAndOptions = async () => {
// 按 myindex 排序
subFormFieldsMap.value[fieldName] = subFields.sort((a, b) => (a.myindex || 0) - (b.myindex || 0))
- // 加载子表单中 select 类型字段的选项数据(如果有 selection_model)
- const subSelectFields = subFields.filter(field =>
- (field.type === 'select' || field.type === 'radio') &&
+ // 加载子表单中 select/choice 类型字段的选项数据(如果有 selection_model)
+ const subSelectFields = subFields.filter(field =>
+ (field.type === 'select' || field.type === 'radio' || field.type === 'choice' || field.type === 'choices') &&
field.selection_model
)
for (const subSelectField of subSelectFields) {
diff --git a/src/components/RelationFieldDisplay.vue b/src/components/RelationFieldDisplay.vue
index 2da67d8..018437c 100644
--- a/src/components/RelationFieldDisplay.vue
+++ b/src/components/RelationFieldDisplay.vue
@@ -83,6 +83,27 @@ const relationData = computed(() => {
return []
})
+// 将 choice/choices 的值解析成 ID 数组(支持数组、| 或 , 分隔、单值)
+const parseChoiceValueToIds = (value) => {
+ if (value === null || value === undefined || value === '') return []
+ if (Array.isArray(value)) {
+ return value
+ .map(v => (v === null || v === undefined ? '' : String(v).trim()))
+ .filter(v => v.length > 0)
+ }
+ if (typeof value === 'object') {
+ const id = value.id ?? value.value
+ if (id === null || id === undefined || id === '') return []
+ return [String(id).trim()].filter(v => v.length > 0)
+ }
+ const s = String(value).trim()
+ if (!s) return []
+ return s
+ .split(/[|,]/)
+ .map(v => v.trim())
+ .filter(v => v.length > 0)
+}
+
// 格式化子表单字段值
const formatSubFieldValue = (value, field) => {
if (value === null || value === undefined || value === '') {
@@ -101,39 +122,34 @@ const formatSubFieldValue = (value, field) => {
return value ? '是' : '否'
case 'select':
case 'radio':
- // 如果有 selection_model,从 selectionOptionsMap 中查找
if (selectionModel && props.selectionOptionsMap[selectionModel]) {
const options = props.selectionOptionsMap[selectionModel]
-
- // 处理多值情况(逗号分隔)
- const valueStr = String(value)
- if (valueStr.includes(',')) {
- // 分割多个值
- const values = valueStr.split(',').map(v => v.trim()).filter(v => v)
- const labels = values.map(val => {
- const option = options.find(opt =>
- opt.id == val ||
- opt.value == val ||
- String(opt.id) === String(val) ||
- String(opt.value) === String(val)
- )
- return option ? (option.label || option.name || val) : val
- })
- return labels.length > 0 ? labels.join(', ') : valueStr
- } else {
- // 单值情况
- const option = options.find(opt =>
- opt.id == value ||
- opt.value == value ||
- String(opt.id) === String(value) ||
- String(opt.value) === String(value)
- )
- if (option) {
- return option.label || option.name || value
- }
+ const option = options.find(opt =>
+ opt.id == value || opt.value == value ||
+ String(opt.id) === String(value) || String(opt.value) === String(value)
+ )
+ if (option) {
+ return option.label || option.name || value
}
}
return String(value)
+ case 'choice':
+ case 'choices': {
+ const ids = parseChoiceValueToIds(value)
+ if (ids.length === 0) return '-'
+ if (selectionModel && props.selectionOptionsMap[selectionModel]) {
+ const options = props.selectionOptionsMap[selectionModel]
+ const names = ids.map(id => {
+ const opt = options.find(o =>
+ o.id == id || o.value == id ||
+ String(o.id) === String(id) || String(o.value) === String(id)
+ )
+ return opt ? (opt.label || opt.name || id) : id
+ })
+ return names.join('、')
+ }
+ return ids.join('、')
+ }
default:
return String(value)
}
diff --git a/src/views/payment/IndirectPayment.vue b/src/views/payment/IndirectPayment.vue
index 45a4bf7..b6d210e 100644
--- a/src/views/payment/IndirectPayment.vue
+++ b/src/views/payment/IndirectPayment.vue
@@ -2871,33 +2871,14 @@ const formatRelationSubFieldValue = (value, field) => {
case 'radio':
if (selectionModel && relationSelectionOptionsMap.value[selectionModel]) {
const options = relationSelectionOptionsMap.value[selectionModel]
-
- // 处理多值情况(逗号分隔)
- const valueStr = String(value)
- if (valueStr.includes(',')) {
- // 分割多个值
- const values = valueStr.split(',').map(v => v.trim()).filter(v => v)
- const labels = values.map(val => {
- const option = options.find(opt =>
- opt.id == val ||
- opt.value == val ||
- String(opt.id) === String(val) ||
- String(opt.value) === String(val)
- )
- return option ? (option.label || option.name || val) : val
- })
- return labels.length > 0 ? labels.join(', ') : valueStr
- } else {
- // 单值情况
- const option = options.find(opt =>
- opt.id == value ||
- opt.value == value ||
- String(opt.id) === String(value) ||
- String(opt.value) === String(value)
- )
- if (option) {
- return option.label || option.name || value
- }
+ const option = options.find(opt =>
+ opt.id == value ||
+ opt.value == value ||
+ String(opt.id) === String(value) ||
+ String(opt.value) === String(value)
+ )
+ if (option) {
+ return option.label || option.name || value
}
}
return String(value)
diff --git a/src/views/pre-approval/ProcessQuery.vue b/src/views/pre-approval/ProcessQuery.vue
index a8821bc..d429d43 100644
--- a/src/views/pre-approval/ProcessQuery.vue
+++ b/src/views/pre-approval/ProcessQuery.vue
@@ -507,58 +507,26 @@ const formatSubFieldValue = (rowData, subField) => {
return '-'
case 'select':
case 'radio':
- // 处理多值情况(逗号分隔)
- const valueStr = String(value)
- if (valueStr.includes(',')) {
- // 分割多个值
- const values = valueStr.split(',').map(v => v.trim()).filter(v => v)
- const labels = values.map(val => {
- // 优先从 selectionOptionsMap 中查找
- if (subField.selection_model && selectionOptionsMap.value[subField.selection_model]) {
- const options = selectionOptionsMap.value[subField.selection_model]
- const option = options.find(opt =>
- opt.id == val ||
- opt.value == val ||
- String(opt.id) === String(val) ||
- String(opt.value) === String(val)
- )
- if (option) {
- return option.label || option.name || val
- }
- }
- // 如果没有 selection_model 或找不到,尝试使用 subField.options
- if (subField.options && Array.isArray(subField.options)) {
- const option = subField.options.find(opt => opt.value === val || opt.id === val)
- if (option) {
- return option.label || option.name || val
- }
- }
- return val
- })
- return labels.length > 0 ? labels.join(', ') : valueStr
- } else {
- // 单值情况
- // 如果有 selection_model,从 selectionOptionsMap 中查找
- if (subField.selection_model && selectionOptionsMap.value[subField.selection_model]) {
- const options = selectionOptionsMap.value[subField.selection_model]
- const option = options.find(opt =>
- opt.id == value ||
- opt.value == value ||
- String(opt.id) === String(value) ||
- String(opt.value) === String(value)
- )
- if (option) {
- return option.label || option.name || value
- }
- }
-
- // 如果没有 selection_model 或找不到,尝试使用 subField.options
- if (subField.options && Array.isArray(subField.options)) {
- const option = subField.options.find(opt => opt.value === value || opt.id === value)
- return option ? (option.label || option.name || value) : value
+ // 如果有 selection_model,从 selectionOptionsMap 中查找
+ if (subField.selection_model && selectionOptionsMap.value[subField.selection_model]) {
+ const options = selectionOptionsMap.value[subField.selection_model]
+ const option = options.find(opt =>
+ opt.id == value ||
+ opt.value == value ||
+ String(opt.id) === String(value) ||
+ String(opt.value) === String(value)
+ )
+ if (option) {
+ return option.label || option.name || value
}
- return value
}
+
+ // 如果没有 selection_model 或找不到,尝试使用 subField.options
+ if (subField.options && Array.isArray(subField.options)) {
+ const option = subField.options.find(opt => opt.value === value || opt.id === value)
+ return option ? (option.label || option.name || value) : value
+ }
+ return value
case 'choice':
case 'choices': {
const ids = parseChoiceValueToIds(value)
@@ -778,58 +746,26 @@ const formatFieldValue = (data, field) => {
return '-'
case 'select':
case 'radio':
- // 处理多值情况(逗号分隔)
- const valueStr2 = String(value)
- if (valueStr2.includes(',')) {
- // 分割多个值
- const values = valueStr2.split(',').map(v => v.trim()).filter(v => v)
- const labels = values.map(val => {
- // 优先从 selectionOptionsMap 中查找
- if (field.selection_model && selectionOptionsMap.value[field.selection_model]) {
- const options = selectionOptionsMap.value[field.selection_model]
- const option = options.find(opt =>
- opt.id == val ||
- opt.value == val ||
- String(opt.id) === String(val) ||
- String(opt.value) === String(val)
- )
- if (option) {
- return option.label || option.name || val
- }
- }
- // 如果没有 selection_model 或找不到,尝试使用 field.options
- if (field.options && Array.isArray(field.options)) {
- const option = field.options.find(opt => opt.value === val || opt.id === val)
- if (option) {
- return option.label || option.name || val
- }
- }
- return val
- })
- return labels.length > 0 ? labels.join(', ') : valueStr2
- } else {
- // 单值情况
- // 如果有 selection_model,从 selectionOptionsMap 中查找
- if (field.selection_model && selectionOptionsMap.value[field.selection_model]) {
- const options = selectionOptionsMap.value[field.selection_model]
- const option = options.find(opt =>
- opt.id == value ||
- opt.value == value ||
- String(opt.id) === String(value) ||
- String(opt.value) === String(value)
- )
- if (option) {
- return option.label || option.name || value
- }
- }
-
- // 如果没有 selection_model 或找不到,尝试使用 field.options
- if (field.options && Array.isArray(field.options)) {
- const option = field.options.find(opt => opt.value === value || opt.id === value)
- return option ? (option.label || option.name || value) : value
+ // 如果有 selection_model,从 selectionOptionsMap 中查找
+ if (field.selection_model && selectionOptionsMap.value[field.selection_model]) {
+ const options = selectionOptionsMap.value[field.selection_model]
+ const option = options.find(opt =>
+ opt.id == value ||
+ opt.value == value ||
+ String(opt.id) === String(value) ||
+ String(opt.value) === String(value)
+ )
+ if (option) {
+ return option.label || option.name || value
}
- return value
}
+
+ // 如果没有 selection_model 或找不到,尝试使用 field.options
+ if (field.options && Array.isArray(field.options)) {
+ const option = field.options.find(opt => opt.value === value || opt.id === value)
+ return option ? (option.label || option.name || value) : value
+ }
+ return value
case 'choice':
case 'choices': {
// 人员选择:后端通常会配置 selection_model=App\Models\User