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/间接支付-第三步-选择事前流程按钮逻辑.md

616 lines
17 KiB

3 months ago
# 间接支付页面 - 第三步"选择事前流程"按钮逻辑说明
## 页面信息
- **页面路径**: `http://localhost:3000/#/payment/indirect-payment`
- **页面组件**: `czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue`
- **步骤**: 第三步 - 填写信息
- **相关组件**: `czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue`
## 一、按钮出现逻辑
### 1.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}`"
/>
```
**触发条件**:
1. 当前字段的 `element_type === 'oa_custom_model'`(事前流程类型)
2. 字段必须存在于模板配置中(从 `templateConfig` 中获取)
3. 字段的 `visible !== false`(字段可见)
4. 字段通过显示条件检查(`checkFieldDisplayConditions(f)`
### 1.2 按钮显示/隐藏逻辑
**位置**: `PreApprovalFlowPicker.vue` 第4-40行
```4:40:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
<!-- 录入态 -->
<div v-if="!readonly" class="input-container">
<!-- 已选择流程的展示区域 -->
<div v-if="selectedFlow" class="selected-display">
<div class="selected-item">
<el-icon class="item-icon"><DocumentChecked /></el-icon>
<div class="item-content">
<div class="item-title">{{ getFlowDisplayName(selectedFlow) }}</div>
<div v-if="selectedFlow.status" class="item-meta">
<el-tag :type="getStatusType(selectedFlow.status)" size="small">
{{ getStatusText(selectedFlow.status) }}
</el-tag>
</div>
</div>
<el-button
type="danger"
link
size="small"
@click="clearSelection"
class="item-remove"
>
<el-icon><Close /></el-icon>
</el-button>
</div>
</div>
<!-- 未选择时的操作区域 -->
<div v-else class="action-container">
<el-button
type="primary"
size="small"
@click="showSelectDialog = true"
:disabled="disabled"
>
<el-icon><Document /></el-icon>
选择事前流程
</el-button>
</div>
</div>
```
**显示逻辑**:
- **显示"选择事前流程"按钮**的条件:
1. `readonly === false`(非只读模式)
2. `selectedFlow === null``selectedFlow === undefined`(未选择流程)
3. `disabled === false`(按钮未禁用)
- **显示已选择流程卡片**的条件:
1. `readonly === false`(非只读模式)
2. `selectedFlow !== null``selectedFlow !== undefined`(已选择流程)
### 1.3 状态判断流程
```mermaid
flowchart TD
A[字段类型检查] --> B{是否为 oa_custom_model?}
B -->|否| C[不渲染组件]
B -->|是| D{字段是否可见?}
D -->|否| C
D -->|是| E{是否通过显示条件?}
E -->|否| C
E -->|是| F[渲染 PreApprovalFlowPicker]
F --> G{readonly?}
G -->|是| H[显示只读状态]
G -->|否| I{selectedFlow?}
I -->|有值| J[显示已选择流程卡片]
I -->|无值| K[显示"选择事前流程"按钮]
K --> L{disabled?}
L -->|是| M[按钮禁用状态]
L -->|否| N[按钮可点击]
```
## 二、点击按钮后的操作步骤
### 2.1 打开选择对话框
**触发**: 点击"选择事前流程"按钮
**操作**: 设置 `showSelectDialog.value = true`
**位置**: `PreApprovalFlowPicker.vue` 第34行
```34:34:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
@click="showSelectDialog = true"
```
### 2.2 对话框打开事件处理
**函数**: `handleDialogOpened()`第623-631行
```623:631:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 对话框打开时先加载年份列表,然后加载流程列表
const handleDialogOpened = async () => {
// 先加载年份列表
await loadAvailableYears()
// 年份加载完成后,如果有选中的年份,再加载流程列表
if (selectedYear.value) {
loadFlowInstances()
}
}
```
**执行顺序**:
1. 调用 `loadAvailableYears()` 加载可用年份列表
2. 自动选定年份(优先当年,否则最新年份)
3. 如果有选中的年份,调用 `loadFlowInstances()` 加载流程列表
### 2.3 加载年份列表
**函数**: `loadAvailableYears()`第416-453行
**API调用**: `GET /api/budget/planned-expenditures/oa-flow-years`
**请求参数**:
```javascript
{
custom_model_id: props.modelId // 流程模型ID
}
```
**响应处理**:
- 如果返回年份数组,设置 `availableYears.value`
- 自动选定年份:
- 优先选择当前年份(如果存在)
- 否则选择数组中的第一个年份(最新年份)
- 如果无数据或出错,默认使用当前年份
**关键代码**:
```432:444:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 自动选定当年
const currentYear = new Date().getFullYear()
if (availableYears.value.includes(currentYear)) {
selectedYear.value = currentYear
} else if (availableYears.value.length > 0) {
// 如果没有当年,选择最新的年份
selectedYear.value = availableYears.value[0]
}
```
### 2.4 加载流程实例列表
**函数**: `loadFlowInstances()`第456-522行
**触发时机**:
1. 对话框打开后,年份加载完成且有选中年份
2. 年份切换时
3. 搜索关键词变化时
4. 分页参数变化时
**API调用**: `GET /api/budget/planned-expenditures/oa-flow-instances`
**请求参数**:
```javascript
{
custom_model_id: props.modelId, // 流程模型ID必填
year: selectedYear.value, // 年份(必填)
page: pagination.value.currentPage, // 当前页码
page_size: pagination.value.pageSize, // 每页数量
keyword: searchKeyword.value // 搜索关键词(可选)
}
```
**响应数据结构**:
```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'
}
]
}
}
```
**处理逻辑**:
1. 提取 `list_columns` 作为动态表格列
2. 提取 `data` 作为流程列表数据
3. 更新分页信息(`total`
4. 设置加载状态
### 2.5 流程列表展示
**位置**: 对话框中的表格第107-166行
**表格列结构**:
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 条每页)
- 支持查看详情(打开详情对话框)
### 2.6 年份切换
**函数**: `handleYearChange()`第608-614行
**触发**: 用户切换年份单选按钮
**操作**:
1. 重置分页到第一页
2. 清空搜索关键词
3. 清空已选中的行
4. 重新加载流程列表
```608:614:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 年份切换
const handleYearChange = () => {
pagination.value.currentPage = 1
searchKeyword.value = ''
selectedRow.value = null
loadFlowInstances()
}
```
### 2.7 搜索功能
**函数**: `handleSearch()`第616-620行
**触发**: 搜索输入框内容变化(`@input` 事件)
**操作**:
1. 重置分页到第一页
2. 重新加载流程列表(带搜索关键词)
```616:620:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 搜索
const handleSearch = () => {
pagination.value.currentPage = 1
loadFlowInstances()
}
```
### 2.8 选择流程
**函数**: `handleSelectFlow(row)`第572-575行
**触发**: 点击表格行
**操作**: 设置 `selectedRow.value = row`(高亮当前行)
```572:575:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 处理选择行
const handleSelectFlow = (row) => {
selectedRow.value = row
}
```
### 2.9 确认选择
**函数**: `confirmSelect()`第577-592行
**触发**: 点击对话框底部的"确定"按钮
**前置条件**: `selectedRow.value !== null`(必须已选择一行)
**操作步骤**:
1. 验证是否已选择流程
2. 设置 `selectedFlow.value = selectedRow.value`
3. 触发 `update:modelValue` 事件传递流程ID
4. 触发 `change` 事件传递流程ID
5. 加载流程详情用于展示
6. 关闭对话框
```577:592:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 确认选择
const confirmSelect = () => {
if (!selectedRow.value) {
ElMessage.warning('请选择一条流程')
return
}
selectedFlow.value = selectedRow.value
emit('update:modelValue', selectedRow.value.id)
emit('change', selectedRow.value.id)
// 加载详情用于展示
loadFlowDetail(selectedRow.value.id)
showSelectDialog.value = false
}
```
### 2.10 加载流程详情
**函数**: `loadFlowDetail(flowId)`第524-570行
**触发时机**:
1. 确认选择后,加载选中流程的详情
2. 查看详情时,加载指定流程的详情
3. 组件初始化时,如果有初始值,加载详情
**缓存机制**:
1. 先检查缓存 `flowDetailsCache.value[flowId]`
2. 如果缓存存在,直接使用
3. 否则从列表数据中查找
4. 如果列表中没有调用API加载
**API调用**: `GET /api/budget/planned-expenditures/oa-flow-details`
**请求参数**:
```javascript
{
flow_ids: [flowId] // 流程ID数组
}
```
**响应数据**:
```javascript
{
code: 0,
data: {
'flowId': { // key为流程ID
id: 1,
no: 'FLOW-001',
title: '流程标题',
display_name: 'FLOW-001 - 流程标题',
status: 'approved',
list_fields: [], // 列表字段
all_fields: [] // 全部字段(用于详情)
}
}
}
```
### 2.11 更新显示状态
**位置**: 组件模板第6-27行
**显示内容**:
- 流程图标DocumentChecked
- 流程显示名称(编号 - 标题)
- 流程状态标签(带颜色)
- 清除按钮(右上角)
**样式**: 绿色背景卡片,带边框
### 2.12 清除选择
**函数**: `clearSelection()`第594-600行
**触发**: 点击已选择流程卡片右上角的清除按钮
**操作**:
1. 清空 `selectedFlow.value`
2. 清空 `selectedRow.value`
3. 触发 `update:modelValue` 事件,传递 `null`
4. 触发 `change` 事件,传递 `null`
**结果**: 恢复显示"选择事前流程"按钮
```594:600:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 清除选择
const clearSelection = () => {
selectedFlow.value = null
selectedRow.value = null
emit('update:modelValue', null)
emit('change', null)
}
```
## 三、完整操作流程图
```mermaid
sequenceDiagram
participant U as 用户
participant C as PreApprovalFlowPicker组件
participant API as 后端API
U->>C: 点击"选择事前流程"按钮
C->>C: showSelectDialog = true
C->>C: handleDialogOpened()
C->>API: GET /oa-flow-years (加载年份列表)
API-->>C: 返回年份数组
C->>C: 自动选定年份(优先当年)
C->>API: GET /oa-flow-instances (加载流程列表)
API-->>C: 返回流程列表和动态列
C->>U: 显示流程列表对话框
alt 用户切换年份
U->>C: 切换年份
C->>C: handleYearChange()
C->>C: 重置分页和搜索
C->>API: GET /oa-flow-instances (重新加载)
API-->>C: 返回新年份的流程列表
end
alt 用户搜索
U->>C: 输入搜索关键词
C->>C: handleSearch()
C->>API: GET /oa-flow-instances (带关键词)
API-->>C: 返回搜索结果
end
U->>C: 点击表格行选择流程
C->>C: handleSelectFlow(row)
C->>C: selectedRow = row (高亮行)
U->>C: 点击"确定"按钮
C->>C: confirmSelect()
C->>C: selectedFlow = selectedRow
C->>C: emit('update:modelValue', flowId)
C->>API: GET /oa-flow-details (加载详情)
API-->>C: 返回流程详情
C->>C: 缓存详情数据
C->>C: showSelectDialog = false
C->>U: 显示已选择流程卡片
alt 用户清除选择
U->>C: 点击清除按钮
C->>C: clearSelection()
C->>C: emit('update:modelValue', null)
C->>U: 恢复显示"选择事前流程"按钮
end
```
## 四、数据流
### 4.1 数据绑定
**父组件** (`IndirectPayment.vue`):
- `v-model="formData[field.key]"` - 双向绑定流程ID
- `:model-id="field.model_id"` - 传递流程模型ID
**子组件** (`PreApprovalFlowPicker.vue`):
- `props.modelValue` - 接收流程ID
- `props.modelId` - 接收流程模型ID
- `emit('update:modelValue', flowId)` - 更新流程ID
### 4.2 数据存储
**表单数据**:
```javascript
formData = {
'element_123': 456, // 字段key: 流程ID
// ... 其他字段
}
```
**组件内部状态**:
```javascript
selectedFlow.value = {
id: 456,
no: 'FLOW-001',
title: '流程标题',
status: 'approved',
// ... 其他字段
}
```
### 4.3 监听数据变化
**监听 modelValue 变化**第634-647行:
- 当外部传入的 `modelValue` 变化时
- 如果有值且与当前选中的流程ID不同重新加载详情
- 如果值为空,清空选中状态
```634:647:czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue
// 监听 modelValue 变化,加载详情
watch(() => props.modelValue, (newValue) => {
if (newValue) {
// 如果有值但没有选中流程,需要加载
if (!selectedFlow.value || selectedFlow.value.id !== newValue) {
loadFlowDetail(newValue).then(() => {
if (detailFlow.value) {
selectedFlow.value = detailFlow.value
}
})
}
} else {
selectedFlow.value = null
}
}, { immediate: true })
```
## 五、关键配置和参数
### 5.1 组件属性
| 属性 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| `modelValue` | Number/String/null | 否 | null | 双向绑定流程ID |
| `modelId` | Number/String | 是 | - | 流程模型ID |
| `placeholder` | String | 否 | '请选择事前流程实例' | 占位符文本 |
| `disabled` | Boolean | 否 | false | 是否禁用 |
| `readonly` | Boolean | 否 | false | 是否只读 |
| `autoLoad` | Boolean | 否 | false | 是否自动加载 |
### 5.2 对话框配置
- **宽度**: 80%
- **标题**: "选择事前流程实例"
- **关闭方式**: 点击遮罩不关闭(`:close-on-click-modal="false"`
### 5.3 分页配置
- **默认每页**: 15条
- **可选每页**: 15/30/50/100条
- **布局**: total, sizes, prev, pager, next
## 六、注意事项
### 6.1 必填参数
- `modelId` 必须提供,否则无法加载流程列表
- 年份必须选择,否则不加载流程列表
### 6.2 数据一致性
- 流程ID必须与 `modelId` 对应的流程模型匹配
- 选择流程后,流程详情会在确认时加载并缓存
- 如果流程不存在,会显示友好的错误信息
### 6.3 性能优化
- 流程列表支持分页,避免一次性加载过多数据
- 流程详情使用缓存机制,避免重复请求
- 年份列表只在对话框打开时加载一次
### 6.4 错误处理
- API 请求失败时显示错误提示
- 流程不存在时显示友好的提示信息
- 网络错误时不影响其他功能
- 年份加载失败时默认使用当前年份
### 6.5 用户体验
- 自动选定当前年份,减少用户操作
- 支持搜索,快速定位流程
- 支持查看详情,了解流程完整信息
- 已选择状态清晰展示,带状态标签
- 支持清除选择,方便重新选择
## 七、相关文件
### 7.1 组件文件
- `czemc-budget-execution-frontend/src/components/PreApprovalFlowPicker.vue`
### 7.2 页面文件
- `czemc-budget-execution-frontend/src/views/payment/IndirectPayment.vue`
### 7.3 API 文件
- `czemc-budget-execution-frontend/src/utils/api.js`
- `plannedExpenditureAPI.getOaFlowYears()` - 获取可用年份列表
- `plannedExpenditureAPI.getOaFlowInstances()` - 获取流程实例列表
- `plannedExpenditureAPI.getOaFlowDetails()` - 批量获取流程详情
### 7.4 后端接口
- `GET /api/budget/planned-expenditures/oa-flow-years` - 获取可用年份列表
- `GET /api/budget/planned-expenditures/oa-flow-instances` - 获取流程实例列表
- `GET /api/budget/planned-expenditures/oa-flow-details` - 批量获取流程详情