|
|
---
|
|
|
name: 第三步-事前流程字段交互逻辑
|
|
|
overview: 详细说明非直接支付页面第三步中 pre_approval_flow 字段(事前流程选择)的交互逻辑、组件使用情况、数据流和预览展示。
|
|
|
status: 已完成
|
|
|
---
|
|
|
|
|
|
# 第三步 - 事前流程字段交互逻辑
|
|
|
|
|
|
## 一、组件使用
|
|
|
|
|
|
### 1.1 组件引入
|
|
|
|
|
|
**文件**: `czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue`
|
|
|
|
|
|
```vue
|
|
|
import PreApprovalFlowPicker from '@/components/PreApprovalFlowPicker.vue'
|
|
|
```
|
|
|
|
|
|
### 1.2 组件渲染
|
|
|
|
|
|
**位置**: 第三步动态表单渲染部分(约第 296 行)
|
|
|
|
|
|
**触发条件**: 当字段的 `element_type === 'oa_custom_model'` 时
|
|
|
|
|
|
```vue
|
|
|
<!-- 事前流程实例 -->
|
|
|
<PreApprovalFlowPicker
|
|
|
v-else-if="field.element_type === 'oa_custom_model'"
|
|
|
v-model="formData[field.key]"
|
|
|
:model-id="field.model_id"
|
|
|
:placeholder="`请选择${field.label}`"
|
|
|
/>
|
|
|
```
|
|
|
|
|
|
### 1.3 组件属性说明
|
|
|
|
|
|
| 属性 | 类型 | 说明 | 来源 |
|
|
|
|------|------|------|------|
|
|
|
| `v-model` | `Number\|String\|null` | 双向绑定,存储选中的流程ID | `formData[field.key]` |
|
|
|
| `model-id` | `Number\|String` | 流程模型ID,用于查询对应的流程实例 | `field.model_id`(从模板配置获取) |
|
|
|
| `placeholder` | `String` | 占位符文本 | `请选择${field.label}` |
|
|
|
|
|
|
## 二、交互流程
|
|
|
|
|
|
### 2.1 交互流程图
|
|
|
|
|
|
```mermaid
|
|
|
flowchart TD
|
|
|
A[用户进入第三步] --> B{字段类型是否为 oa_custom_model?}
|
|
|
B -->|是| C[渲染 PreApprovalFlowPicker 组件]
|
|
|
B -->|否| D[渲染其他字段类型]
|
|
|
|
|
|
C --> E{是否已选择流程?}
|
|
|
E -->|否| F[显示"选择事前流程"按钮]
|
|
|
E -->|是| G[显示已选择的流程信息]
|
|
|
|
|
|
F --> H[用户点击按钮]
|
|
|
H --> I[打开选择对话框]
|
|
|
I --> J[加载流程实例列表]
|
|
|
J --> K[用户搜索/浏览流程]
|
|
|
K --> L[用户点击表格行选择]
|
|
|
L --> M[点击确定按钮]
|
|
|
M --> N[更新 v-model 值]
|
|
|
N --> O[关闭对话框]
|
|
|
O --> G
|
|
|
|
|
|
G --> P[显示流程编号和标题]
|
|
|
P --> Q[显示流程状态标签]
|
|
|
Q --> R[提供清除按钮]
|
|
|
R --> S{用户点击清除?}
|
|
|
S -->|是| T[清空选择]
|
|
|
T --> F
|
|
|
S -->|否| U[保持选择状态]
|
|
|
```
|
|
|
|
|
|
### 2.2 详细交互步骤
|
|
|
|
|
|
#### 步骤1:初始状态
|
|
|
|
|
|
- **未选择时**:显示"选择事前流程"按钮
|
|
|
- **已选择时**:显示已选择的流程信息卡片,包含:
|
|
|
- 流程编号和标题
|
|
|
- 流程状态标签(带颜色)
|
|
|
- 清除按钮(右上角)
|
|
|
|
|
|
#### 步骤2:打开选择对话框
|
|
|
|
|
|
- 点击"选择事前流程"按钮
|
|
|
- 打开宽度为 80% 的对话框
|
|
|
- 对话框标题:"选择事前流程实例"
|
|
|
|
|
|
#### 步骤3:加载流程列表
|
|
|
|
|
|
**API 调用**: `GET /api/budget/planned-expenditures/oa-flow-instances`
|
|
|
|
|
|
**请求参数**:
|
|
|
```javascript
|
|
|
{
|
|
|
custom_model_id: field.model_id, // 流程模型ID
|
|
|
page: 1, // 当前页码
|
|
|
page_size: 15, // 每页数量
|
|
|
keyword: '' // 搜索关键词(可选)
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**响应数据结构**:
|
|
|
```javascript
|
|
|
{
|
|
|
code: 0,
|
|
|
data: {
|
|
|
data: [ // 流程实例列表
|
|
|
{
|
|
|
id: 1,
|
|
|
no: 'FLOW-20250101-001',
|
|
|
title: '流程标题',
|
|
|
status: 'approved',
|
|
|
status_text: '已通过',
|
|
|
created_at: '2025-01-01 10:00:00',
|
|
|
related_count: 2, // 已关联数量
|
|
|
list_field_values: {}, // 列表字段值
|
|
|
list_fields: [] // 列表字段定义
|
|
|
}
|
|
|
],
|
|
|
total: 100, // 总记录数
|
|
|
list_columns: [ // 动态列定义(show_in_list=1的字段)
|
|
|
{
|
|
|
label: '字段标签',
|
|
|
name: 'field_name',
|
|
|
type: 'text'
|
|
|
}
|
|
|
]
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
#### 步骤4:流程列表展示
|
|
|
|
|
|
**表格列**:
|
|
|
1. **序号列** (#) - 宽度 60px
|
|
|
2. **流程编号** (no) - 宽度 150px,支持溢出提示
|
|
|
3. **流程标题** (title) - 最小宽度 250px,支持溢出提示
|
|
|
4. **动态列** - 根据 `list_columns` 动态生成,显示 `show_in_list=1` 的字段
|
|
|
5. **流程状态** - 宽度 120px,显示状态标签
|
|
|
6. **创建时间** - 宽度 180px
|
|
|
7. **关联信息** - 宽度 200px,显示已关联数量
|
|
|
8. **操作** - 宽度 120px,固定右侧,包含"查看详情"按钮
|
|
|
|
|
|
**交互特性**:
|
|
|
- 支持行点击选择(高亮当前行)
|
|
|
- 支持搜索功能(搜索流程编号、标题、列表字段)
|
|
|
- 支持分页(15/30/50/100 条每页)
|
|
|
- 支持查看详情(打开详情对话框)
|
|
|
|
|
|
#### 步骤5:选择流程
|
|
|
|
|
|
- 用户点击表格行(高亮显示)
|
|
|
- 点击"确定"按钮
|
|
|
- 更新 `v-model` 绑定的值(流程ID)
|
|
|
- 关闭对话框
|
|
|
- 更新显示区域,展示已选择的流程信息
|
|
|
|
|
|
#### 步骤6:清除选择
|
|
|
|
|
|
- 点击已选择流程卡片右上角的清除按钮(红色链接按钮)
|
|
|
- 清空 `v-model` 绑定的值
|
|
|
- 恢复显示"选择事前流程"按钮
|
|
|
|
|
|
## 三、数据存储
|
|
|
|
|
|
### 3.1 表单数据存储
|
|
|
|
|
|
**存储位置**: `formData[field.key]`
|
|
|
|
|
|
**存储格式**:
|
|
|
- 未选择:`null` 或 `undefined`
|
|
|
- 已选择:流程ID(`Number` 或 `String`)
|
|
|
|
|
|
**示例**:
|
|
|
```javascript
|
|
|
formData = {
|
|
|
'element_123': 456, // 字段key: 流程ID
|
|
|
// ... 其他字段
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 3.2 后端存储
|
|
|
|
|
|
**表**: `budget_planned_expenditure_element_values`
|
|
|
|
|
|
**字段映射**:
|
|
|
- `planned_expenditure_id`: 支出记录ID
|
|
|
- `element_id`: 模板元素ID
|
|
|
- `field_key`: 字段键名(如 `element_123`)
|
|
|
- `element_type`: `'oa_custom_model'`
|
|
|
- `value_text`: 流程ID(字符串格式)
|
|
|
- `flow_custom_model_id`: 流程模型ID(冗余字段)
|
|
|
- `flow_instance_id`: 流程实例ID(冗余字段)
|
|
|
- `flow_display_name`: 流程显示名称(冗余字段)
|
|
|
|
|
|
**存储逻辑**: 通过 `PlannedExpenditureController::persistElementValues()` 方法保存
|
|
|
|
|
|
## 四、预览页面展示
|
|
|
|
|
|
### 4.1 流程详情加载
|
|
|
|
|
|
**触发时机**: 进入预览页面(第6步)时
|
|
|
|
|
|
**函数**: `loadFlowDetailsForPreview()`
|
|
|
|
|
|
**逻辑**:
|
|
|
1. 遍历模板配置,收集所有 `oa_custom_model` 类型的字段
|
|
|
2. 提取这些字段的值(流程ID)
|
|
|
3. 去重后调用批量查询接口
|
|
|
|
|
|
**API 调用**: `GET /api/budget/planned-expenditures/oa-flow-details`
|
|
|
|
|
|
**请求参数**:
|
|
|
```javascript
|
|
|
{
|
|
|
flow_ids: [1, 2, 3, ...] // 流程ID数组
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**响应数据**:
|
|
|
```javascript
|
|
|
{
|
|
|
code: 0,
|
|
|
data: {
|
|
|
'1': { // key为流程ID
|
|
|
id: 1,
|
|
|
no: 'FLOW-001',
|
|
|
title: '流程标题',
|
|
|
display_name: 'FLOW-001 - 流程标题',
|
|
|
status: 'approved',
|
|
|
list_fields: [], // 列表字段
|
|
|
all_fields: [] // 全部字段(用于详情)
|
|
|
},
|
|
|
// ...
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**存储位置**: `flowDetailsMap.value`(流程详情映射表)
|
|
|
|
|
|
### 4.2 预览格式化
|
|
|
|
|
|
**函数**: `formatPreviewFieldValue()`
|
|
|
|
|
|
**处理逻辑**:
|
|
|
```javascript
|
|
|
if (elementType === 'oa_custom_model') {
|
|
|
if (!value) {
|
|
|
return '未选择流程'
|
|
|
}
|
|
|
const flowDetail = flowDetailsMap.value[value]
|
|
|
if (flowDetail) {
|
|
|
// 生成HTML,包含:
|
|
|
// 1. 流程基础信息(编号-标题)
|
|
|
// 2. 列表字段表格(如果有)
|
|
|
return html
|
|
|
}
|
|
|
return `流程ID: ${value}`
|
|
|
}
|
|
|
```
|
|
|
|
|
|
**展示内容**:
|
|
|
- **基础信息**: 流程编号和标题(加粗显示)
|
|
|
- **列表字段表格**: 显示所有 `show_in_list=1` 的字段及其值
|
|
|
|
|
|
**HTML 结构**:
|
|
|
```html
|
|
|
<div class="flow-preview">
|
|
|
<div class="flow-basic-info">
|
|
|
<strong>FLOW-001 - 流程标题</strong>
|
|
|
</div>
|
|
|
<table class="flow-list-fields">
|
|
|
<tr>
|
|
|
<td class="field-label">字段1:</td>
|
|
|
<td class="field-value">值1</td>
|
|
|
</tr>
|
|
|
<!-- ... 更多字段 ... -->
|
|
|
</table>
|
|
|
</div>
|
|
|
```
|
|
|
|
|
|
## 五、组件内部实现
|
|
|
|
|
|
### 5.1 核心状态
|
|
|
|
|
|
```javascript
|
|
|
const selectedFlow = ref(null) // 当前选中的流程对象
|
|
|
const showSelectDialog = ref(false) // 选择对话框显示状态
|
|
|
const showDetailDialog = ref(false) // 详情对话框显示状态
|
|
|
const flowList = ref([]) // 流程列表数据
|
|
|
const listColumns = ref([]) // 动态列定义
|
|
|
const searchKeyword = ref('') // 搜索关键词
|
|
|
const loading = ref(false) // 加载状态
|
|
|
```
|
|
|
|
|
|
### 5.2 核心方法
|
|
|
|
|
|
#### `loadFlowInstances()`
|
|
|
- 加载流程实例列表
|
|
|
- 支持分页和搜索
|
|
|
- 处理动态列定义
|
|
|
|
|
|
#### `handleSelectFlow(row)`
|
|
|
- 处理表格行点击
|
|
|
- 设置 `selectedRow`
|
|
|
|
|
|
#### `confirmSelect()`
|
|
|
- 确认选择
|
|
|
- 更新 `v-model`
|
|
|
- 关闭对话框
|
|
|
|
|
|
#### `clearSelection()`
|
|
|
- 清除选择
|
|
|
- 重置 `v-model` 为 `null`
|
|
|
|
|
|
#### `viewFlowDetail(row)`
|
|
|
- 查看流程详情
|
|
|
- 打开详情对话框
|
|
|
|
|
|
#### `loadFlowDetail(flowId)`
|
|
|
- 加载流程详情
|
|
|
- 使用缓存机制
|
|
|
- 优先从列表获取,其次从API加载
|
|
|
|
|
|
### 5.3 计算属性
|
|
|
|
|
|
```javascript
|
|
|
const detailFields = computed(() => {
|
|
|
// 优先使用 all_fields,否则使用 list_fields
|
|
|
const flow = detailFlow.value
|
|
|
if (!flow) return []
|
|
|
if (Array.isArray(flow.all_fields) && flow.all_fields.length > 0) {
|
|
|
return flow.all_fields
|
|
|
}
|
|
|
if (Array.isArray(flow.list_fields) && flow.list_fields.length > 0) {
|
|
|
return flow.list_fields
|
|
|
}
|
|
|
return []
|
|
|
})
|
|
|
```
|
|
|
|
|
|
## 六、样式和UI
|
|
|
|
|
|
### 6.1 已选择状态展示
|
|
|
|
|
|
```css
|
|
|
.selected-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 12px;
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 4px;
|
|
|
border: 1px solid #e4e7ed;
|
|
|
}
|
|
|
|
|
|
.item-icon {
|
|
|
color: #409eff;
|
|
|
font-size: 20px;
|
|
|
margin-right: 12px;
|
|
|
}
|
|
|
|
|
|
.item-content {
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.item-title {
|
|
|
font-weight: 500;
|
|
|
color: #303133;
|
|
|
}
|
|
|
|
|
|
.item-meta {
|
|
|
margin-top: 4px;
|
|
|
}
|
|
|
|
|
|
.item-remove {
|
|
|
margin-left: 12px;
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### 6.2 选择对话框
|
|
|
|
|
|
- 宽度:80%
|
|
|
- 支持搜索
|
|
|
- 表格支持行点击高亮
|
|
|
- 分页控件在底部
|
|
|
- 操作按钮在对话框底部
|
|
|
|
|
|
### 6.3 详情对话框
|
|
|
|
|
|
- 宽度:70%
|
|
|
- 使用 `el-descriptions` 展示基础信息
|
|
|
- 使用 `el-table` 展示列表字段
|
|
|
- 支持关联信息展示(如果有)
|
|
|
|
|
|
## 七、注意事项
|
|
|
|
|
|
### 7.1 数据一致性
|
|
|
|
|
|
- 流程ID必须与 `model_id` 对应的流程模型匹配
|
|
|
- 选择流程后,流程详情会在预览页面加载
|
|
|
- 如果流程不存在,预览页面会显示"流程ID: xxx"
|
|
|
|
|
|
### 7.2 性能优化
|
|
|
|
|
|
- 流程列表支持分页,避免一次性加载过多数据
|
|
|
- 流程详情使用缓存机制,避免重复请求
|
|
|
- 预览页面批量加载所有流程详情,减少请求次数
|
|
|
|
|
|
### 7.3 错误处理
|
|
|
|
|
|
- API 请求失败时显示错误提示
|
|
|
- 流程不存在时显示友好的提示信息
|
|
|
- 网络错误时不影响其他功能
|
|
|
|
|
|
## 八、相关文件
|
|
|
|
|
|
### 8.1 组件文件
|
|
|
- `czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue`
|
|
|
|
|
|
### 8.2 页面文件
|
|
|
- `czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue`
|
|
|
|
|
|
### 8.3 API 文件
|
|
|
- `czemc-budget-execution-frontend/src/utils/api.js`
|
|
|
- `plannedExpenditureAPI.getOaFlowInstances()` - 获取流程实例列表
|
|
|
- `plannedExpenditureAPI.getOaFlowDetails()` - 批量获取流程详情
|
|
|
|
|
|
### 8.4 后端接口
|
|
|
- `GET /api/budget/planned-expenditures/oa-flow-instances` - 获取流程实例列表
|
|
|
- `GET /api/budget/planned-expenditures/oa-flow-details` - 批量获取流程详情
|
|
|
|