16 KiB
间接支付页面 - modelId 参数来源分析
概述
本文档详细分析间接支付页面第三步中 PreApprovalFlowPicker 组件的 modelId 参数的完整数据流,从数据库存储到前端组件使用的全链路追踪。
一、数据源头
1.1 数据库表结构
表名: budget_template_elements
关键字段:
id: 模板元素ID(主键)name: 元素名称type: 元素类型(如oa_custom_model)model_id: OA流程模型ID(关联oa_custom_model表的id)category: 元素分类(如pre_approval_flow)options: 选项配置(JSON)display_conditions: 显示条件(JSON)
模型文件: backend/Modules/Budget/app/Models/TemplateElement.php
protected $fillable = [
'name',
'type',
'model_id',
'category',
'options',
'description',
'sort_order',
'is_active',
'display_conditions',
'created_by',
'updated_by',
];
类型转换:
protected $casts = [
'model_id' => 'integer',
'sort_order' => 'integer',
'is_active' => 'boolean',
'options' => 'array',
'display_conditions' => 'array',
'created_by' => 'integer',
'updated_by' => 'integer',
];
1.2 model_id 的含义
model_id 指向 OA 系统中的自定义流程模型(oa_custom_model 表),用于标识该模板元素关联的是哪个事前流程模型。
关联表: oa_custom_model
- 存储 OA 系统中的自定义流程模型定义
- 每个模型代表一种事前流程类型(如:合同审批、采购申请等)
二、模板元素创建流程
2.1 创建模板元素时设置 model_id
页面: 非直接支付模版元素设置页面
路径: /settings/template-element-settings
组件: czemc-budget-execution-frontend/src/views/settings/TemplateElementSettings.vue
关键逻辑(第583-620行):
// 分类改变处理
const handleCategoryChange = () => {
// 清除相关字段
if (formData.category !== 'form_field') {
formData.type = ''
}
if (formData.category !== 'checklist') {
formData.options = []
}
if (formData.category !== 'pre_approval_flow') {
formData.model_id = null
}
if (formData.category !== 'detail_table') {
formData.fields = []
}
// 事前流程自动设置类型
if (formData.category === 'pre_approval_flow') {
formData.type = 'oa_custom_model'
// 如果是事前流程且还没有加载模型列表,则加载
if (availablePreApprovalFlowModels.value.length === 0) {
loadPreApprovalFlowModels()
}
}
// 勾选清单自动设置类型
if (formData.category === 'checklist') {
formData.type = 'checklist'
}
// 会议纪要自动设置类型
if (formData.category === 'meeting_minutes') {
formData.type = 'meeting_minutes'
formData.model_id = null
}
// 明细表格自动设置类型
if (formData.category === 'detail_table') {
formData.type = 'detail_table'
formData.model_id = null
}
}
设置规则:
- 当
category === 'pre_approval_flow'时:- 自动设置
type = 'oa_custom_model' - 必须选择
model_id(从 OA 流程模型列表中选择)
- 自动设置
- 其他分类时,
model_id会被清空
2.2 后端验证
控制器: backend/Modules/Budget/app/Http/Controllers/Api/TemplateElementController.php
验证逻辑(第152-158行):
// 验证:事前流程自动设置类型并验证model_id
if ($request->category === 'pre_approval_flow') {
$request->merge(['type' => 'oa_custom_model']);
if (!$request->model_id) {
return $this->fail([400, '事前流程必须提供关联模型ID']);
}
}
验证规则:
- 事前流程类型的元素必须提供
model_id - 如果未提供,返回 400 错误
三、模板配置存储
3.1 模板配置结构
表名: budget_planned_expenditure_templates
字段: config (JSON类型,自动转换为数组)
配置结构示例:
{
"group_1": {
"title": "基本信息",
"fields": [
{
"key": "element_123",
"label": "关联合同审批",
"element_type": "oa_custom_model",
"element_id": 123,
"model_id": 456, // ← 这里存储了 model_id
"required": false,
"visible": true
}
],
"group_id": 1,
"group_code": "GROUP_XXX"
}
}
3.2 模板配置生成流程
页面: 计划支出模板设置页面
路径: /settings/planned-expenditure-template-settings
组件: czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue
3.2.1 从分组加载元素时设置 model_id
位置: CanvasSettings.vue 第1158-1200行
// 如果不存在(新分组),从模版加载元素
const elementsResponse = await preApprovalTemplateGroupAPI.getElements(group.id)
if (elementsResponse.code === 0 && elementsResponse.data) {
const elements = elementsResponse.data || []
// 确保availableElements已加载
if (availableElements.value.length === 0) {
await loadAvailableElements()
}
// 将元素转换为字段配置
const fields = elements.map(element => {
// 从availableElements中查找对应的元素获取完整信息
const templateElement = availableElements.value.find(e => e.id === element.element_id)
const field = {
key: `element_${element.element_id}`,
label: element.element_name,
element_type: element.element_type,
required: false,
visible: true,
element_id: element.element_id
}
// 如果是事前流程类型,需要获取model_id
if (element.element_type === 'oa_custom_model' && templateElement && templateElement.model_id) {
field.model_id = templateElement.model_id
}
// 如果是勾选清单类型,需要获取options
if (element.element_type === 'checklist' && templateElement && templateElement.options) {
field.options = templateElement.options
}
return field
})
newConfig[sectionKey] = {
title: group.name,
fields: fields,
group_id: group.id,
group_code: group.code
}
}
关键步骤:
- 从分组中获取元素列表
- 从
availableElements中查找对应的模板元素 - 如果是
oa_custom_model类型,从模板元素中提取model_id - 将
model_id添加到字段配置中
3.2.2 手动添加元素时设置 model_id
位置: CanvasSettings.vue 第706-736行
// 元素选择改变时的处理
const onElementSelectChange = () => {
const selectedElement = availableElements.value.find(e => e.id === elementForm.value.element_id)
if (selectedElement) {
elementForm.value.label = selectedElement.name
elementForm.value.key = `element_${selectedElement.id}`
// 根据元素的category和type设置element_type
if (selectedElement.category === 'form_field') {
elementForm.value.element_type = selectedElement.type || 'text'
elementForm.value.model_id = null
} else if (selectedElement.category === 'pre_approval_flow' ||
(!selectedElement.category && selectedElement.model_id && (selectedElement.type === 'oa_custom_model' || selectedElement.type === 'pre_approval_flow'))) {
// 事前流程:category === 'pre_approval_flow' 或 category为空但有model_id
elementForm.value.element_type = 'oa_custom_model'
elementForm.value.model_id = selectedElement.model_id || null
} else if (selectedElement.category === 'checklist') {
// 勾选清单
elementForm.value.element_type = 'checklist'
elementForm.value.model_id = null
} else if (selectedElement.category === 'meeting_minutes' || selectedElement.type === 'meeting_minutes') {
// 会议纪要
elementForm.value.element_type = 'meeting_minutes'
elementForm.value.model_id = null
} else if (selectedElement.category === 'detail_table') {
// 明细表格
elementForm.value.element_type = 'detail_table'
elementForm.value.model_id = null
}
}
}
关键逻辑:
- 当选择事前流程类型的元素时,自动从
selectedElement.model_id获取并设置
四、模板配置加载流程
4.1 间接支付页面加载模板
位置: IndirectPayment.vue 第2239-2274行
// 加载分类的模板
const loadTemplateForCategory = async (categoryId) => {
try {
const response = await plannedExpenditureTemplateAPI.getByCategory(categoryId)
if (response.code === 0) {
const templates = response.data || []
if (templates.length > 0) {
const template = templates[0]
selectedTemplate.value = template
let config = template.config || {}
// 按照分组的 sort_order 重新排序配置
config = await sortConfigByGroupOrder(config)
templateConfig.value = config
// 补充勾选清单的 options 数据
await enrichChecklistOptions(templateConfig.value)
// 补充明细表格的字段定义
await enrichDetailTableFields(templateConfig.value)
// 初始化表单数据(使用排序后的配置)
initializeFormDataFromTemplate(config)
} else {
// 没有模板,清空选择
selectedTemplate.value = null
templateConfig.value = {}
ElMessage.warning('该分类未设置模板,无法继续')
}
} else {
ElMessage.error(response.msg || '加载模板失败')
}
} catch (error) {
ElMessage.error('加载模板失败:' + error.message)
}
}
数据流:
- 调用 API 获取分类的模板配置
- 从
template.config中获取配置(JSON格式) - 配置中已包含字段的
model_id(如果字段类型为oa_custom_model) - 将配置存储到
templateConfig.value
4.2 补充数据(enrichChecklistOptions)
位置: IndirectPayment.vue 第2276-2348行
虽然这个函数主要用于补充 checklist 的 options,但它也会加载所有模板元素,确保字段信息完整。
注意: 当前实现中,model_id 已经在模板配置中,不需要额外补充。但如果配置中缺少 model_id,可以通过以下方式补充:
// 为所有字段补充 model_id(如果需要)
allFields.forEach(field => {
const element = elementMap.get(field.element_id)
if (element && element.model_id && field.element_type === 'oa_custom_model') {
field.model_id = element.model_id
}
})
五、组件使用
5.1 字段渲染
位置: IndirectPayment.vue 第296-301行
<!-- 事前流程实例 -->
<PreApprovalFlowPicker
v-else-if="field.element_type === 'oa_custom_model'"
v-model="formData[field.key]"
:model-id="field.model_id"
:placeholder="`请选择${field.label}`"
/>
数据来源:
field.model_id: 来自templateConfig中的字段配置templateConfig: 从数据库模板配置加载
5.2 组件接收
组件: PreApprovalFlowPicker.vue
Props定义(第269-294行):
const props = defineProps({
modelValue: {
type: [Number, String, null],
default: null
},
modelId: {
type: [Number, String],
required: true
},
placeholder: {
type: String,
default: '请选择事前流程实例'
},
disabled: {
type: Boolean,
default: false
},
readonly: {
type: Boolean,
default: false
},
autoLoad: {
type: Boolean,
default: false
}
})
使用:
modelId是必填属性(required: true)- 用于查询对应流程模型的流程实例列表
六、完整数据流图
graph TD
A[OA系统 oa_custom_model表] -->|创建流程模型| B[模板元素设置页面]
B -->|选择流程模型| C[创建TemplateElement记录]
C -->|存储model_id| D[budget_template_elements表]
D -->|包含model_id字段| E[模板配置生成]
E -->|从TemplateElement获取model_id| F[模板配置JSON]
F -->|保存到config字段| G[budget_planned_expenditure_templates表]
G -->|加载模板配置| H[间接支付页面]
H -->|从config中读取| I[templateConfig.value]
I -->|字段配置包含model_id| J[PreApprovalFlowPicker组件]
J -->|使用modelId查询| K[OA流程实例列表API]
七、数据流详细步骤
步骤1: 创建模板元素
- 用户在"非直接支付模版元素设置"页面创建元素
- 选择分类为"事前流程"(
category = 'pre_approval_flow') - 从OA流程模型列表中选择一个模型(设置
model_id) - 保存到
budget_template_elements表
步骤2: 配置模板
- 用户在"计划支出模板设置"页面配置模板
- 从分组中选择事前流程类型的元素
- 系统从
TemplateElement中获取model_id - 将
model_id写入模板配置的字段中 - 保存模板配置到
budget_planned_expenditure_templates表
步骤3: 加载模板
- 用户在间接支付页面选择分类
- 调用 API 获取该分类的模板配置
- 从
template.config中解析字段配置 - 字段配置中包含
model_id(如果类型为oa_custom_model)
步骤4: 渲染组件
- 遍历
templateConfig中的字段 - 当
field.element_type === 'oa_custom_model'时 - 渲染
PreApprovalFlowPicker组件 - 传递
:model-id="field.model_id"
步骤5: 组件使用
PreApprovalFlowPicker接收modelIdprop- 使用
modelId调用 API 获取流程实例列表 - API:
GET /api/budget/planned-expenditures/oa-flow-instances?custom_model_id={modelId}
八、关键API接口
8.1 获取模板配置
前端API: plannedExpenditureTemplateAPI.getByCategory(categoryId)
后端路由: GET /budget/planned-expenditure-templates/by-category/{categoryId}
返回数据:
{
"code": 0,
"data": [
{
"id": 1,
"category_id": 32,
"config": {
"group_1": {
"fields": [
{
"key": "element_123",
"label": "关联合同审批",
"element_type": "oa_custom_model",
"element_id": 123,
"model_id": 456 // ← 关键字段
}
]
}
}
}
]
}
8.2 获取流程实例列表
前端API: plannedExpenditureAPI.getOaFlowInstances(params)
后端路由: GET /api/budget/planned-expenditures/oa-flow-instances
请求参数:
{
custom_model_id: modelId, // ← 使用传入的 modelId
year: 2025,
page: 1,
page_size: 15,
keyword: '' // 可选
}
九、数据验证和错误处理
9.1 model_id 缺失的情况
可能原因:
- 模板配置生成时未正确提取
model_id - 模板元素被删除或修改
- 数据迁移或导入时丢失
处理方式:
- 如果
model_id缺失,PreApprovalFlowPicker组件会无法加载流程列表 - 应该在前端补充数据时检查并补充
model_id
9.2 model_id 无效的情况
可能原因:
- OA 系统中的流程模型被删除
model_id指向不存在的模型
处理方式:
- API 调用会返回错误
- 前端显示错误提示
十、总结
10.1 数据流向
OA系统 (oa_custom_model)
↓
模板元素 (budget_template_elements.model_id)
↓
模板配置 (budget_planned_expenditure_templates.config)
↓
前端模板配置 (templateConfig.value)
↓
字段配置 (field.model_id)
↓
组件属性 (:model-id="field.model_id")
↓
API查询参数 (custom_model_id)
10.2 关键点
- 数据源头:
budget_template_elements.model_id存储 OA 流程模型ID - 配置存储: 模板配置 JSON 中保存字段的
model_id - 数据传递: 通过组件 props 传递
modelId - API使用: 使用
modelId查询对应模型的流程实例列表
10.3 注意事项
model_id必须在创建模板元素时设置(事前流程类型)- 模板配置生成时会自动从模板元素中提取
model_id - 如果模板配置中缺少
model_id,需要补充数据 model_id必须指向有效的 OA 流程模型