|
|
@@ -136,12 +136,29 @@
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
|
|
|
+ <el-row :gutter="16" style="margin-bottom: 16px;">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="hover" v-loading="loadingNodeCount">
|
|
|
+ <template #header>
|
|
|
+ <div class="detail-header">
|
|
|
+ <span>各节点统计</span>
|
|
|
+ <el-select v-model="nodeCountDefId" placeholder="请选择流程" style="width: 260px;" @change="loadNodeCount">
|
|
|
+ <el-option v-for="def in definitionList" :key="def.id" :label="def.name" :value="def.id" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <v-chart v-if="nodeCountData.length > 0" :option="nodeCountOption" autoresize style="height: 60px;" :style="{ height: Math.max(nodeCountData.length * 36, 200) + 'px' }" />
|
|
|
+ <el-empty v-else description="请选择流程查看各节点统计" />
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+
|
|
|
<el-card shadow="hover" class="detail-card" v-loading="loadingInstanceList">
|
|
|
<template #header>
|
|
|
<div class="detail-header">
|
|
|
<span>流程明细</span>
|
|
|
<div class="detail-actions">
|
|
|
- <el-select v-model="instanceQuery.processName" placeholder="流程名称" clearable style="width: 160px;" @change="loadInstanceList">
|
|
|
+ <el-select v-model="instanceQuery.processName" placeholder="流程名称" clearable style="width: 160px;" @change="onInstanceProcessChange">
|
|
|
<el-option v-for="def in definitionList" :key="def.id" :label="def.name" :value="def.name" />
|
|
|
</el-select>
|
|
|
<el-input v-model="instanceQuery.applicantName" placeholder="归属用户" clearable style="width: 140px; margin-left: 8px;" @change="loadInstanceList" />
|
|
|
@@ -211,7 +228,8 @@ import {
|
|
|
getStuckInstances,
|
|
|
getOverview,
|
|
|
getStatusDistribution,
|
|
|
- getTrend
|
|
|
+ getTrend,
|
|
|
+ getNodeCount
|
|
|
} from '@/api/analysis'
|
|
|
import { listEnabled } from '@/api/flow/definition'
|
|
|
import { listInstance } from '@/api/flow/instance'
|
|
|
@@ -636,14 +654,25 @@ const loadingInstanceList = ref(false)
|
|
|
const definitionList = ref<FlowDefinition[]>([])
|
|
|
const currentNodeOptions = ref<string[]>([])
|
|
|
|
|
|
-async function loadDefinitionsFull() {
|
|
|
- try { definitionList.value = await listEnabled() } catch { /* ignore */ }
|
|
|
-}
|
|
|
-
|
|
|
const instanceDetailVisible = ref(false)
|
|
|
const instanceDetailId = ref(0)
|
|
|
function openInstanceDetail(id: number) { instanceDetailId.value = id; instanceDetailVisible.value = true }
|
|
|
|
|
|
+function onInstanceProcessChange() {
|
|
|
+ instanceQuery.currentNodeName = ''
|
|
|
+ const def = definitionList.value.find(d => d.name === instanceQuery.processName)
|
|
|
+ if (def?.flowJson) {
|
|
|
+ try {
|
|
|
+ currentNodeOptions.value = JSON.parse(def.flowJson).nodes
|
|
|
+ .filter((n: any) => n.type !== 'start' && n.type !== 'end' && n.type !== 'condition' && n.type !== 'cc')
|
|
|
+ .map((n: any) => n.name)
|
|
|
+ } catch { currentNodeOptions.value = [] }
|
|
|
+ } else {
|
|
|
+ currentNodeOptions.value = []
|
|
|
+ }
|
|
|
+ loadInstanceList()
|
|
|
+}
|
|
|
+
|
|
|
async function loadInstanceList() {
|
|
|
loadingInstanceList.value = true
|
|
|
try {
|
|
|
@@ -658,6 +687,40 @@ async function loadInstanceList() {
|
|
|
} finally { loadingInstanceList.value = false }
|
|
|
}
|
|
|
|
|
|
+// 各节点统计图
|
|
|
+const nodeCountDefId = ref<number | null>(null)
|
|
|
+const nodeCountData = ref<{ nodeName: string; count: number }[]>([])
|
|
|
+const loadingNodeCount = ref(false)
|
|
|
+const nodeCountOption = computed(() => ({
|
|
|
+ tooltip: { trigger: 'axis' as const, axisPointer: { type: 'shadow' as const } },
|
|
|
+ grid: { left: 120, right: 40, top: 10, bottom: 10 },
|
|
|
+ xAxis: { type: 'value' as const, minInterval: 1 },
|
|
|
+ yAxis: { type: 'category' as const, data: nodeCountData.value.map(d => d.nodeName), inverse: true },
|
|
|
+ series: [{
|
|
|
+ type: 'bar',
|
|
|
+ data: nodeCountData.value.map((d, i) => ({ value: d.count, itemStyle: { color: colorPalette[i % colorPalette.length] } })),
|
|
|
+ barMaxWidth: 30,
|
|
|
+ label: { show: true, position: 'right' as const }
|
|
|
+ }]
|
|
|
+}))
|
|
|
+
|
|
|
+async function loadNodeCount() {
|
|
|
+ if (!nodeCountDefId.value) return
|
|
|
+ loadingNodeCount.value = true
|
|
|
+ try { nodeCountData.value = await getNodeCount(nodeCountDefId.value) } catch { nodeCountData.value = [] }
|
|
|
+ finally { loadingNodeCount.value = false }
|
|
|
+}
|
|
|
+
|
|
|
+async function loadDefinitionsFull() {
|
|
|
+ try {
|
|
|
+ definitionList.value = await listEnabled()
|
|
|
+ if (definitionList.value.length > 0 && !nodeCountDefId.value) {
|
|
|
+ nodeCountDefId.value = definitionList.value[definitionList.value.length - 1].id
|
|
|
+ loadNodeCount()
|
|
|
+ }
|
|
|
+ } catch { /* ignore */ }
|
|
|
+}
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
loadDefinitions()
|
|
|
loadAll()
|