소스 검색

行为记录优化

ye-zhaojia 2 주 전
부모
커밋
ba8ea223d3
1개의 변경된 파일25개의 추가작업 그리고 315개의 파일을 삭제
  1. 25 315
      src/views/flow/execute/FlowTimeline.vue

+ 25 - 315
src/views/flow/execute/FlowTimeline.vue

@@ -1,88 +1,19 @@
 <template>
   <div class="flow-timeline">
-    <template v-for="(row, idx) in rows" :key="rowKey(row, idx)">
-      <!-- 起点 / 终点 -->
-      <div v-if="row.kind === 'start' || row.kind === 'end'" class="flow-endpoint" :class="row.kind">
-        <div class="endpoint-dot" :class="row.kind" />
-        <div class="endpoint-name">{{ row.nodes?.[0]?.name }}</div>
-      </div>
-
-      <!-- 分支区(线性:在分支处展示可能的分支路线) -->
-      <div v-else-if="row.kind === 'branch'" class="flow-branch">
-        <div class="branch-title">
-          <span class="branch-icon"><el-icon><Operation /></el-icon></span>
-          <span>{{ row.title || '条件分支' }}</span>
-        </div>
-        <div class="branch-routes">
-          <div v-for="(br, bi) in row.branches" :key="bi" class="branch-route" :class="{ chosen: br.isChosen }">
-            <div class="branch-label">
-              <span class="branch-mark">{{ bi === 0 ? '├─' : '└─' }}</span>
-              <span class="branch-name" :class="{ default: br.isDefault }">{{ br.label }}</span>
-              <span v-if="br.condition" class="branch-cond">{{ br.condition }}</span>
-              <el-tag v-if="br.isDefault" size="small" type="info" effect="plain">默认</el-tag>
-              <el-tag v-if="br.isChosen" size="small" type="success" effect="light">✓ 已选择</el-tag>
-            </div>
-            <div class="branch-nodes">
-              <template v-if="br.nodes.length">
-                <div
-                  v-for="n in br.nodes"
-                  :key="n.id"
-                  class="branch-chip"
-                  :class="n.status"
-                  @click="toggleBranchDetail(n.id)"
-                >
-                  <span class="chip-dot" :class="n.status" />
-                  <span class="chip-name">{{ n.name }}</span>
-                  <span v-if="n.isMyTurn" class="my-turn-tag">待我处理</span>
-                  <span class="chip-expand">{{ branchDetailId === n.id ? '收起' : '详情' }}</span>
-                </div>
-              </template>
-              <span v-else class="branch-empty">直连下一节点</span>
-            </div>
-
-            <!-- 分支节点详情(任务 + 审批记录),仅在该节点所属的分支内展示 -->
-            <div v-if="branchDetailNode && br.nodes.some(n => n.id === branchDetailId)" class="branch-detail">
-              <template v-if="branchDetailNode.nodeProgress?.tasks?.length">
-                <div class="detail-title">任务</div>
-                <div v-for="t in branchDetailNode.nodeProgress.tasks" :key="t.id" class="task-item">
-                  <span class="task-assignee">{{ t.assigneeName || '用户' + t.assigneeId }}</span>
-                  <span class="task-status" :class="taskStatusType(t.status)">{{ taskStatusText(t.status) }}</span>
-                  <span v-if="t.comment" class="task-comment">{{ t.comment }}</span>
-                </div>
-              </template>
-              <template v-if="branchDetailNode.records.length">
-                <div class="detail-title">审批记录</div>
-                <div v-for="r in branchDetailNode.records" :key="r.id" class="record-item">
-                  <span class="record-operator">{{ r.operatorName || '用户' + r.operatorId }}</span>
-                  <span class="record-action" :class="recordActionType(r.actionResult)">{{ recordActionText(r.actionResult) }}</span>
-                  <span v-if="r.comment" class="record-comment">{{ r.comment }}</span>
-                  <span v-if="r.createTime" class="record-time">{{ r.createTime }}</span>
-                </div>
-              </template>
-            </div>
-          </div>
-        </div>
-      </div>
-
-      <!-- 主流程节点 / 抄送节点(线性主线) -->
-      <div v-else class="flow-main-row">
-        <NodeCard
-          v-for="n in row.nodes"
-          :key="n.id"
-          :node="n"
-          :is-expanded="isNodeExpanded(n.id)"
-          @toggle-node="toggleNodeExpand"
-        />
-      </div>
-    </template>
+    <!-- 线性展示:所有审批节点按流程拓扑顺序依次排列(分支节点同样顺序展示) -->
+    <div v-for="node in linearNodes" :key="node.id" class="flow-main-row">
+      <NodeCard
+        :node="node"
+        :is-expanded="isNodeExpanded(node.id)"
+        @toggle-node="toggleNodeExpand"
+      />
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
 import { computed, ref, watch } from 'vue'
-import { Operation } from '@element-plus/icons-vue'
-import { buildFlowLayout, type FlowRow, type FlowLayoutNode } from '@/utils/flowLayout'
-import { taskStatusText, taskStatusType, recordActionText, recordActionType } from '@/utils/flow'
+import { buildFlowLayout, type FlowLayoutNode } from '@/utils/flowLayout'
 import type { ProcessProgress } from '@/types/flow'
 import NodeCard from './NodeCard.vue'
 
@@ -90,28 +21,36 @@ const props = defineProps<{
   progress: ProcessProgress | null
 }>()
 
-const rows = computed<FlowRow[]>(() => {
+// 用布局算法解析状态,但输出"线性平铺"的节点列表(分支节点按顺序列出,不分组展示)
+const linearNodes = computed<FlowLayoutNode[]>(() => {
   const def = props.progress?.definition
   if (!def?.flowJson) return []
   try {
     const model = JSON.parse(def.flowJson)
-    return buildFlowLayout(
+    const rows = buildFlowLayout(
       model.nodes || [],
       model.edges || [],
       props.progress?.nodes || [],
       props.progress?.records || [],
       props.progress?.instance?.currentNode
     )
+    const nodes: FlowLayoutNode[] = []
+    for (const row of rows) {
+      if (row.kind === 'start' || row.kind === 'end') continue // 线性视图不展示起止标记
+      if (row.kind === 'branch') {
+        // 分支行:按分支顺序平铺其中的节点
+        for (const br of row.branches || []) nodes.push(...br.nodes)
+      } else {
+        nodes.push(...(row.nodes || []))
+      }
+    }
+    return nodes
   } catch {
     return []
   }
 })
 
-function rowKey(row: FlowRow, idx: number): string {
-  return `${idx}-${row.kind}-${row.nodes?.[0]?.id || row.title || ''}`
-}
-
-// 主流程节点展开/收起
+// 展开/收起(子节点)
 const expandedSet = ref<Set<string>>(new Set())
 function isNodeExpanded(id: string): boolean {
   return expandedSet.value.has(id)
@@ -123,24 +62,6 @@ function toggleNodeExpand(id: string) {
   expandedSet.value = next
 }
 
-// 分支节点详情展开/收起(同一时间仅展开一个)
-const branchDetailId = ref<string | null>(null)
-function toggleBranchDetail(id: string) {
-  branchDetailId.value = branchDetailId.value === id ? null : id
-}
-const branchDetailNode = computed<FlowLayoutNode | null>(() => {
-  const id = branchDetailId.value
-  if (!id) return null
-  for (const row of rows.value) {
-    if (row.kind !== 'branch') continue
-    for (const br of row.branches || []) {
-      const found = br.nodes.find((n) => n.id === id)
-      if (found) return found
-    }
-  }
-  return null
-})
-
 // 数据变化后自动展开"进行中"的节点及子节点
 watch(
   () => props.progress,
@@ -179,221 +100,10 @@ watch(
   background: #e4e7ed;
 }
 
-/* 起点/终点 */
-.flow-endpoint {
-  position: relative;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 6px 0;
-  z-index: 1;
-}
-.endpoint-dot {
-  width: 16px;
-  height: 16px;
-  border-radius: 50%;
-  border: 3px solid #fff;
-  box-shadow: 0 0 0 2px #e4e7ed;
-  margin-left: -11px;
-  flex-shrink: 0;
-}
-.endpoint-dot.start {
-  background: #67c23a;
-}
-.endpoint-dot.end {
-  background: #f56c6c;
-}
-.endpoint-name {
-  font-size: 13px;
-  font-weight: 600;
-  color: #606266;
-}
-
-/* 主流程行 */
+/* 线性主流程行 */
 .flow-main-row {
   position: relative;
   padding: 4px 0;
   z-index: 1;
 }
-
-/* 分支区 */
-.flow-branch {
-  position: relative;
-  margin: 8px 0;
-  z-index: 1;
-  background: #fafafa;
-  border: 1px solid #ebeef5;
-  border-left: 4px solid #909399;
-  border-radius: 6px;
-  padding: 8px 12px;
-}
-.branch-title {
-  display: flex;
-  align-items: center;
-  gap: 4px;
-  font-size: 12px;
-  font-weight: 600;
-  color: #909399;
-  margin-bottom: 6px;
-}
-.branch-icon {
-  display: inline-flex;
-  align-items: center;
-}
-.branch-routes {
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
-}
-.branch-route {
-  border-left: 2px solid #e4e7ed;
-  padding-left: 8px;
-  padding-bottom: 4px;
-  margin-bottom: 2px;
-  border-radius: 0 4px 4px 0;
-}
-.branch-route.chosen {
-  border-left-color: #67c23a;
-  background: #f0f9eb;
-  padding: 4px 8px;
-}
-.branch-label {
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  flex-wrap: wrap;
-  font-size: 12px;
-}
-.branch-mark {
-  color: #c0c4cc;
-  font-family: monospace;
-}
-.branch-name {
-  font-weight: 600;
-  color: #303133;
-}
-.branch-cond {
-  color: #909399;
-  font-family: monospace;
-}
-.branch-nodes {
-  display: flex;
-  align-items: center;
-  flex-wrap: wrap;
-  gap: 6px;
-  margin-top: 4px;
-}
-.branch-chip {
-  display: inline-flex;
-  align-items: center;
-  gap: 5px;
-  padding: 2px 8px;
-  border: 1px solid #ebeef5;
-  border-radius: 12px;
-  background: #fff;
-  font-size: 12px;
-  color: #606266;
-  cursor: pointer;
-  user-select: none;
-  transition: all 0.15s;
-}
-.branch-chip:hover {
-  border-color: #c6e2ff;
-  box-shadow: 0 1px 4px rgba(64, 158, 255, 0.15);
-}
-.branch-chip.completed {
-  border-color: #b3e19d;
-  background: #f0f9eb;
-  color: #529b2e;
-}
-.branch-chip.current {
-  border-color: #f0c78a;
-  background: #fdf6ec;
-  color: #b88230;
-}
-.branch-chip.pending {
-  border-color: #d5d5d5;
-  background: #f5f7fa;
-  color: #909399;
-}
-.branch-chip.passed {
-  border-color: #b3e19d;
-  background: #f0f9eb;
-  color: #529b2e;
-}
-.chip-dot {
-  width: 8px;
-  height: 8px;
-  border-radius: 50%;
-  flex-shrink: 0;
-}
-.chip-dot.completed {
-  background: #67c23a;
-}
-.chip-dot.current {
-  background: #e6a23c;
-}
-.chip-dot.pending {
-  background: #c0c4cc;
-}
-.chip-dot.passed {
-  background: #67c23a;
-}
-.chip-name {
-  font-weight: 500;
-}
-.chip-expand {
-  font-size: 11px;
-  color: #409eff;
-}
-.my-turn-tag {
-  font-size: 11px;
-  color: #fff;
-  background: #f56c6c;
-  border-radius: 3px;
-  padding: 0 5px;
-  line-height: 16px;
-}
-.branch-empty {
-  font-size: 12px;
-  color: #c0c4cc;
-}
-.branch-detail {
-  margin-top: 6px;
-  padding: 6px 8px;
-  background: #fff;
-  border: 1px dashed #e4e7ed;
-  border-radius: 4px;
-}
-.detail-title {
-  font-size: 11px;
-  color: #909399;
-  margin-bottom: 2px;
-}
-.task-item,
-.record-item {
-  display: flex;
-  align-items: center;
-  gap: 6px;
-  font-size: 12px;
-  color: #606266;
-  padding: 2px 0;
-  flex-wrap: wrap;
-}
-.task-assignee,
-.record-operator {
-  font-weight: 600;
-}
-.task-status,
-.record-action {
-  font-size: 11px;
-}
-.task-comment,
-.record-comment {
-  color: #909399;
-}
-.record-time {
-  color: #c0c4cc;
-  margin-left: auto;
-}
 </style>