|
|
@@ -10,15 +10,34 @@
|
|
|
>
|
|
|
</div>
|
|
|
<div class="basicContent">
|
|
|
- <p>作业名称: <span>{{JobForm.ticketName}}</span> </p>
|
|
|
- <p>作业区域: <span>{{JobForm.workstationName}}</span> </p>
|
|
|
- <p>工艺设备: <span>{{JobForm.machineryName}}</span> </p>
|
|
|
- <p>作业类型: <span>{{ ticketTypeLabel }}</span></p>
|
|
|
- <p>作业状态: <span>{{ ticketStatusLabel }}</span></p>
|
|
|
- <p>开始时间: <span>{{JobForm.ticketStartTime}}</span> </p>
|
|
|
- <p>结束时间: <span>{{JobForm.ticketEndTime || '-'}} </span> </p>
|
|
|
- <p>当前步骤: <span class="specialText">{{ currentStepTitle }}</span> </p>
|
|
|
- <p>最新日志: <span>待更新</span> </p>
|
|
|
+ <p
|
|
|
+ >作业名称: <span>{{ JobForm.ticketName }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >作业区域: <span>{{ JobForm.workstationName }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >工艺设备: <span>{{ JobForm.machineryName }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >作业类型: <span>{{ ticketTypeLabel }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >作业状态: <span>{{ ticketStatusLabel }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >开始时间: <span>{{ JobForm.ticketStartTime }}</span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >结束时间: <span>{{ JobForm.ticketEndTime || '-' }} </span></p
|
|
|
+ >
|
|
|
+ <p
|
|
|
+ >当前步骤: <span class="specialText">{{ currentStepTitle }}</span></p
|
|
|
+ >
|
|
|
+ <p>最新日志:
|
|
|
+ <span v-if="latestLog" class="hh" v-html="renderLogContent(latestLog.operationContent)"></span>
|
|
|
+ </p>
|
|
|
+
|
|
|
</div>
|
|
|
</div>
|
|
|
</ContentWrap>
|
|
|
@@ -32,9 +51,16 @@
|
|
|
</div>
|
|
|
<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" >
|
|
|
+ <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">
|
|
|
<div class="node-content">
|
|
|
<!-- 图标显示 -->
|
|
|
<div style="font-size: 30px">
|
|
|
@@ -129,14 +155,14 @@
|
|
|
</div>
|
|
|
</template>
|
|
|
<div class="jobMemberBox">
|
|
|
- <div
|
|
|
- class="group-box"
|
|
|
- v-for="(group, index) in jobGroupList"
|
|
|
- :key="group.id"
|
|
|
- >
|
|
|
+ <div class="group-box" v-for="(group, index) in jobGroupList" :key="group.id">
|
|
|
<div class="tab-header">
|
|
|
- <p class="tab-title">{{ group.groupName }}(待锁定:{{group.
|
|
|
- waitLock}} 已锁定:{{group.locked}} 已解锁:{{group.unlocked}})</p>
|
|
|
+ <p class="tab-title"
|
|
|
+ >{{ group.groupName }}(待锁定:{{ group.waitLock }} 已锁定:{{
|
|
|
+ group.locked
|
|
|
+ }}
|
|
|
+ 已解锁:{{ group.unlocked }})</p
|
|
|
+ >
|
|
|
</div>
|
|
|
<!-- 表格数据 -->
|
|
|
<div class="tableCon">
|
|
|
@@ -144,22 +170,15 @@
|
|
|
<el-radio-button value="fixed">常规</el-radio-button>
|
|
|
<el-radio-button value="auto">扩展</el-radio-button>
|
|
|
</el-radio-group>
|
|
|
- <el-table
|
|
|
- :data="group.ticketPointsRespVOList"
|
|
|
- :table-layout="tableLayouts[index]"
|
|
|
- >
|
|
|
+ <el-table :data="group.ticketPointsRespVOList" :table-layout="tableLayouts[index]">
|
|
|
<el-table-column prop="pointName" label="隔离点" />
|
|
|
<el-table-column prop="ability" label="作用" />
|
|
|
<el-table-column prop="lockUserName" label="锁定人" />
|
|
|
- <el-table-column prop="pointStatus" label="锁定状态" >
|
|
|
+ <el-table-column prop="pointStatus" label="锁定状态">
|
|
|
<template #default="scope">
|
|
|
- <dict-tag
|
|
|
- :type="DICT_TYPE.POINT_STATUS"
|
|
|
- :value="scope.row.pointStatus"
|
|
|
- />
|
|
|
+ <dict-tag :type="DICT_TYPE.POINT_STATUS" :value="scope.row.pointStatus" />
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
-
|
|
|
</el-table>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -180,8 +199,8 @@
|
|
|
<span class="tab-title">待共锁({{ waitLockUsers.length }})</span>
|
|
|
</div>
|
|
|
<div class="user">
|
|
|
- <div class="userItem" v-for="user in waitLockUsers" :key="user.userId" >
|
|
|
- <img src="@/assets/images/icon_co-lock.png" alt=""/>
|
|
|
+ <div class="userItem" v-for="user in waitLockUsers" :key="user.userId">
|
|
|
+ <img :src="user.avatar" alt="" />
|
|
|
<p>{{ user.userName }}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -196,7 +215,7 @@
|
|
|
</div>
|
|
|
<div class="user">
|
|
|
<div class="userItem" v-for="user in lockedUsers" :key="user.userId">
|
|
|
- <img src="@/assets/images/icon_co-lock.png" alt=""/>
|
|
|
+ <img :src="user.avatar" alt="" />
|
|
|
<p>{{ user.userName }}</p>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -211,7 +230,7 @@
|
|
|
</div>
|
|
|
<div class="user">
|
|
|
<div class="userItem" v-for="user in unlockPendingUsers" :key="user.userId">
|
|
|
- <img src="@/assets/images/icon_co-lock.png" alt=""/>
|
|
|
+ <img :src="user.avatar" alt="" />
|
|
|
<p>{{ user.userName }} </p>
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -239,8 +258,7 @@
|
|
|
:indeterminate="isIndeterminate"
|
|
|
:checked="checkAlljoblog"
|
|
|
@change="handleCheckAllChange"
|
|
|
- style="margin:3px 25px;font-size: 16px"
|
|
|
-
|
|
|
+ style="margin: 0 25px; font-size: 16px"
|
|
|
>
|
|
|
全部
|
|
|
</el-checkbox>
|
|
|
@@ -266,22 +284,11 @@
|
|
|
</el-tabs>
|
|
|
</div>
|
|
|
</ContentWrap>
|
|
|
-<!-- 作业日志弹框-->
|
|
|
- <el-dialog
|
|
|
- v-model="joblogDialogVisible"
|
|
|
- title="Warning"
|
|
|
- width="500"
|
|
|
- align-center
|
|
|
- >
|
|
|
- <span>Open the dialog from the center from the screen</span>
|
|
|
- <template #footer>
|
|
|
- <div class="dialog-footer">
|
|
|
- <el-button @click="joblogDialogVisible = false">Cancel</el-button>
|
|
|
- <el-button type="primary" @click="joblogDialogVisible = false">
|
|
|
- Confirm
|
|
|
- </el-button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
+ <!-- 作业日志弹框-->
|
|
|
+ <el-dialog v-model="JoblogDialogVisible" title="新日志提醒" width="500" align-center>
|
|
|
+ <p>
|
|
|
+ <span v-html="renderLogContent(dialogLog.operationContent)"></span>
|
|
|
+ </p>
|
|
|
</el-dialog>
|
|
|
</template>
|
|
|
|
|
|
@@ -294,6 +301,9 @@ import * as JobApi from '@/api/job/index'
|
|
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|
|
import type { TableInstance } from 'element-plus'
|
|
|
import { CaretRight } from '@element-plus/icons-vue'
|
|
|
+import { getTicketOperLogPage } from '@/api/job/index'
|
|
|
+import { connectWebsocket, closeWebsocket } from '@/utils/webSocket'
|
|
|
+import { getIsSystemAttributeByKey } from '@/api/basic/configuration/index'
|
|
|
|
|
|
const JobForm = reactive({
|
|
|
createTime: null,
|
|
|
@@ -317,7 +327,8 @@ const JobForm = reactive({
|
|
|
ticketGroupList: null,
|
|
|
ticketPointsList: null,
|
|
|
ticketStepList: null,
|
|
|
- ticketUserList: null
|
|
|
+ ticketUserList: null,
|
|
|
+ ticketOperLogList:null
|
|
|
})
|
|
|
const allGroups = ref<any[]>([]) //获取所有分组
|
|
|
const groupList = ref([]) //获取当前sopId的分组
|
|
|
@@ -329,30 +340,29 @@ const jobStatusOptions = ref([])
|
|
|
const currentStepTitle = ref('') // 当前步骤名称
|
|
|
|
|
|
// 表格数据
|
|
|
-const jobGroupList=ref([]) //获取作业分组信息
|
|
|
+const jobGroupList = ref([]) //获取作业分组信息
|
|
|
const ticketPointsList = ref([]) // 隔离点数据
|
|
|
const tableLayouts = ref([]) // 每组表格的布局
|
|
|
|
|
|
// 人员共锁
|
|
|
const jobclockerList = ref([])
|
|
|
-const isShowclocker=ref(true) //是否展示人员共锁
|
|
|
+const isShowclocker = ref(true) //是否展示人员共锁
|
|
|
const waitLockUsers = ref([])
|
|
|
const lockedUsers = ref([])
|
|
|
const unlockPendingUsers = ref([])
|
|
|
|
|
|
// 作业日志
|
|
|
-const joblogList = ref([])//原始作业日志数据列表
|
|
|
+const joblogList = ref([]) //原始作业日志数据列表
|
|
|
const filteredJoblogList = ref([]) // 过滤后日志
|
|
|
// 作业日志底部查询
|
|
|
-const isIndeterminate= ref(false)
|
|
|
+const isIndeterminate = ref(false)
|
|
|
const checkedJoblogs = ref([])
|
|
|
-const jobLogtypes= ref([])
|
|
|
-const joblogsOptions= ref([]) // 只存所有 dictValue,用于全选逻辑,
|
|
|
-const checkAlljoblog =ref(true)
|
|
|
-const JoblogdialogVisible =ref(false)
|
|
|
-const dialogLog= ref('')
|
|
|
-const isJoblogVisible= ref(false)
|
|
|
-const joblogDialogVisible= ref(false)
|
|
|
+const jobLogtypes = ref([])
|
|
|
+const joblogsOptions = ref([]) // 只存所有 dictValue,用于全选逻辑,
|
|
|
+const checkAlljoblog = ref(true)
|
|
|
+const dialogLog = ref('')
|
|
|
+const isJoblogVisible = ref(false)
|
|
|
+const JoblogDialogVisible = ref(false)
|
|
|
|
|
|
|
|
|
// 初始化
|
|
|
@@ -362,25 +372,33 @@ onMounted(async () => {
|
|
|
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)
|
|
|
onResize()
|
|
|
+ getWebSocket()
|
|
|
+
|
|
|
})
|
|
|
onBeforeUnmount(() => {
|
|
|
window.removeEventListener('resize', onResize)
|
|
|
+ closeWebsocket()
|
|
|
})
|
|
|
console.log(route.query.id, '是否传递成功')
|
|
|
|
|
|
-// 基础信息
|
|
|
+// 基础信息作业类型
|
|
|
const ticketTypeLabel = computed(() => {
|
|
|
- const match = jobTypeOptions.value.find(item => item.value == JobForm.ticketType)
|
|
|
+ const match = jobTypeOptions.value.find((item) => item.value == JobForm.ticketType)
|
|
|
return match ? match.label : JobForm.ticketType
|
|
|
})
|
|
|
-
|
|
|
+// 基础信息作业状态
|
|
|
const ticketStatusLabel = computed(() => {
|
|
|
- const match = jobStatusOptions.value.find(item => item.value == JobForm.ticketStatus)
|
|
|
+ const match = jobStatusOptions.value.find((item) => item.value == JobForm.ticketStatus)
|
|
|
return match ? match.label : JobForm.ticketStatus
|
|
|
})
|
|
|
+// 基础信息最新日志
|
|
|
+const latestLog = computed(() => {
|
|
|
+ if (!joblogList.value || joblogList.value.length === 0) return null
|
|
|
+ return joblogList.value.reduce((max, item) => item.id > max.id ? item : max)
|
|
|
+})
|
|
|
|
|
|
// 初始化详情数据
|
|
|
const getDetail = async () => {
|
|
|
@@ -390,18 +408,28 @@ const getDetail = async () => {
|
|
|
// 点位锁定
|
|
|
jobGroupList.value = JobForm.ticketGroupList
|
|
|
ticketPointsList.value = JobForm.ticketGroupList.ticketPointsRespVOList
|
|
|
- console.log(JobData,'数据有哪些',ticketPointsList.value,'隔离点数据',jobGroupList.value,'分组信息')
|
|
|
+
|
|
|
+ console.log(
|
|
|
+ JobData,
|
|
|
+ '数据有哪些',
|
|
|
+ ticketPointsList.value,
|
|
|
+ '隔离点数据',
|
|
|
+ jobGroupList.value,
|
|
|
+ '分组信息'
|
|
|
+ )
|
|
|
// 初始化每个表格的布局设置为 'fixed'
|
|
|
tableLayouts.value = jobGroupList.value.map(() => 'fixed')
|
|
|
-
|
|
|
-// 人员共锁
|
|
|
+ // 人员共锁
|
|
|
jobclockerList.value = JobForm.ticketUserList
|
|
|
// 判断是否存在共锁人
|
|
|
- isShowclocker.value = jobclockerList.value.some(item => item.userRole === 'jtcolocker')
|
|
|
+ isShowclocker.value = jobclockerList.value.some((item) => item.userRole === 'jtcolocker')
|
|
|
updateUserGroups() // 分类数据
|
|
|
+// 作业日志
|
|
|
+ joblogList.value = JobForm.ticketOperLogList
|
|
|
+ // ✨强制刷新筛选后的列表
|
|
|
+ filterJoblogs()
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// 作业流程绘制
|
|
|
const scrollContainer = ref(null)
|
|
|
const nodes = ref([]) //储存节点
|
|
|
@@ -414,7 +442,7 @@ const scrollToCurrentNode = () => {
|
|
|
const container = scrollContainer.value
|
|
|
if (!container) return
|
|
|
|
|
|
- const currentIndex = nodes.value.findIndex(n => n.data.stepStatus === 0)
|
|
|
+ const currentIndex = nodes.value.findIndex((n) => n.data.stepStatus === 0)
|
|
|
if (currentIndex === -1) return
|
|
|
|
|
|
const currentNodeId = nodes.value[currentIndex]?.id
|
|
|
@@ -451,10 +479,11 @@ const onResize = () => {
|
|
|
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 // 大屏恢复全部
|
|
|
+ const currentIndex = stepsData.findIndex((item) => item.stepStatus === 0)
|
|
|
+ const filtered =
|
|
|
+ isMobile && currentIndex !== -1
|
|
|
+ ? stepsData.slice(currentIndex) // 小屏只显示当前及后续
|
|
|
+ : stepsData // 大屏恢复全部
|
|
|
|
|
|
renderNodesFromData(filtered)
|
|
|
renderEdgesFromData(filtered)
|
|
|
@@ -464,7 +493,6 @@ const onResize = () => {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
-
|
|
|
// 模式初始化
|
|
|
const onModeContent = async (value) => {
|
|
|
JobForm.modeId = value
|
|
|
@@ -476,14 +504,17 @@ const onModeContent = async (value) => {
|
|
|
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 // 全部步骤
|
|
|
+ const currentIndex = stepsData.findIndex((item) => item.stepStatus === 0)
|
|
|
+ const filtered =
|
|
|
+ isMobile && currentIndex !== -1
|
|
|
+ ? stepsData.slice(currentIndex) // 从当前步骤开始
|
|
|
+ : stepsData // 全部步骤
|
|
|
|
|
|
renderNodesFromData(filtered)
|
|
|
renderEdgesFromData(filtered)
|
|
|
@@ -504,7 +535,7 @@ const clearCanvasProperly = async () => {
|
|
|
const renderNodesFromData = (data) => {
|
|
|
nodes.value = []
|
|
|
|
|
|
- const firstZeroIndex = data.findIndex(item => item.stepStatus === 0)
|
|
|
+ const firstZeroIndex = data.findIndex((item) => item.stepStatus === 0)
|
|
|
if (firstZeroIndex !== -1) {
|
|
|
currentStepTitle.value = data[firstZeroIndex].stepTitleShort || '无标题'
|
|
|
} else {
|
|
|
@@ -557,7 +588,6 @@ const renderNodesFromData = (data) => {
|
|
|
nextTick(() => {
|
|
|
scrollToCurrentNode()
|
|
|
})
|
|
|
-
|
|
|
}
|
|
|
|
|
|
// const renderNodesFromData = (data) => {
|
|
|
@@ -657,27 +687,26 @@ const renderEdgesFromData = (data) => {
|
|
|
|
|
|
// 底部tabbar点击事件
|
|
|
const handleClick = async (tab: TabsPaneContext, event: Event) => {
|
|
|
- console.log(tab, event)
|
|
|
- // console.log(activeName.value,'名称')
|
|
|
- console.log(tab.paneName, '当前点击的 tab 名称')
|
|
|
-
|
|
|
- if(tab.paneName === 'fourth') {
|
|
|
- console.log('作业日志')
|
|
|
- const dictRes= await getIntDictOptions(DICT_TYPE.JOB_LOG_TYPE)
|
|
|
- console.log(dictRes,'接口调用是否成功')
|
|
|
- const dicts = dictRes.map(item => ({
|
|
|
+// 当选中作业日志时传递isJoblogVisible给onNewSocketLog判断日志弹框的显示
|
|
|
+ isJoblogVisible.value = (tab.paneName === 'fourth');
|
|
|
+ await getDetail()
|
|
|
+ if (tab.paneName === 'fourth') {
|
|
|
+ // console.log('作业日志')
|
|
|
+ const dictRes = await getIntDictOptions(DICT_TYPE.JOB_LOG_TYPE)
|
|
|
+ console.log(dictRes, '接口调用是否成功')
|
|
|
+ const dicts = dictRes.map((item) => ({
|
|
|
label: item.label,
|
|
|
- value: String(item.value), // 确保是字符串类型
|
|
|
- }));
|
|
|
-
|
|
|
- jobLogtypes.value = dicts;
|
|
|
- joblogsOptions.value = dicts.map(item => item.value);
|
|
|
- checkedJoblogs.value = [...joblogsOptions.value]; // 初始化全选
|
|
|
- console.log(checkAlljoblog.value,'选中状态');
|
|
|
- checkAlljoblog.value = true;
|
|
|
- console.log(checkAlljoblog.value,'是否选中');
|
|
|
- isIndeterminate.value = false;
|
|
|
- filterJoblogs();
|
|
|
+ value: String(item.value) // 确保是字符串类型
|
|
|
+ }))
|
|
|
+
|
|
|
+ jobLogtypes.value = dicts
|
|
|
+ joblogsOptions.value = dicts.map((item) => item.value)
|
|
|
+ checkedJoblogs.value = [...joblogsOptions.value] // 初始化全选
|
|
|
+ // console.log(checkAlljoblog.value, '选中状态')
|
|
|
+ checkAlljoblog.value = true
|
|
|
+ // console.log(checkAlljoblog.value, '是否选中')
|
|
|
+ isIndeterminate.value = false
|
|
|
+ filterJoblogs()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -766,7 +795,6 @@ const resolvedGroupedPoints = computed(() => {
|
|
|
return result
|
|
|
})
|
|
|
|
|
|
-
|
|
|
// 获取某个分组下的隔离点
|
|
|
// const getPointsByGroupId = (groupId) => {
|
|
|
// return ticketPointsList.value.filter((point) => point.groupId === groupId)
|
|
|
@@ -794,23 +822,67 @@ const coLockUsers = computed(() => {
|
|
|
return JobForm.ticketUserList?.filter((u) => u.userRole === 'jtcolocker') || []
|
|
|
})
|
|
|
|
|
|
-
|
|
|
// 人员共锁
|
|
|
const updateUserGroups = () => {
|
|
|
const allUsers = jobclockerList.value
|
|
|
- console.log(allUsers,jobclockerList.value,'数据拿到了吗')
|
|
|
-
|
|
|
- waitLockUsers.value = jobclockerList.value.filter(u => u.userRole == 'jtcolocker' && u.jobStatus == 0)
|
|
|
- console.log(waitLockUsers.value,'待共锁')
|
|
|
- lockedUsers.value = allUsers.filter(u => u.userRole === 'jtcolocker' && u.jobStatus == 1)
|
|
|
- unlockPendingUsers.value = allUsers.filter(u => u.userRole === 'jtcolocker' && u.jobStatus == 2)
|
|
|
+ console.log(allUsers, jobclockerList.value, '数据拿到了吗')
|
|
|
+
|
|
|
+ waitLockUsers.value = jobclockerList.value.filter(
|
|
|
+ (u) => u.userRole == 'jtcolocker' && u.jobStatus == 0
|
|
|
+ )
|
|
|
+ console.log(waitLockUsers.value, '待共锁')
|
|
|
+ lockedUsers.value = allUsers.filter((u) => u.userRole === 'jtcolocker' && u.jobStatus == 1)
|
|
|
+ unlockPendingUsers.value = allUsers.filter((u) => u.userRole === 'jtcolocker' && u.jobStatus == 2)
|
|
|
}
|
|
|
|
|
|
+const getWebSocket = async () => {
|
|
|
+ const code= route.query.id;
|
|
|
+ const address = 'sys.websocket.address'
|
|
|
+ const addressData = await getIsSystemAttributeByKey(address)
|
|
|
+ const url = addressData.sysAttrValue
|
|
|
+ const isLocalDev = window.location.hostname === 'localhost'
|
|
|
+ const baseAddress = isLocalDev
|
|
|
+ ? 'ws://192.168.0.10:48080'
|
|
|
+ : url;
|
|
|
+ connectWebsocket(
|
|
|
+ `${baseAddress}/websocket/jobTicketLog/${code}`,
|
|
|
+ { w: 'S' },
|
|
|
+ async(msg) => {
|
|
|
+ console.log('接收消息:', msg)
|
|
|
+ // const parts = msg.split('operationType=');
|
|
|
+ if (msg !== 'heartbeat') {
|
|
|
+ // 判断是否需要弹窗显示
|
|
|
+ onNewSocketLog(msg); // 👈传入最新的 WebSocket 消息
|
|
|
+ await getDetail(); // 抽出专用接口函数
|
|
|
+ }
|
|
|
+ // 你的处理逻辑
|
|
|
+ },
|
|
|
+ (err) => {
|
|
|
+ console.error('WebSocket 错误:', err)
|
|
|
+ }
|
|
|
+ )
|
|
|
+}
|
|
|
+//获取作业日志数据
|
|
|
+// const getJobLogs = async () => {
|
|
|
+// const joblogData = {
|
|
|
+// titcketId: route.query.id,
|
|
|
+// current: 1,
|
|
|
+// size: -1
|
|
|
+// }
|
|
|
+// try {
|
|
|
+// const res = await JobApi.getTicketOperLogPage(joblogData)
|
|
|
+// joblogList.value = res.list
|
|
|
+// // ✨强制刷新筛选后的列表
|
|
|
+// filterJoblogs()
|
|
|
+// console.log(res.list, '日志数据', joblogList.value, '复制是否成功')
|
|
|
+// } catch (error) {
|
|
|
+// console.error('获取日志数据失败', error)
|
|
|
+// }
|
|
|
+//
|
|
|
+// }
|
|
|
|
|
|
-
|
|
|
-// 作用日志
|
|
|
// 作业日志页面数据展示样式分割
|
|
|
-const renderLogContent=(content)=> {
|
|
|
+const renderLogContent = (content) => {
|
|
|
if (!content) return ''
|
|
|
// 第一步:先处理 <url>[label] 为 <img> + label
|
|
|
content = content.replace(/<([^>]+)>\[([^\]]+)\]/g, (match, url, label) => {
|
|
|
@@ -822,30 +894,72 @@ const renderLogContent=(content)=> {
|
|
|
}
|
|
|
|
|
|
// 作业日志底部筛选功能
|
|
|
-const handleCheckedJoblogsChange=(val)=> {
|
|
|
- const checkedCount = val.length;
|
|
|
- checkAlljoblog.value = checkedCount === joblogsOptions.value.length;
|
|
|
- isIndeterminate.value = checkedCount > 0 && checkedCount < joblogsOptions.value.length;
|
|
|
+const handleCheckedJoblogsChange = (val) => {
|
|
|
+ const checkedCount = val.length
|
|
|
+ checkAlljoblog.value = checkedCount === joblogsOptions.value.length
|
|
|
+ isIndeterminate.value = checkedCount > 0 && checkedCount < joblogsOptions.value.length
|
|
|
|
|
|
- filterJoblogs();
|
|
|
+ filterJoblogs()
|
|
|
}
|
|
|
-const handleCheckAllChange=(val)=> {
|
|
|
- checkedJoblogs.value = val ? [...joblogsOptions.value] : [];
|
|
|
- isIndeterminate.value = false;
|
|
|
- filterJoblogs();
|
|
|
+const handleCheckAllChange = (val) => {
|
|
|
+ checkedJoblogs.value = val ? [...joblogsOptions.value] : []
|
|
|
+ isIndeterminate.value = false
|
|
|
+ filterJoblogs()
|
|
|
}
|
|
|
|
|
|
// 实际日志筛选逻辑
|
|
|
const filterJoblogs = () => {
|
|
|
if (checkedJoblogs.value.includes('ALL')) {
|
|
|
- filteredJoblogList.value = joblogList.value;
|
|
|
+ filteredJoblogList.value = joblogList.value
|
|
|
} else {
|
|
|
- filteredJoblogList.value = joblogList.value.filter(log =>
|
|
|
+ filteredJoblogList.value = joblogList.value.filter((log) =>
|
|
|
checkedJoblogs.value.includes(String(log.operationType))
|
|
|
- );
|
|
|
+ )
|
|
|
}
|
|
|
}
|
|
|
+// 作业日志弹框
|
|
|
+const parseLogString=(logStr)=> {
|
|
|
+ const obj = {};
|
|
|
+ const match = logStr.match(/^[^(]+?\((.*)\)$/);
|
|
|
+ if (!match) return obj;
|
|
|
+ const keyValuePairs = match[1].split(/,\s*(?=\w+=)/); // 处理 "key=value" 中的逗号
|
|
|
+ keyValuePairs.forEach(pair => {
|
|
|
+ const [key, value] = pair.split('=');
|
|
|
+ if (value === 'null') {
|
|
|
+ obj[key] = null;
|
|
|
+ } else if (!isNaN(value)) {
|
|
|
+ obj[key] = Number(value);
|
|
|
+ } else {
|
|
|
+ obj[key] = value;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return obj;
|
|
|
+}
|
|
|
|
|
|
+// 判断某条日志是否在当前筛选之外(即是否未被选中)
|
|
|
+const onNewSocketLog=async (rawLogStr)=> {
|
|
|
+ // 1. 将字符串解析为对象
|
|
|
+ const newLog = parseLogString(rawLogStr);
|
|
|
+
|
|
|
+ // 2. 获取当前勾选的类型(都转成字符串)
|
|
|
+ const selectedTypes = checkedJoblogs.value.map(String);
|
|
|
+ const newLogType = String(newLog.operationType);
|
|
|
+ // console.log('新日志类型:', newLogType, '勾选类型:', selectedTypes);
|
|
|
+ // 3. 判断是否未被选中 → 弹窗
|
|
|
+ if (!selectedTypes.includes(newLogType)) {
|
|
|
+ dialogLog.value = newLog;
|
|
|
+ console.log(isJoblogVisible.value,'this.isJoblogVisible');
|
|
|
+ if (isJoblogVisible.value){
|
|
|
+ JoblogDialogVisible.value = true;
|
|
|
+ console.log('弹框内容:', newLog);
|
|
|
+ }
|
|
|
+
|
|
|
+ } else {
|
|
|
+ JoblogDialogVisible.value = false;
|
|
|
+ // console.log('该类型已勾选,不弹框');
|
|
|
+ }
|
|
|
+}
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@@ -888,12 +1002,14 @@ const filterJoblogs = () => {
|
|
|
height: 25px;
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
+
|
|
|
.tab-title {
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
color: #303133;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
.processDetail {
|
|
|
width: 100%;
|
|
|
height: 300px;
|
|
|
@@ -915,6 +1031,7 @@ const filterJoblogs = () => {
|
|
|
height: 25px;
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
+
|
|
|
.tab-text {
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
@@ -929,6 +1046,7 @@ const filterJoblogs = () => {
|
|
|
flex-wrap: wrap;
|
|
|
//background: #000;
|
|
|
}
|
|
|
+
|
|
|
.left_box {
|
|
|
width: 500px;
|
|
|
margin-right: 10px;
|
|
|
@@ -951,6 +1069,7 @@ const filterJoblogs = () => {
|
|
|
height: 80px;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
//作业流程
|
|
|
.custom-node {
|
|
|
position: relative;
|
|
|
@@ -1165,33 +1284,35 @@ const filterJoblogs = () => {
|
|
|
word-break: break-all;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
//点位数据
|
|
|
- .group-box{
|
|
|
+ .group-box {
|
|
|
margin: 0 20px;
|
|
|
}
|
|
|
|
|
|
//人员共锁
|
|
|
- .biglockBox{
|
|
|
+ .biglockBox {
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
//background: green;
|
|
|
- .mumberbox{
|
|
|
+ .mumberbox {
|
|
|
width: 28%;
|
|
|
height: 100%;
|
|
|
margin: 0 15px;
|
|
|
- .user{
|
|
|
+
|
|
|
+ .user {
|
|
|
width: 100%;
|
|
|
max-height: 500px;
|
|
|
overflow-y: auto;
|
|
|
overflow-x: hidden;
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
- padding:2px 5px;
|
|
|
+ padding: 2px 5px;
|
|
|
box-sizing: border-box;
|
|
|
//background: pink;
|
|
|
- .userItem{
|
|
|
+ .userItem {
|
|
|
width: 20%;
|
|
|
height: 80px;
|
|
|
padding: 2px 3px;
|
|
|
@@ -1199,46 +1320,51 @@ const filterJoblogs = () => {
|
|
|
text-align: center;
|
|
|
//background: #000;
|
|
|
box-sizing: border-box;
|
|
|
- img{
|
|
|
- width: 50px;
|
|
|
- height:50px;
|
|
|
- }
|
|
|
+
|
|
|
+ img {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ }
|
|
|
+
|
|
|
p {
|
|
|
font-size: 16px;
|
|
|
display: -webkit-box;
|
|
|
- -webkit-line-clamp: 1; /* 最多显示1行 */
|
|
|
+ -webkit-line-clamp: 1; /* 最多显示1行 */
|
|
|
-webkit-box-orient: vertical;
|
|
|
overflow: hidden;
|
|
|
text-overflow: ellipsis;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- .arrow{
|
|
|
- margin-top: 15%;
|
|
|
+
|
|
|
+ .arrow {
|
|
|
+ margin-top: 15%;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
//作业日志
|
|
|
- .joblogCon{
|
|
|
+ .joblogCon {
|
|
|
width: 98%;
|
|
|
height: 75%;
|
|
|
margin: auto;
|
|
|
//background: greenyellow;
|
|
|
- .joblogTop{
|
|
|
+ .joblogTop {
|
|
|
width: 100%;
|
|
|
height: 490px;
|
|
|
margin: auto;
|
|
|
overflow-y: auto;
|
|
|
//background: cadetblue;
|
|
|
- p{
|
|
|
+ p {
|
|
|
font-size: 16px;
|
|
|
line-height: 20px;
|
|
|
+ margin: 10px 0;
|
|
|
}
|
|
|
}
|
|
|
- .bottomCheck{
|
|
|
- width:100%;
|
|
|
- height: 50px;
|
|
|
+
|
|
|
+ .bottomCheck {
|
|
|
+ width: 100%;
|
|
|
+ min-height: 50px;
|
|
|
line-height: 50px;
|
|
|
font-size: 23px;
|
|
|
display: flex;
|
|
|
@@ -1249,11 +1375,9 @@ const filterJoblogs = () => {
|
|
|
.big-checkbox {
|
|
|
font-size: 23px; /* 字体大小 */
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
}
|
|
|
+
|
|
|
/* 小屏幕下隐藏文字,只显示图标 */
|
|
|
@media (max-width: 768px) {
|
|
|
.tab-label {
|
|
|
@@ -1277,13 +1401,12 @@ const filterJoblogs = () => {
|
|
|
.group-card-user {
|
|
|
width: 160px;
|
|
|
min-height: 130px;
|
|
|
-
|
|
|
}
|
|
|
//点位数据
|
|
|
- .group-box{
|
|
|
+ .group-box {
|
|
|
margin: 10px 5px;
|
|
|
}
|
|
|
- // 人员共锁
|
|
|
+ // 人员共锁
|
|
|
.biglockBox {
|
|
|
overflow-y: auto;
|
|
|
flex-direction: column;
|
|
|
@@ -1293,6 +1416,7 @@ const filterJoblogs = () => {
|
|
|
width: 95%;
|
|
|
margin: 15px 0;
|
|
|
}
|
|
|
+
|
|
|
.arrow {
|
|
|
transform: rotate(90deg); // 向右 → 向下
|
|
|
margin-top: 10px;
|
|
|
@@ -1300,6 +1424,5 @@ const filterJoblogs = () => {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
</style>
|