#
zhou zhou
8 小时以前 333a93571452073a9e628c6256044d345099aa50
rsf-design/src/views/basic-info/task-path-template/modules/task-path-template-flow-drawer.vue
@@ -2,60 +2,203 @@
  <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 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 v-else class="space-y-4">
        <ElCard shadow="never" class="art-table-card">
          <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 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>
            </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>
            </div>
            <ElTag :type="detail.isCurrentType || 'info'" effect="light">
              {{ detail.isCurrentText || '--' }}
            </ElTag>
          </ElSpace>
          </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>
      <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">
              <span class="font-medium">模板节点</span>
              <span class="text-xs text-[var(--art-text-secondary)]">
                {{ nodeLoading ? '加载中' : `第 ${nodePagination.current} 页 / 共 ${nodePagination.total} 条` }}
              </span>
      </div>
    </ElScrollbar>
          </template>
          <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>
            </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)]">
                {{
                  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>
    </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 },
@@ -70,9 +213,194 @@
    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>