|
|
<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>
|
|
|
|
|
|
<!-- 高级查询(仅在选择了子流程时显示) -->
|
|
|
<template v-if="selectedSubprocess && allModelFields.length > 0">
|
|
|
<el-form-item label="字段">
|
|
|
<el-select
|
|
|
v-model="filterForm.field_id"
|
|
|
placeholder="请选择字段"
|
|
|
style="width: 180px"
|
|
|
filterable
|
|
|
clearable
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="field in allModelFields"
|
|
|
:key="field.id"
|
|
|
:value="field.id"
|
|
|
:label="field.label"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="类型">
|
|
|
<el-select
|
|
|
v-model="filterForm.operator"
|
|
|
placeholder="请选择类型"
|
|
|
style="width: 150px"
|
|
|
clearable
|
|
|
>
|
|
|
<el-option
|
|
|
v-for="op in operatorOptions"
|
|
|
:key="op.id"
|
|
|
:value="op.id"
|
|
|
:label="op.label"
|
|
|
/>
|
|
|
</el-select>
|
|
|
</el-form-item>
|
|
|
<el-form-item label="关键词">
|
|
|
<el-input
|
|
|
v-model="filterForm.field_keyword"
|
|
|
placeholder="请输入关键词"
|
|
|
style="width: 200px"
|
|
|
clearable
|
|
|
/>
|
|
|
</el-form-item>
|
|
|
</template>
|
|
|
|
|
|
<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="field.type === 'relation' ? 300 : getFieldWidth(field.type)"
|
|
|
>
|
|
|
<template #default="scope">
|
|
|
<div v-if="field.type === 'relation'" class="relation-field-renderer">
|
|
|
<el-table
|
|
|
:data="getRelationFieldData(scope.row.data, field)"
|
|
|
border
|
|
|
size="small"
|
|
|
style="width: 100%"
|
|
|
:show-header="true"
|
|
|
>
|
|
|
<el-table-column
|
|
|
v-for="subField in getSubFormFields(field.name)"
|
|
|
:key="subField.id"
|
|
|
:prop="subField.name"
|
|
|
:label="subField.label"
|
|
|
:min-width="100"
|
|
|
>
|
|
|
<template #default="subScope">
|
|
|
{{ formatSubFieldValue(subScope.row, subField) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</el-table>
|
|
|
</div>
|
|
|
<span v-else>{{ formatFieldValue(scope.row.data, field) }}</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
</template>
|
|
|
<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="hasGlobalFlowSupervisionRole"
|
|
|
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, computed } 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'
|
|
|
import { useUserStore } from '@/store/user'
|
|
|
|
|
|
const router = useRouter()
|
|
|
const userStore = useUserStore()
|
|
|
|
|
|
// 加载状态
|
|
|
const loading = ref(false)
|
|
|
const subprocessLoading = ref(false)
|
|
|
const loadingYears = ref(false)
|
|
|
|
|
|
// 请求序列号,用于防止竞态条件
|
|
|
let requestSequence = 0
|
|
|
|
|
|
// 可用子流程列表
|
|
|
const availableSubprocesses = ref([])
|
|
|
// 选中的子流程
|
|
|
const selectedSubprocess = ref(null)
|
|
|
|
|
|
// 查询类型:not-linked(未关联支付)或 linked(已关联支付)
|
|
|
const queryType = ref('not-linked')
|
|
|
|
|
|
// 动态字段列表(show_in_list=1的字段)
|
|
|
const dynamicFields = ref([])
|
|
|
// 子表单字段映射(用于 relation 类型字段)
|
|
|
const subFormFieldsMap = ref({})
|
|
|
// 选项数据映射(用于 select 类型字段,key 为 selection_model,value 为选项数组)
|
|
|
const selectionOptionsMap = ref({})
|
|
|
|
|
|
// 表格数据
|
|
|
const tableData = ref([])
|
|
|
|
|
|
// 年份相关
|
|
|
const availableYears = ref([]) // 可用年份列表
|
|
|
const selectedYear = ref(null) // 选中的年份
|
|
|
|
|
|
// 筛选表单
|
|
|
const filterForm = ref({
|
|
|
keyword: '',
|
|
|
field_id: '',
|
|
|
operator: '',
|
|
|
field_keyword: ''
|
|
|
})
|
|
|
|
|
|
// 操作符选项
|
|
|
const operatorOptions = [
|
|
|
{ id: 'eq', label: '等于' },
|
|
|
{ id: 'neq', label: '不等于' },
|
|
|
{ id: 'like', label: '包含' }
|
|
|
]
|
|
|
|
|
|
// 所有模型字段(用于高级查询,不仅仅是show_in_list=1的字段)
|
|
|
const allModelFields = ref([])
|
|
|
|
|
|
// 分页
|
|
|
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 getSubFormFields = (fieldName) => {
|
|
|
return subFormFieldsMap.value[fieldName] || []
|
|
|
}
|
|
|
|
|
|
// 获取 relation 字段的数据(转换为数组格式)
|
|
|
const getRelationFieldData = (data, field) => {
|
|
|
if (!data || !field || !field.name) return []
|
|
|
|
|
|
let value = data[field.name]
|
|
|
|
|
|
// 如果 value 是字符串,尝试解析为 JSON
|
|
|
if (typeof value === 'string') {
|
|
|
try {
|
|
|
value = JSON.parse(value)
|
|
|
} catch (e) {
|
|
|
console.warn('解析 relation 字段 JSON 失败:', value, e)
|
|
|
return []
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 确保是数组
|
|
|
if (!Array.isArray(value)) {
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
return value
|
|
|
}
|
|
|
|
|
|
// 格式化子表单字段值
|
|
|
const formatSubFieldValue = (rowData, subField) => {
|
|
|
if (!rowData || !subField || !subField.name) return '-'
|
|
|
|
|
|
let value = rowData[subField.name]
|
|
|
|
|
|
if (value === null || value === undefined || value === '') return '-'
|
|
|
|
|
|
switch (subField.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':
|
|
|
// 如果有 selection_model,从 selectionOptionsMap 中查找
|
|
|
if (subField.selection_model && selectionOptionsMap.value[subField.selection_model]) {
|
|
|
const options = selectionOptionsMap.value[subField.selection_model]
|
|
|
const option = options.find(opt =>
|
|
|
opt.id == value ||
|
|
|
opt.value == value ||
|
|
|
String(opt.id) === String(value) ||
|
|
|
String(opt.value) === String(value)
|
|
|
)
|
|
|
if (option) {
|
|
|
return option.label || option.name || value
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有 selection_model 或找不到,尝试使用 subField.options
|
|
|
if (subField.options && Array.isArray(subField.options)) {
|
|
|
const option = subField.options.find(opt => opt.value === value || opt.id === value)
|
|
|
return option ? (option.label || option.name || value) : value
|
|
|
}
|
|
|
return value
|
|
|
default:
|
|
|
return String(value)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 格式化字段值
|
|
|
const formatFieldValue = (data, field) => {
|
|
|
if (!data || !field || !field.name) return '-'
|
|
|
|
|
|
// relation 类型已经在模板中特殊处理,这里不需要处理
|
|
|
if (field.type === 'relation') {
|
|
|
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':
|
|
|
// 如果有 selection_model,从 selectionOptionsMap 中查找
|
|
|
if (field.selection_model && selectionOptionsMap.value[field.selection_model]) {
|
|
|
const options = selectionOptionsMap.value[field.selection_model]
|
|
|
const option = options.find(opt =>
|
|
|
opt.id == value ||
|
|
|
opt.value == value ||
|
|
|
String(opt.id) === String(value) ||
|
|
|
String(opt.value) === String(value)
|
|
|
)
|
|
|
if (option) {
|
|
|
return option.label || option.name || value
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 如果没有 selection_model 或找不到,尝试使用 field.options
|
|
|
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 (expectedCustomModelId = null) => {
|
|
|
if (!selectedSubprocess.value || !selectedSubprocess.value.custom_model_id) {
|
|
|
availableYears.value = []
|
|
|
return
|
|
|
}
|
|
|
|
|
|
const currentCustomModelId = selectedSubprocess.value.custom_model_id
|
|
|
|
|
|
// 如果传入了期望的 custom_model_id,检查是否仍然匹配
|
|
|
if (expectedCustomModelId && currentCustomModelId !== expectedCustomModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
loadingYears.value = true
|
|
|
try {
|
|
|
const response = await oaFlowAPI.getFlowYears({
|
|
|
custom_model_id: currentCustomModelId
|
|
|
})
|
|
|
|
|
|
// 再次检查 custom_model_id 是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== currentCustomModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
// 只有在 custom_model_id 仍然匹配时才处理错误
|
|
|
if (selectedSubprocess.value?.custom_model_id === currentCustomModelId) {
|
|
|
console.error('加载年份列表失败:', error)
|
|
|
availableYears.value = []
|
|
|
// 出错时默认使用当年
|
|
|
selectedYear.value = new Date().getFullYear()
|
|
|
}
|
|
|
} finally {
|
|
|
// 只有在 custom_model_id 仍然匹配时才更新加载状态
|
|
|
if (selectedSubprocess.value?.custom_model_id === currentCustomModelId) {
|
|
|
loadingYears.value = false
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 年份切换
|
|
|
const handleYearChange = () => {
|
|
|
pagination.value.currentPage = 1
|
|
|
// 年份切换时只清空关键词,保留高级查询条件
|
|
|
filterForm.value.keyword = ''
|
|
|
// 增加请求序列号
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
|
|
|
// 选择子流程
|
|
|
const handleSubprocessSelect = async (subprocess) => {
|
|
|
if (!subprocess.custom_model_id) {
|
|
|
ElMessage.warning('该流程项未关联OA模型')
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 增加请求序列号,取消之前的请求
|
|
|
requestSequence++
|
|
|
const currentSequence = requestSequence
|
|
|
|
|
|
// 重置分页和筛选
|
|
|
pagination.value.currentPage = 1
|
|
|
filterForm.value = {
|
|
|
keyword: '',
|
|
|
field_id: '',
|
|
|
operator: '',
|
|
|
field_keyword: ''
|
|
|
}
|
|
|
|
|
|
selectedSubprocess.value = subprocess
|
|
|
|
|
|
// 清空子表单字段映射、选项数据映射和所有字段
|
|
|
subFormFieldsMap.value = {}
|
|
|
selectionOptionsMap.value = {}
|
|
|
allModelFields.value = []
|
|
|
|
|
|
// 清空表格数据,避免显示旧数据
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
|
|
|
try {
|
|
|
// 加载模型字段(传入期望的 custom_model_id 用于验证)
|
|
|
await loadModelFields(subprocess.custom_model_id, subprocess.custom_model_id)
|
|
|
|
|
|
// 检查是否仍然是最新的请求
|
|
|
if (currentSequence !== requestSequence) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 再次检查 selectedSubprocess 是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== subprocess.custom_model_id) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 先加载年份列表(传入期望的 custom_model_id 用于验证)
|
|
|
await loadAvailableYears(subprocess.custom_model_id)
|
|
|
|
|
|
// 检查是否仍然是最新的请求
|
|
|
if (currentSequence !== requestSequence) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 再次检查 selectedSubprocess 是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== subprocess.custom_model_id) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 年份加载完成后,再加载流程列表
|
|
|
if (selectedYear.value && selectedSubprocess.value?.custom_model_id === subprocess.custom_model_id) {
|
|
|
await loadFlowList(currentSequence)
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 如果请求被取消或出错,不显示错误(可能是用户快速切换导致的)
|
|
|
if (currentSequence === requestSequence) {
|
|
|
console.error('加载子流程数据失败:', error)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 查询类型改变
|
|
|
const handleQueryTypeChange = () => {
|
|
|
if (selectedSubprocess.value) {
|
|
|
// 重置分页
|
|
|
pagination.value.currentPage = 1
|
|
|
// 增加请求序列号
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载可用子流程
|
|
|
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, expectedCustomModelId = null) => {
|
|
|
// 如果传入了期望的 custom_model_id,检查是否仍然匹配
|
|
|
if (expectedCustomModelId && customModelId !== expectedCustomModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
const res = await oaFlowAPI.getCustomModelFields(customModelId)
|
|
|
|
|
|
// 再次检查 custom_model_id 是否仍然匹配
|
|
|
if (expectedCustomModelId && customModelId !== expectedCustomModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 检查当前选中的子流程是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
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))
|
|
|
|
|
|
// 所有字段(用于高级查询,过滤掉没有name的字段)
|
|
|
const allFields = fieldsArray
|
|
|
.filter(field => field && field.name)
|
|
|
.sort((a, b) => (a.myindex || 0) - (b.myindex || 0))
|
|
|
|
|
|
// 再次检查是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
dynamicFields.value = fields
|
|
|
allModelFields.value = allFields
|
|
|
|
|
|
// 加载 select 类型字段的选项数据(如果有 selection_model)
|
|
|
const selectFields = fields.filter(field =>
|
|
|
(field.type === 'select' || field.type === 'radio') &&
|
|
|
field.selection_model
|
|
|
)
|
|
|
for (const selectField of selectFields) {
|
|
|
// 检查是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
break
|
|
|
}
|
|
|
|
|
|
// 如果还没有加载过这个 selection_model 的选项,则加载
|
|
|
if (!selectionOptionsMap.value[selectField.selection_model]) {
|
|
|
try {
|
|
|
const optionsRes = await oaFlowAPI.getSelectionOptions(selectField.selection_model)
|
|
|
|
|
|
// 再次检查是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
break
|
|
|
}
|
|
|
|
|
|
if (optionsRes.code === 0 && Array.isArray(optionsRes.data)) {
|
|
|
selectionOptionsMap.value[selectField.selection_model] = optionsRes.data
|
|
|
} else {
|
|
|
selectionOptionsMap.value[selectField.selection_model] = []
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 只有在仍然匹配时才记录错误
|
|
|
if (selectedSubprocess.value?.custom_model_id === customModelId) {
|
|
|
console.error(`加载选项数据失败 (${selectField.selection_model}):`, error)
|
|
|
selectionOptionsMap.value[selectField.selection_model] = []
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载 relation 类型字段的子表单字段
|
|
|
const relationFields = fields.filter(field => field.type === 'relation' && field.sub_custom_model_id)
|
|
|
for (const relationField of relationFields) {
|
|
|
// 检查是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
break
|
|
|
}
|
|
|
|
|
|
if (relationField.sub_custom_model_id && !subFormFieldsMap.value[relationField.name]) {
|
|
|
try {
|
|
|
const subRes = await oaFlowAPI.getCustomModelFields(relationField.sub_custom_model_id)
|
|
|
|
|
|
// 再次检查是否仍然匹配
|
|
|
if (selectedSubprocess.value?.custom_model_id !== customModelId) {
|
|
|
break
|
|
|
}
|
|
|
|
|
|
if (subRes.code === 0 && subRes.data?.customModel?.fields) {
|
|
|
const subFields = Array.isArray(subRes.data.customModel.fields)
|
|
|
? subRes.data.customModel.fields
|
|
|
: []
|
|
|
// 按 myindex 排序
|
|
|
subFormFieldsMap.value[relationField.name] = subFields.sort((a, b) => (a.myindex || 0) - (b.myindex || 0))
|
|
|
|
|
|
// 加载子表单中 select 类型字段的选项数据(如果有 selection_model)
|
|
|
const subSelectFields = subFields.filter(field =>
|
|
|
(field.type === 'select' || field.type === 'radio') &&
|
|
|
field.selection_model
|
|
|
)
|
|
|
for (const subSelectField of subSelectFields) {
|
|
|
// 如果还没有加载过这个 selection_model 的选项,则加载
|
|
|
if (!selectionOptionsMap.value[subSelectField.selection_model]) {
|
|
|
try {
|
|
|
const optionsRes = await oaFlowAPI.getSelectionOptions(subSelectField.selection_model)
|
|
|
if (optionsRes.code === 0 && Array.isArray(optionsRes.data)) {
|
|
|
selectionOptionsMap.value[subSelectField.selection_model] = optionsRes.data
|
|
|
} else {
|
|
|
selectionOptionsMap.value[subSelectField.selection_model] = []
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error(`加载子表单选项数据失败 (${subSelectField.selection_model}):`, error)
|
|
|
selectionOptionsMap.value[subSelectField.selection_model] = []
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 只有在仍然匹配时才记录错误
|
|
|
if (selectedSubprocess.value?.custom_model_id === customModelId) {
|
|
|
console.error(`加载子表单字段失败 (${relationField.name}):`, error)
|
|
|
subFormFieldsMap.value[relationField.name] = []
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} else {
|
|
|
// 只有在仍然匹配时才更新
|
|
|
if (selectedSubprocess.value?.custom_model_id === customModelId) {
|
|
|
dynamicFields.value = []
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 只有在仍然匹配时才记录错误
|
|
|
if (selectedSubprocess.value?.custom_model_id === customModelId) {
|
|
|
console.error('获取模型字段失败:', error)
|
|
|
dynamicFields.value = []
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 加载流程列表
|
|
|
const loadFlowList = async (sequence = null) => {
|
|
|
// 如果没有传入序列号,使用当前序列号
|
|
|
if (sequence === null) {
|
|
|
requestSequence++
|
|
|
sequence = requestSequence
|
|
|
}
|
|
|
|
|
|
if (!selectedSubprocess.value || !selectedSubprocess.value.custom_model_id) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 如果没有选中年份,不加载数据
|
|
|
if (!selectedYear.value) {
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 保存当前请求的关键参数,用于验证返回结果是否仍然有效
|
|
|
const currentCustomModelId = selectedSubprocess.value.custom_model_id
|
|
|
const currentYear = selectedYear.value
|
|
|
const currentQueryType = queryType.value
|
|
|
const currentPage = pagination.value.currentPage
|
|
|
|
|
|
loading.value = true
|
|
|
try {
|
|
|
const params = {
|
|
|
custom_model_id: currentCustomModelId,
|
|
|
year: currentYear, // 必填:年份参数
|
|
|
page: currentPage,
|
|
|
page_size: pagination.value.pageSize,
|
|
|
is_simple: 0, // 完整版本,包含data字段
|
|
|
payment_link_status: currentQueryType, // 关联支付状态:not-linked 或 linked
|
|
|
keyword: filterForm.value.keyword, // 流程编号关键词
|
|
|
// 高级查询参数
|
|
|
field_id: filterForm.value.field_id || undefined,
|
|
|
operator: filterForm.value.operator || undefined,
|
|
|
field_keyword: filterForm.value.field_keyword || undefined
|
|
|
}
|
|
|
|
|
|
// 移除空值
|
|
|
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 (sequence !== requestSequence) {
|
|
|
// 请求已被新的请求取代,忽略此结果
|
|
|
return
|
|
|
}
|
|
|
|
|
|
// 验证关键参数是否仍然匹配
|
|
|
if (
|
|
|
selectedSubprocess.value?.custom_model_id !== currentCustomModelId ||
|
|
|
selectedYear.value !== currentYear ||
|
|
|
queryType.value !== currentQueryType ||
|
|
|
pagination.value.currentPage !== currentPage
|
|
|
) {
|
|
|
// 关键参数已改变,忽略此结果
|
|
|
return
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
// 只有在请求仍然有效时才显示错误
|
|
|
if (sequence === requestSequence) {
|
|
|
ElMessage.error(res.msg || res.message || '获取流程列表失败')
|
|
|
}
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 只有在请求仍然有效时才显示错误
|
|
|
if (sequence === requestSequence) {
|
|
|
ElMessage.error('获取流程列表失败:' + error.message)
|
|
|
}
|
|
|
tableData.value = []
|
|
|
pagination.value.total = 0
|
|
|
} finally {
|
|
|
// 只有在请求仍然有效时才更新加载状态
|
|
|
if (sequence === requestSequence) {
|
|
|
loading.value = false
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 查询
|
|
|
const handleSearch = () => {
|
|
|
pagination.value.currentPage = 1
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
|
|
|
// 重置
|
|
|
const handleReset = () => {
|
|
|
filterForm.value = {
|
|
|
keyword: '',
|
|
|
field_id: '',
|
|
|
operator: '',
|
|
|
field_keyword: ''
|
|
|
}
|
|
|
pagination.value.currentPage = 1
|
|
|
// 年份不重置,保持当前选择
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
|
|
|
// 查看支付详情
|
|
|
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/detail'
|
|
|
const params = new URLSearchParams({
|
|
|
module_id: (row.custom_model_id || row.custom_model?.id || row.customModel?.id || '').toString(),
|
|
|
flow_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 hasGlobalFlowSupervisionRole = computed(() => {
|
|
|
return userStore.roles && userStore.roles.includes('全局流程监管')
|
|
|
})
|
|
|
|
|
|
// 编辑
|
|
|
const handleEdit = (row) => {
|
|
|
// 跳转到OA流程编辑页面
|
|
|
const token = getToken()
|
|
|
const baseUrl = '/oa/#/flow/edit'
|
|
|
const params = new URLSearchParams({
|
|
|
flow_id: row.id.toString(),
|
|
|
isSinglePage: '1'
|
|
|
})
|
|
|
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
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
|
|
|
// 当前页改变
|
|
|
const handleCurrentChange = (val) => {
|
|
|
pagination.value.currentPage = val
|
|
|
requestSequence++
|
|
|
loadFlowList(requestSequence)
|
|
|
}
|
|
|
|
|
|
// 页面加载
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
.relation-field-renderer {
|
|
|
max-width: 100%;
|
|
|
overflow-x: auto;
|
|
|
}
|
|
|
|
|
|
.relation-field-renderer :deep(.el-table) {
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.relation-field-renderer :deep(.el-table th) {
|
|
|
padding: 8px 4px;
|
|
|
background-color: #f5f7fa;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.relation-field-renderer :deep(.el-table td) {
|
|
|
padding: 6px 4px;
|
|
|
}
|
|
|
|
|
|
@media (max-width: 768px) {
|
|
|
.subprocess-grid {
|
|
|
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
|
|
gap: 12px;
|
|
|
}
|
|
|
|
|
|
.subprocess-card {
|
|
|
padding: 16px;
|
|
|
}
|
|
|
}
|
|
|
</style>
|