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

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}`"
/>

触发条件:

  1. 当前字段的 element_type === 'oa_custom_model'(事前流程类型)
  2. 字段必须存在于模板配置中(从 templateConfig 中获取)
  3. 字段的 visible !== false(字段可见)
  4. 字段通过显示条件检查(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>

显示逻辑:

  • 显示"选择事前流程"按钮的条件:

    1. readonly === false(非只读模式)
    2. selectedFlow === nullselectedFlow === undefined(未选择流程)
    3. disabled === false(按钮未禁用)
  • 显示已选择流程卡片的条件:

    1. readonly === false(非只读模式)
    2. selectedFlow !== nullselectedFlow !== 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()
  }
}

执行顺序:

  1. 调用 loadAvailableYears() 加载可用年份列表
  2. 自动选定年份(优先当年,否则最新年份)
  3. 如果有选中的年份,调用 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行

触发时机:

  1. 对话框打开后,年份加载完成且有选中年份
  2. 年份切换时
  3. 搜索关键词变化时
  4. 分页参数变化时

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'
      }
    ]
  }
}

处理逻辑:

  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. 重新加载流程列表
// 年份切换
const handleYearChange = () => {
  pagination.value.currentPage = 1
  searchKeyword.value = ''
  selectedRow.value = null
  loadFlowInstances()
}

2.7 搜索功能

函数: handleSearch()第616-620行

触发: 搜索输入框内容变化(@input 事件)

操作:

  1. 重置分页到第一页
  2. 重新加载流程列表(带搜索关键词)
// 搜索
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(必须已选择一行)

操作步骤:

  1. 验证是否已选择流程
  2. 设置 selectedFlow.value = selectedRow.value
  3. 触发 update:modelValue 事件传递流程ID
  4. 触发 change 事件传递流程ID
  5. 加载流程详情用于展示
  6. 关闭对话框
// 确认选择
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

请求参数:

{
  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行

触发: 点击已选择流程卡片右上角的清除按钮

操作:

  1. 清空 selectedFlow.value
  2. 清空 selectedRow.value
  3. 触发 update:modelValue 事件,传递 null
  4. 触发 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 - 接收流程ID
  • props.modelId - 接收流程模型ID
  • emit('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.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 - 批量获取流程详情