# 间接支付页面 - 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` ```15:27: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', ]; ``` **类型转换**: ```29:37:backend/Modules/Budget/app/Models/TemplateElement.php 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行): ```583:620:czemc-budget-execution-frontend/src/views/settings/TemplateElementSettings.vue // 分类改变处理 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 } } ``` **设置规则**: 1. 当 `category === 'pre_approval_flow'` 时: - 自动设置 `type = 'oa_custom_model'` - **必须选择 `model_id`**(从 OA 流程模型列表中选择) 2. 其他分类时,`model_id` 会被清空 ### 2.2 后端验证 **控制器**: `backend/Modules/Budget/app/Http/Controllers/Api/TemplateElementController.php` **验证逻辑**(第152-158行): ```152:158:backend/Modules/Budget/app/Http/Controllers/Api/TemplateElementController.php // 验证:事前流程自动设置类型并验证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类型,自动转换为数组) **配置结构示例**: ```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行 ```1158:1200:czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue // 如果不存在(新分组),从模版加载元素 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 } } ``` **关键步骤**: 1. 从分组中获取元素列表 2. 从 `availableElements` 中查找对应的模板元素 3. **如果是 `oa_custom_model` 类型,从模板元素中提取 `model_id`** 4. 将 `model_id` 添加到字段配置中 #### 3.2.2 手动添加元素时设置 model_id **位置**: `CanvasSettings.vue` 第706-736行 ```706:736:czemc-budget-execution-frontend/src/views/settings/CanvasSettings.vue // 元素选择改变时的处理 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行 ```2239:2274:czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue // 加载分类的模板 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) } } ``` **数据流**: 1. 调用 API 获取分类的模板配置 2. 从 `template.config` 中获取配置(JSON格式) 3. 配置中已包含字段的 `model_id`(如果字段类型为 `oa_custom_model`) 4. 将配置存储到 `templateConfig.value` ### 4.2 补充数据(enrichChecklistOptions) **位置**: `IndirectPayment.vue` 第2276-2348行 虽然这个函数主要用于补充 checklist 的 options,但它也会加载所有模板元素,确保字段信息完整。 **注意**: 当前实现中,`model_id` 已经在模板配置中,**不需要额外补充**。但如果配置中缺少 `model_id`,可以通过以下方式补充: ```javascript // 为所有字段补充 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行 ```296:301:czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue ``` **数据来源**: - `field.model_id`: 来自 `templateConfig` 中的字段配置 - `templateConfig`: 从数据库模板配置加载 ### 5.2 组件接收 **组件**: `PreApprovalFlowPicker.vue` **Props定义**(第269-294行): ```269:294:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue 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`) - 用于查询对应流程模型的流程实例列表 ## 六、完整数据流图 ```mermaid 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: 创建模板元素 1. 用户在"非直接支付模版元素设置"页面创建元素 2. 选择分类为"事前流程"(`category = 'pre_approval_flow'`) 3. 从OA流程模型列表中选择一个模型(设置 `model_id`) 4. 保存到 `budget_template_elements` 表 ### 步骤2: 配置模板 1. 用户在"计划支出模板设置"页面配置模板 2. 从分组中选择事前流程类型的元素 3. 系统从 `TemplateElement` 中获取 `model_id` 4. 将 `model_id` 写入模板配置的字段中 5. 保存模板配置到 `budget_planned_expenditure_templates` 表 ### 步骤3: 加载模板 1. 用户在间接支付页面选择分类 2. 调用 API 获取该分类的模板配置 3. 从 `template.config` 中解析字段配置 4. 字段配置中包含 `model_id`(如果类型为 `oa_custom_model`) ### 步骤4: 渲染组件 1. 遍历 `templateConfig` 中的字段 2. 当 `field.element_type === 'oa_custom_model'` 时 3. 渲染 `PreApprovalFlowPicker` 组件 4. 传递 `:model-id="field.model_id"` ### 步骤5: 组件使用 1. `PreApprovalFlowPicker` 接收 `modelId` prop 2. 使用 `modelId` 调用 API 获取流程实例列表 3. 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}` **返回数据**: ```json { "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` **请求参数**: ```javascript { custom_model_id: modelId, // ← 使用传入的 modelId year: 2025, page: 1, page_size: 15, keyword: '' // 可选 } ``` ## 九、数据验证和错误处理 ### 9.1 model_id 缺失的情况 **可能原因**: 1. 模板配置生成时未正确提取 `model_id` 2. 模板元素被删除或修改 3. 数据迁移或导入时丢失 **处理方式**: - 如果 `model_id` 缺失,`PreApprovalFlowPicker` 组件会无法加载流程列表 - 应该在前端补充数据时检查并补充 `model_id` ### 9.2 model_id 无效的情况 **可能原因**: 1. OA 系统中的流程模型被删除 2. `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 关键点 1. **数据源头**: `budget_template_elements.model_id` 存储 OA 流程模型ID 2. **配置存储**: 模板配置 JSON 中保存字段的 `model_id` 3. **数据传递**: 通过组件 props 传递 `modelId` 4. **API使用**: 使用 `modelId` 查询对应模型的流程实例列表 ### 10.3 注意事项 1. `model_id` 必须在创建模板元素时设置(事前流程类型) 2. 模板配置生成时会自动从模板元素中提取 `model_id` 3. 如果模板配置中缺少 `model_id`,需要补充数据 4. `model_id` 必须指向有效的 OA 流程模型