|
|
<template>
|
|
|
<div class="process-query-container">
|
|
|
<!-- 页面头部 -->
|
|
|
<div class="page-header">
|
|
|
<h1 class="page-title">
|
|
|
<el-icon :size="24"><Search /></el-icon>
|
|
|
流程查询
|
|
|
</h1>
|
|
|
</div>
|
|
|
|
|
|
<!-- 子流程卡片网格选择器 -->
|
|
|
<el-card class="subprocess-selector-section" shadow="never" v-loading="subprocessLoading">
|
|
|
<div class="subprocess-grid" v-if="availableSubprocesses.length > 0">
|
|
|
<div
|
|
|
v-for="subprocess in availableSubprocesses"
|
|
|
:key="subprocess.id"
|
|
|
class="subprocess-card"
|
|
|
:class="{ active: selectedSubprocess?.id === subprocess.id }"
|
|
|
@click="handleSubprocessSelect(subprocess)"
|
|
|
>
|
|
|
<div class="subprocess-icon">
|
|
|
<el-icon :size="32">
|
|
|
<component :is="getIcon(subprocess.icon || subprocess.custom_model?.icon)" />
|
|
|
</el-icon>
|
|
|
</div>
|
|
|
<div class="subprocess-name">{{ subprocess.name || subprocess.custom_model?.name || '未命名流程' }}</div>
|
|
|
<div class="subprocess-description" v-if="subprocess.description">
|
|
|
{{ subprocess.description }}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<el-empty v-else description="暂无可用流程" />
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 查询类型和筛选区域 -->
|
|
|
<el-card class="filter-section" shadow="never">
|
|
|
<!-- 查询类型和年份选择(合并到同一行) -->
|
|
|
<div class="filter-bar" v-if="selectedSubprocess">
|
|
|
<div class="filter-group">
|
|
|
<span class="filter-label">关联状态:</span>
|
|
|
<el-radio-group v-model="queryType" @change="handleQueryTypeChange">
|
|
|
<el-radio-button label="not-linked">未关联支付</el-radio-button>
|
|
|
<el-radio-button label="linked">已关联支付</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</div>
|
|
|
<div class="filter-group" v-loading="loadingYears">
|
|
|
<span class="filter-label">发起年份:</span>
|
|
|
<el-radio-group v-model="selectedYear" @change="handleYearChange">
|
|
|
<el-radio-button
|
|
|
v-for="year in availableYears"
|
|
|
:key="year"
|
|
|
:label="year"
|
|
|
>
|
|
|
{{ year }}年
|
|
|
</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- 如果未选择子流程,只显示关联状态选择 -->
|
|
|
<div class="filter-bar" v-else>
|
|
|
<div class="filter-group">
|
|
|
<span class="filter-label">关联状态:</span>
|
|
|
<el-radio-group v-model="queryType" @change="handleQueryTypeChange">
|
|
|
<el-radio-button label="not-linked">未关联支付</el-radio-button>
|
|
|
<el-radio-button label="linked">已关联支付</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 筛选表单 -->
|
|
|
<el-form :model="filterForm" inline style="margin-top: 16px">
|
|
|
<el-form-item label="流程编号">
|
|
|
<el-input
|
|
|
v-model="filterForm.keyword"
|
|
|
placeholder="请输入流程编号或关键词"
|
|
|
style="width: 200px"
|
|
|
clearable
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-button type="primary" @click="handleSearch">
|
|
|
<el-icon><Search /></el-icon>
|
|
|
查询
|
|
|
</el-button>
|
|
|
</el-form-item>
|
|
|
<el-form-item>
|
|
|
<el-button @click="handleReset">
|
|
|
<el-icon><Refresh /></el-icon>
|
|
|
重置
|
|
|
</el-button>
|
|
|
</el-form-item>
|
|
|
</el-form>
|
|
|
</el-card>
|
|
|
|
|
|
<!-- 表格区域 -->
|
|
|
<el-card shadow="never">
|
|
|
<el-table :data="tableData" style="width: 100%" v-loading="loading" border>
|
|
|
<el-table-column type="index" label="序号" width="60" />
|
|
|
<el-table-column prop="no" label="流程编号" width="180" />
|
|
|
<!-- 动态字段列(放在流程编号后面) -->
|
|
|
<template v-if="Array.isArray(dynamicFields) && dynamicFields.length > 0">
|
|
|
<el-table-column
|
|
|
v-for="field in dynamicFields"
|
|
|
:key="field.id"
|
|
|
:prop="`data.${field.name}`"
|
|
|
:label="field.label"
|
|
|
:min-width="getFieldWidth(field.type)"
|
|
|
>
|
|
|
<template #default="scope">
|
|
|
{{ formatFieldValue(scope.row.data, field) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</template>
|
|
|
<el-table-column prop="custom_model" label="流程类型" min-width="150">
|
|
|
<template #default="scope">
|
|
|
<div class="type-cell">
|
|
|
<el-icon :size="16" v-if="scope.row.custom_model?.icon || scope.row.customModel?.icon">
|
|
|
<component :is="getIcon(scope.row.custom_model?.icon || scope.row.customModel?.icon)" />
|
|
|
</el-icon>
|
|
|
<span>{{ scope.row.custom_model?.name || scope.row.customModel?.name || '-' }}</span>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip>
|
|
|
<template #default="scope">
|
|
|
<div class="title-cell">
|
|
|
<span>{{ scope.row.title || '-' }}</span>
|
|
|
<el-tooltip
|
|
|
:content="scope.row.is_created_by_me ? '我创建的' : '我办理过的'"
|
|
|
placement="top"
|
|
|
>
|
|
|
<el-icon
|
|
|
:size="14"
|
|
|
:color="scope.row.is_created_by_me ? '#409eff' : '#67c23a'"
|
|
|
style="margin-left: 6px; cursor: pointer;"
|
|
|
>
|
|
|
<component :is="scope.row.is_created_by_me ? User : Check" />
|
|
|
</el-icon>
|
|
|
</el-tooltip>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="creator" label="申请人" width="100">
|
|
|
<template #default="scope">
|
|
|
{{ scope.row.creator?.name || scope.row.creator_name || '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="creator_department" label="部门" width="120">
|
|
|
<template #default="scope">
|
|
|
{{ scope.row.creator_department?.name || scope.row.creator_department_name || '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="status" label="状态" width="100">
|
|
|
<template #default="scope">
|
|
|
<el-tag :type="getStatusType(scope.row.status)">
|
|
|
{{ getStatusText(scope.row.status) }}
|
|
|
</el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="current_node" label="当前步骤" width="120">
|
|
|
<template #default="scope">
|
|
|
{{ scope.row.current_node?.name || scope.row.currentNode?.name || '-' }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="created_at" label="创建时间" width="180">
|
|
|
<template #default="scope">
|
|
|
{{ formatDateTime(scope.row.created_at) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="相关支付情况" width="200" fixed="right">
|
|
|
<template #default="scope">
|
|
|
<div v-if="scope.row.related_payments && scope.row.related_payments.length > 0" class="related-payments">
|
|
|
<el-tag
|
|
|
v-for="(payment, idx) in scope.row.related_payments"
|
|
|
:key="payment.id"
|
|
|
type="primary"
|
|
|
size="small"
|
|
|
class="payment-tag"
|
|
|
@click.stop="handleViewPayment(payment)"
|
|
|
>
|
|
|
{{ payment.serial_number }}
|
|
|
</el-tag>
|
|
|
</div>
|
|
|
<span v-else class="no-payment">-</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" width="150" fixed="right">
|
|
|
<template #default="scope">
|
|
|
<el-button type="primary" link size="small" @click="handleView(scope.row)">
|
|
|
查看
|
|
|
</el-button>
|
|
|
<el-button
|
|
|
v-if="scope.row.status === 0"
|
|
|
type="success"
|
|
|
link
|
|
|
size="small"
|
|
|
@click="handleEdit(scope.row)"
|
|
|
>
|
|
|
编辑
|
|
|
</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
|
|
|
<div class="pagination-container">
|
|
|
<el-pagination
|
|
|
v-model:current-page="pagination.currentPage"
|
|
|
v-model:page-size="pagination.pageSize"
|
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
|
:total="pagination.total"
|
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
|
@size-change="handleSizeChange"
|
|
|
@current-change="handleCurrentChange"
|
|
|
/>
|
|
|
</div>
|
|
|
</el-card>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, onMounted, watch } from 'vue'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
import { ElMessage } from 'element-plus'
|
|
|
import {
|
|
|
Search,
|
|
|
Refresh,
|
|
|
Document,
|
|
|
User,
|
|
|
Check
|
|
|
} from '@element-plus/icons-vue'
|
|
|
// 动态导入所有图标
|
|
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
|
|
import { preApprovalProcessConfigAPI, oaFlowAPI } from '@/utils/api'
|
|
|
import { getToken } from '@/utils/auth'
|
|
|
import config from '@/config'
|
|
|
|
|
|
const router = useRouter()
|
|
|
|
|
|
// 加载状态
|
|
|
const loading = ref(false)
|
|
|
const subprocessLoading = ref(false)
|
|
|
const loadingYears = ref(false)
|
|
|
|
|
|
// 可用子流程列表
|
|
|
const availableSubprocesses = ref([])
|
|
|
// 选中的子流程
|
|
|
const selectedSubprocess = ref(null)
|
|
|
|
|
|
// 查询类型:not-linked(未关联支付)或 linked(已关联支付)
|
|
|
const queryType = ref('not-linked')
|
|
|
|
|
|
// 动态字段列表(show_in_list=1的字段)
|
|
|
const dynamicFields = ref([])
|
|
|
|
|
|
// 表格数据
|
|
|
const tableData = ref([])
|
|
|
|
|
|
// 年份相关
|
|
|
const availableYears = ref([]) // 可用年份列表
|
|
|
const selectedYear = ref(null) // 选中的年份
|
|
|
|
|
|
// 筛选表单
|
|
|
const filterForm = ref({
|
|
|
keyword: ''
|
|
|
})
|
|
|
|
|
|
// 分页
|
|
|
const pagination = ref({
|
|
|
currentPage: 1,
|
|
|
pageSize: 10,
|
|
|
total: 0
|
|
|
})
|
|
|
|
|
|
// 图标映射
|
|
|
const iconMap = {
|
|
|
Document
|
|
|
}
|
|
|
|
|
|
// 获取图标组件
|
|
|
const getIcon = (iconName) => {
|
|
|
if (!iconName) {
|
|
|
return Document
|
|
|
}
|
|
|
// 首先尝试从静态映射表获取
|
|
|
if (iconMap[iconName]) {
|
|
|
return iconMap[iconName]
|
|
|
}
|
|
|
// 然后尝试从ElementPlusIconsVue动态获取
|
|
|
if (ElementPlusIconsVue[iconName]) {
|
|
|
return ElementPlusIconsVue[iconName]
|
|
|
}
|
|
|
// 如果找不到,返回默认图标
|
|
|
return Document
|
|
|
}
|
|
|
|
|
|
// 获取状态类型
|
|
|
const getStatusType = (status) => {
|
|
|
const statusMap = {
|
|
|
0: 'warning', // 待审批
|
|
|
1: 'success', // 已批准
|
|
|
2: 'danger', // 已拒绝
|
|
|
3: 'info' // 已撤回
|
|
|
}
|
|
|
return statusMap[status] || ''
|
|
|
}
|
|
|
|
|
|
// 获取状态文本
|
|
|
const getStatusText = (status) => {
|
|
|
const statusMap = {
|
|
|
0: '待审批',
|
|
|
1: '已批准',
|
|
|
2: '已拒绝',
|
|
|
3: '已撤回'
|
|
|
}
|
|
|
return statusMap[status] || '未知'
|
|
|
}
|
|
|
|
|
|
// 格式化日期时间
|
|
|
const formatDateTime = (dateTime) => {
|
|
|
if (!dateTime) return '-'
|
|
|
const date = new Date(dateTime)
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
year: 'numeric',
|
|
|
month: '2-digit',
|
|
|
day: '2-digit',
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit'
|
|
|
})
|
|
|
}
|
|
|
|
|
|
// 获取字段宽度
|
|
|
const getFieldWidth = (fieldType) => {
|
|
|
const widthMap = {
|
|
|
'text': 120,
|
|
|
'textarea': 200,
|
|
|
'number': 100,
|
|
|
'money': 120,
|
|
|
'date': 120,
|
|
|
'datetime': 150,
|
|
|
'select': 120,
|
|
|
'file': 150
|
|
|
}
|
|
|
return widthMap[fieldType] || 120
|
|
|
}
|
|
|
|
|
|
// 格式化字段值
|
|
|
const formatFieldValue = (data, field) => {
|
|
|
if (!data || !field || !field.name) return '-'
|
|
|
|
|
|
// 尝试多种方式获取值
|
|
|
let value = data[field.name]
|
|
|
|
|
|
// 如果 data 是对象但没有该字段,尝试其他路径
|
|
|
if (value === undefined && typeof data === 'object') {
|
|
|
// 尝试使用字段的 label 或其他可能的键
|
|
|
value = data[field.label] || data[`${field.name}_text`] || null
|
|
|
}
|
|
|
|
|
|
if (value === null || value === undefined || value === '') return '-'
|
|
|
|
|
|
switch (field.type) {
|
|
|
case 'number':
|
|
|
case 'money':
|
|
|
return typeof value === 'number' ? value.toLocaleString('zh-CN') : value
|
|
|
case 'date':
|
|
|
if (value) {
|
|
|
try {
|
|
|
const date = new Date(value)
|
|
|
if (!isNaN(date.getTime())) {
|
|
|
return date.toLocaleDateString('zh-CN')
|
|
|
}
|
|
|
} catch (e) {
|
|
|
console.warn('日期格式化失败:', value, e)
|
|
|
}
|
|
|
}
|
|
|
return '-'
|
|
|
case 'datetime':
|
|
|
if (value) {
|
|
|
try {
|
|
|
return formatDateTime(value)
|
|
|
} catch (e) {
|
|
|
console.warn('日期时间格式化失败:', value, e)
|
|
|
}
|
|
|
}
|
|
|
return '-'
|
|
|
case 'select':
|
|
|
case 'radio':
|
|
|
// 如果是选项类型,尝试显示选项文本
|
|
|
if (field.options && Array.isArray(field.options)) {
|
|
|
const option = field.options.find(opt => opt.value === value || opt.id === value)
|
|
|
return option ? (option.label || option.name || value) : value
|
|
|
}
|
|
|
return value
|
|
|
case 'file':
|
|
|
case 'files':
|
|
|
// 文件类型,显示文件数量或文件名
|
|
|
if (Array.isArray(value)) {
|
|
|
return `${value.length} 个文件`
|
|
|
}
|
|
|
return value
|
|
|
default:
|
|
|
return String(value)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载年份列表
|
|
|
const loadAvailableYears = async () => {
|
|
|
if (!selectedSubprocess.value || !selectedSubprocess.value.custom_model_id) {
|
|
|
availableYears.value = []
|
|
|
return
|
|
|
}
|
|
|
|
|
|
loadingYears.value = true
|
|
|
try {
|
|
|
const response = await oaFlowAPI.getFlowYears({
|
|
|
custom_model_id: selectedSubprocess.value.custom_model_id
|
|
|
})
|
|
|
|
|
|
if (response.code === 0 && Array.isArray(response.data)) {
|
|
|
availableYears.value = response.data
|
|
|
|
|
|
// 自动选定当年
|
|
|
const currentYear = new Date().getFullYear()
|
|
|
if (availableYears.value.includes(currentYear)) {
|
|
|
selectedYear.value = currentYear
|
|
|
} else if (availableYears.value.length > 0) {
|
|
|
// 如果没有当年,选择最新的年份
|
|
|
selectedYear.value = availableYears.value[0]
|
|
|
} else {
|
|
|
selectedYear.value = null
|
|
|
}
|
|
|
} else {
|
|
|
availableYears.value = []
|
|
|
// 如果没有年份数据,默认使用当年
|
|
|
selectedYear.value = new Date().getFullYear()
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('加载年份列表失败:', error)
|
|
|
availableYears.value = []
|
|
|
// 出错时默认使用当年
|
|
|
selectedYear.value = new Date().getFullYear()
|
|
|
} finally {
|
|
|
loadingYears.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 年份切换
|
|
|
const handleYearChange = () => {
|
|
|
pagination.value.currentPage = 1
|
|
|
filterForm.value.keyword = ''
|
|
|
loadFlowList()
|
|
|
}
|
|
|
|
|
|
// 选择子流程
|
|
|
const handleSubprocessSelect = async (subprocess) => {
|
|
|
if (!subprocess.custom_model_id) {
|
|
|
ElMessage.warning('该流程项未关联OA模型')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 立即清空表格数据,避免显示上一个流程的数据
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
pagination.value.currentPage = 1
|
|
|
|
|
|
selectedSubprocess.value = subprocess
|
|
|
|
|
|
// 加载模型字段
|
|
|
await loadModelFields(subprocess.custom_model_id)
|
|
|
|
|
|
// 先加载年份列表
|
|
|
await loadAvailableYears()
|
|
|
|
|
|
// 年份加载完成后,再加载流程列表
|
|
|
// 即使没有年份,loadFlowList 内部也会处理清空逻辑,但这里已经提前清空了
|
|
|
if (selectedYear.value) {
|
|
|
await loadFlowList()
|
|
|
} else {
|
|
|
// 如果没有年份数据,确保表格是空的(已经在上面清空了)
|
|
|
// 可以显示提示信息
|
|
|
console.log('该流程类型暂无数据')
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 查询类型改变
|
|
|
const handleQueryTypeChange = () => {
|
|
|
if (selectedSubprocess.value) {
|
|
|
loadFlowList()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载可用子流程
|
|
|
const loadAvailableSubprocesses = async () => {
|
|
|
subprocessLoading.value = true
|
|
|
try {
|
|
|
const res = await preApprovalProcessConfigAPI.getForStartProcess()
|
|
|
if (res.code === 0) {
|
|
|
// 提取所有流程项(第二层)
|
|
|
const subprocesses = []
|
|
|
|
|
|
// 确保 res.data 是数组
|
|
|
const data = Array.isArray(res.data) ? res.data : []
|
|
|
|
|
|
data.forEach(group => {
|
|
|
// 确保 children 是数组
|
|
|
const children = Array.isArray(group.active_children)
|
|
|
? group.active_children
|
|
|
: Array.isArray(group.activeChildren)
|
|
|
? group.activeChildren
|
|
|
: Array.isArray(group.children)
|
|
|
? group.children
|
|
|
: []
|
|
|
|
|
|
children.forEach(child => {
|
|
|
if (child && child.custom_model_id) {
|
|
|
subprocesses.push({
|
|
|
id: child.id,
|
|
|
name: child.name,
|
|
|
description: child.description,
|
|
|
icon: child.icon || child.custom_model?.icon,
|
|
|
custom_model_id: child.custom_model_id,
|
|
|
custom_model: child.custom_model
|
|
|
})
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
|
|
|
availableSubprocesses.value = subprocesses
|
|
|
|
|
|
// 默认选中第一个
|
|
|
if (subprocesses.length > 0) {
|
|
|
await handleSubprocessSelect(subprocesses[0])
|
|
|
}
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || res.message || '获取流程配置失败')
|
|
|
availableSubprocesses.value = []
|
|
|
}
|
|
|
} catch (error) {
|
|
|
ElMessage.error('获取流程配置失败:' + error.message)
|
|
|
availableSubprocesses.value = []
|
|
|
} finally {
|
|
|
subprocessLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载模型字段
|
|
|
const loadModelFields = async (customModelId) => {
|
|
|
try {
|
|
|
const res = await oaFlowAPI.getCustomModelFields(customModelId)
|
|
|
if (res.code === 0 && res.data?.customModel?.fields) {
|
|
|
// 确保 fields 是数组
|
|
|
const fieldsArray = Array.isArray(res.data.customModel.fields)
|
|
|
? res.data.customModel.fields
|
|
|
: []
|
|
|
|
|
|
// 筛选 show_in_list = 1 的字段,并按 myindex 排序
|
|
|
const fields = fieldsArray
|
|
|
.filter(field => field && field.show_in_list === 1)
|
|
|
.sort((a, b) => (a.myindex || 0) - (b.myindex || 0))
|
|
|
|
|
|
dynamicFields.value = fields
|
|
|
} else {
|
|
|
dynamicFields.value = []
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error('获取模型字段失败:', error)
|
|
|
dynamicFields.value = []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载流程列表
|
|
|
const loadFlowList = async () => {
|
|
|
if (!selectedSubprocess.value || !selectedSubprocess.value.custom_model_id) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 如果没有选中年份,不加载数据
|
|
|
if (!selectedYear.value) {
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
return
|
|
|
}
|
|
|
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const params = {
|
|
|
custom_model_id: selectedSubprocess.value.custom_model_id,
|
|
|
year: selectedYear.value, // 必填:年份参数
|
|
|
page: pagination.value.currentPage,
|
|
|
page_size: pagination.value.pageSize,
|
|
|
is_simple: 0, // 完整版本,包含data字段
|
|
|
payment_link_status: queryType.value, // 关联支付状态:not-linked 或 linked
|
|
|
...filterForm.value
|
|
|
}
|
|
|
|
|
|
// 移除空值
|
|
|
Object.keys(params).forEach(key => {
|
|
|
if (params[key] === '' || params[key] === null || params[key] === undefined) {
|
|
|
delete params[key]
|
|
|
}
|
|
|
})
|
|
|
|
|
|
// 使用 "all" 类型,因为我们现在通过 payment_link_status 参数来过滤
|
|
|
const res = await oaFlowAPI.getFlowList('all', params)
|
|
|
if (res.code === 0) {
|
|
|
// 后端返回结构:res.data.data 是分页对象(Laravel Paginator)
|
|
|
// 分页对象包含:{ data: [...], total: 100, current_page: 1, per_page: 10, ... }
|
|
|
const paginationData = res.data?.data
|
|
|
|
|
|
if (paginationData) {
|
|
|
// 如果 paginationData 有 data 属性,说明是分页对象
|
|
|
if (paginationData.data && Array.isArray(paginationData.data)) {
|
|
|
tableData.value = paginationData.data
|
|
|
pagination.value.total = paginationData.total || 0
|
|
|
}
|
|
|
// 如果 paginationData 本身就是数组,直接使用
|
|
|
else if (Array.isArray(paginationData)) {
|
|
|
tableData.value = paginationData
|
|
|
// 尝试从其他地方获取总数
|
|
|
pagination.value.total = res.data?.total || paginationData.length || 0
|
|
|
}
|
|
|
// 其他情况,尝试作为数组处理
|
|
|
else {
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
console.warn('未识别的数据格式,完整响应:', res)
|
|
|
}
|
|
|
} else {
|
|
|
// 如果没有 paginationData,尝试直接使用 res.data.data
|
|
|
const data = res.data?.data
|
|
|
if (Array.isArray(data)) {
|
|
|
tableData.value = data
|
|
|
pagination.value.total = res.data?.total || data.length || 0
|
|
|
} else {
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
console.warn('数据格式异常,完整响应:', res)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 调试信息:检查数据是否正确加载
|
|
|
if (tableData.value.length === 0 && pagination.value.total > 0) {
|
|
|
console.warn('数据为空但总数不为0,可能数据格式不匹配')
|
|
|
}
|
|
|
} else {
|
|
|
ElMessage.error(res.msg || res.message || '获取流程列表失败')
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
}
|
|
|
} catch (error) {
|
|
|
ElMessage.error('获取流程列表失败:' + error.message)
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
} finally {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 查询
|
|
|
const handleSearch = () => {
|
|
|
pagination.value.currentPage = 1
|
|
|
loadFlowList()
|
|
|
}
|
|
|
|
|
|
// 重置
|
|
|
const handleReset = () => {
|
|
|
filterForm.value = {
|
|
|
keyword: ''
|
|
|
}
|
|
|
pagination.value.currentPage = 1
|
|
|
// 年份不重置,保持当前选择
|
|
|
loadFlowList()
|
|
|
}
|
|
|
|
|
|
// 查看支付详情
|
|
|
const handleViewPayment = (payment) => {
|
|
|
// 打开新窗口显示打印预览页
|
|
|
const url = router.resolve({
|
|
|
name: 'PaymentDetailPrint',
|
|
|
params: { id: payment.id }
|
|
|
}).href
|
|
|
window.open(url, '_blank')
|
|
|
}
|
|
|
|
|
|
// 查看
|
|
|
const handleView = (row) => {
|
|
|
// 跳转到OA流程详情页面
|
|
|
const token = getToken()
|
|
|
const baseUrl = '/oa/#/flow/view'
|
|
|
const params = new URLSearchParams({
|
|
|
id: row.id.toString(),
|
|
|
isSinglePage: '1',
|
|
|
module_name: 'oa',
|
|
|
form_canal: 'budget'
|
|
|
})
|
|
|
if (token) {
|
|
|
params.set('auth_token', token)
|
|
|
}
|
|
|
const fullUrl = `${baseUrl}?${params.toString()}`
|
|
|
|
|
|
// 在新窗口打开
|
|
|
window.open(fullUrl, '_blank')
|
|
|
}
|
|
|
|
|
|
// 编辑
|
|
|
const handleEdit = (row) => {
|
|
|
// 跳转到OA流程编辑页面
|
|
|
const token = getToken()
|
|
|
const baseUrl = '/oa/#/flow/deal'
|
|
|
const params = new URLSearchParams({
|
|
|
id: row.id.toString(),
|
|
|
isSinglePage: '1',
|
|
|
module_name: 'oa',
|
|
|
form_canal: 'budget'
|
|
|
})
|
|
|
if (token) {
|
|
|
params.set('auth_token', token)
|
|
|
}
|
|
|
const fullUrl = `${baseUrl}?${params.toString()}`
|
|
|
|
|
|
// 在新窗口打开
|
|
|
window.open(fullUrl, '_blank')
|
|
|
}
|
|
|
|
|
|
// 分页大小改变
|
|
|
const handleSizeChange = (val) => {
|
|
|
pagination.value.pageSize = val
|
|
|
pagination.value.currentPage = 1
|
|
|
loadFlowList()
|
|
|
}
|
|
|
|
|
|
// 当前页改变
|
|
|
const handleCurrentChange = (val) => {
|
|
|
pagination.value.currentPage = val
|
|
|
loadFlowList()
|
|
|
}
|
|
|
|
|
|
// 页面加载
|
|
|
onMounted(() => {
|
|
|
loadAvailableSubprocesses()
|
|
|
})
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.process-query-container {
|
|
|
padding: 20px;
|
|
|
background: #f5f7fa;
|
|
|
min-height: 100%;
|
|
|
}
|
|
|
|
|
|
.page-header {
|
|
|
background: white;
|
|
|
padding: 25px 30px;
|
|
|
border-radius: 10px;
|
|
|
margin-bottom: 20px;
|
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
|
|
}
|
|
|
|
|
|
.page-title {
|
|
|
font-size: 24px;
|
|
|
font-weight: 600;
|
|
|
color: #333;
|
|
|
margin: 0;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.subprocess-selector-section {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.subprocess-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
|
gap: 16px;
|
|
|
}
|
|
|
|
|
|
.subprocess-card {
|
|
|
padding: 20px;
|
|
|
border: 2px solid #e4e7ed;
|
|
|
border-radius: 8px;
|
|
|
background: white;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.subprocess-card:hover {
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
transform: translateY(-2px);
|
|
|
}
|
|
|
|
|
|
.subprocess-card.active {
|
|
|
border-color: #409eff;
|
|
|
background: #409eff;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.subprocess-card.active .subprocess-icon {
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.subprocess-icon {
|
|
|
margin-bottom: 12px;
|
|
|
color: #409eff;
|
|
|
}
|
|
|
|
|
|
.subprocess-card.active .subprocess-icon {
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.subprocess-name {
|
|
|
font-size: 16px;
|
|
|
font-weight: 600;
|
|
|
margin-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
.subprocess-description {
|
|
|
font-size: 12px;
|
|
|
color: #909399;
|
|
|
margin-top: 8px;
|
|
|
}
|
|
|
|
|
|
.subprocess-card.active .subprocess-description {
|
|
|
color: rgba(255, 255, 255, 0.8);
|
|
|
}
|
|
|
|
|
|
.filter-section {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.filter-bar {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 24px;
|
|
|
margin-bottom: 16px;
|
|
|
padding: 12px;
|
|
|
background: #f5f7fa;
|
|
|
border-radius: 4px;
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.filter-group {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.filter-label {
|
|
|
font-size: 14px;
|
|
|
color: #606266;
|
|
|
font-weight: 500;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
|
|
|
.type-cell {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.title-cell {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 0;
|
|
|
}
|
|
|
|
|
|
.related-payments {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
gap: 6px;
|
|
|
}
|
|
|
|
|
|
.payment-tag {
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s;
|
|
|
}
|
|
|
|
|
|
.payment-tag:hover {
|
|
|
opacity: 0.8;
|
|
|
transform: scale(1.05);
|
|
|
}
|
|
|
|
|
|
.no-payment {
|
|
|
color: #909399;
|
|
|
font-style: italic;
|
|
|
}
|
|
|
|
|
|
.pagination-container {
|
|
|
margin-top: 20px;
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
.subprocess-grid {
|
|
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
.subprocess-card {
|
|
|
padding: 16px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|