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

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.

# 间接支付页面 - 第三步"选择事前流程"按钮逻辑说明
## 页面信息
- **页面路径**: `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` - 批量获取流程详情