17 KiB
间接支付页面 - 第三步"选择事前流程"按钮逻辑说明
页面信息
- 页面路径:
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行
<!-- 事前流程实例 -->
<PreApprovalFlowPicker
v-else-if="field.element_type === 'oa_custom_model'"
v-model="formData[field.key]"
:model-id="field.model_id"
:placeholder="`请选择${field.label}`"
/>
触发条件:
- 当前字段的
element_type === 'oa_custom_model'(事前流程类型) - 字段必须存在于模板配置中(从
templateConfig中获取) - 字段的
visible !== false(字段可见) - 字段通过显示条件检查(
checkFieldDisplayConditions(f))
1.2 按钮显示/隐藏逻辑
位置: PreApprovalFlowPicker.vue 第4-40行
<!-- 录入态 -->
<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>
显示逻辑:
-
显示"选择事前流程"按钮的条件:
readonly === false(非只读模式)selectedFlow === null或selectedFlow === undefined(未选择流程)disabled === false(按钮未禁用)
-
显示已选择流程卡片的条件:
readonly === false(非只读模式)selectedFlow !== null且selectedFlow !== undefined(已选择流程)
1.3 状态判断流程
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行
@click="showSelectDialog = true"
2.2 对话框打开事件处理
函数: handleDialogOpened()(第623-631行)
// 对话框打开时先加载年份列表,然后加载流程列表
const handleDialogOpened = async () => {
// 先加载年份列表
await loadAvailableYears()
// 年份加载完成后,如果有选中的年份,再加载流程列表
if (selectedYear.value) {
loadFlowInstances()
}
}
执行顺序:
- 调用
loadAvailableYears()加载可用年份列表 - 自动选定年份(优先当年,否则最新年份)
- 如果有选中的年份,调用
loadFlowInstances()加载流程列表
2.3 加载年份列表
函数: loadAvailableYears()(第416-453行)
API调用: GET /api/budget/planned-expenditures/oa-flow-years
请求参数:
{
custom_model_id: props.modelId // 流程模型ID
}
响应处理:
- 如果返回年份数组,设置
availableYears.value - 自动选定年份:
- 优先选择当前年份(如果存在)
- 否则选择数组中的第一个年份(最新年份)
- 如果无数据或出错,默认使用当前年份
关键代码:
// 自动选定当年
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行)
触发时机:
- 对话框打开后,年份加载完成且有选中年份
- 年份切换时
- 搜索关键词变化时
- 分页参数变化时
API调用: GET /api/budget/planned-expenditures/oa-flow-instances
请求参数:
{
custom_model_id: props.modelId, // 流程模型ID(必填)
year: selectedYear.value, // 年份(必填)
page: pagination.value.currentPage, // 当前页码
page_size: pagination.value.pageSize, // 每页数量
keyword: searchKeyword.value // 搜索关键词(可选)
}
响应数据结构:
{
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'
}
]
}
}
处理逻辑:
- 提取
list_columns作为动态表格列 - 提取
data作为流程列表数据 - 更新分页信息(
total) - 设置加载状态
2.5 流程列表展示
位置: 对话框中的表格(第107-166行)
表格列结构:
- 序号列 (#) - 宽度 60px
- 流程编号 (no) - 宽度 150px,支持溢出提示
- 流程标题 (title) - 最小宽度 250px,支持溢出提示
- 动态列 - 根据
list_columns动态生成,显示show_in_list=1的字段 - 流程状态 - 宽度 120px,显示状态标签(带颜色)
- 创建时间 - 宽度 180px
- 关联信息 - 宽度 200px,显示已关联数量
- 操作 - 宽度 120px,固定右侧,包含"查看详情"按钮
交互特性:
- 支持行点击选择(高亮当前行)
- 支持搜索功能(搜索流程编号、标题、列表字段)
- 支持分页(15/30/50/100 条每页)
- 支持查看详情(打开详情对话框)
2.6 年份切换
函数: handleYearChange()(第608-614行)
触发: 用户切换年份单选按钮
操作:
- 重置分页到第一页
- 清空搜索关键词
- 清空已选中的行
- 重新加载流程列表
// 年份切换
const handleYearChange = () => {
pagination.value.currentPage = 1
searchKeyword.value = ''
selectedRow.value = null
loadFlowInstances()
}
2.7 搜索功能
函数: handleSearch()(第616-620行)
触发: 搜索输入框内容变化(@input 事件)
操作:
- 重置分页到第一页
- 重新加载流程列表(带搜索关键词)
// 搜索
const handleSearch = () => {
pagination.value.currentPage = 1
loadFlowInstances()
}
2.8 选择流程
函数: handleSelectFlow(row)(第572-575行)
触发: 点击表格行
操作: 设置 selectedRow.value = row(高亮当前行)
// 处理选择行
const handleSelectFlow = (row) => {
selectedRow.value = row
}
2.9 确认选择
函数: confirmSelect()(第577-592行)
触发: 点击对话框底部的"确定"按钮
前置条件: selectedRow.value !== null(必须已选择一行)
操作步骤:
- 验证是否已选择流程
- 设置
selectedFlow.value = selectedRow.value - 触发
update:modelValue事件,传递流程ID - 触发
change事件,传递流程ID - 加载流程详情用于展示
- 关闭对话框
// 确认选择
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行)
触发时机:
- 确认选择后,加载选中流程的详情
- 查看详情时,加载指定流程的详情
- 组件初始化时,如果有初始值,加载详情
缓存机制:
- 先检查缓存
flowDetailsCache.value[flowId] - 如果缓存存在,直接使用
- 否则从列表数据中查找
- 如果列表中没有,调用API加载
API调用: GET /api/budget/planned-expenditures/oa-flow-details
请求参数:
{
flow_ids: [flowId] // 流程ID数组
}
响应数据:
{
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行)
触发: 点击已选择流程卡片右上角的清除按钮
操作:
- 清空
selectedFlow.value - 清空
selectedRow.value - 触发
update:modelValue事件,传递null - 触发
change事件,传递null
结果: 恢复显示"选择事前流程"按钮
// 清除选择
const clearSelection = () => {
selectedFlow.value = null
selectedRow.value = null
emit('update:modelValue', null)
emit('change', null)
}
三、完整操作流程图
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- 接收流程IDprops.modelId- 接收流程模型IDemit('update:modelValue', flowId)- 更新流程ID
4.2 数据存储
表单数据:
formData = {
'element_123': 456, // 字段key: 流程ID
// ... 其他字段
}
组件内部状态:
selectedFlow.value = {
id: 456,
no: 'FLOW-001',
title: '流程标题',
status: 'approved',
// ... 其他字段
}
4.3 监听数据变化
监听 modelValue 变化(第634-647行):
- 当外部传入的
modelValue变化时 - 如果有值且与当前选中的流程ID不同,重新加载详情
- 如果值为空,清空选中状态
// 监听 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.jsplannedExpenditureAPI.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- 批量获取流程详情