| | |
| | | <ElDrawer |
| | | :model-value="visible" |
| | | title="流程图查看" |
| | | size="900px" |
| | | size="92%" |
| | | destroy-on-close |
| | | @update:model-value="handleVisibleChange" |
| | | > |
| | | <ElScrollbar class="h-[calc(100vh-180px)] pr-1"> |
| | | <div v-if="loading" class="py-6"> |
| | | <ElSkeleton :rows="10" animated /> |
| | | </div> |
| | | <div v-else class="space-y-4"> |
| | | <ElCard shadow="never" class="art-table-card"> |
| | | <div class="flex h-[calc(100vh-160px)] flex-col gap-4"> |
| | | <ElCard shadow="never" class="shrink-0"> |
| | | <div class="flex items-start justify-between gap-4"> |
| | | <div class="min-w-0"> |
| | | <div class="text-base font-semibold text-[var(--art-text-primary)]"> |
| | | {{ detail.templateName || detail.templateCode || '--' }} |
| | | </div> |
| | | <div class="mt-1 text-sm text-[var(--art-text-secondary)]"> |
| | | 模板编码 {{ detail.templateCode || '--' }},起点 {{ detail.sourceType || '--' }},终点 |
| | | {{ detail.targetType || '--' }} |
| | | </div> |
| | | </div> |
| | | <ElSpace wrap> |
| | | <ElTag :type="detail.statusType || 'info'" effect="light"> |
| | | {{ detail.statusText || '--' }} |
| | | </ElTag> |
| | | <ElTag :type="detail.isCurrentType || 'info'" effect="light"> |
| | | {{ detail.isCurrentText || '--' }} |
| | | </ElTag> |
| | | </ElSpace> |
| | | </div> |
| | | </ElCard> |
| | | |
| | | <div class="grid min-h-0 flex-1 gap-4 xl:grid-cols-3"> |
| | | <ElCard shadow="never" class="min-h-0"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between gap-3"> |
| | | <div> |
| | | <h3 class="m-0 text-base font-semibold">模板流程快照</h3> |
| | | <p class="m-0 text-sm text-[var(--art-text-secondary)]"> |
| | | 这里展示的是后端模板字段组合出的真实流程信息,不做额外假数据推演。 |
| | | </p> |
| | | </div> |
| | | <ElTag :type="detail.statusType || 'info'" effect="light"> |
| | | {{ detail.statusText || '--' }} |
| | | </ElTag> |
| | | <span class="font-medium">模板节点</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]"> |
| | | {{ nodeLoading ? '加载中' : `第 ${nodePagination.current} 页 / 共 ${nodePagination.total} 条` }} |
| | | </span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="grid gap-3 md:grid-cols-4"> |
| | | <div |
| | | v-for="item in flowSnapshot" |
| | | :key="item.key" |
| | | class="rounded-lg border border-[var(--art-border-color)] bg-[var(--art-main-bg-color)] p-4" |
| | | > |
| | | <div class="text-sm text-[var(--art-text-secondary)]">{{ item.title }}</div> |
| | | <div class="mt-2 text-base font-semibold text-[var(--art-text-primary)]"> |
| | | {{ item.value }} |
| | | <div class="flex h-[calc(100vh-300px)] flex-col"> |
| | | <ElSkeleton v-if="loading || nodeLoading" :rows="8" animated /> |
| | | <ElEmpty |
| | | v-else-if="nodeRows.length === 0" |
| | | description="暂无节点数据" |
| | | :image-size="100" |
| | | /> |
| | | <template v-else> |
| | | <ElTable |
| | | :data="nodeRows" |
| | | border |
| | | highlight-current-row |
| | | height="100%" |
| | | :current-row-key="selectedNodeId" |
| | | row-key="id" |
| | | @current-change="handleNodeClick" |
| | | > |
| | | <ElTableColumn prop="nodeOrder" label="顺序" width="72" align="center" /> |
| | | <ElTableColumn prop="nodeCode" label="节点编码" min-width="140" show-overflow-tooltip /> |
| | | <ElTableColumn prop="nodeName" label="节点名称" min-width="160" show-overflow-tooltip /> |
| | | <ElTableColumn prop="systemCode" label="系统编码" min-width="140" show-overflow-tooltip /> |
| | | </ElTable> |
| | | <div class="mt-3 flex justify-end"> |
| | | <ElPagination |
| | | small |
| | | background |
| | | layout="prev, pager, next" |
| | | :current-page="nodePagination.current" |
| | | :page-size="nodePagination.pageSize" |
| | | :total="nodePagination.total" |
| | | @current-change="handleNodePageChange" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </ElCard> |
| | | |
| | | <ElDescriptions title="流程依据" :column="2" border> |
| | | <ElDescriptionsItem label="模板编码">{{ detail.templateCode || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="模板名称">{{ detail.templateName || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="起点类型">{{ detail.sourceType || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="终点类型">{{ detail.targetType || '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="步序长度">{{ detail.stepSize ?? '--' }}</ElDescriptionsItem> |
| | | <ElDescriptionsItem label="优先级">{{ detail.priority ?? '--' }}</ElDescriptionsItem> |
| | | </ElDescriptions> |
| | | <ElCard shadow="never" class="min-h-0"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between gap-3"> |
| | | <span class="font-medium">子系统流程</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]"> |
| | | {{ |
| | | flowLoading |
| | | ? '加载中' |
| | | : selectedNodeId |
| | | ? `第 ${flowPagination.current} 页 / 共 ${flowPagination.total} 条` |
| | | : '待选择节点' |
| | | }} |
| | | </span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="flex h-[calc(100vh-300px)] flex-col"> |
| | | <div |
| | | v-if="!selectedNodeId" |
| | | class="flex h-full items-center justify-center text-sm text-[var(--art-text-secondary)]" |
| | | > |
| | | 请先选择左侧模板节点 |
| | | </div> |
| | | <ElSkeleton v-else-if="flowLoading" :rows="8" animated /> |
| | | <ElEmpty |
| | | v-else-if="flowRows.length === 0" |
| | | description="暂无流程数据" |
| | | :image-size="100" |
| | | /> |
| | | <template v-else> |
| | | <ElTable |
| | | :data="flowRows" |
| | | border |
| | | highlight-current-row |
| | | height="100%" |
| | | :current-row-key="selectedFlowId" |
| | | row-key="id" |
| | | @current-change="handleFlowClick" |
| | | > |
| | | <ElTableColumn prop="flowCode" label="流程编码" min-width="160" show-overflow-tooltip /> |
| | | <ElTableColumn prop="flowName" label="流程名称" min-width="180" show-overflow-tooltip /> |
| | | <ElTableColumn prop="systemCode" label="系统编码" min-width="140" show-overflow-tooltip /> |
| | | </ElTable> |
| | | <div class="mt-3 flex justify-end"> |
| | | <ElPagination |
| | | small |
| | | background |
| | | layout="prev, pager, next" |
| | | :current-page="flowPagination.current" |
| | | :page-size="flowPagination.pageSize" |
| | | :total="flowPagination.total" |
| | | @current-change="handleFlowPageChange" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </ElCard> |
| | | |
| | | <ElCard shadow="never" class="min-h-0"> |
| | | <template #header> |
| | | <div class="flex items-center justify-between gap-3"> |
| | | <span class="font-medium">流程步骤</span> |
| | | <span class="text-xs text-[var(--art-text-secondary)]"> |
| | | {{ |
| | | stepLoading |
| | | ? '加载中' |
| | | : selectedFlowId |
| | | ? `第 ${stepPagination.current} 页 / 共 ${stepPagination.total} 条` |
| | | : '待选择流程' |
| | | }} |
| | | </span> |
| | | </div> |
| | | </template> |
| | | |
| | | <div class="flex h-[calc(100vh-300px)] flex-col"> |
| | | <div |
| | | v-if="!selectedFlowId" |
| | | class="flex h-full items-center justify-center text-sm text-[var(--art-text-secondary)]" |
| | | > |
| | | 请先选择中间子系统流程 |
| | | </div> |
| | | <ElSkeleton v-else-if="stepLoading" :rows="8" animated /> |
| | | <ElEmpty |
| | | v-else-if="stepRows.length === 0" |
| | | description="暂无步骤数据" |
| | | :image-size="100" |
| | | /> |
| | | <template v-else> |
| | | <ElTable :data="stepRows" border height="100%" row-key="id"> |
| | | <ElTableColumn prop="stepOrder" label="顺序" width="72" align="center" /> |
| | | <ElTableColumn prop="stepCode" label="步骤编码" min-width="140" show-overflow-tooltip /> |
| | | <ElTableColumn prop="stepName" label="步骤名称" min-width="180" show-overflow-tooltip /> |
| | | <ElTableColumn prop="stepType" label="步骤类型" min-width="140" show-overflow-tooltip /> |
| | | </ElTable> |
| | | <div class="mt-3 flex justify-end"> |
| | | <ElPagination |
| | | small |
| | | background |
| | | layout="prev, pager, next" |
| | | :current-page="stepPagination.current" |
| | | :page-size="stepPagination.pageSize" |
| | | :total="stepPagination.total" |
| | | @current-change="handleStepPageChange" |
| | | /> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </ElCard> |
| | | </div> |
| | | </ElScrollbar> |
| | | </div> |
| | | </ElDrawer> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { computed } from 'vue' |
| | | import { buildTaskPathTemplateFlowSnapshot } from '../taskPathTemplatePage.helpers' |
| | | import { computed, ref, watch } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { guardRequestWithMessage } from '@/utils/sys/requestGuard' |
| | | import { |
| | | fetchTaskPathTemplateNodePage |
| | | } from '@/api/task-path-template-node' |
| | | import { fetchSubsystemFlowTemplatePage } from '@/api/subsystem-flow-template' |
| | | import { fetchFlowStepTemplatePage } from '@/api/flow-step-template' |
| | | |
| | | const props = defineProps({ |
| | | visible: { type: Boolean, default: false }, |
| | |
| | | set: (value) => emit('update:visible', value) |
| | | }) |
| | | |
| | | const flowSnapshot = computed(() => buildTaskPathTemplateFlowSnapshot(props.detail)) |
| | | const nodeLoading = ref(false) |
| | | const flowLoading = ref(false) |
| | | const stepLoading = ref(false) |
| | | const nodeRows = ref([]) |
| | | const flowRows = ref([]) |
| | | const stepRows = ref([]) |
| | | const selectedNodeId = ref(null) |
| | | const selectedFlowId = ref(null) |
| | | const DEFAULT_PAGE_SIZE = 20 |
| | | const nodePagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 }) |
| | | const flowPagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 }) |
| | | const stepPagination = ref({ current: 1, pageSize: DEFAULT_PAGE_SIZE, total: 0 }) |
| | | |
| | | function normalizeRecords(response) { |
| | | return Array.isArray(response?.records) ? response.records : [] |
| | | } |
| | | |
| | | function normalizeTotal(response) { |
| | | const total = Number(response?.total) |
| | | return Number.isNaN(total) ? 0 : total |
| | | } |
| | | |
| | | function resetFlowState() { |
| | | nodeRows.value = [] |
| | | flowRows.value = [] |
| | | stepRows.value = [] |
| | | selectedNodeId.value = null |
| | | selectedFlowId.value = null |
| | | nodePagination.value.current = 1 |
| | | nodePagination.value.total = 0 |
| | | flowPagination.value.current = 1 |
| | | flowPagination.value.total = 0 |
| | | stepPagination.value.current = 1 |
| | | stepPagination.value.total = 0 |
| | | } |
| | | |
| | | async function loadStepRows(flowId, current = stepPagination.value.current) { |
| | | if (!flowId) { |
| | | stepRows.value = [] |
| | | selectedFlowId.value = null |
| | | stepPagination.value.total = 0 |
| | | return |
| | | } |
| | | |
| | | stepLoading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchFlowStepTemplatePage({ |
| | | current, |
| | | pageSize: stepPagination.value.pageSize, |
| | | flowId |
| | | }), |
| | | { records: [] }, |
| | | { timeoutMessage: '流程步骤加载超时,已停止等待' } |
| | | ) |
| | | stepRows.value = normalizeRecords(response) |
| | | stepPagination.value.current = current |
| | | stepPagination.value.total = normalizeTotal(response) |
| | | } catch (error) { |
| | | stepRows.value = [] |
| | | stepPagination.value.total = 0 |
| | | ElMessage.error(error?.message || '流程步骤加载失败') |
| | | } finally { |
| | | stepLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadFlowRows(node, current = flowPagination.value.current) { |
| | | if (!node?.nodeCode) { |
| | | flowRows.value = [] |
| | | stepRows.value = [] |
| | | selectedFlowId.value = null |
| | | flowPagination.value.total = 0 |
| | | stepPagination.value.total = 0 |
| | | return |
| | | } |
| | | |
| | | flowLoading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchSubsystemFlowTemplatePage({ |
| | | current, |
| | | pageSize: flowPagination.value.pageSize, |
| | | flowCode: node.nodeCode |
| | | }), |
| | | { records: [] }, |
| | | { timeoutMessage: '子系统流程加载超时,已停止等待' } |
| | | ) |
| | | flowRows.value = normalizeRecords(response) |
| | | flowPagination.value.current = current |
| | | flowPagination.value.total = normalizeTotal(response) |
| | | selectedFlowId.value = null |
| | | stepRows.value = [] |
| | | stepPagination.value.current = 1 |
| | | stepPagination.value.total = 0 |
| | | } catch (error) { |
| | | flowRows.value = [] |
| | | stepRows.value = [] |
| | | selectedFlowId.value = null |
| | | flowPagination.value.total = 0 |
| | | stepPagination.value.total = 0 |
| | | ElMessage.error(error?.message || '子系统流程加载失败') |
| | | } finally { |
| | | flowLoading.value = false |
| | | } |
| | | } |
| | | |
| | | async function loadNodeRows(templateId, current = nodePagination.value.current) { |
| | | if (!templateId) { |
| | | resetFlowState() |
| | | return |
| | | } |
| | | |
| | | nodeLoading.value = true |
| | | try { |
| | | const response = await guardRequestWithMessage( |
| | | fetchTaskPathTemplateNodePage({ |
| | | current, |
| | | pageSize: nodePagination.value.pageSize, |
| | | templateId |
| | | }), |
| | | { records: [] }, |
| | | { timeoutMessage: '模板节点加载超时,已停止等待' } |
| | | ) |
| | | nodeRows.value = normalizeRecords(response) |
| | | nodePagination.value.current = current |
| | | nodePagination.value.total = normalizeTotal(response) |
| | | selectedNodeId.value = null |
| | | flowRows.value = [] |
| | | stepRows.value = [] |
| | | flowPagination.value.current = 1 |
| | | flowPagination.value.total = 0 |
| | | stepPagination.value.current = 1 |
| | | stepPagination.value.total = 0 |
| | | } catch (error) { |
| | | resetFlowState() |
| | | ElMessage.error(error?.message || '模板节点加载失败') |
| | | } finally { |
| | | nodeLoading.value = false |
| | | } |
| | | } |
| | | |
| | | function handleNodeClick(node) { |
| | | if (!node || selectedNodeId.value === node.id) return |
| | | selectedNodeId.value = node.id |
| | | selectedFlowId.value = null |
| | | stepRows.value = [] |
| | | flowPagination.value.current = 1 |
| | | stepPagination.value.current = 1 |
| | | loadFlowRows(node) |
| | | } |
| | | |
| | | function handleFlowClick(flow) { |
| | | if (!flow || selectedFlowId.value === flow.id) return |
| | | selectedFlowId.value = flow.id |
| | | stepPagination.value.current = 1 |
| | | loadStepRows(flow.id) |
| | | } |
| | | |
| | | function handleNodePageChange(current) { |
| | | loadNodeRows(props.detail?.id, current) |
| | | } |
| | | |
| | | function handleFlowPageChange(current) { |
| | | const node = nodeRows.value.find((item) => item.id === selectedNodeId.value) |
| | | if (!node) return |
| | | loadFlowRows(node, current) |
| | | } |
| | | |
| | | function handleStepPageChange(current) { |
| | | if (!selectedFlowId.value) return |
| | | loadStepRows(selectedFlowId.value, current) |
| | | } |
| | | |
| | | function handleVisibleChange(value) { |
| | | visible.value = value |
| | | } |
| | | |
| | | watch( |
| | | () => [props.visible, props.detail?.id], |
| | | ([isVisible, detailId]) => { |
| | | if (isVisible && detailId) { |
| | | loadNodeRows(detailId) |
| | | } |
| | | if (!isVisible) { |
| | | resetFlowState() |
| | | } |
| | | }, |
| | | { immediate: true } |
| | | ) |
| | | </script> |