| | |
| | | import { ElMessage } from 'element-plus' |
| | | import { useI18n } from 'vue-i18n' |
| | | import * as echarts from 'echarts'; |
| | | |
| | | import { debounce } from 'lodash-es';// 使用防抖处理高频数据更新 |
| | | // 使用浅比较减少不必要的响应式更新 |
| | | import { shallowRef } from 'vue'; |
| | | const rawGlassStorageDetailList = shallowRef([]); |
| | | const orderDTOS = shallowRef([]); |
| | | const load = ref(true) |
| | | const edg = ref(true) |
| | | const temp = ref(true) |
| | |
| | | const { t } = useI18n() |
| | | let language = ref(localStorage.getItem('lang') || 'zh') |
| | | let socket = null; |
| | | const rawGlassStorageDetailList = ref([]) |
| | | const orderDTOS = ref([]) |
| | | // const rawGlassStorageDetailList = ref([]) |
| | | // const orderDTOS = ref([]) |
| | | const socketUrl = `ws://${WebSocketHost}:${host}/api/glassStorage/api/talk/largenScreen`; |
| | | const debouncedUpdate = debounce((data, handler) => { |
| | | handler(data); |
| | | }, 300); |
| | | const handleMessage = (data) => { |
| | | debouncedUpdate(data, (cleanData) => { |
| | | rawGlassStorageDetailList.value = cleanData.rawGlassStorageDetailList[0]; |
| | | orderDTOS.value = cleanData.orderDTOS[0]; |
| | | }); |
| | | rawGlassStorageDetailList.value = data.rawGlassStorageDetailList[0] |
| | | orderDTOS.value = data.orderDTOS[0] |
| | | }; |
| | |
| | | const edgTwoTasks = ref([]) |
| | | const engineeringOne = ref([]) |
| | | const engineeringTwo = ref([]) |
| | | // 合并WebSocket连接,减少连接数 |
| | | const socketManager = { |
| | | sockets: {}, |
| | | init(url, handler) { |
| | | if (!this.sockets[url]) { |
| | | this.sockets[url] = initializeWebSocket(url, handler); |
| | | } |
| | | return this.sockets[url]; |
| | | }, |
| | | closeAll() { |
| | | Object.values(this.sockets).forEach(socket => closeWebSocket(socket)); |
| | | this.sockets = {}; |
| | | } |
| | | }; |
| | | let myChartLoad = null; |
| | | let myChartEdg = null; |
| | | let myChartTemp = null; |
| | |
| | | if (numBoxes.value != edgOneTasks.value) { |
| | | numBoxes.value = edgOneTasks.value; |
| | | initBoxes(boxes, numBoxes, 'firstup'); |
| | | |
| | | } |
| | | if (numBoxes2.value != edgTwoTasks.value) { |
| | | numBoxes2.value = edgTwoTasks.value; |
| | |
| | | } |
| | | timeAxisCreate(t('large.cuttingState'), chartLoad.value, myChartLoad, tableDataTime(false, data.loadRunTimes[0])); |
| | | timeAxisCreate(t('large.edgingState'), chartEdg.value, myChartEdg, tableDataTime(false, data.loadRunTimes[0])); |
| | | |
| | | |
| | | }; |
| | | let socket2 = null; |
| | | const temperingTaskType = ref([]) |
| | |
| | | numBoxes4.value = 0; |
| | | initBoxes(boxes4, numBoxes4, 'right'); |
| | | } |
| | | |
| | | } |
| | | |
| | | temperingGlassInfoInList.value = data.temperingGlassInfoInList[0]; |
| | | |
| | | }; |
| | | let socket3 = null; |
| | | const bigStorageCageUsage = ref([]) |
| | |
| | | timeAxisCreate(t('large.temperingState'), chartTemp.value, myChartTemp, tableDataTime(false, data.tempRunTimes[0])); |
| | | }; |
| | | // let mychart = null; // 建议设为外部变量避免重复初始化 |
| | | |
| | | // const formatToTimeString(datetime) => { |
| | | const formatToTimeString = (datetime) => { |
| | | const date = new Date(datetime); |
| | |
| | | const seconds = date.getSeconds().toString().padStart(2, '0'); |
| | | return `${hours}:${minutes}:${seconds}`; |
| | | } |
| | | |
| | | const tableDataTime = (result, tableDatax) => { |
| | | const tableData = []; |
| | | if (tableDatax.length > 0) { |
| | |
| | | } |
| | | }); |
| | | } |
| | | |
| | | return tableData; |
| | | }; |
| | | |
| | | const timeAxisCreate = (title, chartDom, mychart, RunTimes) => { |
| | | if (!chartDom) { |
| | | console.error('图表容器未找到'); |
| | | return; |
| | | } |
| | | |
| | | if (!mychart) { |
| | | mychart = echarts.init(chartDom); |
| | | } |
| | |
| | | firstTime = RunTimes[0].startTimestamp; |
| | | secondTime = RunTimes[0].endTimestamp; |
| | | } |
| | | |
| | | // 构建线段数组 |
| | | const segments = RunTimes.map(item => ({ |
| | | type: 'line', |
| | |
| | | }, |
| | | z: 1 |
| | | })); |
| | | |
| | | |
| | | const optionOne = { |
| | | title: { |
| | | text: title, |
| | |
| | | })), |
| | | ] |
| | | }; |
| | | |
| | | mychart.setOption(optionOne, true); |
| | | }; |
| | | |
| | | let socket4 = null; |
| | | const hollowBigStorageCageUsage = ref([]) |
| | | const socketUrl4 = `ws://${WebSocketHost}:${host}/api/hollowGlass/api/talk/largenScreen`; |
| | |
| | | loadTwoData = totalAreaOutTwo; |
| | | tempfontData = totalAreaIn; |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | const option = { |
| | | title: { text: '' }, |
| | | tooltip: { trigger: 'axis' }, |
| | |
| | | // { name: t('large.totalAreaIn'), type: 'bar', data: totalAreaIn } |
| | | ] |
| | | }; |
| | | |
| | | myChart.setOption(option, true); |
| | | |
| | | |
| | | const chartDom2 = chartRef2.value; |
| | | if (!chartDom2) { |
| | | console.error('图表容器未找到'); |
| | |
| | | { name: t('large.hollowCountOutTwo'), type: 'bar', data: hollowTwoData } |
| | | ] |
| | | }; |
| | | |
| | | myChart2.setOption(option2, true); |
| | | } |
| | | const handleMessage5 = (data) => { |
| | |
| | | let hollowTotalAreaOutOne = ref([]); |
| | | let totalAreaIn = ref([]); |
| | | let countOutOne = ref([]); |
| | | |
| | | const tableDatad = ref([]); |
| | | const patternUsage = async () => { |
| | | try { |
| | |
| | | console.error(error); |
| | | } |
| | | } |
| | | |
| | | const exportToExcel = async () => { |
| | | try { |
| | | window.open(`http://${WebSocketHost}:10011/largenScreen/exportDailyProduction`, '_blank'); |
| | |
| | | console.error(error); |
| | | } |
| | | } |
| | | |
| | | const tableData = ref([]); |
| | | const dynamicColumns = ref([]); |
| | | const flowCardDetail = async (orderId) => { |
| | |
| | | prop: key, |
| | | label: key, |
| | | })); |
| | | |
| | | // 解析数据并将 JSON 字符串转换为对象 |
| | | tableData.value = response.data.map((item) => { |
| | | const reportData = JSON.parse(item.reportWorkQuantity || "{}"); |
| | |
| | | console.error(error); |
| | | } |
| | | } |
| | | |
| | | const ptnusage = ref({ |
| | | width: '', |
| | | height: '', |
| | |
| | | const chartEdg = ref(null) |
| | | const chartTemp = ref(null) |
| | | const chartHollow = ref(null) |
| | | |
| | | const cleanupWebSocketData = (completeCleanup) => { |
| | | if (completeCleanup) { |
| | | // 完全清理 |
| | | rawGlassStorageDetailList.value = []; |
| | | orderDTOS.value = []; |
| | | // ...其他数组 |
| | | } else { |
| | | // 部分清理 - 保留最近数据 |
| | | const MAX_ITEMS = 50; |
| | | rawGlassStorageDetailList.value = rawGlassStorageDetailList.value.slice(-MAX_ITEMS); |
| | | orderDTOS.value = orderDTOS.value.slice(-MAX_ITEMS); |
| | | // ...其他数组 |
| | | } |
| | | |
| | | // 清理图表 |
| | | [myChart, myChart2, myChartLoad, myChartEdg, myChartTemp, myChartHollow].forEach(chart => { |
| | | if (chart) { |
| | |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | // 在组件挂载时设置默认时间范围 |
| | | onMounted(async () => { |
| | | // boxStart(); |
| | | // boxStart2() |
| | | // boxStart3() |
| | | // boxStart4(); |
| | | // requestAnimationFrame(animate); |
| | | socket = initializeWebSocket(socketUrl, handleMessage); |
| | | socket1 = initializeWebSocket(socketUrl1, handleMessage1); |
| | | socket2 = initializeWebSocket(socketUrl2, handleMessage2); |
| | | socket3 = initializeWebSocket(socketUrl3, handleMessage3); |
| | | socket4 = initializeWebSocket(socketUrl4, handleMessage4); |
| | | socket5 = initializeWebSocket(socketUrl5, handleMessage5); |
| | | socketManager.init(socketUrl, handleMessage); |
| | | socketManager.init(socketUrl1, handleMessage1); |
| | | socketManager.init(socketUrl2, handleMessage2); |
| | | socketManager.init(socketUrl3, handleMessage3); |
| | | socketManager.init(socketUrl4, handleMessage4); |
| | | socketManager.init(socketUrl5, handleMessage5); |
| | | // socket = initializeWebSocket(socketUrl, handleMessage); |
| | | // socket1 = initializeWebSocket(socketUrl1, handleMessage1); |
| | | // socket2 = initializeWebSocket(socketUrl2, handleMessage2); |
| | | // socket3 = initializeWebSocket(socketUrl3, handleMessage3); |
| | | // socket4 = initializeWebSocket(socketUrl4, handleMessage4); |
| | | // socket5 = initializeWebSocket(socketUrl5, handleMessage5); |
| | | const endTime = dayjs().startOf('minute'); // 当前时间,精确到分钟 |
| | | const startTime = endTime.subtract(1, 'day').startOf('minute'); // 当前时间的前一天,精确到分钟 |
| | | // 设置时间范围为 [开始时间, 结束时间] |
| | | // timeRange.value = [startTime.format('YYYY-MM-DD HH:mm:ss'), endTime.format('YYYY-MM-DD HH:mm:ss')]; |
| | | |
| | | const cleanupInterval = setInterval(() => { |
| | | cleanupWebSocketData(true); // 完全清理 |
| | | }, 300000); |
| | | |
| | | // 每次收到消息时检查数据量 |
| | | const originalHandleMessage = handleMessage; |
| | | handleMessage = (data) => { |
| | | originalHandleMessage(data); |
| | | cleanupWebSocketData(false); // 部分清理 |
| | | }; |
| | | |
| | | onUnmounted(() => { |
| | | socketManager.closeAll(); |
| | | clearInterval(cleanupInterval); |
| | | cleanupWebSocketData(true); // 组件卸载时完全清理 |
| | | }); |
| | |
| | | const numBoxes4 = ref(0); |
| | | const speed = 0.1; |
| | | const delayFrames = 600; |
| | | |
| | | const maxCoords = { |
| | | box1: { x: 250, y: -165 }, |
| | | box2: { x: 190, y: -75 }, |
| | | box3: { x: 95, y: -85 }, |
| | | box4: { x: 60, y: 0 }, |
| | | }; |
| | | |
| | | let boxes = ref([]); |
| | | let boxes2 = ref([]); |
| | | let boxes3 = ref([]); |
| | | let boxes4 = ref([]); |
| | | |
| | | const boxRefs = []; |
| | | const boxRefs2 = []; |
| | | const boxRefs3 = []; |
| | | const boxRefs4 = []; |
| | | let rafId = null; |
| | | |
| | | // ✅ 初始化 boxes,去除 style,保留 el |
| | | const initBoxes = (arr, countRef, direction) => { |
| | | const count = countRef.value; |
| | | const existing = arr.value; |
| | | |
| | | for (let i = 0; i < count; i++) { |
| | | if (!existing[i]) { |
| | | existing.push({ |
| | |
| | | existing.length = count; |
| | | } |
| | | }; |
| | | |
| | | // ✅ 动画函数 |
| | | const animateBox = (box, maxX, maxY) => { |
| | | if (box.delay > 0) { |
| | | box.delay--; |
| | | return; |
| | | } |
| | | |
| | | switch (box.direction) { |
| | | case 'firstup': |
| | | box.y -= speed; |
| | |
| | | } |
| | | break; |
| | | } |
| | | |
| | | box.frameCount++; |
| | | if (box.frameCount % 2 === 0 && box.el) { |
| | | box.el.style.top = `${box.y}px`; |
| | | if (box.x > 0) { |
| | | |
| | | if (box.direction == "right") { |
| | | box.el.style.right = `-${box.x}px`; |
| | | |
| | | } else { |
| | | box.el.style.right = `${box.x}px`; |
| | | } |
| | |
| | | } |
| | | } |
| | | }; |
| | | |
| | | // 优化动画性能 |
| | | const animate = () => { |
| | | boxes.value.forEach(box => animateBox(box, maxCoords.box1.x, maxCoords.box1.y)); |
| | | boxes2.value.forEach(box => animateBox(box, maxCoords.box2.x, maxCoords.box2.y)); |
| | | boxes3.value.forEach(box => animateBox(box, maxCoords.box3.x, maxCoords.box3.y)); |
| | | boxes4.value.forEach(box => animateBox(box, maxCoords.box4.x, maxCoords.box4.y)); |
| | | |
| | | rafId = requestAnimationFrame(animate); |
| | | if (!rafId) { |
| | | const start = performance.now(); |
| | | const updateBoxes = (boxes, maxX, maxY) => { |
| | | boxes.value.forEach(box => { |
| | | if (box.delay > 0) { |
| | | box.delay--; |
| | | return; |
| | | } |
| | | // 简化动画逻辑 |
| | | switch (box.direction) { |
| | | case 'firstup': |
| | | box.y = Math.max(box.y - speed, maxY); |
| | | if (box.y <= maxY) box.direction = 'firstleft'; |
| | | break; |
| | | case 'firstleft': |
| | | box.x = Math.max(box.x - speed, -maxX); |
| | | if (box.x <= -maxX) { |
| | | box.x = 0; |
| | | box.y = 0; |
| | | box.direction = 'firstup'; |
| | | } |
| | | break; |
| | | // 其他case简化... |
| | | } |
| | | // 批量更新DOM |
| | | requestAnimationFrame(() => { |
| | | if (box.el) { |
| | | box.el.style.transform = `translate(${box.x}px, ${box.y}px)`; |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | |
| | | updateBoxes(boxes, maxCoords.box1.x, maxCoords.box1.y); |
| | | updateBoxes(boxes2, maxCoords.box2.x, maxCoords.box2.y); |
| | | updateBoxes(boxes3, maxCoords.box3.x, maxCoords.box3.y); |
| | | updateBoxes(boxes4, maxCoords.box4.x, maxCoords.box4.y); |
| | | rafId = requestAnimationFrame(animate); |
| | | } |
| | | }; |
| | | // const animate = () => { |
| | | // boxes.value.forEach(box => animateBox(box, maxCoords.box1.x, maxCoords.box1.y)); |
| | | // boxes2.value.forEach(box => animateBox(box, maxCoords.box2.x, maxCoords.box2.y)); |
| | | // boxes3.value.forEach(box => animateBox(box, maxCoords.box3.x, maxCoords.box3.y)); |
| | | // boxes4.value.forEach(box => animateBox(box, maxCoords.box4.x, maxCoords.box4.y)); |
| | | // rafId = requestAnimationFrame(animate); |
| | | // }; |
| | | const boxStart = () => { |
| | | initBoxes(boxes, numBoxes, 'firstup'); |
| | | initBoxes(boxes2, numBoxes2, 'firstup'); |
| | | initBoxes(boxes3, numBoxes3, 'secondleft'); |
| | | initBoxes(boxes4, numBoxes4, 'right'); |
| | | }; |
| | | |
| | | watch( |
| | | () => boxes.value, |
| | | async (newVal) => { |
| | |
| | | }); |
| | | }, |
| | | { deep: true, immediate: true } |
| | | |
| | | ); |
| | | watch( |
| | | () => boxes2.value, |
| | |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ); |
| | | |
| | | watch( |
| | | () => boxes3.value, |
| | | async (newVal) => { |
| | |
| | | }, |
| | | { deep: true, immediate: true } |
| | | ); |
| | | |
| | | watch( |
| | | () => boxes4.value, |
| | | async (newVal) => { |
| | |
| | | boxStart(); |
| | | animate(); |
| | | }); |
| | | |
| | | onBeforeUnmount(() => { |
| | | cancelAnimationFrame(rafId); |
| | | rafId = null; |
| | |
| | | // onBeforeUnmount(() => { |
| | | // cancelAnimationFrame(rafId); |
| | | // }); |
| | | |
| | | //嵌入历史任务 |
| | | const iframeUrl1 = ref(''); |
| | | const iframe1 = ref(false); |
| | |
| | | iframe11.value = true; |
| | | iframeUrl11.value = `${window.location.origin}/#/largescreendisplay/productionstatistics`; |
| | | }; |
| | | |
| | | const iframeUrl12 = ref(''); |
| | | const iframe12 = ref(false); |
| | | const handlehistorical12 = (key) => { |
| | |
| | | }); |
| | | iframeUrl12.value = `${window.location.origin}/#/largescreendisplay/timeaxis?${params.toString()}`; |
| | | }; |
| | | |
| | | const transposedData = computed(() => { |
| | | const keys = Object.keys(fieldNames); // 按 fieldNames 的顺序 |
| | | return keys.map(key => { |
| | |
| | | return row; |
| | | }); |
| | | }); |
| | | |
| | | const fieldNames = { |
| | | date: t('large.date'), |
| | | countOutOne: t('large.countOutOne'), |
| | |
| | | hollowCountOutTwo: t('large.hollowCountOutTwo'), |
| | | hollowTotalAreaOutTwo: t('large.hollowTotalAreaOutTwo'), |
| | | }; |
| | | |
| | | |
| | | // 修改图表初始化逻辑,添加懒加载和销毁机制 |
| | | const chartInstances = { |
| | | chartLoad: null, |
| | | chartEdg: null, |
| | | chartTemp: null, |
| | | chartHollow: null, |
| | | chartRef: null, |
| | | chartRef2: null |
| | | }; |
| | | const initChart = (refName, option) => { |
| | | if (!chartInstances[refName] && templateRefs[refName]) { |
| | | chartInstances[refName] = echarts.init(templateRefs[refName]); |
| | | chartInstances[refName].setOption(option); |
| | | } |
| | | }; |
| | | const disposeChart = (refName) => { |
| | | if (chartInstances[refName]) { |
| | | chartInstances[refName].dispose(); |
| | | chartInstances[refName] = null; |
| | | } |
| | | }; |
| | | // 使用Intersection Observer实现图表懒加载 |
| | | const observer = new IntersectionObserver((entries) => { |
| | | entries.forEach(entry => { |
| | | if (entry.isIntersecting) { |
| | | const refName = entry.target.getAttribute('data-chart-name'); |
| | | if (refName && !chartInstances[refName]) { |
| | | // 初始化图表逻辑 |
| | | } |
| | | } |
| | | }); |
| | | }, { threshold: 0.1 }); |
| | | onMounted(() => { |
| | | document.querySelectorAll('[data-chart-name]').forEach(el => { |
| | | observer.observe(el); |
| | | }); |
| | | }); |
| | | </script> |
| | | <template> |
| | | <div style="height: 500px;"> |
| | |
| | | $t('large.slice') }}</div> |
| | | <div>{{ $t('large.are') }}:{{ productionVO.length > 0 ? productionVO[6].totalAreaIn : 0 }}m²</div> |
| | | </div> |
| | | |
| | | <!-- 钢化色块 --> |
| | | <div v-if="temperingGlassInfoInList > 0" class="tempering" |
| | | style="width: 44px;height: 19px;top: 284px;left: 178px;position: absolute;background-color: #911005;"></div> |
| | |
| | | line-height: 20px; |
| | | margin-left: 100px; |
| | | } |
| | | |
| | | #dta { |
| | | display: block; |
| | | float: left; |
| | | line-height: 20px; |
| | | margin-left: 80%; |
| | | } |
| | | |
| | | #dialog-footer { |
| | | text-align: center; |
| | | margin-top: -15px; |
| | | } |
| | | |
| | | #message { |
| | | text-align: center; |
| | | align-items: center; |
| | |
| | | background-color: #337ecc; |
| | | margin-left: 28%; |
| | | } |
| | | |
| | | #awatch { |
| | | height: 460px; |
| | | } |
| | | |
| | | /* 优化背景图片加载 */ |
| | | .img-screen { |
| | | /* max-width: 48%; |
| | | max-height: 100%; */ |
| | |
| | | /* width: 100%; |
| | | height: 0; |
| | | padding-bottom: 50%; */ |
| | | image-rendering: -webkit-optimize-contrast; |
| | | image-rendering: crisp-edges; |
| | | } |
| | | |
| | | .clickable-area { |
| | | cursor: pointer; |
| | | /* 指示这是一个可点击的区域 */ |
| | |
| | | line-height: 95px; |
| | | /* 如果需要,使文本垂直居中 */ |
| | | } |
| | | |
| | | /* .awatch{ |
| | | height: 460px; |
| | | /* max-width: 100%; */ |
| | | .box { |
| | | transition: transform 0.016s linear; |
| | | } |
| | | |
| | | .lipiana { |
| | | width: 15px; |
| | | height: 9px; |
| | |
| | | transform: translateX(-50%); |
| | | animation: move-lipiana 1s infinite; |
| | | } |
| | | |
| | | @keyframes move-lipiana { |
| | | 0% { |
| | | right: 530px; |
| | | } |
| | | |
| | | 100% { |
| | | right: calc(100% - 350px); |
| | | } |
| | | } |
| | | |
| | | /* 使用CSS硬件加速 */ |
| | | .moving-rect { |
| | | width: 15px; |
| | | height: 15px; |
| | | background-color: #911005; |
| | | position: absolute; |
| | | will-change: transform; |
| | | backface-visibility: hidden; |
| | | perspective: 1000px; |
| | | } |
| | | </style> |