master
weizong song 5 months ago
parent ad6876d72f
commit 488be3263e

@ -169,3 +169,4 @@ budget_template_elements (元素表)

@ -319,3 +319,4 @@ const formatDefaultValue = (value) => {

@ -364,6 +364,11 @@ export const oaFlowAPI = {
return request.get(`/oa/flow/custom-model-fields/${customModelId}`)
},
// 根据 selection_model 获取选项数据
getSelectionOptions: (selectionModel) => {
return request.get('/oa/flow/selection-options', { selection_model: selectionModel })
},
// 获取创建流程参数
getCreatePre: (customModelId, params) => {
return request.get(`/oa/flow/create-pre/${customModelId}`, params)

@ -77,6 +77,50 @@
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>
@ -104,23 +148,34 @@
:key="field.id"
:prop="`data.${field.name}`"
:label="field.label"
:min-width="getFieldWidth(field.type)"
:min-width="field.type === 'relation' ? 300 : getFieldWidth(field.type)"
>
<template #default="scope">
{{ formatFieldValue(scope.row.data, field) }}
<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="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">
@ -190,7 +245,7 @@
查看
</el-button>
<el-button
v-if="scope.row.status === 0"
v-if="hasGlobalFlowSupervisionRole"
type="success"
link
size="small"
@ -218,7 +273,7 @@
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import { ref, onMounted, watch, computed } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
@ -233,14 +288,19 @@ 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([])
//
@ -251,6 +311,10 @@ const queryType = ref('not-linked')
// show_in_list=1
const dynamicFields = ref([])
// relation
const subFormFieldsMap = ref({})
// select key selection_modelvalue
const selectionOptionsMap = ref({})
//
const tableData = ref([])
@ -261,9 +325,22 @@ const selectedYear = ref(null) // 选中的年份
//
const filterForm = ref({
keyword: ''
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,
@ -343,10 +420,104 @@ const getFieldWidth = (fieldType) => {
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]
@ -385,7 +556,21 @@ const formatFieldValue = (data, field) => {
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
@ -404,18 +589,30 @@ const formatFieldValue = (data, field) => {
}
//
const loadAvailableYears = async () => {
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: selectedSubprocess.value.custom_model_id
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
@ -435,20 +632,29 @@ const loadAvailableYears = async () => {
selectedYear.value = new Date().getFullYear()
}
} catch (error) {
console.error('加载年份列表失败:', error)
availableYears.value = []
// 使
selectedYear.value = new Date().getFullYear()
// custom_model_id
if (selectedSubprocess.value?.custom_model_id === currentCustomModelId) {
console.error('加载年份列表失败:', error)
availableYears.value = []
// 使
selectedYear.value = new Date().getFullYear()
}
} finally {
loadingYears.value = false
// custom_model_id
if (selectedSubprocess.value?.custom_model_id === currentCustomModelId) {
loadingYears.value = false
}
}
}
//
const handleYearChange = () => {
pagination.value.currentPage = 1
//
filterForm.value.keyword = ''
loadFlowList()
//
requestSequence++
loadFlowList(requestSequence)
}
//
@ -458,24 +664,77 @@ const handleSubprocessSelect = async (subprocess) => {
return
}
//
requestSequence++
const currentSequence = requestSequence
//
pagination.value.currentPage = 1
filterForm.value = {
keyword: '',
field_id: '',
operator: '',
field_keyword: ''
}
selectedSubprocess.value = subprocess
//
await loadModelFields(subprocess.custom_model_id)
//
subFormFieldsMap.value = {}
selectionOptionsMap.value = {}
allModelFields.value = []
//
await loadAvailableYears()
//
tableData.value = []
pagination.value.total = 0
//
if (selectedYear.value) {
await loadFlowList()
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) {
loadFlowList()
//
pagination.value.currentPage = 1
//
requestSequence++
loadFlowList(requestSequence)
}
}
@ -534,32 +793,163 @@ const loadAvailableSubprocesses = async () => {
}
//
const loadModelFields = async (customModelId) => {
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
// 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 {
dynamicFields.value = []
//
if (selectedSubprocess.value?.custom_model_id === customModelId) {
dynamicFields.value = []
}
}
} catch (error) {
console.error('获取模型字段失败:', error)
dynamicFields.value = []
//
if (selectedSubprocess.value?.custom_model_id === customModelId) {
console.error('获取模型字段失败:', error)
dynamicFields.value = []
}
}
}
//
const loadFlowList = async () => {
const loadFlowList = async (sequence = null) => {
// 使
if (sequence === null) {
requestSequence++
sequence = requestSequence
}
if (!selectedSubprocess.value || !selectedSubprocess.value.custom_model_id) {
return
}
@ -571,16 +961,26 @@ const loadFlowList = async () => {
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: selectedSubprocess.value.custom_model_id,
year: selectedYear.value, //
page: pagination.value.currentPage,
custom_model_id: currentCustomModelId,
year: currentYear, //
page: currentPage,
page_size: pagination.value.pageSize,
is_simple: 0, // data
payment_link_status: queryType.value, // not-linked linked
...filterForm.value
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
}
//
@ -592,6 +992,24 @@ const loadFlowList = async () => {
// 使 "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, ... }
@ -633,33 +1051,47 @@ const loadFlowList = async () => {
console.warn('数据为空但总数不为0可能数据格式不匹配')
}
} else {
ElMessage.error(res.msg || res.message || '获取流程列表失败')
//
if (sequence === requestSequence) {
ElMessage.error(res.msg || res.message || '获取流程列表失败')
}
tableData.value = []
pagination.value.total = 0
}
} catch (error) {
ElMessage.error('获取流程列表失败:' + error.message)
//
if (sequence === requestSequence) {
ElMessage.error('获取流程列表失败:' + error.message)
}
tableData.value = []
pagination.value.total = 0
} finally {
loading.value = false
//
if (sequence === requestSequence) {
loading.value = false
}
}
}
//
const handleSearch = () => {
pagination.value.currentPage = 1
loadFlowList()
requestSequence++
loadFlowList(requestSequence)
}
//
const handleReset = () => {
filterForm.value = {
keyword: ''
keyword: '',
field_id: '',
operator: '',
field_keyword: ''
}
pagination.value.currentPage = 1
//
loadFlowList()
requestSequence++
loadFlowList(requestSequence)
}
//
@ -676,9 +1108,10 @@ const handleViewPayment = (payment) => {
const handleView = (row) => {
// OA
const token = getToken()
const baseUrl = '/oa/#/flow/view'
const baseUrl = '/oa/#/flow/detail'
const params = new URLSearchParams({
id: row.id.toString(),
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'
@ -692,16 +1125,19 @@ const handleView = (row) => {
window.open(fullUrl, '_blank')
}
// ""
const hasGlobalFlowSupervisionRole = computed(() => {
return userStore.roles && userStore.roles.includes('全局流程监管')
})
//
const handleEdit = (row) => {
// OA
const token = getToken()
const baseUrl = '/oa/#/flow/deal'
const baseUrl = '/oa/#/flow/edit'
const params = new URLSearchParams({
id: row.id.toString(),
isSinglePage: '1',
module_name: 'oa',
form_canal: 'budget'
flow_id: row.id.toString(),
isSinglePage: '1'
})
if (token) {
params.set('auth_token', token)
@ -716,13 +1152,15 @@ const handleEdit = (row) => {
const handleSizeChange = (val) => {
pagination.value.pageSize = val
pagination.value.currentPage = 1
loadFlowList()
requestSequence++
loadFlowList(requestSequence)
}
//
const handleCurrentChange = (val) => {
pagination.value.currentPage = val
loadFlowList()
requestSequence++
loadFlowList(requestSequence)
}
//
@ -883,6 +1321,25 @@ onMounted(() => {
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));

Loading…
Cancel
Save