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/间接支付-modelId参数来源分析.md

557 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 间接支付页面 - 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
<!-- 事前流程实例 -->
<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行:
```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 流程模型