Compare commits
20 Commits
da17bba934
...
c835e38d82
Author | SHA1 | Date |
---|---|---|
|
c835e38d82 | |
|
d44fc687dc | |
|
e6814c5f04 | |
|
e9995630a3 | |
|
c6adf30b68 | |
|
5493068847 | |
|
3e1848ffe4 | |
|
c85995c024 | |
|
cd08c70068 | |
|
847619bb3e | |
|
69f45097f6 | |
|
0af30f2e0a | |
|
06a9d4541c | |
|
9b22abac43 | |
|
ca99c0ff68 | |
|
8b9813c901 | |
|
e66293b4dc | |
|
417d28752d | |
|
77f356b4f1 | |
|
fd2c2f303d |
|
@ -21,3 +21,7 @@ pnpm-debug.log*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# 部署配置
|
||||||
|
deploy.config.sh
|
||||||
|
*.tar
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
if [ -f "./deploy.config.sh" ]; then
|
||||||
|
source ./deploy.config.sh
|
||||||
|
else
|
||||||
|
echo "配置文件 deploy.config.sh 不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "开始远程部署 - $(date)"
|
||||||
|
|
||||||
|
echo "开始部署您的项目..."
|
||||||
|
|
||||||
|
# 1. 克隆项目代码
|
||||||
|
# echo "1. 克隆项目代码..."
|
||||||
|
# git clone https://git.zhouxhere.com/wonderzhao/cola-web.git
|
||||||
|
|
||||||
|
# 2. 进入项目目录
|
||||||
|
# echo "2. 进入项目目录..."
|
||||||
|
# cd cola-web
|
||||||
|
|
||||||
|
# 3. 安装依赖
|
||||||
|
# echo "3. 安装依赖..."
|
||||||
|
# npm install
|
||||||
|
|
||||||
|
# 4. 构建项目
|
||||||
|
# echo "4. 构建项目..."
|
||||||
|
# npm run build
|
||||||
|
|
||||||
|
# # 5. 构建 Docker 镜像
|
||||||
|
# echo "5. 构建 Docker 镜像..."
|
||||||
|
# docker build -t ${DOCKER_IMAGE_NAME} .
|
||||||
|
|
||||||
|
# # 6. 保存镜像为 tar 文件
|
||||||
|
# echo "6. 保存 Docker 镜像..."
|
||||||
|
# docker save ${DOCKER_IMAGE_NAME} > ${DOCKER_IMAGE_NAME}.tar
|
||||||
|
|
||||||
|
# 7. 传输文件到服务器
|
||||||
|
# echo "7. 传输文件到服务器..."
|
||||||
|
# ssh -p 22 owner@192.168.1.199
|
||||||
|
# scp -v -P ${REMOTE_PORT} ${DOCKER_IMAGE_NAME}.tar ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
|
||||||
|
|
||||||
|
# 8. 在远程服务器上执行部署
|
||||||
|
echo "8. 在远程服务器上执行部署..."
|
||||||
|
ssh -p ${REMOTE_PORT} ${REMOTE_USER}@${REMOTE_HOST} << EOF
|
||||||
|
set -e # 遇到错误立即退出
|
||||||
|
|
||||||
|
echo "===== 开始执行远程部署 ====="
|
||||||
|
cd ${REMOTE_PATH}
|
||||||
|
docker load < ${DOCKER_IMAGE_NAME}.tar
|
||||||
|
docker stop ${DOCKER_CONTAINER_NAME} 2>/dev/null || true
|
||||||
|
docker rm ${DOCKER_CONTAINER_NAME} 2>/dev/null || true
|
||||||
|
docker run -d \
|
||||||
|
--name ${DOCKER_CONTAINER_NAME} \
|
||||||
|
-p ${APP_PORT}:80 \
|
||||||
|
--restart unless-stopped \
|
||||||
|
${DOCKER_IMAGE_NAME}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "部署完成!"
|
||||||
|
# echo "部署完成!"
|
||||||
|
|
||||||
|
# 10. 检查部署状态
|
||||||
|
# echo "10. 检查部署状态..."
|
||||||
|
# ssh owner@192.168.1.199 "docker ps | grep cola-web"
|
||||||
|
|
||||||
|
# 在另一个终端窗口查看日志
|
||||||
|
# tail -f deploy.log
|
|
@ -0,0 +1,96 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# 使用后端本地调用api方式部署
|
||||||
|
|
||||||
|
# 加载配置
|
||||||
|
if [ -f "./deploy.config.sh" ]; then
|
||||||
|
source ./deploy.config.sh
|
||||||
|
else
|
||||||
|
echo "配置文件 deploy.config.sh 不存在"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "开始远程部署 - $(date)"
|
||||||
|
|
||||||
|
echo "开始部署您的项目..."
|
||||||
|
|
||||||
|
# 1. 克隆项目代码
|
||||||
|
# echo "1. 克隆项目代码..."
|
||||||
|
# git clone https://git.zhouxhere.com/wonderzhao/cola-web.git
|
||||||
|
|
||||||
|
# 2. 进入项目目录
|
||||||
|
# echo "2. 进入项目目录..."
|
||||||
|
# cd cola-web
|
||||||
|
|
||||||
|
# 3. 安装依赖
|
||||||
|
# echo "3. 安装依赖..."
|
||||||
|
# npm install
|
||||||
|
|
||||||
|
# 4. 构建项目
|
||||||
|
echo "4. 构建项目..."
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# 5. 构建 Docker 镜像
|
||||||
|
echo "5. 构建 Docker 镜像..."
|
||||||
|
docker build -t ${DOCKER_IMAGE_NAME} .
|
||||||
|
|
||||||
|
# 6. 保存镜像为 tar 文件
|
||||||
|
echo "6. 保存 Docker 镜像..."
|
||||||
|
docker save ${DOCKER_IMAGE_NAME} > ${DOCKER_IMAGE_NAME}.tar
|
||||||
|
|
||||||
|
# 7. 创建 nginx 配置文件
|
||||||
|
echo "7. 创建 nginx 配置文件..."
|
||||||
|
cat > nginx.conf << 'NGINX_EOF'
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api {
|
||||||
|
proxy_pass http://192.168.1.199:8080;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX_EOF
|
||||||
|
|
||||||
|
# 8. 传输文件到服务器
|
||||||
|
echo "8. 传输文件到服务器..."
|
||||||
|
scp -v -P ${REMOTE_PORT} ${DOCKER_IMAGE_NAME}.tar ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
|
||||||
|
scp -v -P ${REMOTE_PORT} nginx.conf ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PATH}/
|
||||||
|
|
||||||
|
# 9. 在远程服务器上执行部署
|
||||||
|
echo "9. 在远程服务器上执行部署..."
|
||||||
|
ssh -p ${REMOTE_PORT} ${REMOTE_USER}@${REMOTE_HOST} << EOF
|
||||||
|
set -e # 遇到错误立即退出
|
||||||
|
|
||||||
|
echo "===== 开始执行远程部署 ====="
|
||||||
|
cd ${REMOTE_PATH}
|
||||||
|
docker load < ${DOCKER_IMAGE_NAME}.tar
|
||||||
|
docker stop ${DOCKER_CONTAINER_NAME} 2>/dev/null || true
|
||||||
|
docker rm ${DOCKER_CONTAINER_NAME} 2>/dev/null || true
|
||||||
|
docker run -d \\
|
||||||
|
--name ${DOCKER_CONTAINER_NAME} \\
|
||||||
|
-p ${APP_PORT}:80 \\
|
||||||
|
-v ${REMOTE_PATH}/nginx.conf:/etc/nginx/conf.d/default.conf \\
|
||||||
|
--restart unless-stopped \\
|
||||||
|
${DOCKER_IMAGE_NAME}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "部署完成!"
|
||||||
|
|
||||||
|
# 清理本地临时文件
|
||||||
|
rm -f nginx.conf
|
||||||
|
rm -f ${DOCKER_IMAGE_NAME}.tar
|
||||||
|
|
||||||
|
# 10. 检查部署状态
|
||||||
|
# echo "10. 检查部署状态..."
|
||||||
|
# ssh owner@192.168.1.199 "docker ps | grep cola-web"
|
||||||
|
|
||||||
|
# 在另一个终端窗口查看日志
|
||||||
|
# tail -f deploy.log
|
|
@ -29,7 +29,7 @@ docker save cola-web:latest > cola-web.tar
|
||||||
|
|
||||||
# 7. 传输文件到服务器
|
# 7. 传输文件到服务器
|
||||||
echo "7. 传输文件到服务器..."
|
echo "7. 传输文件到服务器..."
|
||||||
ssh -p 22 owner@192.168.1.199 "mkdir -p web"
|
ssh -p 22 owner@192.168.1.199
|
||||||
scp -P 22 cola-web.tar owner@192.168.1.199:web/
|
scp -P 22 cola-web.tar owner@192.168.1.199:web/
|
||||||
|
|
||||||
# 8. 在远程服务器上执行部署
|
# 8. 在远程服务器上执行部署
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -8,12 +8,13 @@
|
||||||
"lint": "vue-cli-service lint"
|
"lint": "vue-cli-service lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@siemens/ix": "^2.5.0",
|
"@siemens/ix": "^2.7.0",
|
||||||
"@siemens/ix-icons": "^2.2.0",
|
"@siemens/ix-icons": "^2.2.0",
|
||||||
"@siemens/ix-vue": "^2.5.0",
|
"@siemens/ix-vue": "^2.7.0",
|
||||||
"axios": "^1.7.9",
|
"axios": "^1.7.9",
|
||||||
"core-js": "^3.8.3",
|
"core-js": "^3.8.3",
|
||||||
"echarts": "^5.6.0",
|
"echarts": "^5.6.0",
|
||||||
|
"element-plus": "^2.9.5",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"moment-timezone": "^0.5.47",
|
"moment-timezone": "^0.5.47",
|
||||||
"pinia": "^2.2.2",
|
"pinia": "^2.2.2",
|
||||||
|
@ -27,7 +28,8 @@
|
||||||
"@vue/cli-plugin-eslint": "~5.0.0",
|
"@vue/cli-plugin-eslint": "~5.0.0",
|
||||||
"@vue/cli-service": "~5.0.0",
|
"@vue/cli-service": "~5.0.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-plugin-vue": "^8.0.3"
|
"eslint-plugin-vue": "^8.0.3",
|
||||||
|
"vite": "^6.2.1"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"root": true,
|
"root": true,
|
||||||
|
|
|
@ -16,6 +16,5 @@
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
border: var(--theme-primary-bdr-1);
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -19,7 +19,32 @@ export function getHistoryReport(inputTime) {
|
||||||
// 获取前处理页面甘特图数据
|
// 获取前处理页面甘特图数据
|
||||||
export function getGanttData(deviceId, inputTime) {
|
export function getGanttData(deviceId, inputTime) {
|
||||||
return request({
|
return request({
|
||||||
url: `/State?deviceId=${deviceId}&inputTime=${inputTime}`,
|
url: `/State?deviceId=${deviceId}&inputTime=${inputTime}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取停机原因列表
|
||||||
|
export function getStopReason() {
|
||||||
|
return request({
|
||||||
|
url: '/State/stop-reason',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 停机原因维护
|
||||||
|
export function setStopReason(data) {
|
||||||
|
return request({
|
||||||
|
url: '/State/stop-reason',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备状态列表
|
||||||
|
export function getStatusColor() {
|
||||||
|
return request({
|
||||||
|
url: '/State/DeviceColor',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
|
@ -3,15 +3,58 @@ import request from '@/utils/request';
|
||||||
// 获取点检页面当前值
|
// 获取点检页面当前值
|
||||||
export function getInspectionCurrent(deviceId, inputTime) {
|
export function getInspectionCurrent(deviceId, inputTime) {
|
||||||
return request({
|
return request({
|
||||||
url: `/Check/currunt?deviceId=${deviceId}&inputTime=${inputTime}`,
|
url: `/Check/currunt?deviceTypeId=${deviceId}&inputTime=${inputTime}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取点检页面整点数值
|
// 获取点检页面整点数值
|
||||||
export function getInspectionData(deviceId, inputTime) {
|
export function getInspectionData(deviceId, inputTime, shift) {
|
||||||
return request({
|
return request({
|
||||||
url: `/Check/sharp?deviceId=${deviceId}&inputTime=${inputTime}`,
|
url: `/Check/sharp?deviceTypeId=${deviceId}&inputTime=${inputTime}&shift=${shift}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取点检页面参数
|
||||||
|
export function getCheckParas(deviceId, inputTime) {
|
||||||
|
return request({
|
||||||
|
url: `/Check/CheckParas?deviceTypeId=${deviceId}&inputTime=${inputTime}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取设备列表
|
||||||
|
export function getDeviceList() {
|
||||||
|
return request({
|
||||||
|
url: '/Check/deviceList',
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出excel
|
||||||
|
export function exportExcel(deviceId, inputTime, shift) {
|
||||||
|
return request({
|
||||||
|
url: `/Check/Export/Juice?deviceTypeId=${deviceId}&inputTime=${inputTime}&shift=${shift}`,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'arraybuffer' // 关键:设置响应类型为 arraybuffer
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 报警相关接口
|
||||||
|
// 时间点确认点检,不传时间,默认当前时间
|
||||||
|
export function sharpConfirm(alarmId, confirmTime, checkUser) {
|
||||||
|
return request({
|
||||||
|
url: `/Check/sharpConfirm?alarmId=${alarmId}&checkUser=${checkUser}`,
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据项点检
|
||||||
|
export function alarmReasonConfirm(data) {
|
||||||
|
return request({
|
||||||
|
url: '/Check/AlarmReason',
|
||||||
|
method: 'post',
|
||||||
|
data
|
||||||
|
})
|
||||||
}
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||||
|
<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M512 960c-92.8 0-160-200-160-448S419.2 64 512 64s160 200 160 448-67.2 448-160 448z m0-32c65.6 0 128-185.6 128-416S577.6 96 512 96s-128 185.6-128 416 62.4 416 128 416z" fill="#050D42" /><path d="M124.8 736c-48-80 92.8-238.4 307.2-363.2S852.8 208 899.2 288 806.4 526.4 592 651.2 171.2 816 124.8 736z m27.2-16c33.6 57.6 225.6 17.6 424-97.6S905.6 361.6 872 304 646.4 286.4 448 401.6 118.4 662.4 152 720z" fill="#050D42" /><path d="M899.2 736c-46.4 80-254.4 38.4-467.2-84.8S76.8 368 124.8 288s254.4-38.4 467.2 84.8S947.2 656 899.2 736z m-27.2-16c33.6-57.6-97.6-203.2-296-318.4S184 246.4 152 304 249.6 507.2 448 622.4s392 155.2 424 97.6z" fill="#050D42" /><path d="M512 592c-44.8 0-80-35.2-80-80s35.2-80 80-80 80 35.2 80 80-35.2 80-80 80zM272 312c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48zM416 880c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48z m448-432c-27.2 0-48-20.8-48-48s20.8-48 48-48 48 20.8 48 48-20.8 48-48 48z" fill="#2F4BFF" /></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1,72 @@
|
||||||
|
<template>
|
||||||
|
<el-date-picker ref="datePickerRef" v-model="internalValue" type="datetime" :clearable="false" v-bind="$attrs" format="YYYY/MM/DD HH:mm"
|
||||||
|
:disabled-minutes="disabledMinutes" popper-class="popperTime" :visible-change="visibleChange" style="display: none"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"></el-date-picker>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup name="DataTime">
|
||||||
|
import { ref, nextTick } from 'vue';
|
||||||
|
import { defineExpose } from 'vue';
|
||||||
|
|
||||||
|
const datePickerRef = ref(null); // 获取组件实例
|
||||||
|
|
||||||
|
const internalValue = ref(null);
|
||||||
|
|
||||||
|
// 暴露给父组件的方法
|
||||||
|
const openPicker = () => {
|
||||||
|
datePickerRef.value.focus(); // 触发 Element 的 focus 方法
|
||||||
|
};
|
||||||
|
defineExpose({ openPicker }); // 暴露方法
|
||||||
|
|
||||||
|
const disabledMinutes = () => {
|
||||||
|
hideInvalidMinutes();
|
||||||
|
return Array.from({ length: 60 }, (v, k) => k).filter(minute => ![0, 15, 30, 45].includes(minute));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideInvalidMinutes = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const minuteItems = document.querySelectorAll('.el-time-panel .el-time-spinner__wrapper:nth-child(2n) .el-time-spinner__item');
|
||||||
|
minuteItems.forEach(item => {
|
||||||
|
const minute = parseInt(item.textContent, 10);
|
||||||
|
if (![0, 15, 30, 45].includes(minute)) {
|
||||||
|
item.classList.add('is-disabled');
|
||||||
|
item.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
item.classList.remove('is-disabled');
|
||||||
|
item.style.display = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGlobalFocus = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target && target.closest('.el-date-range-picker__time-picker-wrap:nth-child(2n) .el-input__inner')) {
|
||||||
|
hideInvalidMinutes();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const visibleChange = () => {
|
||||||
|
document.addEventListener('focus', handleGlobalFocus, true);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="css">
|
||||||
|
.popperTime {
|
||||||
|
|
||||||
|
.el-time-panel__content:after,
|
||||||
|
.el-time-panel__content:before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-spinner__list:after,
|
||||||
|
.el-time-spinner__list:before {
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-spinner__item.is-active:not(.is-disabled) {
|
||||||
|
background-color: var(--el-datepicker-active-color);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,52 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="hello">
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
<p>
|
|
||||||
For a guide and recipes on how to configure / customize this project,<br>
|
|
||||||
check out the
|
|
||||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Installed CLI Plugins</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Essential Links</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
|
||||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
|
||||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
|
||||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
|
||||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
|
||||||
</ul>
|
|
||||||
<h3>Ecosystem</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
|
||||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
|
||||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
|
||||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
||||||
<style scoped>
|
|
||||||
h3 {
|
|
||||||
margin: 40px 0 0;
|
|
||||||
}
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
li {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #42b983;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,12 +1,37 @@
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 甘特图容器 -->
|
||||||
<div class="gantt-chart-container">
|
<div class="gantt-chart-container">
|
||||||
<div ref="ganttChart" class="gantt-chart"></div>
|
<!-- 甘特图 -->
|
||||||
|
<div ref="ganttChart" class="gantt-chart">
|
||||||
|
<!-- 甘特图条目 -->
|
||||||
|
<div v-for="(item, index) in mappedData" :key="index" class="gantt-bar" :style="getBarStyle(item)" @click="handleChartClick(item)">
|
||||||
|
<!-- 甘特图条目提示 -->
|
||||||
|
<!-- <div class="gantt-bar-tooltip">
|
||||||
|
<div>开始时间:{{ item.beginTime }}</div>
|
||||||
|
<div>结束时间:{{ item.endTime }}</div>
|
||||||
|
<div>生产步骤:{{ item.mixerStep || '--' }}</div>
|
||||||
|
<div>流量:{{ item.productFlowRate || '--' }}</div>
|
||||||
|
<div>配方:{{ item.formula || '--' }}</div>
|
||||||
|
<div>持续时长:{{ item.duration || '--' }}</div>
|
||||||
|
</div> -->
|
||||||
|
</div>
|
||||||
|
<!-- 甘特图间隙条目 -->
|
||||||
|
<div v-for="(gap, index) in gaps" :key="'gap-' + index" class="gantt-bar gap-bar" :style="getGapStyle(gap)">
|
||||||
|
</div>
|
||||||
|
<!-- 甘特图 X 轴 -->
|
||||||
|
<div class="gantt-x-axis">
|
||||||
|
<div v-for="(tick, index) in xTicks" :key="index" class="gantt-x-tick" :style="getTickStyle(tick)">
|
||||||
|
<div class="gantt-x-tick-line"></div>
|
||||||
|
<div class="gantt-x-tick-label">{{ tick.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="gantt-x-axis-line"></div> -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onBeforeUnmount, watch, nextTick, shallowRef } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue'
|
||||||
import * as echarts from 'echarts'
|
|
||||||
|
|
||||||
// Props 定义
|
// Props 定义
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -18,6 +43,10 @@ const props = defineProps({
|
||||||
currentRange: {
|
currentRange: {
|
||||||
type: String,
|
type: String,
|
||||||
default: 'all'
|
default: 'all'
|
||||||
|
},
|
||||||
|
endTime: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,7 +55,6 @@ const emit = defineEmits(['segment-click'])
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const currentRange = ref('all')
|
const currentRange = ref('all')
|
||||||
const chart = shallowRef(null)
|
|
||||||
const ganttChart = ref(null)
|
const ganttChart = ref(null)
|
||||||
const currentStatus = ref('')
|
const currentStatus = ref('')
|
||||||
const startTimeFormatted = ref('')
|
const startTimeFormatted = ref('')
|
||||||
|
@ -34,159 +62,72 @@ const endTimeFormatted = ref('')
|
||||||
const duration = ref('')
|
const duration = ref('')
|
||||||
const productFlowRate = ref('')
|
const productFlowRate = ref('')
|
||||||
const formula = ref('')
|
const formula = ref('')
|
||||||
|
const mappedData = ref([]) // 修改为 ref
|
||||||
|
const xTicks = ref([])
|
||||||
|
const gaps = ref([])
|
||||||
|
|
||||||
// 修改 resize 处理函数
|
// 初始化甘特图
|
||||||
const handleResize = echarts.throttle(() => {
|
|
||||||
if (chart.value) {
|
|
||||||
chart.value.resize();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
// 方法
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
try {
|
if (ganttChart.value) {
|
||||||
if (chart.value) {
|
|
||||||
chart.value.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ganttChart.value) return;
|
|
||||||
|
|
||||||
// 创建新实例
|
|
||||||
chart.value = echarts.init(ganttChart.value);
|
|
||||||
|
|
||||||
// 设置基础配置
|
|
||||||
const baseOption = {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
data: []
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 1
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
type: 'bar',
|
|
||||||
data: []
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
|
|
||||||
chart.value.setOption(baseOption);
|
|
||||||
|
|
||||||
// 绑定resize事件
|
|
||||||
window.addEventListener('resize', handleResize);
|
|
||||||
|
|
||||||
// 更新数据
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
updateChart();
|
updateChart();
|
||||||
|
generateXTicks();
|
||||||
|
generateGaps();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateChart = () => {
|
// 获取当班开始时间
|
||||||
if (!chart.value || !ganttChart.value) return;
|
const getShiftStartTime = (endTime) => {
|
||||||
|
const now = endTime ? new Date(endTime) : new Date();
|
||||||
try {
|
const hour = now.getHours();
|
||||||
const timeLabels = generateTimeLabels();
|
if (hour >= 7 && hour < 19) {
|
||||||
const baseDate = new Date().toISOString().split('T')[0];
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 7, 0, 0).getTime();
|
||||||
|
} else {
|
||||||
const option = {
|
// 晚班:19:00 - 次日7:00
|
||||||
backgroundColor: 'transparent',
|
if (hour >= 19) {
|
||||||
tooltip: {
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate(), 19, 0, 0).getTime();
|
||||||
trigger: 'item',
|
} else {
|
||||||
formatter: (params) => {
|
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 19, 0, 0).getTime();
|
||||||
if (!params.data || typeof params.data !== 'object') return ''
|
|
||||||
return `
|
|
||||||
<div style="white-space: normal;">
|
|
||||||
<div>开始时间:${formatTime(params.data.beginTime)}</div>
|
|
||||||
<div>结束时间:${formatTime(params.data.endTime)}</div>
|
|
||||||
<div>生产步骤:${params.data.mixerStep || '--'}</div> <!-- 确保 step 字段正确显示 -->
|
|
||||||
<div>流量:${params.data.productFlowRate || '--'}</div>
|
|
||||||
<div>配方:${params.data.formula || '--'}</div>
|
|
||||||
<div>持续时长:${params.data.duration || '--'}</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
},
|
|
||||||
position: function (point, params, dom, rect, size) {
|
|
||||||
// 这里自定义位置逻辑
|
|
||||||
// point 是鼠标位置,params 是数据项相关参数,dom 是提示框的 DOM 对象,rect 是图表区域,size 是提示框大小
|
|
||||||
return [point[0] + 10, point[1] - 100];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
top: 40,
|
|
||||||
bottom: 20,
|
|
||||||
left: 10,
|
|
||||||
right: 10,
|
|
||||||
containLabel: true
|
|
||||||
},
|
|
||||||
xAxis: {
|
|
||||||
type: 'category',
|
|
||||||
position: 'bottom', // 修改位置到底部
|
|
||||||
data: timeLabels,
|
|
||||||
axisLine: {
|
|
||||||
show: true,
|
|
||||||
lineStyle: {
|
|
||||||
color: 'rgba(255, 255, 255, 0.3)'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
axisLabel: {
|
|
||||||
color: 'rgba(255, 255, 255, 0.7)',
|
|
||||||
interval: 3, // 每4个标签显示一个(15分钟 * 4 = 1小时)
|
|
||||||
fontSize: 12,
|
|
||||||
margin: 15,
|
|
||||||
rotate: 0,
|
|
||||||
formatter: function (value) {
|
|
||||||
// 只显示整点时间
|
|
||||||
if (value.includes('/')) {
|
|
||||||
const [date, time] = value.split(' ')
|
|
||||||
const [year, month, day] = date.split('/')
|
|
||||||
|
|
||||||
if (time === '0') {
|
|
||||||
return `${year}/${month}/${day} ${time}:00`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [hour, minute] = value.split(':')
|
|
||||||
if (minute === '00') {
|
|
||||||
return `${hour}:00`
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
yAxis: {
|
|
||||||
show: false,
|
|
||||||
type: 'value',
|
|
||||||
min: 0,
|
|
||||||
max: 1
|
|
||||||
},
|
|
||||||
series: [{
|
|
||||||
type: 'bar',
|
|
||||||
barWidth: '108%',
|
|
||||||
barGap: 0,
|
|
||||||
itemStyle: {
|
|
||||||
borderRadius: 0
|
|
||||||
},
|
|
||||||
data: mapDataToTimeline(timeLabels, baseDate) || [] // 确保有默认值
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
chart.value.setOption(option, true)
|
|
||||||
// 重新绑定点击事件
|
|
||||||
chart.value.off('click')
|
|
||||||
chart.value.on('click', 'series', (params) => {
|
|
||||||
if (params.data && typeof params.data === 'object') {
|
|
||||||
handleChartClick(params.data)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Update chart error:', error);
|
|
||||||
// 如果更新失败,尝试重新初始化
|
|
||||||
initChart();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新甘特图数据
|
||||||
|
const updateChart = () => {
|
||||||
|
const now = new Date();
|
||||||
|
let startTime;
|
||||||
|
let endTime = props.endTime ? new Date(props.endTime).getTime() : now.getTime();
|
||||||
|
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
startTime = endTime - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
startTime = endTime - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
startTime = getShiftStartTime(endTime);
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
startTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : now.getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredData = props.processData.filter(item => {
|
||||||
|
const itemStartTime = new Date(item.beginTime).getTime();
|
||||||
|
const itemEndTime = new Date(item.endTime).getTime();
|
||||||
|
return itemEndTime >= startTime && itemStartTime <= endTime;
|
||||||
|
});
|
||||||
|
mappedData.value = mapDataToTimeline(filteredData, startTime, endTime);
|
||||||
|
nextTick(() => {
|
||||||
|
generateXTicks(startTime, endTime);
|
||||||
|
generateGaps(startTime, endTime);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间
|
||||||
const formatTime = (timeString) => {
|
const formatTime = (timeString) => {
|
||||||
if (!timeString) return '--';
|
if (!timeString) return '--';
|
||||||
const date = new Date(timeString);
|
const date = new Date(timeString);
|
||||||
|
@ -194,93 +135,36 @@ const formatTime = (timeString) => {
|
||||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDataToTimeline = (timeLabels, baseDate) => {
|
// 将数据映射到时间轴
|
||||||
return timeLabels.map((label) => {
|
const mapDataToTimeline = (data, startTime, endTime) => {
|
||||||
// 获取当前时间标签对应的小时和分钟
|
return data.map(item => {
|
||||||
let currentTime;
|
const beginTime = new Date(item.beginTime);
|
||||||
if (label.includes('/')) {
|
const currentEndTime = new Date(item.endTime);
|
||||||
const [date, time] = label.split(' ');
|
const adjustedBeginTime = beginTime.getTime() < startTime ? new Date(startTime) : beginTime;
|
||||||
const [year, month, day] = date.split('/').map(Number);
|
const adjustedEndTime = currentEndTime.getTime() < endTime ? currentEndTime : new Date(endTime);
|
||||||
const [hour, minute] = time.split(':').map(Number);
|
const adjustedDuration = adjustedEndTime.getTime() - adjustedBeginTime.getTime();
|
||||||
currentTime = new Date(year, month - 1, day, hour, minute, 0, 0);
|
return {
|
||||||
} else {
|
id: item.id,
|
||||||
const [hour, minute] = label.split(':').map(Number);
|
name: 'Process',
|
||||||
currentTime = new Date(baseDate);
|
stopReason: item.stopReason,
|
||||||
currentTime.setHours(hour, minute, 0, 0);
|
blendStatus: item.blendStatus,
|
||||||
}
|
capacity: item.capacity,
|
||||||
|
value: adjustedDuration,
|
||||||
// 查找是否有数据在这个时间点
|
deviceStatus: item.deviceStatus,
|
||||||
const matchingData = props.processData.find(item => {
|
mixerStep: item.mixerStep,
|
||||||
const beginTime = new Date(item.beginTime).getTime();
|
beginTime: formatTime(beginTime), // 保持原始开始时间
|
||||||
const endTime = new Date(item.endTime).getTime();
|
endTime: formatTime(adjustedEndTime), // 保持原始结束时间
|
||||||
return currentTime.getTime() >= beginTime && currentTime.getTime() <= endTime; // 修改比较条件,确保数据正确对齐
|
productFlowRate: item.productFlowRate,
|
||||||
});
|
formula: item.formula,
|
||||||
|
duration: `${Math.floor(adjustedDuration / 1000)}秒`,
|
||||||
if (matchingData) {
|
itemStyle: {
|
||||||
return {
|
color: item.color
|
||||||
value: 1,
|
|
||||||
deviceStatus: matchingData.deviceStatus,
|
|
||||||
mixerStep: matchingData.mixerStep,
|
|
||||||
beginTime: formatTime(matchingData.beginTime),
|
|
||||||
endTime: formatTime(matchingData.endTime),
|
|
||||||
productFlowRate: matchingData.productFlowRate,
|
|
||||||
formula: matchingData.formula,
|
|
||||||
duration: matchingData.duration,
|
|
||||||
itemStyle: {
|
|
||||||
color: getStatusColor(matchingData.deviceStatus) // 确保颜色正确
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return { value: 0 }; // 确保返回对象格式一致
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const generateTimeLabels = () => {
|
|
||||||
const times = [];
|
|
||||||
const now = new Date();
|
|
||||||
let startHour, endHour;
|
|
||||||
|
|
||||||
switch (currentRange.value) {
|
|
||||||
case '1h':
|
|
||||||
startHour = Math.max(now.getHours() - 1, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case '2h':
|
|
||||||
startHour = Math.max(now.getHours() - 2, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case 'shift':
|
|
||||||
startHour = Math.max(now.getHours() - 12, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
case 'all':
|
|
||||||
default:
|
|
||||||
startHour = Math.max(now.getHours() - 24, 0); // 确保 startHour 不为负值
|
|
||||||
endHour = now.getHours();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let hour = startHour; hour <= endHour; hour++) {
|
|
||||||
const currentHour = hour % 24;
|
|
||||||
const isNextDay = hour >= 24;
|
|
||||||
const labelDate = new Date(now);
|
|
||||||
|
|
||||||
if (isNextDay) {
|
|
||||||
labelDate.setDate(labelDate.getDate() + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let minute = 0; minute < 60; minute += 15) {
|
|
||||||
let timeLabel = `${currentHour}:${minute.toString().padStart(2, '0')}`;
|
|
||||||
if (currentHour === 0 && minute === 0) {
|
|
||||||
const dateStr = `${labelDate.getFullYear().toString().slice(-2)}/${(labelDate.getMonth() + 1).toString().padStart(2, '0')}/${(labelDate.getDate()).toString().padStart(2, '0')}`;
|
|
||||||
timeLabel = `${dateStr} ${currentHour}`;
|
|
||||||
}
|
}
|
||||||
times.push(timeLabel);
|
};
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
return times;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// 更新当前信息
|
||||||
const updateCurrentInfo = () => {
|
const updateCurrentInfo = () => {
|
||||||
const currentData = props.processData[props.processData.length - 1] || {}
|
const currentData = props.processData[props.processData.length - 1] || {}
|
||||||
currentStatus.value = currentData.deviceStatus || '--'
|
currentStatus.value = currentData.deviceStatus || '--'
|
||||||
|
@ -291,7 +175,7 @@ const updateCurrentInfo = () => {
|
||||||
const start = new Date(currentData.beginTime)
|
const start = new Date(currentData.beginTime)
|
||||||
const end = new Date(currentData.endTime)
|
const end = new Date(currentData.endTime)
|
||||||
const diff = end - start
|
const diff = end - start
|
||||||
duration.value = Math.floor(diff / (1000 * 60)) + ' 分钟'
|
duration.value = Math.floor(diff / (1000 * 60)) + '分钟'
|
||||||
} else {
|
} else {
|
||||||
duration.value = '--'
|
duration.value = '--'
|
||||||
}
|
}
|
||||||
|
@ -300,32 +184,9 @@ const updateCurrentInfo = () => {
|
||||||
formula.value = currentData.formula || '--'
|
formula.value = currentData.formula || '--'
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTimeRange = (range) => {
|
// 处理甘特图点击事件
|
||||||
currentRange.value = range;
|
|
||||||
updateChart();
|
|
||||||
}
|
|
||||||
|
|
||||||
const getStatusColor = (status) => {
|
|
||||||
const colors = {
|
|
||||||
'running': '#00E4FF',
|
|
||||||
'cleaning': '#47D3BD', // 清洗状态
|
|
||||||
'waiting': '#FFA849',
|
|
||||||
'completed': '#47D3BD',
|
|
||||||
production: '#4CAF50',
|
|
||||||
downtime: '#F44336',
|
|
||||||
standby: '#FFC107'
|
|
||||||
}
|
|
||||||
return colors[status] || '#00E4FF'
|
|
||||||
}
|
|
||||||
|
|
||||||
// const formatTime = (time) => {
|
|
||||||
// if (!time) return ''
|
|
||||||
// const date = new Date(time)
|
|
||||||
// return `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
|
|
||||||
// }
|
|
||||||
|
|
||||||
const handleChartClick = (data) => {
|
const handleChartClick = (data) => {
|
||||||
currentStatus.value = data.status
|
currentStatus.value = data.deviceStatus
|
||||||
startTimeFormatted.value = formatTime(data.beginTime)
|
startTimeFormatted.value = formatTime(data.beginTime)
|
||||||
endTimeFormatted.value = formatTime(data.endTime)
|
endTimeFormatted.value = formatTime(data.endTime)
|
||||||
|
|
||||||
|
@ -333,7 +194,7 @@ const handleChartClick = (data) => {
|
||||||
const start = new Date(data.beginTime)
|
const start = new Date(data.beginTime)
|
||||||
const end = new Date(data.endTime)
|
const end = new Date(data.endTime)
|
||||||
const diff = end - start
|
const diff = end - start
|
||||||
duration.value = Math.floor(diff / (1000 * 60)) + ' 分钟'
|
duration.value = Math.floor(diff / (1000 * 60)) + '分钟'
|
||||||
} else {
|
} else {
|
||||||
duration.value = '--'
|
duration.value = '--'
|
||||||
}
|
}
|
||||||
|
@ -345,34 +206,168 @@ const handleChartClick = (data) => {
|
||||||
emit('segment-click', data)
|
emit('segment-click', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取甘特图条目样式
|
||||||
|
const getBarStyle = (item) => {
|
||||||
|
const startTime = new Date(item.beginTime).getTime();
|
||||||
|
const endTime = new Date(item.endTime).getTime();
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0;
|
||||||
|
|
||||||
|
let chartStartTime;
|
||||||
|
let chartEndTime = props.endTime ? new Date(props.endTime).getTime() : new Date().getTime();
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
chartStartTime = chartEndTime - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
chartStartTime = chartEndTime - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
chartStartTime = getShiftStartTime(chartEndTime);
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
chartStartTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : new Date().getTime();
|
||||||
|
chartEndTime = props.endTime ? new Date(props.endTime).getTime() : new Date().getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalChartDuration = chartEndTime - chartStartTime;
|
||||||
|
const adjustedStartTime = startTime < chartStartTime ? chartStartTime : startTime;
|
||||||
|
const adjustedDuration = endTime - adjustedStartTime;
|
||||||
|
const barWidth = (adjustedDuration / totalChartDuration) * containerWidth;
|
||||||
|
const leftPosition = ((adjustedStartTime - chartStartTime) / totalChartDuration) * containerWidth;
|
||||||
|
return {
|
||||||
|
width: `${barWidth / 16}rem`, // 将 px 转换为 rem
|
||||||
|
backgroundColor: item.itemStyle.color,
|
||||||
|
left: `calc(${leftPosition / 16}rem + 1rem)` // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取甘特图间隙条目样式
|
||||||
|
const getGapStyle = (gap) => {
|
||||||
|
const startTime = new Date(gap.startTime).getTime()
|
||||||
|
const endTime = new Date(gap.endTime).getTime()
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0
|
||||||
|
|
||||||
|
let chartStartTime;
|
||||||
|
let chartEndTime = props.endTime ? new Date(props.endTime).getTime() : new Date().getTime(); // 使用历史时间或当前时间作为结束时间
|
||||||
|
switch (currentRange.value) {
|
||||||
|
case '1h':
|
||||||
|
chartStartTime = chartEndTime - 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case '2h':
|
||||||
|
chartStartTime = chartEndTime - 2 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'shift':
|
||||||
|
chartStartTime = getShiftStartTime(chartEndTime);//new Date().getTime() - 12 * 60 * 60 * 1000;
|
||||||
|
break;
|
||||||
|
case 'all':
|
||||||
|
default:
|
||||||
|
chartStartTime = props.processData.length > 0 ? new Date(props.processData[0].beginTime).getTime() : new Date().getTime();
|
||||||
|
chartEndTime = props.endTime ? new Date(props.endTime).getTime() : new Date().getTime();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const totalChartDuration = chartEndTime - chartStartTime
|
||||||
|
const adjustedStartTime = startTime < chartStartTime ? chartStartTime : startTime;
|
||||||
|
const adjustedDuration = endTime - adjustedStartTime;
|
||||||
|
const barWidth = (adjustedDuration / totalChartDuration) * containerWidth
|
||||||
|
const leftPosition = ((adjustedStartTime - chartStartTime) / totalChartDuration) * containerWidth
|
||||||
|
|
||||||
|
return {
|
||||||
|
width: `${barWidth / 16}rem`, // 将 px 转换为 rem
|
||||||
|
backgroundColor: '#D3D3D3', // 灰色
|
||||||
|
left: `calc(${leftPosition / 16}rem + 1rem)` // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 X 轴刻度
|
||||||
|
const generateXTicks = (startTime, endTime) => {
|
||||||
|
const containerWidth = ganttChart.value ? ganttChart.value.clientWidth : 0;
|
||||||
|
const totalDuration = endTime - startTime;
|
||||||
|
const tickInterval = totalDuration / 10; // 将时间段分为10个刻度
|
||||||
|
const ticks = [];
|
||||||
|
|
||||||
|
for (let i = 0; i <= 10; i++) {
|
||||||
|
const time = startTime + i * tickInterval;
|
||||||
|
const tickTime = new Date(time);
|
||||||
|
const label = `${(tickTime.getMonth() + 1).toString().padStart(2, '0')}-${tickTime.getDate().toString().padStart(2, '0')} ${tickTime.getHours().toString().padStart(2, '0')}:${tickTime.getMinutes().toString().padStart(2, '0')}`;
|
||||||
|
const position = (i / 10) * containerWidth;
|
||||||
|
ticks.push({ label, position });
|
||||||
|
}
|
||||||
|
|
||||||
|
xTicks.value = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成甘特图间隙
|
||||||
|
const generateGaps = (startTime, endTime) => {
|
||||||
|
const gapsArray = [];
|
||||||
|
if (props.processData.length > 0) {
|
||||||
|
let previousEndTime = startTime;
|
||||||
|
for (let i = 0; i < props.processData.length; i++) {
|
||||||
|
const currentStartTime = new Date(props.processData[i].beginTime).getTime();
|
||||||
|
if (i === 0 && currentStartTime < startTime) {
|
||||||
|
// 如果第一条数据的开始时间小于X轴的开始时间,添加一个间隙
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: currentStartTime
|
||||||
|
});
|
||||||
|
} else if (currentStartTime > previousEndTime) {
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: previousEndTime,
|
||||||
|
endTime: currentStartTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previousEndTime = new Date(props.processData[i].endTime).getTime();
|
||||||
|
}
|
||||||
|
if (endTime > previousEndTime) {
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: previousEndTime,
|
||||||
|
endTime: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果没有数据,直接添加一个从 startTime 到 endTime 的间隙
|
||||||
|
gapsArray.push({
|
||||||
|
startTime: startTime,
|
||||||
|
endTime: endTime
|
||||||
|
});
|
||||||
|
}
|
||||||
|
gaps.value = gapsArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 X 轴刻度样式
|
||||||
|
const getTickStyle = (tick) => {
|
||||||
|
return {
|
||||||
|
left: `calc(${tick.position / 16}rem + 1rem)`, // 将 px 转换为 rem,并增加 1rem 的 gap
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
whiteSpace: 'nowrap' // 确保标签不换行
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 修改生命周期钩子
|
// 修改生命周期钩子
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 确保 DOM 已经渲染完成
|
// 确保 DOM 已经渲染完成
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
initChart();
|
if (ganttChart.value) {
|
||||||
updateCurrentInfo();
|
initChart();
|
||||||
|
updateCurrentInfo();
|
||||||
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
try {
|
// 清理工作
|
||||||
window.removeEventListener('resize', handleResize);
|
|
||||||
if (chart.value) {
|
|
||||||
chart.value.dispose();
|
|
||||||
chart.value = null;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cleanup error:', error);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 修改监听器
|
// 修改监听器
|
||||||
watch(() => props.processData, () => {
|
watch(() => props.processData, () => {
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (chart.value) {
|
if (ganttChart.value) {
|
||||||
updateChart();
|
updateChart();
|
||||||
|
generateXTicks(); // 确保 x 轴刻度更新
|
||||||
|
generateGaps(); // 确保 gaps 更新
|
||||||
updateCurrentInfo();
|
updateCurrentInfo();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -385,22 +380,97 @@ watch(() => props.currentRange, (newRange) => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 甘特图容器样式 */
|
||||||
.gantt-chart-container {
|
.gantt-chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 75%;
|
height: 75%;
|
||||||
/* background: #23233C; */
|
border-radius: 0.5rem;
|
||||||
border-radius: 8px;
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
padding: 0 1rem; /* 增加左右两边的 gap */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 甘特图样式 */
|
||||||
.gantt-chart {
|
.gantt-chart {
|
||||||
background: #23233C;
|
background: #23233C;
|
||||||
flex: 1;
|
width: calc(100% - 2rem); /* 减去左右两边的 gap */
|
||||||
|
height: 70%;
|
||||||
|
min-height: 5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴样式 */
|
||||||
|
.gantt-x-axis {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 1.8rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度样式 */
|
||||||
|
.gantt-x-tick {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 80px;
|
font-size: 0.75rem;
|
||||||
|
color: #fff;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap; /* 确保标签不换行 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度线样式 */
|
||||||
|
.gantt-x-tick-line {
|
||||||
|
width: 0.0625rem;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图 X 轴刻度标签样式 */
|
||||||
|
.gantt-x-tick-label {
|
||||||
|
margin-top: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图条目样式 */
|
||||||
|
.gantt-bar {
|
||||||
|
position: absolute;
|
||||||
|
height: 1.875rem;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 甘特图条目提示样式 */
|
||||||
|
.gantt-bar-tooltip {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: -3.75rem;
|
||||||
|
left: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
color: #fff;
|
||||||
|
padding: 0.3125rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-bar:hover .gantt-bar-tooltip {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gantt-x-axis-line {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.0625rem;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gap-bar {
|
||||||
|
background-color: #D3D3D3; /* 灰色 */
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -17,6 +17,10 @@ import pinia from './store';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
import MessagePlugin from './plugins/message';
|
import MessagePlugin from './plugins/message';
|
||||||
|
|
||||||
|
// element-plus
|
||||||
|
import ElementPlus from 'element-plus'
|
||||||
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
// ix-siemens
|
// ix-siemens
|
||||||
import '@siemens/ix/dist/siemens-ix/siemens-ix.css';
|
import '@siemens/ix/dist/siemens-ix/siemens-ix.css';
|
||||||
import { ixPlugin } from '@siemens/ix-vue';
|
import { ixPlugin } from '@siemens/ix-vue';
|
||||||
|
@ -27,4 +31,4 @@ const app = createApp(App);
|
||||||
app.config.globalProperties.$echarts = echarts;
|
app.config.globalProperties.$echarts = echarts;
|
||||||
|
|
||||||
// 使用use将文件挂载上去
|
// 使用use将文件挂载上去
|
||||||
app.use(router).use(pinia).use(ixPlugin).use(MessagePlugin).mount('#app');
|
app.use(router).use(pinia).use(ixPlugin).use(ElementPlus).use(MessagePlugin).mount('#app');
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
export const useAlarmStore = defineStore('alarm', () => {
|
||||||
|
const alarmCount = ref(0);
|
||||||
|
|
||||||
|
const setAlarmCount = (count) => {
|
||||||
|
alarmCount.value = count;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
alarmCount,
|
||||||
|
setAlarmCount
|
||||||
|
};
|
||||||
|
});
|
|
@ -1,7 +1,9 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
baseURL: 'http://192.168.1.199:8080/api', // 接口的基础路径'http://cs-vsc.siemens.com.cn:8005',//
|
// baseURL: 'http://192.168.1.199:8080/api', // 办公室测试接口
|
||||||
|
// baseURL: 'http://39.105.9.124:8090/api', // 家用测试接口
|
||||||
|
baseURL: '/api',
|
||||||
timeout: 5000, // 请求超时时间
|
timeout: 5000, // 请求超时时间
|
||||||
headers: {
|
headers: {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -10,27 +10,32 @@
|
||||||
<div class="item-chip-container">
|
<div class="item-chip-container">
|
||||||
<p>{{ itemName }}</p>
|
<p>{{ itemName }}</p>
|
||||||
<IxChip class="chip" background="#FF2640" chip-color="#000028" variant="custom">
|
<IxChip class="chip" background="#FF2640" chip-color="#000028" variant="custom">
|
||||||
09:00 超出上下限
|
{{ itemTime }} 超出上下限
|
||||||
</IxChip>
|
</IxChip>
|
||||||
</div>
|
</div>
|
||||||
<label for="reason">请输入原因</label>
|
<label for="reason">请输入原因</label>
|
||||||
<textarea id="reason" v-model="reason"></textarea>
|
<textarea id="reason" v-model="reason"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-footer">
|
<div class="form-footer">
|
||||||
<IxButton type="submit">提交</IxButton>
|
<!-- <IxButton type="submit">提交</IxButton> -->
|
||||||
<IxButton Outline @click="resetForm">重置</IxButton>
|
<IxButton Outline @click="resetForm">重置</IxButton>
|
||||||
<IxButton Outline @click="$emit('close')">关闭</IxButton>
|
<IxButton Outline @click="$emit('close')">关闭</IxButton>
|
||||||
|
<IxButton @click="handleSubmit">提交</IxButton>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineProps } from 'vue';
|
import { ref, defineProps, defineEmits, onMounted } from 'vue';
|
||||||
import { IxButton, IxChip } from '@siemens/ix-vue';
|
import { IxButton, IxChip } from '@siemens/ix-vue';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
itemName: String
|
itemName: String,
|
||||||
|
itemTime: String,
|
||||||
|
timezone: String,
|
||||||
|
currentStatus: String,
|
||||||
});
|
});
|
||||||
|
|
||||||
const reason = ref('');
|
const reason = ref('');
|
||||||
|
@ -42,6 +47,20 @@ const submitForm = () => {
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
reason.value = '';
|
reason.value = '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const emit = defineEmits(['close', 'submit']);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
const currentTime = moment().tz(props.timezone).format('HH:mm');
|
||||||
|
emit('submit', currentTime, reason.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if(props.currentStatus === '已处理') {
|
||||||
|
// 数据库读取当前的处理信息
|
||||||
|
reason.value = '已处理';
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -2,112 +2,153 @@
|
||||||
<div class="inspection-container">
|
<div class="inspection-container">
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="header_left">
|
<div class="header_left">
|
||||||
<ix-select v-model="state.selectedShift" :options="shiftOptions" label="label" value="value"></ix-select>
|
<!-- 日期输入框 -->
|
||||||
<ix-select v-model="state.selectedOption1" :options="state.options1" label="label" value="value"></ix-select>
|
<IxDateInput label="日期" name="date" :value="currentDate" class="input-spacing" @dateChange="handleDateChange">
|
||||||
<ix-select v-model="state.selectedOption2" :options="state.options2" label="label" value="value"></ix-select>
|
</IxDateInput>
|
||||||
<IxButton @click="fetchInspectionData"> 查询 </IxButton>
|
|
||||||
<IxButton> 导出全部 </IxButton>
|
<!-- 班次选择框 -->
|
||||||
|
<IxSelect Outline id="triggerId" v-model="shift" label="班次" hideListHeader="true" class="input-spacing" @valueChange="handleShiftChange">
|
||||||
|
<IxSelectItem icon="sun" label="白班" value="0"></IxSelectItem>
|
||||||
|
<IxSelectItem icon="moon" label="晚班" value="1"></IxSelectItem>
|
||||||
|
</IxSelect>
|
||||||
|
|
||||||
|
<!-- 状态选择框 -->
|
||||||
|
<IxSelect Outline id="statusSelect" v-model="status" label="状态" hideListHeader="true" class="input-spacing">
|
||||||
|
<IxSelectItem icon="sun" label="全部" value="0"></IxSelectItem>
|
||||||
|
<IxSelectItem icon="sun" label="正常" value="1"></IxSelectItem>
|
||||||
|
<IxSelectItem label="报警" value="2"></IxSelectItem>
|
||||||
|
</IxSelect>
|
||||||
|
<!-- 查询按钮 -->
|
||||||
|
<IxButton @click="handleSearch" class="input-spacing btn-style"> 刷新 </IxButton>
|
||||||
|
<!-- 导出按钮 -->
|
||||||
|
<IxButton class="input-spacing btn-style" @click="handleExport"> 导出全部 </IxButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="header_right">
|
<div class="header_right">
|
||||||
<IxButton> 灌注机 </IxButton>
|
<!-- 设备列表按钮 -->
|
||||||
<IxButton Outline> 吹瓶机 </IxButton>
|
<span v-for="(item, index) in deviceList" :key="item.id">
|
||||||
<IxButton Outline> 果汁杀菌 </IxButton>
|
<IxButton :outline="selectedDeviceId !== item.id" class="btn-style" @click="handleDeviceListChange(item.id)">
|
||||||
<IxButton Outline> 果肉杀菌 </IxButton>
|
{{ item.name }}
|
||||||
|
</IxButton>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="inspection-table">
|
<div class="inspection-table">
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
|
<!-- 表头部分 -->
|
||||||
|
<div class="table-row fixed-row">
|
||||||
|
<span class="fixed-width title-width"></span>
|
||||||
|
<span class="fixed-width"></span>
|
||||||
|
<span class="fixed-width"></span>
|
||||||
|
<span class="fixed-width"></span>
|
||||||
|
<span class="fixed-width" v-for="(hour, index) in hours" :key="hour">
|
||||||
|
<IxButton :disabled="confirmedHours.includes(hour) || hourCheckStatus[hour] !== null" class="custom-button" @click="handleInspection(hour, index)">
|
||||||
|
{{ hourCheckStatus[hour] !== null ? hourCheckTime[hour] : '确认' }}
|
||||||
|
</IxButton>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="table-row-1 fixed-row">
|
||||||
|
<span class="header-row title-width">点检项目</span>
|
||||||
|
<span class="header-row">单位</span>
|
||||||
|
<span class="header-row">参考值</span>
|
||||||
|
<span class="header-row">当前值</span>
|
||||||
|
<span class="header-row" v-for="hour in hours" :key="hour"
|
||||||
|
:style="{ backgroundColor: getDeviceNameById(selectedDeviceId) === '灌注机' ? '#00FFB9' : '#00E4FF', color: '#000028' }">
|
||||||
|
{{ hour }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- 点检项目列表 -->
|
||||||
<IxEventList>
|
<IxEventList>
|
||||||
<IxEventListItem class="testbg1">
|
<IxEventListItem v-for="(item, index) in inspectionItems" :key="index" color="color-success" class="testbg" :class="{
|
||||||
<div class="table-row">
|
'alarm-cell-title': Object.values(item.data).some(data => data.checkStatus === 0),
|
||||||
<span class="fixed-width title-width"></span>
|
'highlight-cell-title': Object.values(item.data).some(data => data.checkStatus === 1 && !Object.values(item.data).some(data => data.checkStatus === 0))
|
||||||
<span class="fixed-width"></span>
|
}">
|
||||||
<span class="fixed-width"></span>
|
|
||||||
<span class="fixed-width"></span>
|
|
||||||
<!--这里是你外面包裹了一个span,改span的背景就行,index是判断哪些变颜色-->
|
|
||||||
<span class="fixed-width" v-for="(hour,index) in hours" :key="hour">
|
|
||||||
<IxButton class="custom-button" @click="handleInspection(index)"> 确认 </IxButton>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</IxEventListItem>
|
|
||||||
<IxEventListItem class="testbg1">
|
|
||||||
<div class="table-row-1">
|
|
||||||
<span class="header-row title-width">点检项目</span>
|
|
||||||
<span class="header-row">单位</span>
|
|
||||||
<span class="header-row">参考值</span>
|
|
||||||
<span class="header-row">当前值</span>
|
|
||||||
<span class="header-row" v-for="hour in hours" :key="hour" :style="{ backgroundColor: '#00FFB9', color: '#000028' }">
|
|
||||||
{{ hour }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</IxEventListItem>
|
|
||||||
<IxEventListItem v-for="(item, index) in inspectionItems" :key="index" color="color-success" class="testbg">
|
|
||||||
<div class="table-row">
|
<div class="table-row">
|
||||||
<span class="fixed-width title-width">{{ item.label }}</span>
|
<span class="fixed-width title-width">{{ item.label }}</span>
|
||||||
<span class="fixed-width">--</span>
|
<span class="fixed-width">{{ item.unit || '--' }}</span>
|
||||||
<span class="fixed-width">{{ item.reference }}</span>
|
<span class="fixed-width">{{ item.reference || '--' }}</span>
|
||||||
<span class="fixed-width">{{ item.current }}</span> <!-- 确保这里绑定的是 item.current -->
|
<span class="fixed-width">{{ item.current }}</span> <!-- 确保这里绑定的是 item.current -->
|
||||||
<span class="fixed-width" v-for="hour in hours" :key="hour">
|
<span class="fixed-width" v-for="hour in hours" :key="hour"
|
||||||
{{ item.data[hour] || '--' }}
|
:class="{
|
||||||
|
'highlight-cell': item.data[hour]?.checkStatus === 1,
|
||||||
|
'alarm-cell': item.data[hour]?.checkStatus === 0
|
||||||
|
}"
|
||||||
|
@click="item.data[hour]?.checkStatus === 0 || item.data[hour]?.checkStatus === 1 ? handleAlarmInspection(hour, index, item.data[hour]?.checkStatus) : null">
|
||||||
|
{{ item.data[hour]?.value || '--' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</IxEventListItem>
|
</IxEventListItem>
|
||||||
</IxEventList>
|
</IxEventList>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InspectionForm v-if="showForm" :itemName="selectedItemName" @close="showForm.value = false" />
|
<!-- 点检表单 -->
|
||||||
|
<InspectionForm v-if="showForm" :itemName="selectedItemName" :itemTime="selectedItemTime" :timezone="timezone" :currentStatus="currentStatus" @close="showForm = false" @submit="(currentTime, reason) => handleFormSubmit(selectedItemIndex, reason)" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, getCurrentInstance, onMounted } from 'vue';
|
// 引入必要的库和组件
|
||||||
import { IxDatetimePicker, IxButton, IxDivider, IxDropdown, IxDropdownHeader, IxDropdownItem, IxEventList, IxEventListItem, IxSelect } from '@siemens/ix-vue';
|
import moment from 'moment';
|
||||||
|
import momentTimezone from 'moment-timezone';
|
||||||
|
import { ref, computed, getCurrentInstance, onMounted, defineEmits, onUnmounted } from 'vue';
|
||||||
|
import { IxDatePicker, IxButton, IxDropdownItem, IxEventList, IxEventListItem, IxSelect, IxSelectItem, IxDateInput } from '@siemens/ix-vue';
|
||||||
import InspectionForm from './InspectionForm.vue';
|
import InspectionForm from './InspectionForm.vue';
|
||||||
import { getInspectionCurrent, getInspectionData } from '@/api/inspection';
|
import { getInspectionCurrent, getInspectionData, getCheckParas, getDeviceList, exportExcel, sharpConfirm, alarmReasonConfirm } from '@/api/inspection';
|
||||||
import { color } from 'echarts';
|
|
||||||
|
|
||||||
// 定义班次选项
|
const emit = defineEmits(['send-data']);
|
||||||
const shiftOptions = [
|
|
||||||
{ label: '白班', value: 'day' },
|
// 设置为您期望的时区,比如 "Asia/Shanghai"
|
||||||
{ label: '晚班', value: 'night' }
|
const timezone = 'Asia/Shanghai';
|
||||||
];
|
|
||||||
|
// 全局响应式变量
|
||||||
|
const globalTime = ref(moment().tz(timezone).format('YYYY-MM-DD HH:mm'));
|
||||||
|
const isAutoUpdate = ref(true); // 是否自动更新
|
||||||
|
|
||||||
|
const sendDataToParent = () => {
|
||||||
|
emit('send-data', 'SIEMENS MIS 1.0 无菌产线点检界面', 'other-app');
|
||||||
|
};
|
||||||
|
|
||||||
// 获取当前时间的小时数
|
// 获取当前时间的小时数
|
||||||
const currentHour = new Date().getHours();
|
const currentHour = new Date().getHours();
|
||||||
|
|
||||||
// 根据当前时间确定默认班次
|
// 根据当前时间确定默认班次
|
||||||
const defaultShift = currentHour >= 7 && currentHour < 19 ? 'day' : 'night';
|
const defaultShift = currentHour >= 7 && currentHour < 19 ? '0' : '1'; // 早班为0,晚班为1
|
||||||
|
|
||||||
// 定义响应式变量
|
// 定义响应式变量
|
||||||
const state = ref({
|
const shift = ref(defaultShift); // 设置默认班次
|
||||||
selectedShift: defaultShift,
|
const status = ref('0'); // 确保 status 变量已定义
|
||||||
selectedOption1: null,
|
|
||||||
selectedOption2: null,
|
// 设备列表
|
||||||
options1: [
|
const deviceList = ref([]);
|
||||||
{ label: 'Option 1', value: 'option1' },
|
const selectedDeviceId = ref(null);
|
||||||
{ label: 'Option 2', value: 'option2' },
|
|
||||||
{ label: 'Option 3', value: 'option3' }
|
|
||||||
],
|
|
||||||
options2: [
|
|
||||||
{ label: 'Choice A', value: 'choiceA' },
|
|
||||||
{ label: 'Choice B', value: 'choiceB' },
|
|
||||||
{ label: 'Choice C', value: 'choiceC' }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
const showForm = ref(false);
|
const showForm = ref(false);
|
||||||
const selectedItemName = ref('');
|
const selectedItemName = ref('');
|
||||||
|
const selectedItemTime = ref('');
|
||||||
|
const selectedItemIndex = ref(null);
|
||||||
|
|
||||||
|
const selectedDate = ref(null);
|
||||||
|
|
||||||
|
const formatDate = (date) => {
|
||||||
|
const d = new Date(date);
|
||||||
|
const year = d.getFullYear();
|
||||||
|
const month = (d.getMonth() + 1).toString().padStart(2, '0');
|
||||||
|
const day = d.getDate().toString().padStart(2, '0');
|
||||||
|
return `${year}/${month}/${day}`;
|
||||||
|
};
|
||||||
|
const currentStatus = ref('');
|
||||||
|
|
||||||
|
const currentDate = formatDate(new Date());
|
||||||
|
|
||||||
// 计算当前班次的时间段
|
// 计算当前班次的时间段
|
||||||
const hours = computed(() => {
|
const hours = computed(() => {
|
||||||
const shift = state.value.selectedShift;
|
const shiftValue = shift.value;
|
||||||
const hours = [];
|
const hours = [];
|
||||||
if (shift === 'day') {
|
if (shiftValue === '0') {
|
||||||
for (let i = 7; i <= 18; i++) {
|
for (let i = 7; i <= 18; i++) {
|
||||||
hours.push(`${i < 10 ? '0' : ''}${i}:00`); // 白班 07:00-18:00
|
hours.push(`${i < 10 ? '0' : ''}${i}:00`); // 白班 07:00-18:00
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (let i = 19; i <= 23; i++) {
|
for (let i = 19; i <= 23;i++) {
|
||||||
hours.push(`${i}:00`); // 晚班 19:00-23:00
|
hours.push(`${i}:00`); // 晚班 19:00-23:00
|
||||||
}
|
}
|
||||||
for (let i = 0; i <= 6; i++) {
|
for (let i = 0; i <= 6; i++) {
|
||||||
|
@ -118,110 +159,333 @@ const hours = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// JSON 数据
|
// JSON 数据
|
||||||
const data = {
|
const data = ref({});
|
||||||
juiceFlow: {},
|
|
||||||
pulpFlow: {},
|
|
||||||
sterile: {},
|
|
||||||
clean: {},
|
|
||||||
test1: {},
|
|
||||||
test2: {},
|
|
||||||
test3: {},
|
|
||||||
test4: {},
|
|
||||||
test5: {},
|
|
||||||
test6: {},
|
|
||||||
test7: {},
|
|
||||||
test8: {},
|
|
||||||
test9: {},
|
|
||||||
test10: {},
|
|
||||||
test11: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
const inspectionItems = ref([]);
|
const inspectionItems = ref([]);
|
||||||
|
const confirmedHours = ref([]);
|
||||||
|
const confirmedTimes = ref({});
|
||||||
|
|
||||||
|
// 点检状态
|
||||||
|
const hourCheckStatus = ref({});
|
||||||
|
const hourCheckTime = ref({});
|
||||||
|
const alarmId = ref({});
|
||||||
|
const hourCheckValid = ref({});
|
||||||
|
|
||||||
|
const formatTime = (time) => {
|
||||||
|
const date = new Date(time);
|
||||||
|
const hours = date.getHours().toString().padStart(2, '0');
|
||||||
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 点检按钮点击事件 handleAlarmConfirm
|
||||||
|
const handleInspection = (hour, index) => {
|
||||||
|
selectedItemName.value = inspectionItems.value[index].label;
|
||||||
|
selectedItemTime.value = hour;
|
||||||
|
selectedItemIndex.value = index; // 保存当前索引
|
||||||
|
|
||||||
|
// 校验是否已经确认异常数据
|
||||||
|
if (hourCheckValid.value[hour] !== 1) {
|
||||||
|
console.log("🚀 ~ handleInspection ~ hourCheckValid.value[hour]:", hourCheckValid.value[hour])
|
||||||
|
showWarningMessage('存在设备异常,无法执行点检操作!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("🚀 ~ handleInspection ~ inspectionItems.value:", inspectionItems.value)
|
||||||
|
// 校验这个时刻这一列有咩有报警未处理的数据,也就是checkStatus为0的数据
|
||||||
|
if (inspectionItems.value.some(item => item.data[hour]?.checkStatus === 0)) {
|
||||||
|
showWarningMessage('存在报警数据未处理,无法执行点检操作!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showConfirmMessage('确认要执行点检操作吗?', async () => {
|
||||||
|
await sharpConfirm(alarmId.value[hour], 'admin').then(() => {
|
||||||
|
confirmedHours.value.push(selectedItemTime.value);
|
||||||
|
confirmedTimes.value[selectedItemTime.value] = moment().tz(timezone).format('HH:mm');
|
||||||
|
hourCheckStatus.value[hour] = 1; // 更新点检状态
|
||||||
|
hourCheckTime.value[hour] = confirmedTimes.value[selectedItemTime.value]; // 更新点检时间
|
||||||
|
});
|
||||||
|
}, () => {
|
||||||
|
showWarningMessage('取消点检操作');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// 点检按钮点击事件
|
// 点检按钮点击事件
|
||||||
const handleInspection = (index) => {
|
const handleAlarmInspection = (hour, index, status) => {
|
||||||
selectedItemName.value = inspectionItems.value[index].name;
|
selectedItemName.value = inspectionItems.value[index].label;
|
||||||
|
selectedItemTime.value = hour;
|
||||||
|
selectedItemIndex.value = index; // 确保在这里设置 selectedItemIndex
|
||||||
|
currentStatus.value = status === 0 ? '未处理' : '已处理';
|
||||||
showForm.value = true;
|
showForm.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 点检表单提交事件
|
||||||
|
const handleFormSubmit = async (index, reason) => {
|
||||||
|
await alarmReasonConfirm({
|
||||||
|
alarmId: alarmId.value[selectedItemTime.value],
|
||||||
|
checkParamId: inspectionItems.value[index].data[selectedItemTime.value].checkParamId,
|
||||||
|
alarmReason: reason,
|
||||||
|
checkUser: 'admin'
|
||||||
|
}).then(() => {
|
||||||
|
inspectionItems.value[index].data[selectedItemTime.value].checkStatus = 1; // 更新点检项目状态
|
||||||
|
inspectionItems.value[index].data[selectedItemTime.value].checkUser = 'admin'; // 更新点检人员
|
||||||
|
inspectionItems.value[index].data[selectedItemTime.value].checkText = '点检正常'; // 更新点检结果
|
||||||
|
});
|
||||||
|
showForm.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const { proxy } = getCurrentInstance();
|
const { proxy } = getCurrentInstance();
|
||||||
|
|
||||||
const showInfoMessage = (index) => {
|
const showInfoMessage = (message) => {
|
||||||
proxy.$message.info(index + '数据写入成功!');
|
proxy.$message.info(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showDangerMessage = (index) => {
|
const showInfoMessageWithAction = (message, action) => {
|
||||||
proxy.$message.danger(index + '数据未成功写入!');
|
proxy.$message.$confirm(message, action);
|
||||||
};
|
};
|
||||||
|
|
||||||
const showWarningMessage = (index) => {
|
const showDangerMessage = (message) => {
|
||||||
proxy.$message.warning(index + '数据写入异常!');
|
proxy.$message.danger(message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showWarningMessage = (message) => {
|
||||||
|
proxy.$message.warning(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
const showConfirmMessage = (message, onConfirm, onCancel) => {
|
||||||
|
proxy.$message.confirm(message, onConfirm, onCancel);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 日期变化处理函数
|
||||||
|
const handleDateChange = (event) => {
|
||||||
|
selectedDate.value = event.target.value;
|
||||||
|
fetchInspectionData();
|
||||||
|
fetchCurrentValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 班次变化处理函数
|
||||||
|
const handleShiftChange = (event) => {
|
||||||
|
shift.value = event.detail;
|
||||||
|
fetchInspectionData();
|
||||||
|
fetchCurrentValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 切换设备
|
||||||
|
const handleDeviceListChange = (id) => {
|
||||||
|
selectedDeviceId.value = id;
|
||||||
|
fetchInspectionData();
|
||||||
|
fetchCurrentValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取设备列表
|
||||||
|
const fetchDeviceList = async () => {
|
||||||
|
try {
|
||||||
|
const response = await getDeviceList();
|
||||||
|
if (response.code === 200) {
|
||||||
|
deviceList.value = response.data;
|
||||||
|
selectedDeviceId.value = deviceList.value[0].id; // 默认选中第一个设备
|
||||||
|
} else {
|
||||||
|
showWarningMessage('获取设备列表失败!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching device list:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取当前值、参考值、单位等信息
|
||||||
const fetchCurrentValues = async () => {
|
const fetchCurrentValues = async () => {
|
||||||
try {
|
try {
|
||||||
const deviceId = 7; // 根据实际情况设置设备ID
|
const deviceId = selectedDeviceId.value; // 根据实际情况设置设备ID
|
||||||
const inputTime = new Date().toISOString(); // 获取当前时间
|
const dateValue = selectedDate.value || currentDate;
|
||||||
const response = await getInspectionCurrent(deviceId, inputTime);
|
const response = await getInspectionCurrent(deviceId, dateValue);
|
||||||
if (response.code === 200) {
|
const params = await getCheckParas(deviceId, dateValue);
|
||||||
|
|
||||||
|
if (response.data) {
|
||||||
const currentValues = response.data.data;
|
const currentValues = response.data.data;
|
||||||
console.log("🚀 ~ fetchCurrentValues ~ currentValues:", currentValues)
|
|
||||||
inspectionItems.value.forEach(item => {
|
inspectionItems.value.forEach(item => {
|
||||||
item.current = currentValues[item.name] || '--';
|
item.current = currentValues?.[item.name] || '--'; // 添加对 currentValues 的检查
|
||||||
});
|
});
|
||||||
console.log("🚀 ~ fetchCurrentValues ~ inspectionItems:", inspectionItems.value)
|
} else {
|
||||||
|
showWarningMessage('获取当前设备当前值失败!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.data) {
|
||||||
|
const paramData = params.data;
|
||||||
|
|
||||||
|
inspectionItems.value.forEach(item => {
|
||||||
|
const param = paramData.find(param => {
|
||||||
|
return param.keyname === item.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (param) {
|
||||||
|
item.label = param.projectDescription;
|
||||||
|
item.reference = param.referenceValue;
|
||||||
|
item.unit = param.unit;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showWarningMessage('获取设备具体信息失败!');
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching current values:', error);
|
console.error('Error fetching current values:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 获取点检数据
|
||||||
const fetchInspectionData = async () => {
|
const fetchInspectionData = async () => {
|
||||||
try {
|
try {
|
||||||
const deviceId = 7; // 根据实际情况设置设备ID
|
const deviceId = selectedDeviceId.value; // 根据实际情况设置设备ID
|
||||||
const inputTime = new Date().toISOString(); // 获取当前时间
|
const shiftValue = shift.value;
|
||||||
const response = await getInspectionData(deviceId, inputTime);
|
const dateValue = selectedDate.value || currentDate;
|
||||||
if (response.code === 200) {
|
const response = await getInspectionData(deviceId, dateValue, shiftValue);
|
||||||
|
if (response.data) {
|
||||||
const inspectionData = response.data;
|
const inspectionData = response.data;
|
||||||
console.log("🚀 ~ fetchInspectionData ~ inspectionData:", inspectionData)
|
|
||||||
const itemsMap = {};
|
const itemsMap = {};
|
||||||
inspectionData.forEach(record => {
|
inspectionData.forEach(record => {
|
||||||
const recordTime = record.recordTime;
|
const recordTime = record.recordTime;
|
||||||
const data = record.data;
|
const data = record.data;
|
||||||
for (const [name, value] of Object.entries(data)) {
|
hourCheckStatus.value[recordTime] = record.hourCheckStatus;
|
||||||
|
hourCheckTime.value[recordTime] = formatTime(record.hourCheckTime);
|
||||||
|
alarmId.value[recordTime] = record.alarmId;
|
||||||
|
hourCheckValid.value[recordTime] = record.hourCheckValid;
|
||||||
|
|
||||||
|
for (const [name, valueObj] of Object.entries(data)) {
|
||||||
|
if (valueObj === null || valueObj.valule === null) continue; // 过滤掉值为 null 的属性
|
||||||
if (!itemsMap[name]) {
|
if (!itemsMap[name]) {
|
||||||
itemsMap[name] = {
|
itemsMap[name] = {
|
||||||
name,
|
name,
|
||||||
label: name, // 可以根据需要调整label的值
|
label: name, // 可以根据需要调整label的值
|
||||||
reference: 0,
|
reference: 0,
|
||||||
current: 0,
|
current: 0,
|
||||||
|
unit: '', // 添加单位字段
|
||||||
data: {}
|
data: {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
itemsMap[name].data[recordTime] = value;
|
itemsMap[name].data[recordTime] = {
|
||||||
|
value: valueObj.valule,
|
||||||
|
checkStatus: valueObj.checkStatus,
|
||||||
|
checkText: valueObj.checkText,
|
||||||
|
checkParamId: valueObj.checkParamId,
|
||||||
|
checkUser: valueObj.checkUser
|
||||||
|
// hourCheckStatus: valueObj.hourCheckStatus,
|
||||||
|
// hourCheckTime: valueObj.hourCheckTime
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
inspectionItems.value = Object.values(itemsMap);
|
inspectionItems.value = Object.values(itemsMap);
|
||||||
console.log("🚀 ~ fetchInspectionData ~ inspectionItems:", inspectionItems.value)
|
} else {
|
||||||
|
showWarningMessage('获取点检数据失败!');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching inspection data:', error);
|
console.error('Error fetching inspection data:', error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
// 查询接口数据
|
||||||
|
const handleSearch = () => {
|
||||||
|
fetchInspectionData();
|
||||||
|
fetchCurrentValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExport = () => {
|
||||||
|
console.log("🚀 ~ handleExport ~ inspectionItems.value.some(item => Object.values(item.data).some(data => data.checkStatus === 0)):", inspectionItems.value.some(item => Object.values(item.data).some(data => data.checkStatus === 0)))
|
||||||
|
|
||||||
|
// 增加一个判断,判断当前的数据中inspectionItems.value中是否有报警数据,如果有则提示不允许导出
|
||||||
|
if (inspectionItems.value.some(item => Object.values(item.data).some(data => data.checkStatus === 0))) {
|
||||||
|
showWarningMessage('请先处理报警数据,再进行导出!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showInfoMessage('导出全部');
|
||||||
|
exportExcel(selectedDeviceId.value, selectedDate.value || currentDate, shift.value)
|
||||||
|
.then((response) => {
|
||||||
|
if (response) {
|
||||||
|
// 有数据的处理
|
||||||
|
const url = window.URL.createObjectURL(new Blob([response]));
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.setAttribute('download', 'inspection_data.xlsx');
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
showInfoMessage('导出成功');
|
||||||
|
} else {
|
||||||
|
// 没有数据的处理
|
||||||
|
showWarningMessage('没有可导出的数据');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('导出 Excel 失败:', error);
|
||||||
|
showDangerMessage('导出 Excel 失败,请稍后重试');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 通过设备ID获取设备名称
|
||||||
|
const getDeviceNameById = (id) => {
|
||||||
|
const device = deviceList.value.find(device => device.id === id);
|
||||||
|
return device ? device.name : '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// let autoUpdateInterval = null;
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchDeviceList();
|
||||||
fetchCurrentValues();
|
fetchCurrentValues();
|
||||||
fetchInspectionData();
|
fetchInspectionData();
|
||||||
|
sendDataToParent();
|
||||||
|
|
||||||
|
// const setupAutoUpdate = () => {
|
||||||
|
// if (isAutoUpdate.value) {
|
||||||
|
// autoUpdateInterval = setInterval(() => {
|
||||||
|
// fetchCurrentValues();
|
||||||
|
// fetchInspectionData();
|
||||||
|
// }, 60000); // 每分钟刷新一次接口
|
||||||
|
// } else if (autoUpdateInterval) {
|
||||||
|
// clearInterval(autoUpdateInterval);
|
||||||
|
// autoUpdateInterval = null;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
// setupAutoUpdate();
|
||||||
|
|
||||||
|
// 监听 update-history 事件
|
||||||
|
// window.addEventListener('update-history', (event) => {
|
||||||
|
// isAutoUpdate.value = false;
|
||||||
|
// selectedDate.value = event.detail;
|
||||||
|
// fetchCurrentValues();
|
||||||
|
// fetchInspectionData();
|
||||||
|
// setupAutoUpdate();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 监听 reset 事件
|
||||||
|
// window.addEventListener('reset', (event) => {
|
||||||
|
// isAutoUpdate.value = true;
|
||||||
|
// selectedDate.value = moment().tz(timezone).format('YYYY-MM-DD HH:mm');
|
||||||
|
// fetchCurrentValues();
|
||||||
|
// fetchInspectionData();
|
||||||
|
// setupAutoUpdate();
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// onUnmounted(() => {
|
||||||
|
// if (autoUpdateInterval) {
|
||||||
|
// clearInterval(autoUpdateInterval);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 样式定义 */
|
||||||
.inspection-container {
|
.inspection-container {
|
||||||
width: 100%;
|
width: calc(100% + 1.5rem);
|
||||||
height: 100vh; /* 修改为100vh,使其高度铺满整个视口 */
|
margin: 0 -1.5rem 0 0 !important;
|
||||||
|
height: 100vh;
|
||||||
|
/* 修改为100vh,使其高度铺满整个视口 */
|
||||||
background-color: #000028;
|
background-color: #000028;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 1.2rem; /* 调大全局字体大小 */
|
font-size: 1.2rem;
|
||||||
|
/* 调大全局字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
@ -234,36 +498,44 @@ onMounted(() => {
|
||||||
.header_left {
|
.header_left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 70%; /* 设置宽度,确保三个搜索框都能正常显示 */
|
width: 70%;
|
||||||
font-size: 1rem; /* 调大左侧头部字体大小 */
|
/* 设置宽度,确保三个搜索框都能正常显示 */
|
||||||
|
font-size: 1rem;
|
||||||
|
/* 调大左侧头部字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.header_right {
|
.header_right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px; /* 添加间距 */
|
gap: 10px;
|
||||||
font-size: 1rem; /* 调大右侧头部字体大小 */
|
/* 添加间距 */
|
||||||
|
font-size: 1rem;
|
||||||
|
/* 调大右侧头部字体大小 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.inspection-table {
|
.inspection-table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 90%; /* 修改为90%,使其高度铺满剩余部分 */
|
height: 90%;
|
||||||
overflow-x: auto; /* 添加水平滚动条 */
|
/* 修改为90%,使其高度铺满剩余部分 */
|
||||||
|
overflow-x: auto;
|
||||||
|
/* 添加水平滚动条 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-container {
|
.table-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 100%; /* 确保表格宽度至少为容器宽度 */
|
min-width: 100%;
|
||||||
overflow-x: auto; /* 添加水平滚动条 */
|
overflow-x: auto;
|
||||||
|
/* 添加水平滚动条 */
|
||||||
|
position: relative; /* 添加此行以确保表头和数据在同一容器内滚动 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row {
|
.table-row, .table-row-1 {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
/* padding: 10px;
|
align-items: center;
|
||||||
border-bottom: 1px solid #ccc; */
|
flex: 1;
|
||||||
flex: 1; /* 使每一行铺满 */
|
min-width: max-content; /* 添加此行以确保表格内容不会被压缩 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row-1 {
|
.table-row-1 {
|
||||||
|
@ -272,44 +544,105 @@ onMounted(() => {
|
||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
padding: 0 0 0 0.5rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: inherit; /* 添加此行以确保表头背景色一致 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-width {
|
.fixed-width {
|
||||||
width: 7rem; /* 设置固定宽度 */
|
width: 6.5rem;
|
||||||
text-align: center; /* 居中对齐 */
|
/* 设置固定宽度 */
|
||||||
font-size: 1rem; /* 调大固定宽度元素字体大小 */
|
height: 2.5rem;
|
||||||
|
text-align: center;
|
||||||
|
/* 居中对齐 */
|
||||||
|
font-size: 1rem;
|
||||||
|
/* 调大固定宽度元素字体大小 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-row {
|
.header-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 7rem;
|
width: 6.5rem;
|
||||||
height: 100%;
|
height: 2.5rem;
|
||||||
background-color: #23233C;
|
background-color: #23233C;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 1rem; /* 调大表头行字体大小 */
|
font-size: 1rem;
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-width {
|
.title-width {
|
||||||
width: 10rem;
|
width: 15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
ix-button {
|
.btn-style {
|
||||||
margin: 0.25rem;
|
margin: 1.8rem 0.5rem 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testbg {
|
.testbg {
|
||||||
/* 这里是因为它tm组件里的,还用的好像是webcomponent,但是tmd不对外提供修改的东西,好在有个css颜色变量可以修改 */
|
/* 这里是因为它tm组件里的,还用的好像是webcomponent,但是tmd不对外提供修改的东西,好在有个css颜色变量可以修改 */
|
||||||
--theme-event-item--background:#23233C;
|
--theme-event-item--background: #23233C;
|
||||||
/* --theme-event-item--background--hover: blue; */
|
/* --theme-event-item--background--hover: blue; */
|
||||||
|
/* --theme-color-success */
|
||||||
}
|
}
|
||||||
.testbg1{
|
|
||||||
--theme-event-item--background:#000028;
|
.testbg1 {
|
||||||
|
--theme-event-item--background: #000028;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**如果要改按钮的颜色,那么上面就不要用 :style 改成用 :class 来设置,这个总没问题吧,然后再f12看按钮有哪些颜色变量 */
|
/**如果要改按钮的颜色,那么上面就不要用 :style 改成用 :class 来设置,这个总没问题吧,然后再f12看按钮有哪些颜色变量 */
|
||||||
/* 定义按钮颜色的 CSS 类 */
|
/* 定义按钮颜色的 CSS 类 */
|
||||||
.custom-button {
|
.custom-button {
|
||||||
--theme-btn-primary--background: #FFA849;
|
--theme-btn-primary--background: #FFA849;
|
||||||
--theme-btn-primary--background--hover: #FFA849;
|
--theme-btn-primary--background--hover: #FFA849;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drop-down {
|
||||||
|
width: calc(25% - 1.25rem);
|
||||||
|
background-color: #23233C;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(ix-date-input) {
|
||||||
|
--theme-menu--background: #23233C;
|
||||||
|
padding-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-label {
|
||||||
|
margin-right: 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-spacing {
|
||||||
|
margin-right: 1rem;
|
||||||
|
--theme-color-2: #23233C;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fixed-row {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background-color: inherit;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-cell {
|
||||||
|
background-color: orange;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.alarm-cell {
|
||||||
|
background-color: red;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-cell-title {
|
||||||
|
--theme-color-success: orange;
|
||||||
|
}
|
||||||
|
.alarm-cell-title {
|
||||||
|
--theme-color-success: red;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,23 +1,40 @@
|
||||||
<template>
|
<template>
|
||||||
<IxApplication :appSwitchConfig="appSwitchConfig" theme="classic-dark">
|
<IxApplication :appSwitchConfig="appSwitchConfig" theme="classic-dark">
|
||||||
<IxApplicationHeader name="SIEMENS MIS 1.0 前处理界面" :applicationSwitchButton="true">
|
<IxApplicationHeader :name="appName" :applicationSwitchButton="true">
|
||||||
<!-- 添加实时显示时间 -->
|
<!-- 添加实时显示时间 -->
|
||||||
<div class="time-display">{{ currentTime }}</div>
|
<div v-if="appSwitchConfig.currentAppId === 'mis-app'" class="time-display">{{ currentTime }}</div>
|
||||||
|
|
||||||
<!-- 确保图标按钮正确显示 -->
|
<!-- 报警图标 -->
|
||||||
<IxIconButton icon="alarm-bell-filled" variant="secondary" ghost></IxIconButton>
|
<el-badge v-if="appSwitchConfig.currentAppId === 'mis-app'" :value="alarmCount" class="item">
|
||||||
<IxIconButton icon="calendar-filled" variant="secondary" ghost @click="toggleDatetimePicker"></IxIconButton>
|
<IxIconButton icon="alarm-bell-filled" variant="secondary" ghost></IxIconButton>
|
||||||
<IxIconButton icon="alarm-clock-filled" variant="secondary" ghost @click="handleReset"></IxIconButton>
|
</el-badge>
|
||||||
|
|
||||||
|
<!-- 时间选择器整合到日历图标按钮 -->
|
||||||
|
<IxIconButton v-if="appSwitchConfig.currentAppId === 'mis-app'" icon="calendar-filled" variant="secondary" ghost
|
||||||
|
@click="openDatePicker"></IxIconButton>
|
||||||
|
<!-- <el-date-picker v-model="selectedDatetime" type="datetime" format="YYYY-MM-DD HH:mm"
|
||||||
|
:disabled-minutes="disabledMinutes" @change="handleDatetimeChange" ref="datePickerRef"
|
||||||
|
popper-class="custom-picker-popover" style="display: none" :visible-change="visibleChange"
|
||||||
|
:popper-options="pickerOptions" :teleported="false" /> -->
|
||||||
|
<el-date-picker ref="datePickerRef" v-model="selectedDatetime" type="datetime" :clearable="false" v-bind="$attrs" format="YYYY-MM-DD HH:mm"
|
||||||
|
:disabled-minutes="disabledMinutes" popper-class="custom-picker-popover" :visible-change="visibleChange" style="display: none" @change="handleDatetimeChange" :popper-options="pickerOptions" :teleported="false"></el-date-picker>
|
||||||
|
|
||||||
|
<!--重置-->
|
||||||
|
<IxIconButton v-if="appSwitchConfig.currentAppId === 'mis-app'" icon="alarm-clock-filled" variant="secondary"
|
||||||
|
ghost @click="handleReset"></IxIconButton>
|
||||||
|
|
||||||
|
<!-- 添加遮罩层和日期选择器组件 -->
|
||||||
|
<!-- <div v-if="showDatetimePicker" class="overlay" @click="closeDatetimePicker"></div>
|
||||||
|
<div v-if="showDatetimePicker" class="datetime-picker-container">
|
||||||
|
<IxDatetimePicker v-if="appSwitchConfig.currentAppId === 'mis-app'" range="false" showSeconds="false" show-time-reference="true" @done="handleDatetimePickerDone" />
|
||||||
|
<IxDatePicker v-else range="false" @dateChange="handleDatePickerDone" style="--theme-menu--background: '#232323'"/>
|
||||||
|
<button class="close-button" @click="closeDatetimePicker">关闭</button>
|
||||||
|
</div> -->
|
||||||
</IxApplicationHeader>
|
</IxApplicationHeader>
|
||||||
|
|
||||||
<IxContent class="mainContent">
|
<IxContent class="mainContent">
|
||||||
<router-view @update-history="updateHistory" @reset="reset"></router-view>
|
<router-view @update-history="updateHistory" @reset="reset" @send-data="handleDataFromChild"></router-view>
|
||||||
</IxContent>
|
</IxContent>
|
||||||
|
|
||||||
<!-- 添加 IxDatetimePicker 组件 -->
|
|
||||||
<div v-if="showDatetimePicker" class="datetime-picker-container">
|
|
||||||
<IxDatetimePicker range="false" showSeconds="false" show-time-reference="true" @done="handleDatetimePickerDone" />
|
|
||||||
</div>
|
|
||||||
</IxApplication>
|
</IxApplication>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -31,10 +48,15 @@ import {
|
||||||
IxDropdownItem,
|
IxDropdownItem,
|
||||||
IxIconButton,
|
IxIconButton,
|
||||||
IxDatetimePicker,
|
IxDatetimePicker,
|
||||||
|
IxDatePicker,
|
||||||
} from '@siemens/ix-vue';
|
} from '@siemens/ix-vue';
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted, watch, computed, nextTick } from 'vue';
|
||||||
|
import { useAlarmStore } from '@/stores/alarmStore'; // 假设我们有一个 alarmStore
|
||||||
|
|
||||||
const appSwitchConfig = {
|
const alarmStore = useAlarmStore(); // 使用 alarmStore
|
||||||
|
const alarmCount = computed(() => alarmStore.alarmCount); // 从 alarmStore 中获取报警计数
|
||||||
|
|
||||||
|
let appSwitchConfig = {
|
||||||
i18nAppSwitch: '切换应用',
|
i18nAppSwitch: '切换应用',
|
||||||
i18nLoadingApps: '加载应用中...',
|
i18nLoadingApps: '加载应用中...',
|
||||||
currentAppId: 'mis-app',
|
currentAppId: 'mis-app',
|
||||||
|
@ -42,7 +64,7 @@ const appSwitchConfig = {
|
||||||
{
|
{
|
||||||
id: 'mis-app',
|
id: 'mis-app',
|
||||||
name: 'MIS 1.0',
|
name: 'MIS 1.0',
|
||||||
iconSrc: 'https://www.svgrepo.com/show/530661/genetic-data.svg',
|
iconSrc: require('@/assets/genetic-data.svg'),
|
||||||
url: '/',
|
url: '/',
|
||||||
description: '前处理系统',
|
description: '前处理系统',
|
||||||
target: '_self',
|
target: '_self',
|
||||||
|
@ -50,17 +72,51 @@ const appSwitchConfig = {
|
||||||
{
|
{
|
||||||
id: 'other-app',
|
id: 'other-app',
|
||||||
name: '点检系统',
|
name: '点检系统',
|
||||||
iconSrc: 'https://www.svgrepo.com/show/530661/genetic-data.svg',
|
iconSrc: require('@/assets/genetic-data.svg'),
|
||||||
url: '/#/inspection',
|
url: '/#/inspection',
|
||||||
description: '其他系统描述',
|
description: '其他系统描述',
|
||||||
target: '_blank',
|
target: '_self',
|
||||||
|
// target: '_blank',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 弹窗定位配置
|
||||||
|
const pickerOptions = {
|
||||||
|
placement: 'bottom-end',
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: 'offset',
|
||||||
|
options: { offset: [0, 0] } // 调整偏移校准
|
||||||
|
},
|
||||||
|
{ name: 'computeStyles', options: { gpuAcceleration: false } } // 禁用GPU加速定位
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeShortcuts = [
|
||||||
|
{
|
||||||
|
text: '最近15分钟',
|
||||||
|
value: () => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setMinutes(Math.floor(date.getMinutes() / 15) * 15);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '整点时间',
|
||||||
|
value: () => {
|
||||||
|
const date = new Date();
|
||||||
|
date.setMinutes(0);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const currentTime = ref('');
|
const currentTime = ref('');
|
||||||
const showDatetimePicker = ref(false);
|
const showDatetimePicker = ref(false);
|
||||||
const selectedDatetime = ref('');
|
const selectedDatetime = ref('');
|
||||||
|
const isAutoUpdate = ref(true);
|
||||||
|
const appName = ref('SIEMENS MIS 1.0 前处理界面');
|
||||||
|
|
||||||
const updateTime = () => {
|
const updateTime = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
@ -72,55 +128,157 @@ const updateTime = () => {
|
||||||
currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}`;
|
currentTime.value = `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleDatetimePicker = () => {
|
// const toggleDatetimePicker = () => {
|
||||||
showDatetimePicker.value = !showDatetimePicker.value;
|
// showDatetimePicker.value = !showDatetimePicker.value;
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
// 触发主页中的 getHistory 方法
|
// 触发主页中的 getHistory 方法
|
||||||
const event = new CustomEvent('reset');
|
const event = new CustomEvent('reset');
|
||||||
|
updateTime();
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
isAutoUpdate.value = true; // 重置后恢复自动刷新
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDatetimePickerDone = (event) => {
|
// const handleDatetimePickerDone = (event) => {
|
||||||
selectedDatetime.value = event.detail;
|
// selectedDatetime.value = event.detail;
|
||||||
showDatetimePicker.value = false;
|
// showDatetimePicker.value = false;
|
||||||
// 将所选数据传到主页
|
// updateHistoryWithTime(selectedDatetime.value);
|
||||||
console.log('Selected Datetime:', selectedDatetime.value);
|
// isAutoUpdate.value = false; // 选择完历史时间后停止自动刷新
|
||||||
updateHistory(selectedDatetime.value);
|
// };
|
||||||
|
|
||||||
|
// const handleDatePickerDone = (event) => {
|
||||||
|
// selectedDatetime.value = event.detail;
|
||||||
|
// showDatetimePicker.value = false;
|
||||||
|
// updateHistoryWithDate(selectedDatetime.value);
|
||||||
|
// isAutoUpdate.value = false; // 选择完历史时间后停止自动刷新
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const updateHistoryWithTime = (datetime) => {
|
||||||
|
// // 将 datetime 转换为标准的 YYYY-MM-DD HH:mm:ss 格式
|
||||||
|
// const parsedDate = new Date(datetime.replace(/-/g, '/'));
|
||||||
|
// if (isNaN(parsedDate.getTime())) {
|
||||||
|
// console.error('Invalid datetime format:', datetime);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const year = parsedDate.getFullYear();
|
||||||
|
// const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
|
||||||
|
// const day = String(parsedDate.getDate()).padStart(2, '0');
|
||||||
|
// const hours = String(parsedDate.getHours()).padStart(2, '0');
|
||||||
|
// const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
||||||
|
// const seconds = String(parsedDate.getSeconds()).padStart(2, '0');
|
||||||
|
// const formattedDatetime = `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||||
|
// // 触发主页中的 getHistory 方法
|
||||||
|
// const event = new CustomEvent('update-history', { detail: formattedDatetime });
|
||||||
|
|
||||||
|
// currentTime.value = formattedDatetime;
|
||||||
|
|
||||||
|
// window.dispatchEvent(event);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const updateHistoryWithDate = (date) => {
|
||||||
|
// // 将 date 转换为标准的 YYYY-MM-DD 格式
|
||||||
|
// const { from } = date;
|
||||||
|
// const parsedDate = new Date(from);
|
||||||
|
// if (isNaN(parsedDate.getTime())) {
|
||||||
|
// console.error('Invalid date format:', from);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const year = parsedDate.getFullYear();
|
||||||
|
// const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
|
||||||
|
// const day = String(parsedDate.getDate()).padStart(2, '0');
|
||||||
|
// const formattedDate = `${year}-${month}-${day}`;
|
||||||
|
// // 触发主页中的 getHistory 方法
|
||||||
|
// const event = new CustomEvent('update-history', { detail: formattedDate });
|
||||||
|
|
||||||
|
// currentTime.value = formattedDate;
|
||||||
|
|
||||||
|
// window.dispatchEvent(event);
|
||||||
|
// };
|
||||||
|
|
||||||
|
const handleDataFromChild = (data, name) => {
|
||||||
|
appName.value = data;
|
||||||
|
appSwitchConfig.currentAppId = name;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateHistory = (datetime) => {
|
// const closeDatetimePicker = () => {
|
||||||
// 将 datetime 转换为标准的 YYYY-MM-DD HH:mm:ss 格式
|
// showDatetimePicker.value = false;
|
||||||
const parsedDate = new Date(datetime.replace(/-/g, '/'));
|
// };
|
||||||
if (isNaN(parsedDate.getTime())) {
|
|
||||||
console.error('Invalid datetime format:', datetime);
|
// 新增日期选择器相关逻辑
|
||||||
return;
|
const datePickerRef = ref(null);
|
||||||
|
// const selectedDatetime = ref('');
|
||||||
|
|
||||||
|
// 15分钟步长控制
|
||||||
|
const disabledMinutes = () => {
|
||||||
|
hideInvalidMinutes();
|
||||||
|
return Array.from({ length: 60 }, (v, k) => k).filter(minute => ![0, 15, 30, 45].includes(minute));
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideInvalidMinutes = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
const minuteItems = document.querySelectorAll('.el-time-panel .el-time-spinner__wrapper:nth-child(2n) .el-time-spinner__item');
|
||||||
|
minuteItems.forEach(item => {
|
||||||
|
const minute = parseInt(item.textContent, 10);
|
||||||
|
if (![0, 15, 30, 45].includes(minute)) {
|
||||||
|
item.classList.add('is-disabled');
|
||||||
|
item.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
item.classList.remove('is-disabled');
|
||||||
|
item.style.display = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleGlobalFocus = (event) => {
|
||||||
|
const target = event.target;
|
||||||
|
if (target && target.closest('.el-date-range-picker__time-picker-wrap:nth-child(2n) .el-input__inner')) {
|
||||||
|
hideInvalidMinutes();
|
||||||
}
|
}
|
||||||
const year = parsedDate.getFullYear();
|
};
|
||||||
const month = String(parsedDate.getMonth() + 1).padStart(2, '0');
|
|
||||||
const day = String(parsedDate.getDate()).padStart(2, '0');
|
const visibleChange = () => {
|
||||||
const hours = String(parsedDate.getHours()).padStart(2, '0');
|
document.addEventListener('focus', handleGlobalFocus, true);
|
||||||
const minutes = String(parsedDate.getMinutes()).padStart(2, '0');
|
}
|
||||||
const seconds = String(parsedDate.getSeconds()).padStart(2, '0');
|
|
||||||
const formattedDatetime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
// 点击日历图标触发选择器
|
||||||
// 触发主页中的 getHistory 方法
|
const openDatePicker = () => {
|
||||||
console.log("🚀 ~ updateHistory ~ formattedDatetime:", formattedDatetime)
|
datePickerRef.value?.handleOpen();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDatetimeChange = (value) => {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
// 直接使用 Element Plus 返回的 Date 对象
|
||||||
|
const formattedDatetime = `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, '0')}-${String(value.getDate()).padStart(2, '0')} ${String(value.getHours()).padStart(2, '0')}:${String(value.getMinutes()).padStart(2, '0')}`;
|
||||||
|
|
||||||
|
// 更新当前时间显示
|
||||||
|
currentTime.value = formattedDatetime;
|
||||||
|
|
||||||
|
// 触发历史更新
|
||||||
const event = new CustomEvent('update-history', { detail: formattedDatetime });
|
const event = new CustomEvent('update-history', { detail: formattedDatetime });
|
||||||
window.dispatchEvent(event);
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
// 停止自动刷新
|
||||||
|
isAutoUpdate.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
updateTime();
|
updateTime();
|
||||||
const interval = setInterval(updateTime, 60000); // 每分钟更新一次
|
const interval = setInterval(() => {
|
||||||
onUnmounted(() => clearInterval(interval));
|
if (showDatetimePicker.value || !isAutoUpdate.value) return; // 如果显示日期时间选择器或停止自动刷新,则不更新时间
|
||||||
|
updateTime();
|
||||||
|
}, 1000); // 每分钟更新一次
|
||||||
|
onUnmounted(() => clearInterval(interval)); // 监听应用切换事件
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
ix-application {
|
ix-application {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100vh;
|
/* height: 100vh; */
|
||||||
/* 设置应用高度为视口高度 */
|
/* 设置应用高度为视口高度 */
|
||||||
background-color: #000028;
|
background-color: #000028;
|
||||||
font-family: 'Microsoft YaHei', sans-serif;
|
font-family: 'Microsoft YaHei', sans-serif;
|
||||||
|
@ -204,14 +362,39 @@ ix-application {
|
||||||
/* 让内容区域填充剩余空间 */
|
/* 让内容区域填充剩余空间 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
|
||||||
.datetime-picker-container {
|
.datetime-picker-container {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 4rem;
|
top: 1rem;
|
||||||
/* 确保在头部下方 */
|
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
/* 靠右对齐 */
|
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
/* 确保在最上层 */
|
background: #23233C;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
margin-top: 1rem;
|
||||||
|
background: #23233C;
|
||||||
|
color: skyblue;
|
||||||
|
/* border: none; */
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(ix-datetime-picker) {
|
||||||
|
--theme-menu--background: #23233C;
|
||||||
|
border: 1px solid #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(IxApplication) {
|
:deep(IxApplication) {
|
||||||
|
@ -222,17 +405,81 @@ ix-application {
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainContent {
|
.mainContent {
|
||||||
padding: 0;
|
padding: 0 !important;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #000028;
|
background: #000028;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.visible) {
|
:global(.visible) {
|
||||||
--theme-modal--background:#23233C !important;
|
--theme-modal--background: #23233C !important;
|
||||||
|
/* --theme-color-ghost--selected:#23233C !重要; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alarm-count {
|
||||||
|
color: red;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 工业级时间选择器改造 */
|
||||||
|
:deep(.custom-picker-popover) {
|
||||||
|
--el-bg-color-overlay: #23233C;
|
||||||
|
inset: unset !important;
|
||||||
|
top: 2rem !important;
|
||||||
|
right: 5rem !important;
|
||||||
|
border-radius: 2px !important;
|
||||||
|
--el-border-color: #67C23A;
|
||||||
|
.el-time-panel__content:after,
|
||||||
|
.el-time-panel__content:before {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-spinner__list:after,
|
||||||
|
.el-time-spinner__list:before {
|
||||||
|
content: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-time-spinner__item.is-active:not(.is-disabled) {
|
||||||
|
background-color: var(--el-datepicker-active-color);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-picker-panel) {
|
||||||
|
color: #fff;
|
||||||
|
background: #23233C;
|
||||||
|
border: 1px solid #1384b4;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-picker-panel__icon-btn) {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-picker__header-label) {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-date-table th) {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-time-panel) {
|
||||||
|
color: #fff;
|
||||||
|
background: #23233C;
|
||||||
|
border: 1px solid #1384b4;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-time-spinner__item) {
|
||||||
|
color: #ffffff;
|
||||||
|
--el-text-color-primary: #1384b4;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [vue()],
|
||||||
|
css: {
|
||||||
|
preprocessorOptions: {
|
||||||
|
scss: {
|
||||||
|
// 保留 SCSS 配置(如果使用了自定义主题)
|
||||||
|
additionalData: `@use "element-plus/theme-chalk/src/index.scss" as *;`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue