|
|
@@ -16,7 +16,7 @@
|
|
|
<p>作业类型: <span>{{ ticketTypeLabel }}</span></p>
|
|
|
<p>作业状态: <span>{{ ticketStatusLabel }}</span></p>
|
|
|
<p>开始时间: <span>{{JobForm.ticketStartTime}}</span> </p>
|
|
|
- <p>结束时间: <span>{{JobForm.ticketEndTime}}</span> </p>
|
|
|
+ <p>结束时间: <span>{{JobForm.ticketEndTime || '-'}} </span> </p>
|
|
|
<p>当前步骤: <span class="specialText">{{ currentStepTitle }}</span> </p>
|
|
|
<p>最新日志: <span>待更新</span> </p>
|
|
|
</div>
|
|
|
@@ -33,8 +33,8 @@
|
|
|
<div class="processDetail" ref="scrollContainer">
|
|
|
<!-- VueFlow 主画布 -->
|
|
|
<VueFlow style="width: 100%; height: 300px" :min-zoom="1" :max-zoom="1" :default-zoom="1" :nodes="nodes" :edges="edges">
|
|
|
- <template #node-default="{ id, data }">
|
|
|
- <div class="custom-node" :id="id">
|
|
|
+ <template #node-default="{ id, data }" >
|
|
|
+ <div class="custom-node" :id="id" >
|
|
|
<div class="node-content">
|
|
|
<!-- 图标显示 -->
|
|
|
<div style="font-size: 30px">
|
|
|
@@ -295,9 +295,6 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|
|
import type { TableInstance } from 'element-plus'
|
|
|
import { CaretRight } from '@element-plus/icons-vue'
|
|
|
|
|
|
-const tableLayout = ref<TableInstance['tableLayout']>('fixed')
|
|
|
-const tableLayout1 = ref<TableInstance['tableLayout1']>('fixed')
|
|
|
-
|
|
|
const JobForm = reactive({
|
|
|
createTime: null,
|
|
|
id: null,
|
|
|
@@ -361,12 +358,13 @@ const joblogDialogVisible= ref(false)
|
|
|
// 初始化
|
|
|
onMounted(async () => {
|
|
|
await getDetail()
|
|
|
+ //基础信息
|
|
|
jobTypeOptions.value = await getIntDictOptions(DICT_TYPE.TICKET_TYPE)
|
|
|
jobStatusOptions.value = await getIntDictOptions(DICT_TYPE.TICKET_STATUS)
|
|
|
await fetchAllGroupsAndPoints() //获取所有分组和点位 来渲染SOP的首页
|
|
|
- await onModeContent()
|
|
|
+ await onModeContent() //作业流程
|
|
|
window.addEventListener('resize', onResize)
|
|
|
- await onResize()
|
|
|
+ onResize()
|
|
|
})
|
|
|
onBeforeUnmount(() => {
|
|
|
window.removeEventListener('resize', onResize)
|
|
|
@@ -406,41 +404,67 @@ const getDetail = async () => {
|
|
|
|
|
|
// 作业流程绘制
|
|
|
const scrollContainer = ref(null)
|
|
|
+const nodes = ref([]) //储存节点
|
|
|
+const edges = ref([]) // 存储连接线
|
|
|
+const allSteps = ref([]) // 缓存完整流程数据
|
|
|
+const { addNodes, addEdges, setEdges, setNodes } = useVueFlow()
|
|
|
|
|
|
const scrollToCurrentNode = () => {
|
|
|
nextTick(() => {
|
|
|
- if (!scrollContainer.value) return
|
|
|
+ const container = scrollContainer.value
|
|
|
+ if (!container) return
|
|
|
+
|
|
|
+ const currentIndex = nodes.value.findIndex(n => n.data.stepStatus === 0)
|
|
|
+ if (currentIndex === -1) return
|
|
|
|
|
|
- const firstZeroIndex = nodes.value.findIndex(n => n.data.stepStatus === 0)
|
|
|
- if (firstZeroIndex === -1) return
|
|
|
+ const currentNodeId = nodes.value[currentIndex]?.id
|
|
|
+ const nextNodeId = nodes.value[currentIndex + 1]?.id
|
|
|
|
|
|
- const currentNodeId = nodes.value[firstZeroIndex].id
|
|
|
const currentNodeEl = document.getElementById(currentNodeId)
|
|
|
+ const nextNodeEl = nextNodeId ? document.getElementById(nextNodeId) : null
|
|
|
+
|
|
|
if (!currentNodeEl) return
|
|
|
|
|
|
- // 计算滚动,让当前节点居中
|
|
|
- const container = scrollContainer.value
|
|
|
- const containerRect = container.getBoundingClientRect()
|
|
|
- const nodeRect = currentNodeEl.getBoundingClientRect()
|
|
|
+ // 获取节点相对于容器的偏移位置
|
|
|
+ const currentLeft = currentNodeEl.offsetLeft
|
|
|
+ const currentWidth = currentNodeEl.offsetWidth
|
|
|
|
|
|
- const offset = nodeRect.left - containerRect.left - (containerRect.width / 2) + (nodeRect.width / 2)
|
|
|
+ let centerPos = currentLeft + currentWidth / 2
|
|
|
|
|
|
- container.scrollBy({
|
|
|
- left: offset,
|
|
|
+ if (nextNodeEl) {
|
|
|
+ const nextLeft = nextNodeEl.offsetLeft
|
|
|
+ const nextWidth = nextNodeEl.offsetWidth
|
|
|
+ centerPos = (centerPos + nextLeft + nextWidth / 2) / 2
|
|
|
+ }
|
|
|
+
|
|
|
+ // 目标是让 centerPos 居中容器
|
|
|
+ const scrollTarget = centerPos - container.clientWidth / 2
|
|
|
+
|
|
|
+ container.scrollTo({
|
|
|
+ right: scrollTarget,
|
|
|
behavior: 'smooth'
|
|
|
})
|
|
|
})
|
|
|
}
|
|
|
|
|
|
const onResize = () => {
|
|
|
- if (window.innerWidth <= 600) {
|
|
|
+ const isMobile = window.innerWidth <= 600
|
|
|
+ const stepsData = [...allSteps.value] // 缓存的完整流程步骤数据
|
|
|
+
|
|
|
+ const currentIndex = stepsData.findIndex(item => item.stepStatus === 0)
|
|
|
+ const filtered = isMobile && currentIndex !== -1
|
|
|
+ ? stepsData.slice(currentIndex) // 小屏只显示当前及后续
|
|
|
+ : stepsData // 大屏恢复全部
|
|
|
+
|
|
|
+ renderNodesFromData(filtered)
|
|
|
+ renderEdgesFromData(filtered)
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
scrollToCurrentNode()
|
|
|
- }
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
-const nodes = ref([]) //储存节点
|
|
|
-const edges = ref([]) // 存储连接线
|
|
|
-const { addNodes, addEdges, setEdges, setNodes } = useVueFlow()
|
|
|
+
|
|
|
// 模式初始化
|
|
|
const onModeContent = async (value) => {
|
|
|
JobForm.modeId = value
|
|
|
@@ -449,6 +473,24 @@ const onModeContent = async (value) => {
|
|
|
const sortedData = Data.ticketStepList.sort((a, b) => (a.stepIndex || 0) - (b.stepIndex || 0))
|
|
|
renderNodesFromData(sortedData)
|
|
|
renderEdgesFromData(sortedData)
|
|
|
+ allSteps.value = sortedData // 缓存原始数据
|
|
|
+ renderResponsiveSteps()
|
|
|
+}
|
|
|
+const renderResponsiveSteps = () => {
|
|
|
+ const isMobile = window.innerWidth <= 600
|
|
|
+ const stepsData = [...allSteps.value]
|
|
|
+
|
|
|
+ const currentIndex = stepsData.findIndex(item => item.stepStatus === 0)
|
|
|
+ const filtered = isMobile && currentIndex !== -1
|
|
|
+ ? stepsData.slice(currentIndex) // 从当前步骤开始
|
|
|
+ : stepsData // 全部步骤
|
|
|
+
|
|
|
+ renderNodesFromData(filtered)
|
|
|
+ renderEdgesFromData(filtered)
|
|
|
+
|
|
|
+ nextTick(() => {
|
|
|
+ scrollToCurrentNode()
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
// 独立的清空画布函数
|
|
|
@@ -512,6 +554,10 @@ const renderNodesFromData = (data) => {
|
|
|
draggable: false //设置是否可以拖动生成的图
|
|
|
})
|
|
|
})
|
|
|
+ nextTick(() => {
|
|
|
+ scrollToCurrentNode()
|
|
|
+ })
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// const renderNodesFromData = (data) => {
|