e4897e8a61146a1e7a5418373e31375a16366c46..fd0e3fd03817d6610c52b09fe27cccf0d3f96438
2025-08-12 yaowanxin
Merge branch 'dev_ai' of http://114.132.189.42:9002/r/pro...
ywx
fd0e3f 对比 | 目录
2025-08-12 yaowanxin
Merge remote-tracking branch 'origin/dev_ai' into dev_ai
c10006 对比 | 目录
2025-08-12 yaowanxin
Merge remote-tracking branch 'origin/dev_ai' into ywx
7d20c5 对比 | 目录
2025-08-12 yaowanxin
Merge remote-tracking branch 'origin/dev_ai' into ywx
a0dd2b 对比 | 目录
2025-08-12 gaoluyang
瓜州县弘也水泥有限责任公司部署
5a8cc2 对比 | 目录
2025-08-12 gaoluyang
瓜州县弘也水泥有限责任公司部署
742d62 对比 | 目录
2025-08-12 maven
yys 协同审批增加附件上传,预览
33b66b 对比 | 目录
2025-08-11 maven
yys 联调用水管理
ac6213 对比 | 目录
2025-08-11 gaoluyang
1.添加用水管理页面
386375 对比 | 目录
2025-08-08 chenhj
Merge branch 'dev_ai' of http://114.132.189.42:9002/r/product-inventory-man...
1f29e5 对比 | 目录
2025-08-08 chenhj
质量管理原材料检验修改。
ab6f48 对比 | 目录
2025-08-08 maven
yys 优化ai模块
cae930 对比 | 目录
2025-08-08 gaoluyang
1.协同审批-提交时添加签名
f8da71 对比 | 目录
2025-08-08 maven
Merge remote-tracking branch 'origin/dev_ai' into dev_ai
f13984 对比 | 目录
2025-08-08 maven
yys 优化ai模块
db100d 对比 | 目录
2025-08-08 gaoluyang
1.协同审批-提交时添加签名
d10439 对比 | 目录
2025-08-08 gaoluyang
1.薪资管理页面联调
b17cf5 对比 | 目录
2025-08-08 gaoluyang
1.薪资管理页面联调
92224d 对比 | 目录
2025-08-08 gaoluyang
Merge remote-tracking branch 'origin/dev_ai' into dev_ai
4928db 对比 | 目录
2025-08-08 gaoluyang
1.协同审批-提交审核是添加电子签名 2.添加薪资管理页面
d6863f 对比 | 目录
2025-08-08 spring
Merge remote-tracking branch 'origin/dev_7004' into dev_ai
9cc769 对比 | 目录
2025-08-07 gaoluyang
设备台账-生成二维码
c6c16e 对比 | 目录
2025-08-07 maven
yys 新增ai模块
fbae37 对比 | 目录
已修改19个文件
已添加66个文件
5476 ■■■■■ 文件已修改
.env.development 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.production 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.staging 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
index.html 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
package.json 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
public/HYSNico.ico 补丁 | 查看 | 原始文档 | blame | 历史
src/api/energyManagement/waterManagement.js 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/inventoryManagement/stockWarning.js 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/personnelManagement/payrollManagement.js 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/metricMaintenance.js 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/qualityManagement/rawMaterialInspection.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/clown-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/face-screaming-in-fear.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/face-vomiting.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/face-with-tongue.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/face-without-mouth.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/ghost.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/hibiscus.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/jack-o-lantern.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/lips.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/loudly-crying-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/money-bag.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/money-mouth-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/new-moon-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/ok-hand-yellow.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/pile-of-poo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/pouting-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/rainbow.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/rocket.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/shamrock.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/shangchuan.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/slightly-smiling-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/smiling-face-with-heart-eyes.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/smiling-face-with-horns.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/smiling-face-with-sunglasses.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/smiling-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/sparkles.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/star.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/thinking-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/thought-balloon.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/thumbs-up-yellow.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/tired-face.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/two-hearts.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/victory-hand-yellow.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/emoji/取消.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/excel.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/pdf.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/ppt.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/txt.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/unknowfile.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/word.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/fileImg/zpi.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/head_portrait.jpg 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/head_portrait1.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/img/logo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/HYSNLogo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/indexViews/HYSNView.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/styles/element-ui.scss 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/FileCard.vue 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/settings.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/utils/util.js 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/chatHome/chatHomeIndex/MobileChat.vue 461 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/chatHome/chatHomeIndex/ai-jz.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/chatHome/chatHomeIndex/ai-wd.js 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/chatHome/chatHomeIndex/home.vue 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/fileList.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/approvalProcess/index.vue 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/formDia.vue 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/components/waterBillForm.vue 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/index.vue 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/waterBill.vue 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/energyManagement/waterManagement/waterTrends.vue 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/gasTank/simple.vue 566 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/Form.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/equipmentManagement/ledger/index.vue 73 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/inventoryManagement/stockWarning/index.vue 1137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/login.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/components/formDia.vue 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/payrollManagement/index.vue 291 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue 137 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qualityManagement/rawMaterialInspection/index.vue 157 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
vite.config.js 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.env.development
@@ -1,8 +1,8 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = MIS系统(管理信息系统)
VITE_APP_TITLE = å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ
# å¼€å‘环境配置
VITE_APP_ENV = 'development'
# MIS系统(管理信息系统)/开发环境
# å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = MIS系统(管理信息系统)
VITE_APP_TITLE = å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'production'
# MIS系统(管理信息系统)/生产环境
# å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ/生产环境
VITE_APP_BASE_API = '/prod-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
.env.staging
@@ -1,10 +1,10 @@
# é¡µé¢æ ‡é¢˜
VITE_APP_TITLE = MIS系统(管理信息系统)
VITE_APP_TITLE = å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ
# ç”Ÿäº§çŽ¯å¢ƒé…ç½®
VITE_APP_ENV = 'staging'
# MIS系统(管理信息系统)/生产环境
# å¼˜ä¹Ÿæ°´æ³¥ç®¡ç†ç³»ç»Ÿ/生产环境
VITE_APP_BASE_API = '/stage-api'
# æ˜¯å¦åœ¨æ‰“包时开启压缩,支持 gzip å’Œ brotli
index.html
@@ -6,8 +6,8 @@
  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  <meta name="renderer" content="webkit">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/favicon.ico">
  <title>MIS系统(管理信息系统)</title>
  <link rel="icon" href="/HYSNico.ico">
  <title>弘也水泥管理系统</title>
  <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
  <style>
    html,
package.json
@@ -1,7 +1,7 @@
{
  "name": "ruoyi",
  "version": "3.8.9",
  "description": "MIS系统(管理信息系统)",
  "description": "弘也水泥管理系统",
  "author": "若依",
  "license": "MIT",
  "type": "module",
@@ -33,10 +33,12 @@
    "jsencrypt": "3.3.2",
    "nprogress": "0.2.0",
    "pinia": "2.1.7",
    "qrcode": "^1.5.4",
    "sortablejs": "^1.15.6",
    "splitpanes": "3.1.5",
    "vue": "3.4.31",
    "vue-cropper": "1.1.1",
    "vue-esign": "^1.1.4",
    "vue-router": "4.4.0",
    "vuedraggable": "4.1.0"
  },
public/HYSNico.ico
src/api/energyManagement/waterManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
// ç”¨æ°´ç®¡ç†
import request from "@/utils/request";
// ç”¨æ°´è®¾å¤‡-分页查询
export function waterEquipmentListPage(query) {
  return request({
    url: '/waterRecord/listPage',
    method: 'get',
    params: query,
  })
}
// ç”¨æ°´è¶‹åŠ¿-分页查询
export function listPageByWaterTrend(query) {
  return request({
    url: '/waterRecord/listPageByTrend',
    method: 'get',
    params: query,
  })
}
// ç”¨æ°´è®¾å¤‡-删除
export function waterEquipmentDelete(query) {
  return request({
    url: '/waterRecord/delete',
    method: 'delete',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡-新增
export function waterEquipmentAdd(query) {
  return request({
    url: '/waterRecord/add',
    method: 'post',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡-修改
export function waterEquipmentUpdate(query) {
  return request({
    url: '/waterRecord/update',
    method: 'post',
    data: query,
  })
}
// ç”¨æ°´è®¾å¤‡ä¸‹æ‹‰æ¡†æŸ¥è¯¢
export function waterDeviceList(query) {
  return request({
    url: '/device/ledger/page',
    method: 'get',
    params: query,
  })
}
// æ°´è´¹ç®¡ç†-分页查询
export function waterBillListPage(query) {
  return request({
    url: '/waterBill/listPage',
    method: 'get',
    params: query,
  })
}
// æ°´è´¹ç®¡ç†-新增
export function waterBillAdd(query) {
  return request({
    url: '/waterBill/add',
    method: 'post',
    data: query,
  })
}
// æ°´è´¹ç®¡ç†-修改
export function waterBillUpdate(query) {
  return request({
    url: '/waterBill/update',
    method: 'post',
    data: query,
  })
}
// æ°´è´¹ç®¡ç†-删除
export function waterBillDelete(query) {
  return request({
    url: '/waterBill/delete',
    method: 'delete',
    data: query,
  })
}
src/api/inventoryManagement/stockWarning.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
import request from "@/utils/request";
// æŸ¥è¯¢å‚¨æ°”罐预警列表
export const getStockWarningPage = (params) => {
    return request({
        url: "/gasTankWarning/listPage",
        method: "get",
        params,
    });
};
// æ–°å¢žå‚¨æ°”罐预警规则
export const addStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/add",
        method: "post",
        data,
    });
};
// ä¿®æ”¹å‚¨æ°”罐预警规则
export const updateStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/update",
        method: "put",
        data,
    });
};
// åˆ é™¤å‚¨æ°”罐预警规则
export const deleteStockWarning = (ids) => {
    return request({
        url: "/gasTankWarning/delete",
        method: "delete",
        data: { ids },
    });
};
// æ‰¹é‡å¤„理储气罐预警
export const batchProcessStockWarning = (data) => {
    return request({
        url: "/gasTankWarning/batchProcess",
        method: "post",
        data,
    });
};
// å¯¼å‡ºå‚¨æ°”罐预警数据
export const exportStockWarning = (params) => {
    return request({
        url: "/gasTankWarning/export",
        method: "get",
        params,
        responseType: "blob",
    });
};
// æ ¹æ®ID获取储气罐预警详情
export const getStockWarningById = (id) => {
    return request({
        url: `/gasTankWarning/${id}`,
        method: "get",
    });
};
// å¯ç”¨/禁用预警规则
export const toggleStockWarningStatus = (data) => {
    return request({
        url: "/gasTankWarning/toggleStatus",
        method: "put",
        data,
    });
};
// èŽ·å–é¢„è­¦ç»Ÿè®¡ä¿¡æ¯
export const getStockWarningStatistics = () => {
    return request({
        url: "/gasTankWarning/statistics",
        method: "get",
    });
};
src/api/personnelManagement/payrollManagement.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
// è–ªé…¬ç®¡ç†
import request from "@/utils/request";
// æŸ¥è¯¢åˆ—表
export function compensationListPage(query) {
  return request({
    url: "/compensationPerformance/listPage",
    method: "get",
    params: query,
  });
}
// æ–°å¢ž
export function compensationAdd(query) {
  return request({
    url: "/compensationPerformance/add",
    method: "post",
    data: query,
  });
}
// ä¿®æ”¹
export function compensationUpdate(query) {
  return request({
    url: "/compensationPerformance/update",
    method: "post",
    data: query,
  });
}
// åˆ é™¤
export function compensationDelete(query) {
  return request({
    url: "/compensationPerformance/delete",
    method: "delete",
    data: query,
  });
}
src/api/qualityManagement/metricMaintenance.js
@@ -8,6 +8,7 @@
        params: query,
    })
}
// æ–°å¢žæŒ‡æ ‡åˆ—表
export function qualityTestStandardAdd(query) {
    return request({
@@ -16,6 +17,7 @@
        data: query,
    })
}
// ä¿®æ”¹æŒ‡æ ‡åˆ—表
export function qualityTestStandardUpdate(query) {
    return request({
@@ -24,6 +26,7 @@
        data: query,
    })
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityTestStandardDel(query) {
    return request({
@@ -31,4 +34,12 @@
        method: 'delete',
        data: query,
    })
}
// åˆ é™¤æŒ‡æ ‡åˆ—表
export function qualityInspectDetailByProductId(productId) {
    return request({
        url: '/quality/qualityTestStandard/product/' + productId,
        method: 'get',
    })
}
src/api/qualityManagement/rawMaterialInspection.js
@@ -8,6 +8,7 @@
        params: query,
    })
}
// æ–°å¢žåŽŸææ–™æ£€éªŒ
export function qualityInspectAdd(query) {
    return request({
@@ -16,6 +17,7 @@
        data: query,
    })
}
// ä¿®æ”¹åŽŸææ–™æ£€éªŒ
export function qualityInspectUpdate(query) {
    return request({
@@ -24,6 +26,7 @@
        data: query,
    })
}
// åˆ é™¤åŽŸææ–™æ£€éªŒ
export function qualityInspectDel(query) {
    return request({
@@ -32,3 +35,23 @@
        data: query,
    })
}
// æäº¤åŽŸææ–™æ£€éªŒ
export function submitQualityInspect(data) {
    return request({
        url: '/quality/qualityInspect/submit',
        method: 'post',
        data: data,
    })
}
// æäº¤åŽŸææ–™æ£€éªŒ
export function downloadQualityInspect(data) {
    return request({
        url: '/quality/qualityInspect/down',
        method: 'post',
        data: data,
        responseType: "blob",
    })
}
src/assets/img/emoji/clown-face.png
src/assets/img/emoji/face-screaming-in-fear.png
src/assets/img/emoji/face-vomiting.png
src/assets/img/emoji/face-with-tongue.png
src/assets/img/emoji/face-without-mouth.png
src/assets/img/emoji/ghost.png
src/assets/img/emoji/hibiscus.png
src/assets/img/emoji/jack-o-lantern.png
src/assets/img/emoji/lips.png
src/assets/img/emoji/loudly-crying-face.png
src/assets/img/emoji/money-bag.png
src/assets/img/emoji/money-mouth-face.png
src/assets/img/emoji/new-moon-face.png
src/assets/img/emoji/ok-hand-yellow.png
src/assets/img/emoji/pile-of-poo.png
src/assets/img/emoji/pouting-face.png
src/assets/img/emoji/rainbow.png
src/assets/img/emoji/rocket.png
src/assets/img/emoji/shamrock.png
src/assets/img/emoji/shangchuan.png
src/assets/img/emoji/slightly-smiling-face.png
src/assets/img/emoji/smiling-face-with-heart-eyes.png
src/assets/img/emoji/smiling-face-with-horns.png
src/assets/img/emoji/smiling-face-with-sunglasses.png
src/assets/img/emoji/smiling-face.png
src/assets/img/emoji/sparkles.png
src/assets/img/emoji/star.png
src/assets/img/emoji/thinking-face.png
src/assets/img/emoji/thought-balloon.png
src/assets/img/emoji/thumbs-up-yellow.png
src/assets/img/emoji/tired-face.png
src/assets/img/emoji/two-hearts.png
src/assets/img/emoji/victory-hand-yellow.png
src/assets/img/emoji/È¡Ïû.png
src/assets/img/fileImg/excel.png
src/assets/img/fileImg/pdf.png
src/assets/img/fileImg/ppt.png
src/assets/img/fileImg/txt.png
src/assets/img/fileImg/unknowfile.png
src/assets/img/fileImg/word.png
src/assets/img/fileImg/zpi.png
src/assets/img/head_portrait.jpg
src/assets/img/head_portrait1.png
src/assets/img/logo.png
src/assets/indexViews/HYSNLogo.png
src/assets/indexViews/HYSNView.png
src/assets/styles/element-ui.scss
@@ -67,7 +67,7 @@
}
.el-dialog__body {
  padding: 16px 40px 0 40px;
  max-height: 90vh;
  max-height: 74vh;
  overflow-y: auto;
}
.el-dialog__footer {
src/components/FileCard.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,81 @@
<template>
  <div class="file-card">
    <img src="@/assets/img/fileImg/unknowfile.png" alt="" v-if="fileType == 0"/>
    <img src="@/assets/img/fileImg/word.png" alt="" v-else-if="fileType == 1"/>
    <img src="@/assets/img/fileImg/excel.png" alt="" v-else-if="fileType == 2"/>
    <img src="@/assets/img/fileImg/ppt.png" alt="" v-else-if="fileType == 3"/>
    <img src="@/assets/img/fileImg/pdf.png" alt="" v-else-if="fileType == 4"/>
    <img src="@/assets/img/fileImg/zpi.png" alt="" v-else-if="fileType == 5"/>
    <img src="@/assets/img/fileImg/txt.png" alt="" v-else/>
    <div class="word">
      <span
        >{{file.name || '未知'}}</span
      >
      <span>154kb</span>
    </div>
  </div>
</template>
<script>
export default {
  // props: ["fileType", "file"],
  props: {
    fileType: Number,
    file: File,
    default() {
      return {};
    },
  },
  watch: {
    file() {
      console.log(this.file);
    },
  },
  mounted() {
    console.log(this.file);
    console.log(this.fileType);
  }
};
</script>
<style lang="scss" scoped>
.file-card {
  width: 250px;
  height: 100px;
  background-color: rgb(45, 48, 63);
  border-radius: 20px;
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 10px;
  box-sizing: border-box;
  cursor: pointer;
  &:hover {
    background-color: rgb(33, 36, 54);
  }
  img {
    width: 60px;
    height: 60px;
  }
  .word {
    width: 60%;
    margin-left: 10px;
    overflow: hidden;
    span {
      width: 90%;
      display: inline-block;
      color: #fff;
    }
    span:first-child {
      font-size: 14px;
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
    span:last-child {
      font-size: 12px;
      color: rgb(180, 180, 180);
    }
  }
}
</style>
src/router/index.js
@@ -57,6 +57,7 @@
    component: () => import('@/views/error/401'),
    hidden: true
  },
  {
    path: '',
    component: Layout,
@@ -71,6 +72,20 @@
    ]
  },
  {
    path: '/main/MobileChat',
    component: Layout,
    redirect: '',
    hidden: true,
    children: [
      {
        path: '',
        component: () => import('@/views/chatHome/chatHomeIndex/MobileChat'),
        name: 'MobileChat',
        meta: { title: 'AI对话', icon: 'dashboard', affix: true}
      }
    ]
  },
  {
    path: '/user',
    component: Layout,
    hidden: true,
src/settings.js
@@ -30,7 +30,7 @@
  /**
   * æ˜¯å¦æ˜¾ç¤ºlogo
   */
  sidebarLogo: true,
  sidebarLogo: false,
  /**
   * æ˜¯å¦æ˜¾ç¤ºåŠ¨æ€æ ‡é¢˜
src/utils/util.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,92 @@
//防抖
export  function debounce(fn) {
    console.log(1)
    let t = null //只会执行一次
    debugger
    return function (){
        if(t){
            clearTimeout(t)
        }
        t = setTimeout(()=>{
            console.log(temp);  //可以获取
            // console.log(arguments[0]) //undefined
            fn.apply(this,arguments)
            //在这个回调函数里面的argument是这个回调函数的参数,因为没有参数所以undefined,可以通过外面的函数赋值来进行访问
            //也可以改变成箭头函数,箭头函数的this是指向定义函数的那一层的,所以访问到的arguments是上一层函数的arguments
        },1000)
    }
}
//节流
export function throttle(fn, delay = 200) {
    let timer = null
    console.log(fn);
    debugger
    return function () {
        if(timer) return
        timer = setTimeout(() => {
            debugger
          fn.apply(this,arguments)
          timer = null
        })
    }
 }
//下拉动画
 export function animation(obj, target, fn1) {
    // console.log(fn1);
    // fn是一个回调函数,在定时器结束的时候添加
    // æ¯æ¬¡å¼€å®šæ—¶å™¨ä¹‹å‰å…ˆæ¸…除掉定时器
    clearInterval(obj.timer);
    obj.timer = setInterval(function () {
      // æ­¥é•¿è®¡ç®—公式  è¶Šæ¥è¶Šå°
      // æ­¥é•¿å–æ•´
      var step = (target - obj.scrollTop) / 10;
      step = step > 0 ? Math.ceil(step) : Math.floor(step);
      if (obj.scrollTop >= target) {
        clearInterval(obj.timer);
        // å¦‚æžœfn1存在,调用fn
        if (fn1) {
          fn1();
        }
      } else {
        // æ¯30毫秒就将新的值给obj.left
        obj.scrollTop = obj.scrollTop + step;
      }
    }, 10);
  }
  //判断文件类型
  export function judgeFileType(file) {
    if (file == null||file == ""){
         alert("请选择要上传的图片!");
         return false;
    }
    if (file.lastIndexOf('.')==-1){    //如果不存在"."
        alert("路径不正确!");
        return false;
    }
    var AllImgExt=".jpg|.jpeg|.gif|.bmp|.png|";
    var extName = file.substring(file.lastIndexOf(".")).toLowerCase();//(把路径中的所有字母全部转换为小写)
    if(AllImgExt.indexOf(extName+"|")==-1)
    {
        ErrMsg="该文件类型不允许上传。请上传 "+AllImgExt+" ç±»åž‹çš„æ–‡ä»¶ï¼Œå½“前文件类型为"+extName;
        alert(ErrMsg);
        return false;
    }
  }
  //文件类型
  export function fileType() {
    return {
      'application/msword': 'word',
      'application/pdf': 'pdf',
      'application/vnd.ms-powerpoint': 'ppt',
      'application/vnd.ms-excel': 'excel',
      'aplication/zip': 'zpi',
    }
  }
  export function filterArr(arr) {
      return arr.filter(item => item.flag !== false);
  }
src/views/chatHome/chatHomeIndex/MobileChat.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,461 @@
<template>
  <div class="mobile-chat-wrapper" style="height: 91vh;">
    <div class="chat-history">
      <div class="chat-content" ref="chatContent">
        <div class="chat-wrapper" v-for="(item, index) in chatList" :key="index">
          <div class="chat-friend" v-if="item.uid !== '1001'">
            <div class="info-time">
              <img :src="item.headImg" alt="" />
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
            </div>
            <div class="chat-text" v-if="item.chatType == 0">
              <template v-if="isSend && index === chatList.length - 1">
                <span class="flash_cursor"></span>
              </template>
              <template v-else>
                <pre>{{ item.msg }}</pre>
              </template>
            </div>
            <div class="chat-img" v-if="item.chatType == 1">
              <img :src="item.msg" alt="表情" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" />
              <el-image :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image>
            </div>
            <div class="chat-img" v-if="item.chatType == 2">
              <div class="word-file">
                <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard>
              </div>
            </div>
          </div>
          <div class="chat-me" v-else>
            <div class="info-time">
              <span>{{ item.name }}</span>
              <span>{{ item.time }}</span>
              <img :src="item.headImg" alt="" />
            </div>
            <div class="chat-text" v-if="item.chatType == 0">
              {{ item.msg }}
            </div>
            <div class="chat-img" v-if="item.chatType == 1">
              <img :src="item.msg" alt="表情" v-if="item.extend.imgType == 1" style="width: 100px; height: 100px" />
              <el-image style="max-width: 300px; border-radius: 10px" :src="item.msg" :preview-src-list="srcImgList" v-else> </el-image>
            </div>
            <div class="chat-img" v-if="item.chatType == 2">
              <div class="word-file">
                <FileCard :fileType="item.extend.fileType" :file="item.msg"></FileCard>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="chat-input-wrapper">
      <div style="display: flex; align-items: center">
          <input v-model="inputMsg" @change="sendText" :disabled="loading" class="input-text" autofocus placeholder="给小智发送消息" />
          <img class="send-icon" src="@/assets/img/emoji/rocket.png" alt="" @click="sendText" />
      </div>
    </div>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, nextTick,onActivated } from 'vue'
import { useRoute } from 'vue-router'
import { animation } from '@/utils/util'
import chatGPTHeadImg from '@/assets/img/head_portrait1.png'
import headPortrait from '@/assets/img/head_portrait.jpg'
import FileCard from '@/components/FileCard.vue'
import { ElMessage } from "element-plus"
import {checking} from './ai-wd.js'
// å®šä¹‰å“åº”式数据
const route = useRoute()
const chatContent = ref(null)
const ws = ref(null)
const chatList = ref([
  {
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: ' å°æ™ºä¸ºæ‚¨æœåŠ¡',
    chatType: 0,
    uid: '1002'
  }
])
const inputMsg = ref('')
const isSend = ref(false)
const fileList = ref([])
const isProcessing = ref(false)
const loading = ref(true)
const srcImgList = ref([])
// åˆ é™¤å›¾ç‰‡
const deleteImg = (index) => {
  if (index >= 0 && index < fileList.value.length) {
    fileList.value.splice(index, 1)
  }
}
// WebSocket消息接收
const websocketonmessage = (e) => {
  const redata = JSON.parse(e.data)
  //数据接收
  let chatGPT = {
    headImg: headPortrait,
    name: 'DeepSeek',
    time: new Date().toLocaleTimeString(),
    msg: redata[0].text,
    chatType: 0, //信息类型,0文字,1图片
    uid: '1002' //uid
  }
  sendMsg(chatGPT)
  isSend.value = false
}
// WebSocket发送消息
const websocketsend = (Data) => {
  console.log("即将发送消息", Data)
  if (ws.value && ws.value.readyState === WebSocket.OPEN) {
    console.log("发送消息", ws.value)
    console.log("发送消息", Data)
    let fileUrls = fileList.value.map(item => item.file.fileUrl)
    //数据发送
    ws.value.send(Data + ":" + fileUrls.join(","))
    fileList.value = []
    inputMsg.value = ''
  }
}
// å‘送文本消息
const sendText = () => {
  if (inputMsg.value) {
    let chatMsg = {
      headImg: headPortrait,
      name: '卧龙',
      time: new Date().toLocaleTimeString(),
      msg: inputMsg.value,
      chatType: 0, //信息类型,0文字,1图片
      uid: '1001' //uid
    }
    chatList.value.push(chatMsg)
    let chatGPT = {
      headImg: headPortrait,
      name: '小智',
      time: new Date().toLocaleTimeString(),
      msg: "",
      chatType: 0, //信息类型,0文字,1图片
      uid: '1002' //uid
    }
    chatList.value.push(chatGPT) // å°†æŽ¥æ”¶åˆ°çš„æ¶ˆæ¯å­˜å‚¨åˆ° messages æ•°ç»„
    simulateStreamingOutput(chatGPT, inputMsg.value)
    inputMsg.value = ''
  } else {
    ElMessage({
      message: '消息不能为空哦~',
      type: 'warning'
    })
  }
}
// å‘送信息
const sendMsg = (msgList) => {
  chatList.value.push(msgList)
  scrollBottom()
}
// èŽ·å–çª—å£é«˜åº¦å¹¶æ»šåŠ¨è‡³æœ€åº•å±‚
const scrollBottom = () => {
  nextTick(() => {
    const scrollDom = chatContent.value
    animation(scrollDom, scrollDom.scrollHeight - scrollDom.offsetHeight)
  })
}
// ç»„件挂载时执行
onActivated(() => {
  chatList.value = []
  chatList.value.push({
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: '小智为您服务',
    chatType: 0,
    uid: '1002'
  })
  chatList.value.push({
    headImg: chatGPTHeadImg,
    name: '卧龙',
    time: new Date().toLocaleTimeString(),
    msg: route.query.keyWord,
    chatType: 0,
    uid: '1001'
  })
  // æ·»åŠ ä¸€ä¸ªç©ºçš„å›žå¤æ¶ˆæ¯å ä½
  const replyMsg = {
    headImg: chatGPTHeadImg,
    name: '小智',
    time: new Date().toLocaleTimeString(),
    msg: '',
    chatType: 0,
    uid: '1002'
  }
  chatList.value.push(replyMsg)
  scrollBottom()
  loading.value = false
  // å¦‚果有查询关键字,则模拟流式输出
  if (route.query.keyWord) {
    simulateStreamingOutput(replyMsg, route.query.keyWord)
  }
})
// æ¨¡æ‹Ÿæµå¼è¾“出
const simulateStreamingOutput = async (msgObj, keyWord) => {
  loading.value = true
  // ç”Ÿæˆ0.8-1.3秒之间的随机延迟
  const delay = Math.random() * 500 + 800
  // æ¨¡æ‹Ÿå›žå¤å†…容(实际应用中应从API获取)
  const responseText = `关于"${keyWord}"的问题,我来为您解答:\n` + checking(keyWord)
  isSend.value = true
  let index = 0
  setTimeout(() => {
    const interval = setInterval(() => {
      isSend.value = true
      if (index < responseText.length) {
        msgObj.msg += responseText.charAt(index)
        index++
        isSend.value = false
        scrollBottom()
      } else {
        clearInterval(interval)
        isSend.value = false
        loading.value = false
      }
    }, 50) // æ¯50ms输出一个字符,模拟流式效果
  }, delay)
}
</script>
<style lang="scss" scoped>
.mobile-chat-wrapper {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  height: 91vh;
  position: relative;
  background-color: white;
  .chat-history {
    flex: 1 1 0;
    overflow-y: auto;
  }
  .chat-input-wrapper {
    padding: 8px 16px 8px 8px;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
      .file-tt{
          flex-direction: column;
          width: 200px;
          display: flex;
          padding: 5px;
          border-radius: 5px;
          margin-right: 5px;
          background: #cacaca;
          .file-item{
              width: 200px;
              overflow:hidden;
              word-wrap: break-word;
              text-overflow:ellipsis;
              display:-webkit-box;
              -webkit-box-orient:vertical;
              -webkit-line-clamp:2;
          }
      }
    .send-icon {
      height: 40px;
      margin-left: 16px;
    }
      .input-text{
          font-size: 18px;
          width: 100%;
          border-radius: 20px;
          height: 80px;
          padding-left: 10px;
          //padding-top: 10px;
          border: none;
          color: black;                 /* ä¿®æ”¹æ–‡æœ¬é¢œè‰²ä¸ºç™½è‰² */
          background-color: #f5f4f4;   /* ä¿®æ”¹èƒŒæ™¯é¢œè‰²ä¸ºæ·±ç°è‰² */
      }
  }
  .chat-content {
    width: 100%;
    height: 80%;
    overflow-y: scroll;
    padding: 20px;
    box-sizing: border-box;
    &::-webkit-scrollbar {
      width: 0;
      /* Safari,Chrome éšè—æ»šåŠ¨æ¡ */
      height: 0;
      /* Safari,Chrome éšè—æ»šåŠ¨æ¡ */
      display: none;
      /* ç§»åŠ¨ç«¯ã€pad ä¸ŠSafari,Chrome,隐藏滚动条 */
    }
    .chat-wrapper {
      position: relative;
      word-break: break-all;
      .chat-friend {
        width: 100%;
        float: left;
        margin-bottom: 20px;
        display: flex;
        flex-direction: column;
        justify-content: flex-start;
        align-items: flex-start;
        .chat-text {
          max-width: 90%;
          padding: 20px;
          border-radius: 20px 20px 20px 5px;
          background-color: rgb(245, 248, 248);
          color: black;
          &:hover {
            background-color: rgb(232, 232, 232);
          }
          pre {
            white-space: break-spaces;
          }
        }
        .chat-img {
          img {
            width: 100px;
            height: 100px;
          }
        }
        .info-time {
          margin: 10px 0;
          color: black;
          font-size: 14px;
          img {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            vertical-align: middle;
            margin-right: 10px;
          }
          span:last-child {
            color: rgb(101, 104, 115);
            margin-left: 10px;
            vertical-align: middle;
          }
        }
      }
      .chat-me {
        width: 100%;
        float: right;
        margin-bottom: 20px;
        position: relative;
        display: flex;
        flex-direction: column;
        justify-content: flex-end;
        align-items: flex-end;
        .chat-text {
          float: right;
          max-width: 90%;
          padding: 20px;
          border-radius: 20px 20px 5px 20px;
          background-color: rgb(29, 144, 245);
          color: #fff;
          &:hover {
            background-color: rgb(26, 129, 219);
          }
        }
        .chat-img {
          img {
            max-width: 300px;
            max-height: 200px;
            border-radius: 10px;
          }
        }
        .info-time {
          margin: 10px 0;
          color: black;
          font-size: 14px;
          display: flex;
          justify-content: flex-end;
          img {
            width: 30px;
            height: 30px;
            border-radius: 50%;
            vertical-align: middle;
            margin-left: 10px;
          }
          span {
            line-height: 30px;
          }
          span:first-child {
            color: rgb(101, 104, 115);
            margin-right: 10px;
            vertical-align: middle;
          }
        }
      }
    }
  }
  .flash_cursor {
    width: 20px;
    height: 30px;
    display: inline-block;
    background: #d6e3f5;
    opacity: 1;
    animation: glow 800ms ease-out infinite alternate;
  }
  @keyframes glow {
    0% {
      opacity: 1;
    }
    25% {
      opacity: 0.5;
    }
    50% {
      opacity: 0;
    }
    75% {
      opacity: 0.5;
    }
    100% {
      opacity: 1;
    }
  }
}
</style>
src/views/chatHome/chatHomeIndex/ai-jz.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
export function jam() {
    return ""
}
src/views/chatHome/chatHomeIndex/ai-wd.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,393 @@
export function gasLeaks() {
  return "1. è®¾è®¡ä¸Žè®¾å¤‡é€‰æ‹©\n" +
      "选用高质量材料:管道、阀门、储罐等设备应选用耐腐蚀、耐高压的材料,并符合安全标准(如ASME、API等)。\n" +
      "\n" +
      "安全设计:\n" +
      "\n" +
      "安装冗余的安全装置(如双重阀门、爆破片、安全阀等)。\n" +
      "\n" +
      "设置气体泄漏检测系统(如可燃气体报警器、有毒气体传感器)。\n" +
      "\n" +
      "采用封闭式系统设计,减少开放接口。\n" +
      "\n" +
      "通风系统:在可能泄漏的区域安装强制通风设备,防止气体聚集。\n" +
      "\n" +
      "2. å®‰è£…与维护\n" +
      "规范安装:由专业人员进行设备安装,确保管道焊接、密封等环节无缺陷。\n" +
      "\n" +
      "定期检查:\n" +
      "\n" +
      "对管道、阀门、连接处进行泄漏检测(如压力测试、超声波检测、肥皂水检漏)。\n" +
      "\n" +
      "更换老化或腐蚀的部件。\n" +
      "\n" +
      "预防性维护:制定维护计划,定期润滑阀门、更换密封件等。\n" +
      "\n" +
      "3. æ“ä½œç®¡ç†\n" +
      "严格操作规程:\n" +
      "\n" +
      "操作人员需培训上岗,熟悉气体特性和应急流程。\n" +
      "\n" +
      "避免超压、超温或错误操作。\n" +
      "\n" +
      "监控系统:\n" +
      "\n" +
      "实时监测压力、温度、流量等参数,设置自动报警和联锁停机装置。\n" +
      "\n" +
      "使用气体检测仪(如红外、电化学传感器)监测环境浓度。\n" +
      "\n" +
      "明确标识:在危险区域标明气体类型、风险等级及防护要求。\n" +
      "\n" +
      "4. æ³„漏应急措施\n" +
      "应急设备:\n" +
      "\n" +
      "配备防毒面具、呼吸器、防护服等个人防护装备(PPE)。\n" +
      "\n" +
      "就近放置泄漏应急包(如堵漏胶、密封带)。\n" +
      "\n" +
      "紧急响应:\n" +
      "\n" +
      "立即切断气源(关闭上游阀门或启动紧急切断系统)。\n" +
      "\n" +
      "启动通风设备稀释气体浓度。\n" +
      "\n" +
      "疏散人员并上报专业部门(如消防、环保)。\n" +
      "\n" +
      "应急预案:定期演练泄漏处置流程,确保人员熟悉分工。\n" +
      "\n" +
      "5. å‚¨å­˜ä¸Žè¿è¾“安全\n" +
      "储存要求:\n" +
      "\n" +
      "储罐远离火源、高温区,并设置围堰防止扩散。\n" +
      "\n" +
      "液化气体储罐需配备泄压装置。\n" +
      "\n" +
      "运输安全:\n" +
      "\n" +
      "使用合规的运输车辆,固定气瓶防止碰撞。\n" +
      "\n" +
      "运输途中监控车辆状态(如GPS、温度传感器)。\n" +
      "\n" +
      "6. äººå‘˜åŸ¹è®­ä¸Žæ–‡åŒ–\n" +
      "安全培训:定期开展气体危害、防护措施和应急处理的培训。\n" +
      "\n" +
      "安全文化:鼓励员工报告潜在风险,建立“零泄漏”管理目标。\n" +
      "\n" +
      "7. æ³•规与标准\n" +
      "遵守相关法规(如OSHA、GB 50493《石油化工可燃气体和有毒气体检测报警设计规范》)。\n" +
      "\n" +
      "定期进行安全审计,确保符合行业标准。\n" +
      "\n" +
      "常见危险气体泄漏的针对性措施\n" +
      "可燃气体(如甲烷、氢气):防爆电器、消除静电。\n" +
      "\n" +
      "有毒气体(如氯气、硫化氢):配备专用过滤式或供氧式呼吸器。\n" +
      "\n" +
      "窒息性气体(如氮气、二氧化碳):监测氧气浓度,避免密闭空间作业。\n" +
      "\n" +
      "通过以上措施的综合应用,可大幅降低气体泄漏风险,保障人员安全和环境健康。若发生泄漏,需优先确保人员撤离,再由专业人员处置。\n" +
      "\n" +
      "本回答由 AI ç”Ÿæˆï¼Œå†…容仅供参考,请仔细甄别。"
}
export function shipping() {
    return "一、立即应急响应\n" +
        "1. å‘现泄漏时的紧急行动\n" +
        "停车并隔离现场:\n" +
        "\n" +
        "运输车辆立即停靠在空旷、远离人群和火源的位置。\n" +
        "\n" +
        "设置警戒线(至少50~100米半径,根据气体性质调整),禁止无关人员进入。\n" +
        "\n" +
        "切断泄漏源:\n" +
        "\n" +
        "关闭容器阀门或封堵破损处(如使用应急堵漏工具)。\n" +
        "\n" +
        "若阀门损坏,尝试转移剩余气体至备用容器(需专业人员操作)。\n" +
        "\n" +
        "报警与上报:\n" +
        "\n" +
        "拨打应急电话(如消防119、环保部门),说明气体类型、泄漏量、位置等信息。\n" +
        "\n" +
        "联系运输公司及货主,获取技术支援(如MSDS安全数据表)。\n" +
        "\n" +
        "2. äººå‘˜é˜²æŠ¤ä¸Žç–æ•£\n" +
        "穿戴防护装备:\n" +
        "\n" +
        "可燃气体:防爆工具+防静电服;有毒气体:正压式呼吸器+防化服。\n" +
        "\n" +
        "无防护装备时,立即撤离至上风方向。\n" +
        "\n" +
        "疏散周边区域:\n" +
        "\n" +
        "根据气体扩散范围(参考应急响应指南ERG)疏散居民或作业人员。\n" +
        "\n" +
        "避免低洼处滞留(某些气体比空气重,如硫化氢)。\n" +
        "\n" +
        "3. æŽ§åˆ¶æ³„漏扩散\n" +
        "物理方法:\n" +
        "\n" +
        "覆盖泄漏口(如用浸水棉被减少挥发,但禁用于遇水反应气体如氯气)。\n" +
        "\n" +
        "筑堤围堵液体泄漏物,防止流入下水道或河流。\n" +
        "\n" +
        "化学方法:\n" +
        "\n" +
        "中和处理(如氨气泄漏喷洒稀盐酸,需专业人员操作)。\n" +
        "\n" +
        "使用吸附材料(如活性炭、沙土吸附有机气体)。"
}
export function operate(){
    return "一、操作不当发生后的应急处理\n" +
        "1. ç«‹å³æŽ§åˆ¶äº‹æ•…\n" +
        "停止操作:\n" +
        "\n" +
        "按下紧急停机按钮,关闭最近的上游阀门。\n" +
        "\n" +
        "启动应急预案:\n" +
        "\n" +
        "小型泄漏:使用应急堵漏工具(如密封胶、夹具)。\n" +
        "\n" +
        "大型泄漏:疏散人员,报警求助(119/环保部门)。\n" +
        "\n" +
        "2. äººå‘˜å®‰å…¨\n" +
        "撤离与隔离:\n" +
        "\n" +
        "逆风撤离至上风向安全区,避免低洼处(重气体积聚)。\n" +
        "\n" +
        "急救措施:\n" +
        "\n" +
        "吸入有毒气体:移至空气新鲜处,必要时人工呼吸。\n" +
        "\n" +
        "皮肤接触:立即用清水冲洗15分钟(腐蚀性气体)。\n" +
        "\n" +
        "3. äº‹æ•…调查与整改\n" +
        "根本原因分析(RCA):\n" +
        "\n" +
        "是操作失误、培训不足,还是设备缺陷?\n" +
        "\n" +
        "改进措施:\n" +
        "\n" +
        "修订操作规程,增加警示标识。\n" +
        "\n" +
        "对责任人再培训,必要时调岗。"
}
export function emergency(){
    return "一、优化应急响应的关键措施\n" +
        "1. å®Œå–„应急预案\n" +
        "针对性设计:\n" +
        "\n" +
        "基于HAZOP(危险与可操作性分析)识别所有潜在风险场景。\n" +
        "\n" +
        "制定分级响应机制(小泄漏现场处置、大泄漏全员疏散)。\n" +
        "\n" +
        "明确职责:\n" +
        "\n" +
        "设立应急指挥部,指定总指挥、通讯组、抢险组、医疗组等。\n" +
        "\n" +
        "确保24小时值班制度,联系方式实时更新。\n" +
        "\n" +
        "联动外部资源:\n" +
        "\n" +
        "与消防、环保、医院提前签订救援协议,明确协作流程。\n" +
        "\n" +
        "2. å¼ºåŒ–应急资源保障\n" +
        "装备与物资:\n" +
        "\n" +
        "配备足量且有效的应急设备,包括:\n" +
        "\n" +
        "个人防护装备(PPE):防毒面具、化学防护服。\n" +
        "\n" +
        "堵漏工具:密封胶、夹具、快速封堵器。\n" +
        "\n" +
        "吸附/中和材料:活性炭、沙土、稀碱液(用于酸性气体)。\n" +
        "\n" +
        "定期检查、维护和更换(如气瓶压力、传感器电池)。\n" +
        "\n" +
        "应急车辆与通道:\n" +
        "\n" +
        "确保救援车辆可快速抵达泄漏点(清除路障,标识应急路线)。\n" +
        "\n" +
        "关键区域设置应急洗眼器、喷淋系统。\n" +
        "\n" +
        "3. åŠ å¼ºäººå‘˜åŸ¹è®­ä¸Žèƒ½åŠ›å»ºè®¾\n" +
        "分层培训:\n" +
        "\n" +
        "基层人员:掌握基本应急处置(如关闭阀门、使用灭火器)。\n" +
        "\n" +
        "应急小组:专业堵漏、伤员急救、气体检测技能。\n" +
        "\n" +
        "管理层:指挥决策、媒体沟通、法律合规。\n" +
        "\n" +
        "实战化考核:\n" +
        "\n" +
        "通过模拟突发泄漏(如盲演)检验响应速度。\n" +
        "\n" +
        "不合格者需重新培训。\n" +
        "\n" +
        "4. å®šæœŸæ¼”练与持续改进\n" +
        "演练频率:\n" +
        "\n" +
        "每季度至少1次桌面推演,每年2次综合实战演练。\n" +
        "\n" +
        "场景设计:\n" +
        "\n" +
        "模拟复杂情况(如夜间停电、多人受伤)。\n" +
        "\n" +
        "引入“突发变量”(如风向突变、二次泄漏)。\n" +
        "\n" +
        "复盘与优化:\n" +
        "\n" +
        "记录演练中的问题(如通讯延迟、装备缺失)。\n" +
        "\n" +
        "更新预案并下发学习。\n" +
        "\n" +
        "5. æŠ€æœ¯å‡çº§ä¸Žæ™ºèƒ½åŒ–支持\n" +
        "实时监测与预警:\n" +
        "\n" +
        "部署物联网(IoT)传感器,监测气体浓度、设备状态。\n" +
        "\n" +
        "设置自动联锁控制(如泄漏时联动关闭阀门并启动通风)。\n" +
        "\n" +
        "应急通讯系统:\n" +
        "\n" +
        "使用防爆对讲机、卫星电话(保障信号覆盖)。\n" +
        "\n" +
        "建立应急广播系统(如厂区警报、短信群发)。\n" +
        "\n" +
        "数字化预案:\n" +
        "\n" +
        "将应急预案录入移动终端,实现一键调阅、步骤指引。"
}
export function compliance(){
    return "一、常见的合规性问题\n" +
        "1. è®¸å¯ä¸Žèµ„质缺失\n" +
        "问题:\n" +
        "\n" +
        "未取得危险化学品经营许可证或安全生产许可证。\n" +
        "\n" +
        "特种作业人员(如压力容器操作工)无证上岗。\n" +
        "\n" +
        "风险:\n" +
        "\n" +
        "监管部门处罚(如罚款、责令停产)。\n" +
        "\n" +
        "保险拒赔(事故发生时)。\n" +
        "\n" +
        "2. å®‰å…¨è®¾è®¡ä¸è¾¾æ ‡\n" +
        "问题:\n" +
        "\n" +
        "储罐、管道未按GB/T 150(压力容器标准)设计。\n" +
        "\n" +
        "未安装可燃/有毒气体报警器(违反GB 50493)。\n" +
        "\n" +
        "风险:\n" +
        "\n" +
        "设备失效导致泄漏或爆炸。\n" +
        "\n" +
        "不符合应急管理部或OSHA检查要求。\n" +
        "\n" +
        "3. æ“ä½œä¸Žç»´æŠ¤è¿è§„\n" +
        "问题:\n" +
        "\n" +
        "未执行作业票制度(如动火作业未审批)。\n" +
        "\n" +
        "未定期检验压力容器(违反TSG 21-2016)。\n" +
        "\n" +
        "风险:\n" +
        "\n" +
        "违规操作引发事故(如焊接引发可燃气体爆炸)。\n" +
        "\n" +
        "设备老化导致突发泄漏。\n" +
        "\n" +
        "4. åº”急管理不合规\n" +
        "问题:\n" +
        "\n" +
        "未制定应急预案或未备案(违反《生产安全事故应急条例》)。\n" +
        "\n" +
        "未配备应急物资(如防毒面具、堵漏工具)。\n" +
        "\n" +
        "风险:\n" +
        "\n" +
        "事故发生时无法有效控制,导致损失扩大。\n" +
        "\n" +
        "面临生态环境部追责(如化学品污染土壤/水体)。\n" +
        "\n" +
        "5. è®°å½•与报告缺失\n" +
        "问题:\n" +
        "\n" +
        "未保存安全检查记录或培训档案。\n" +
        "\n" +
        "未按规定上报泄漏事故(如瞒报、迟报)。\n" +
        "\n" +
        "风险:\n" +
        "\n" +
        "事故调查时无法自证合规,承担全责。\n" +
        "\n" +
        "被列入安全生产黑名单,影响企业信誉。"
}
export function monitoring(){
    return "一、固定式监测技术\n" +
        "1. å‚¬åŒ–燃烧式传感器\n" +
        "原理:可燃气体在铂丝表面燃烧导致电阻变化\n" +
        "\n" +
        "优势:成本低(¥500-2000/个)、响应快(<10s)\n" +
        "\n" +
        "局限:易中毒(硅/硫化合物)、寿命短(2-3年)\n" +
        "\n" +
        "适用:石化厂可燃气监测(甲烷、氢气等)\n" +
        "\n" +
        "2. ç”µåŒ–学传感器\n" +
        "原理:气体与电解液发生氧化还原反应产生电流\n" +
        "\n" +
        "优势:ppb级检测(如H2S检测下限0.1ppm)\n" +
        "\n" +
        "局限:受温湿度影响(需定期校准)\n" +
        "\n" +
        "适用:有毒气体(Cl₂、NH₃、CO等)\n" +
        "\n" +
        "3. çº¢å¤–吸收式(NDIR)\n" +
        "原理:气体对特定红外波段的吸收率检测\n" +
        "\n" +
        "优势:免校准(寿命5-10年)、抗中毒\n" +
        "\n" +
        "局限:高成本(¥5000+/个)\n" +
        "\n" +
        "适用:CO₂、CH₄等温室气体监测\n" +
        "\n" +
        "4. æ¿€å…‰å…‰è°±ï¼ˆTDLAS)\n" +
        "原理:可调谐激光二极管扫描气体吸收线\n" +
        "\n" +
        "优势:ppm级精度、响应ms级\n" +
        "\n" +
        "局限:需光学对准(安装复杂)\n" +
        "\n" +
        "适用:管道微泄漏检测(天然气长输管线)"
}
export function checking(keyWord){
    if(keyWord.includes("气体泄漏")){
        return gasLeaks();
    }
    if(keyWord.includes("容器失效")){
        return shipping();
    }
    if(keyWord.includes("操作不当")){
        return operate();
    }
    if(keyWord.includes("响应不足")){
        return emergency();
    }
    if(keyWord.includes("合规性")){
        return compliance();
    }
    if(keyWord.includes("监测技术")){
        return monitoring();
    }
    return "不好意思,小智还在成长过程中,您的问题已经超过小智的处理范围了。";
}
src/views/chatHome/chatHomeIndex/home.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,175 @@
<template>
<div class="home">
    <div style="background: white;color: black;font-size: 30px;" class="logo">
        <div class="logo-one" style="font-weight: bold">
<!--            <img src="/src/assets/img/logo.png" style="width: 50px;height: 50px;margin: 0 10px" />-->
            <div><i>大模型AI小智正在为您服务</i></div>
        </div>
        <div class="input">
            <input type="text" v-model="keyWord" class="input-text" placeholder="给小智发送消息"  @keyup.enter="sendMsg" />
            <div style="font-size: 13px;color: #808080;display: flex;justify-content: space-between;padding: 10px;">
                <div style="display: flex;justify-content: center;align-items: center;">
<!--                    <div style="display: flex;justify-content: center;align-items: center;">-->
<!--                        <img src="/src/assets/img/logo.png" style="width: 15px;height: 15px;margin: 0 5px" />-->
<!--                        <span>深度思考(R1)</span>-->
<!--                    </div>-->
<!--                    <div style="display: flex;justify-content: center;align-items: center;">-->
<!--                        <img src="/src/assets/img/logo.png" style="width: 15px;height: 15px;margin: 0 5px" />-->
<!--                        <span>联网搜索</span>-->
<!--                    </div>-->
                </div>
                <div style="display: flex;justify-content: center;align-items: center;margin-right: 5px;">
<!--                    <img src="/src/assets/img/logo.png" style="width: 25px;height: 25px;margin: 0 5px" />-->
                    <img src="@/assets/img/emoji/rocket.png" style="width: 25px;height: 25px;margin: 0 5px" @click="sendMsg"/>
                </div>
            </div>
        </div>
        <div style="width: 780px;">
            <div style="font-weight: bold;margin: 30px 0;">热门推荐</div>
            <div class="keywords">
                <div class="keywordss" @click="sendMsgDefault(keyWordOne)">
                    <p class="fontSize aaa">{{keyWordOne}}</p>
                    <p class="fontSize">阀门、管道或容器密封失效导致气体泄漏(如氯气、氨气)。</p>
                    <p class="fontSize">后果:中毒、爆炸、环境污染。</p>
                </div>
                <div class="keywordss" @click="sendMsgDefault(keyWordTwo)">
                    <p class="fontSize aaa">{{keyWordTwo}}</p>
                    <p class="fontSize">钢瓶或罐体因材料疲劳、腐蚀或超压破裂</p>
                    <p class="fontSize">原因:未定期检测、违规充装或外部撞击。。</p>
                </div>
            </div>
            <div class="keywords">
                <div class="keywordss" @click="sendMsgDefault(keyWordFive)">
                    <p class="fontSize aaa">{{keyWordFive}}</p>
                    <p class="fontSize">装卸过程中违规操作(如野蛮搬运、混装禁忌物质)。</p>
                    <p class="fontSize">运输途中未固定容器,导致碰撞或倾倒。</p>
                </div>
                <div class="keywordss" @click="sendMsgDefault(keyWordSix)">
                    <p class="fontSize aaa">{{keyWordSix}}</p>
                    <p class="fontSize">缺乏泄漏应急预案,人员培训不足。</p>
                    <p class="fontSize">救援设备(如防毒面具、堵漏工具)缺失或失效。</p>
                </div>
            </div>
            <div class="keywords">
                <div class="keywordss" @click="sendMsgDefault(keyWordServen)">
                    <p class="fontSize aaa">{{keyWordServen}}</p>
                    <p class="fontSize">未取得运输资质(如ADR/RID等国际规范)。</p>
                    <p class="fontSize">路线规划不合规(如穿越人口密集区)。</p>
                </div>
                <div class="keywordss" @click="sendMsgDefault(keyWordEight)">
                    <p class="fontSize aaa">{{keyWordEight}}</p>
                    <p class="fontSize">传感器部署(如红外气体探测器、电化学传感器)。</p>
                    <p class="fontSize">实时数据传输至监控平台,触发报警。</p>
                </div>
            </div>
        </div>
    </div>
    <div></div>
</div>
</template>
<script setup>
import { ref,onMounted } from "vue";
import {useRoute,useRouter} from "vue-router"
const route = useRoute();
const router = useRouter();
const keyWord = ref('');
const keyWordOne = ref('危险气体泄漏怎么办');
const keyWordTwo = ref('运输容器失效怎么办');
const keyWordFive = ref('操作不当怎么办');
const keyWordSix = ref('应急响应不足怎么办');
const keyWordServen = ref('合规性问题');
const keyWordEight = ref('泄漏监测技术有哪些');
const sendMsg = () => {
    router.push({ path: '/main/MobileChat',query:{ keyWord: keyWord.value} })
}
const sendMsgDefault = (value) => {
    router.push({ path: '/main/MobileChat',query:{ keyWord: value} })
}
</script>
<style lang="scss" scoped>
.home {
  width: 100%;
  height: 91vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  .logo {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
    z-index: 99;
    width: 100%;
    height: 100%;
    color: #fff;
    cursor: pointer;
    overflow: hidden;
    background-color: #F0F6F9;
    .keywords {
      display: flex;
      width: 100%;
      height: 90px;
      line-height: 80px;
      justify-content: space-between;
      margin: 10px 0;
      .keywordss {
        box-shadow: 0px 2px 5px #b8b8b8;
        width: 48%;
        background: #e0edfc;
        border-radius: 10px;
        .aaa {
          font-weight: bold;
          font-size: 15px !important;
        }
        .fontSize {
          font-size: 13px;
          height: 20px;
          line-height: 20px;
          margin: 6px;
        }
      }
    }
    .logo-one {
      display: flex;
      justify-content: center;
      align-items: center;
      margin-bottom: 20px;
    }
    .input {
      width: 780px;
      height: 150px;
      background: #f5f4f4;
      border-radius: 20px;
      .input-text {
        font-size: 18px;
        width: 568px;
        border-radius: 20px 20px 0 0;
        height: 90px;
        padding-left: 10px;
        border: none;
        color: black;
        background-color: #f5f4f4;
      }
      .input-text:focus {
        outline: none;
        border: none;
      }
    }
  }
}
</style>
src/views/collaborativeApproval/approvalProcess/components/approvalDia.vue
@@ -102,6 +102,10 @@
              <div v-if="!activity.isShen" class="node-reason">
                <span>审批意见:</span>{{ activity.approveNodeReason }}
              </div>
              <div v-if="!activity.isShen" class="node-reason">
                <span>签名:</span>
                                <img :src="activity.urlTem" class="signImg" alt="" v-if="activity.urlTem"/>
              </div>
              <div v-else-if="activity.isShen">
                <el-form-item
                  :prop="'activities.' + index + '.approveNodeReason'"
@@ -117,16 +121,33 @@
      <template #footer v-if="operationType === 'approval'">
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm(2)">不通过</el-button>
          <el-button type="primary" @click="submitForm(1)">通过</el-button>
          <el-button type="primary" @click="openSignatureDialog(1)">通过</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç”µå­ç­¾åå¼¹çª—(vue3-signature-pad) -->
    <el-dialog v-model="signatureDialogVisible" title="电子签名" width="600px" append-to-body>
            <vueEsign
                ref="esign"
                class="mySign"
                :width="800"
                :height="300"
                :isCrop="isCrop"
                :lineWidth="lineWidth"
                :lineColor="lineColor"
            />
      <div style="margin-top:10px;">
        <el-button @click="clearSignature">清除</el-button>
        <el-button type="primary" @click="confirmSignature">确定</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script setup>
import {getCurrentInstance, reactive, ref, toRefs} from "vue";
import { getCurrentInstance, reactive, ref, toRefs } from "vue";
import vueEsign from "vue-esign";
import {
    approveProcessDetails,
    getDept,
@@ -135,6 +156,7 @@
import useUserStore from "@/store/modules/user.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { WarningFilled, Edit, Check, MoreFilled } from '@element-plus/icons-vue'
import { getToken } from "@/utils/auth";
const emit = defineEmits(['close'])
const { proxy } = getCurrentInstance()
@@ -156,6 +178,22 @@
    },
});
const { form } = toRefs(data);
const signatureDialogVisible = ref(false);
const signatureImg = ref('');
let submitStatus = null; // ä¸´æ—¶å­˜å‚¨é€šè¿‡/不通过状态
const isCrop = ref("");
const esign = ref(null);
const lineWidth = ref(0);
const lineColor = ref("#000000");
// ä¸Šä¼ é…ç½®
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
// èŠ‚ç‚¹æ ‡é¢˜
const getNodeTitle = (index, len) => {
  if (index === len - 1) return '结束';
@@ -190,6 +228,11 @@
    activities.value = res.data
    // å¢žåŠ isApproval字段
    activities.value.forEach(item => {
            if (item.url && item.url.includes('word')) {
                item.urlTem = item.url.replaceAll('word', 'img')
            } else {
                item.urlTem = item.url
            }
      if (item.approveNodeStatus === 2) {
        item.isApproval = '已驳回';
      } else if (item.approveNodeStatus === 1) {
@@ -205,17 +248,84 @@
        productOptions.value = res.data;
    });
};
// æ‰“开签名弹窗
const openSignatureDialog = (status) => {
  submitStatus = status;
  signatureDialogVisible.value = true;
};
// æ¸…除签名
const clearSignature = () => {
    esign.value.reset();
};
// ç¡®è®¤ç­¾å
const confirmSignature = () => {
    esign.value.generate().then((res) => {
        console.log(res);
        // å°†base64转换为二进制
        const base64Data = res.split(',')[1]; // ç§»é™¤data:image/png;base64,前缀
        const binaryString = atob(base64Data);
        const bytes = new Uint8Array(binaryString.length);
        for (let i = 0; i < binaryString.length; i++) {
            bytes[i] = binaryString.charCodeAt(i);
        }
        signatureImg.value = bytes;
        // åˆ›å»ºæ–‡ä»¶å¯¹è±¡ç”¨äºŽä¸Šä¼ 
        const blob = new Blob([bytes], { type: 'image/png' });
        const file = new File([blob], 'signature.png', { type: 'image/png' });
        // åˆ›å»ºFormData
        const formData = new FormData();
        formData.append('file', file);
        // ä¸Šä¼ ç­¾åå›¾ç‰‡
        fetch(upload.url, {
            method: 'POST',
            headers: upload.headers,
            body: formData
        })
        .then(response => response.json())
        .then(data => {
            if (data.code === 200) {
                console.log('data---', data)
                let tempFileIds = [];
                tempFileIds.push(data.data.tempId);
                signatureDialogVisible.value = false;
                clearSignature();
                // åªæœ‰é€šè¿‡æ—¶æ‰ä¼ é€’签名文件ID
                if (submitStatus === 1) {
                    submitForm(submitStatus, tempFileIds);
                } else {
                    submitForm(submitStatus);
                }
            } else {
                proxy.$modal.msgError("签名图片上传失败:" + data.msg);
            }
        })
        .catch(error => {
            console.error('上传失败:', error);
            proxy.$modal.msgError("签名图片上传失败");
        });
    }).catch((err) => {
        console.log(err);
        proxy.$modal.msgWarning("请先签名!");
    })
};
// æäº¤å®¡æ‰¹
const submitForm = (status) => {
const submitForm = (status, tempFileIds) => {
  const filteredActivities = activities.value.filter(activity => activity.isShen);
  filteredActivities[0].approveNodeStatus = status
  filteredActivities[0].approveNodeStatus = status;
  // åªæœ‰é€šè¿‡æ—¶æ‰éœ€è¦ç­¾å
  if (status === 1 && tempFileIds) {
    filteredActivities[0].tempFileIds = tempFileIds;
  }
  // åˆ¤æ–­æ˜¯å¦ä¸ºæœ€åŽä¸€æ­¥
  const isLast = activities.value.findIndex(a => a.isShen) === activities.value.length-1;
  updateApproveNode({ ...filteredActivities[0], isLast }).then(() => {
    proxy.$modal.msgSuccess("提交成功");
    closeDia();
  })
}
  });
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
@@ -253,4 +363,9 @@
    height: 30px;
    border-radius: 50px;
}
.signImg {
    cursor: pointer;
    width: 200px;
    height: 60px;
}
</style>
src/views/collaborativeApproval/approvalProcess/components/infoFormDia.vue
@@ -112,6 +112,23 @@
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="24">
            <el-form-item label="附件材料:" prop="remark">
              <el-upload v-model:file-list="fileList" :action="upload.url" multiple ref="fileUpload" auto-upload
                         :headers="upload.headers" :before-upload="handleBeforeUpload" :on-error="handleUploadError"
                         :on-success="handleUploadSuccess" :on-remove="handleRemove">
                <el-button type="primary" v-if="operationType !== 'view'">上传</el-button>
                <template #tip v-if="operationType !== 'view'">
                  <div class="el-upload__tip">
                    æ–‡ä»¶æ ¼å¼æ”¯æŒ
                    doc,docx,xls,xlsx,ppt,pptx,pdf,txt,xml,jpg,jpeg,png,gif,bmp,rar,zip,7z
                  </div>
                </template>
              </el-upload>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
@@ -130,7 +147,11 @@
  approveProcessUpdate,
  getDept
} from "@/api/collaborativeApproval/approvalProcess.js";
import {
  delLedgerFile,
} from "@/api/salesManagement/salesLedger.js";
import {userListNoPageByTenantId} from "@/api/system/user.js";
import { getToken } from "@/utils/auth";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
import useUserStore from "@/store/modules/user";
@@ -138,6 +159,13 @@
const dialogFormVisible = ref(false);
const operationType = ref('')
const fileList = ref([]);
const upload = reactive({
  // ä¸Šä¼ çš„地址
  url: import.meta.env.VITE_APP_BASE_API + "/file/upload",
  // è®¾ç½®ä¸Šä¼ çš„请求头部
  headers: { Authorization: "Bearer " + getToken() },
});
const data = reactive({
  form: {
    approveTime: "",
@@ -146,6 +174,7 @@
        approveDeptId: "",
    approveReason: "",
    checkResult: "",
    tempFileIds: [],
    approverList: [] // æ–°å¢žå­—段,存储所有节点的审批人id
  },
  rules: {
@@ -176,6 +205,7 @@
// æ‰“开弹框
const openDialog = (type, row) => {
  console.log('openDialog', type, row)
  operationType.value = type;
  dialogFormVisible.value = true;
    userListNoPageByTenantId().then((res) => {
@@ -192,6 +222,8 @@
  // èŽ·å–å½“å‰ç”¨æˆ·ä¿¡æ¯å¹¶è®¾ç½®éƒ¨é—¨ID
  form.value.approveDeptId = userStore.currentDeptId
  if (operationType.value === 'edit') {
    fileList.value = row.commonFileList
    form.value.tempFileIds = fileList.value.map(file => file.id)
        currentApproveStatus.value = row.approveStatus
    approveProcessGetInfo({id: row.approveId,approveReason: '1'}).then(res => {
            form.value = {...res.data}
@@ -257,6 +289,7 @@
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  fileList.value = []
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
@@ -269,6 +302,48 @@
  const day = String(today.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}
// ä¸Šä¼ å‰æ ¡æ£€
function handleBeforeUpload(file) {
  // æ ¡æ£€æ–‡ä»¶å¤§å°
  // if (file.size > 1024 * 1024 * 10) {
  //   proxy.$modal.msgError("上传文件大小不能超过10MB!");
  //   return false;
  // }
  proxy.$modal.loading("正在上传文件,请稍候...");
  return true;
}
// ä¸Šä¼ å¤±è´¥
function handleUploadError(err) {
  proxy.$modal.msgError("上传文件失败");
  proxy.$modal.closeLoading();
}
// ä¸Šä¼ æˆåŠŸå›žè°ƒ
function handleUploadSuccess(res, file, uploadFiles) {
  proxy.$modal.closeLoading();
  if (res.code === 200) {
    // ç¡®ä¿ tempFileIds å­˜åœ¨ä¸”为数组
    if (!form.value.tempFileIds) {
      form.value.tempFileIds = [];
    }
    form.value.tempFileIds.push(res.data.tempId);
    proxy.$modal.msgSuccess("上传成功");
  } else {
    proxy.$modal.msgError(res.msg);
    proxy.$refs.fileUpload.handleRemove(file);
  }
}
// ç§»é™¤æ–‡ä»¶
function handleRemove(file) {
  if (operationType.value === "edit") {
    let ids = [];
    ids.push(file.id);
    delLedgerFile(ids).then((res) => {
      proxy.$modal.msgSuccess("删除成功");
    });
  }
}
defineExpose({
  openDialog,
});
src/views/collaborativeApproval/approvalProcess/fileList.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
<template>
  <el-dialog v-model="dialogVisible" title="附件" width="40%" :before-close="handleClose">
    <el-table :data="tableData" border height="40vh">
      <el-table-column label="附件名称" prop="name" min-width="400" show-overflow-tooltip />
      <el-table-column fixed="right" label="操作" width="100" align="center">
        <template #default="scope">
          <el-button link type="primary" size="small" @click="downLoadFile(scope.row)">下载</el-button>
          <el-button link type="primary" size="small" @click="lookFile(scope.row)">预览</el-button>
        </template>
      </el-table-column>
    </el-table>
  </el-dialog>
  <filePreview ref="filePreviewRef" />
</template>
<script setup>
import { ref } from 'vue'
import filePreview from '@/components/filePreview/index.vue'
const dialogVisible = ref(false)
const tableData = ref([])
const { proxy } = getCurrentInstance();
const filePreviewRef = ref()
const handleClose = () => {
  dialogVisible.value = false
}
const open = (list) => {
  dialogVisible.value = true
  tableData.value = list
}
const downLoadFile = (row) => {
  proxy.$download.name(row.url);
}
const lookFile = (row) => {
  filePreviewRef.value.open(row.url)
}
defineExpose({
  open
})
</script>
<style></style>
src/views/collaborativeApproval/approvalProcess/index.vue
@@ -44,10 +44,12 @@
    </div>
    <info-form-dia ref="infoFormDia" @close="handleQuery"></info-form-dia>
    <approval-dia ref="approvalDia" @close="handleQuery"></approval-dia>
    <FileList ref="fileListRef" />
  </div>
</template>
<script setup>
import FileList from "./fileList.vue";
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import {ElMessageBox} from "element-plus";
@@ -116,11 +118,12 @@
  {
    label: "申请人",
    prop: "approveUserName",
    width: 120
  },
  {
    label: "申请日期",
    prop: "approveTime",
        width: 120
        width: 200
  },
  {
    label: "结束日期",
@@ -137,7 +140,7 @@
    label: "操作",
    align: "center",
    fixed: "right",
    width: 150,
    width: 230,
    operation: [
      {
        name: "编辑",
@@ -162,6 +165,13 @@
          openApprovalDia('view', row);
        },
      },
      {
        name: "附件",
        type: "text",
        clickFun: (row) => {
          downLoadFile(row);
        },
      },
    ],
  },
]);
@@ -183,6 +193,11 @@
  page.current = 1;
  getList();
};
const fileListRef = ref(null)
const downLoadFile = (row) => {
  fileListRef.value.open(row.commonFileList)
}
const pagination = (obj) => {
  page.current = obj.page;
  page.size = obj.limit;
src/views/energyManagement/waterManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,221 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="用水设备"
        width="70%"
        @close="closeDia"
    >
            <el-form
                :model="form"
                label-width="140px"
                label-position="top"
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="设备:" prop="deviceModel">
                            <el-select
                                v-model="form.deviceModel"
                                placeholder="请选择"
                                clearable
                                @change="setName"
                                :disabled="operationType !== 'add'"
                            >
                                <el-option
                                    v-for="item in codeList"
                                    :key="item.deviceModel"
                                    :label="item.deviceName"
                                    :value="item.deviceModel"
                                >
                                    {{item.deviceName + '--' + item.deviceModel}}
                                </el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="每日限制水量:" prop="waterDayLimit">
                            <el-input
                                v-model="form.waterDayLimit"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="额定流量:" prop="ratedRate">
                            <el-input
                                v-model="form.ratedRate"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="实际流量:" prop="actualTraffic">
                            <el-input
                                v-model="form.actualTraffic"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="运行时间:" prop="runTime">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.runTime"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="当日用水量:" prop="waterDay">
                            <el-input
                                v-model="form.waterDay"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="水费单价:" prop="waterPrice">
                            <el-input
                                v-model="form.waterPrice"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水类型:" prop="type">
                            <el-select
                                v-model="form.type"
                                placeholder="请选择"
                                clearable
                            >
                                <el-option label="工业用水" value="industrial" />
                                <el-option label="生活用水" value="domestic" />
                                <el-option label="消防用水" value="fire" />
                                <el-option label="绿化用水" value="greening" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">确认</el-button>
                    <el-button @click="closeDia">取消</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, reactive, nextTick} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterEquipmentAdd, waterEquipmentUpdate} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const userStore = useUserStore();
const data = reactive({
    form: {
    deviceName: "",
    deviceModel: "",
    waterDayLimit: "",
    ratedRate: "",
    actualTraffic: "",
    runTime: "",
    waterDay: "",
        waterPrice: "",
    type: "",
    },
    rules: {
    deviceModel: [{ required: true, message: "请选择", trigger: "change" }],
        runTime: [{ required: true, message: "请选择", trigger: "change" }],
    waterDayLimit: [{ required: true, message: "请输入", trigger: "blur" }],
    ratedRate: [{ required: true, message: "请输入", trigger: "blur" }],
    actualTraffic: [{ required: true, message: "请输入", trigger: "blur" }],
    waterDay: [{ required: true, message: "请输入", trigger: "blur" }],
        waterPrice: [{ required: true, message: "请输入", trigger: "blur" }],
    type: [{ required: true, message: "请选择", trigger: "change" }],
    },
})
const { form, rules } = toRefs(data);
const codeList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    form.value = {}
    proxy.resetForm("formRef");
    waterDeviceList({size: -1}).then((res) => {
        codeList.value = res.data.records;
    });
    if (type === "edit") {
        form.value = {...row}
    }
}
const setName = (code) => {
    const index = codeList.value.findIndex(item => item.deviceModel === code);
    if (index > -1) {
        console.log(codeList)
        form.value.name = codeList.value[index].deviceName;
    }
}
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                waterEquipmentAdd(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
                    closeDia()
                })
            } else {
                waterEquipmentUpdate(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
                    closeDia()
                })
            }
        }
    })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
    proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/components/waterBillForm.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,210 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        title="水费管理"
        width="70%"
        @close="closeDia"
    >
            <el-form
                :model="form"
                label-width="140px"
                label-position="top"
                :rules="rules"
                ref="formRef"
            >
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="设备:" prop="code">
                            <el-select
                                v-model="form.code"
                                placeholder="请选择"
                                clearable
                                @change="setName"
                                :disabled="operationType !== 'add'"
                            >
                                <el-option
                                    v-for="item in codeList"
                                    :key="item.deviceModel"
                                    :label="item.deviceName"
                                    :value="item.deviceModel"
                                >
                                    {{item.deviceName + '--' + item.deviceModel}}
                                </el-option>
                            </el-select>
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水量:" prop="waterConsumption">
                            <el-input
                                v-model="form.waterConsumption"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="水费单价:" prop="waterPrice">
                            <el-input
                                v-model="form.waterPrice"
                                placeholder="请输入"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="水费金额:" prop="waterBill">
                            <el-input
                                v-model="form.waterBill"
                                placeholder="自动计算"
                                clearable
                                disabled
                            />
                        </el-form-item>
                    </el-col>
                </el-row>
                <el-row :gutter="30">
                    <el-col :span="12">
                        <el-form-item label="计费日期:" prop="billDate">
                            <el-date-picker
                                style="width: 100%"
                                v-model="form.billDate"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM-DD"
                                type="date"
                                placeholder="请选择"
                                clearable
                            />
                        </el-form-item>
                    </el-col>
                    <el-col :span="12">
                        <el-form-item label="用水类型:" prop="waterType">
                            <el-select
                                v-model="form.waterType"
                                placeholder="请选择"
                                clearable
                            >
                                <el-option label="工业用水" value="industrial" />
                                <el-option label="生活用水" value="domestic" />
                                <el-option label="消防用水" value="fire" />
                                <el-option label="绿化用水" value="greening" />
                            </el-select>
                        </el-form-item>
                    </el-col>
                </el-row>
            </el-form>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitForm">确认</el-button>
                    <el-button @click="closeDia">取消</el-button>
                </div>
            </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref, reactive, nextTick, watch} from "vue";
import useUserStore from "@/store/modules/user.js";
import {waterDeviceList, waterBillAdd, waterBillUpdate} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const userStore = useUserStore();
const data = reactive({
    form: {
        name: "",
        code: "",
        waterConsumption: "",
        waterPrice: "",
        waterBill: "",
        billDate: "",
        waterType: "",
    },
    rules: {
        code: [{ required: true, message: "请选择", trigger: "change" }],
        waterConsumption: [{ required: true, message: "请输入", trigger: "blur" }],
        waterPrice: [{ required: true, message: "请输入", trigger: "blur" }],
        billDate: [{ required: true, message: "请选择", trigger: "change" }],
        waterType: [{ required: true, message: "请选择", trigger: "change" }],
    },
})
const { form, rules } = toRefs(data);
const codeList = ref([])
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    form.value = {}
    proxy.resetForm("formRef");
    waterDeviceList().then((res) => {
        codeList.value = res.data;
    });
    if (type === "edit") {
        form.value = {...row}
    }
}
const setName = (code) => {
    const index = codeList.value.findIndex(item => item.deviceModel === code);
    if (index > -1) {
        console.log(codeList)
        form.value.name = codeList.value[index].deviceName;
    }
}
// è®¡ç®—水费金额
const calculateWaterBill = () => {
    if (form.value.waterConsumption && form.value.waterPrice) {
        form.value.waterBill = (parseFloat(form.value.waterConsumption) * parseFloat(form.value.waterPrice)).toFixed(2);
    }
}
// ç›‘听用水量和水费单价变化
watch([() => form.value.waterConsumption, () => form.value.waterPrice], () => {
    calculateWaterBill();
});
const submitForm = () => {
    proxy.$refs["formRef"].validate(valid => {
        if (valid) {
            if (operationType.value === "add") {
                waterBillAdd(form.value).then(response => {
                    proxy.$modal.msgSuccess("新增成功")
                    closeDia()
                })
            } else {
                waterBillUpdate(form.value).then(response => {
                    proxy.$modal.msgSuccess("修改成功")
                    closeDia()
                })
            }
        }
    })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
    proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
// èŽ·å–å½“å‰æ—¥æœŸå¹¶æ ¼å¼åŒ–ä¸º YYYY-MM-DD
function getCurrentDate() {
    const today = new Date();
    const year = today.getFullYear();
    const month = String(today.getMonth() + 1).padStart(2, "0"); // æœˆä»½ä»Ž0开始
    const day = String(today.getDate()).padStart(2, "0");
    return `${year}-${month}-${day}`;
}
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,312 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.deviceName"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="info" plain icon="Upload" @click="handleImport">导入</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
        <el-dialog
            :title="upload.title"
            v-model="upload.open"
            width="400px"
            append-to-body
            @close="handleDialogClose"
        >
            <el-upload
                ref="uploadRef"
                :limit="1"
                accept=".xlsx, .xls"
                :headers="upload.headers"
                :action="upload.url"
                :disabled="upload.isUploading"
                :before-upload="upload.beforeUpload"
                :on-progress="upload.onProgress"
                :on-success="upload.onSuccess"
                :on-error="upload.onError"
                :on-change="upload.onChange"
                :auto-upload="false"
                drag
            >
                <el-icon class="el-icon--upload"><upload-filled /></el-icon>
                <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                <template #tip>
                    <div class="el-upload__tip text-center">
                        <span>仅允许导入xls、xlsx格式文件。</span>
                        <el-link
                            type="primary"
                            :underline="false"
                            style="font-size: 12px; vertical-align: baseline"
                            @click="importTemplate"
                            >下载模板</el-link
                        >
                    </div>
                </template>
            </el-upload>
            <template #footer>
                <div class="dialog-footer">
                    <el-button type="primary" @click="submitFileForm">ç¡® å®š</el-button>
                    <el-button @click="upload.open = false">取 æ¶ˆ</el-button>
                </div>
            </template>
        </el-dialog>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, nextTick} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {getToken} from "@/utils/auth.js";
import {waterEquipmentDelete, waterEquipmentListPage} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance();
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "deviceName",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "deviceModel",
        width: 200,
    },
    {
        label: "额定流量",
        prop: "ratedRate",
    },
    {
        label: "实际流量",
        prop: "actualTraffic",
    },
    {
        label: "运行时间",
        prop: "runTime",
        width:150
    },
    {
        label: "当日用水量",
        prop: "waterDay",
        width: 150,
    },
    {
        label: "每日限制水量",
        prop: "waterDayLimit",
        width:220
    },
    {
        label: "水费单价",
        prop: "waterPrice",
        width: 120,
    },
    {
        dataType: "action",
        label: "操作",
        align: "center",
        fixed: 'right',
        operation: [
            {
                name: "编辑",
                type: "text",
                clickFun: (row) => {
                    openForm("edit", row);
                },
            },
        ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const formDia = ref()
const upload = reactive({
    // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚(客户导入)
    open: false,
    // å¼¹å‡ºå±‚标题(客户导入)
    title: "",
    // æ˜¯å¦ç¦ç”¨ä¸Šä¼ 
    isUploading: false,
    // è®¾ç½®ä¸Šä¼ çš„请求头部
    headers: { Authorization: "Bearer " + getToken() },
    // ä¸Šä¼ çš„地址
    url: import.meta.env.VITE_APP_BASE_API + "/waterRecord/importData",
    // æ–‡ä»¶ä¸Šä¼ å‰çš„回调
    beforeUpload: (file) => {
        console.log('文件即将上传', file);
        // å¯ä»¥åœ¨æ­¤å¤„做文件类型或大小校验
        const isValid = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' || file.name.endsWith('.xlsx') || file.name.endsWith('.xls');
        if (!isValid) {
            proxy.$modal.msgError("只能上传 Excel æ–‡ä»¶");
        }
        return isValid;
    },
    // æ–‡ä»¶çŠ¶æ€æ”¹å˜æ—¶çš„å›žè°ƒ
    onChange: (file, fileList) => {
        console.log('文件状态改变', file, fileList);
    },
    // æ–‡ä»¶ä¸Šä¼ æˆåŠŸæ—¶çš„å›žè°ƒ
    onSuccess: (response, file, fileList) => {
        console.log('上传成功', response, file, fileList);
        if(response.code === 200){
            proxy.$modal.msgSuccess("文件上传成功");
        }else if(response.code === 500){
            proxy.$modal.msgError(response.msg);
        }else{
            proxy.$modal.msgError("文件上传失败");
        }
        upload.open = false;
        getList();
    },
    // æ–‡ä»¶ä¸Šä¼ å¤±è´¥æ—¶çš„回调
    onError: (error, file, fileList) => {
        console.log('上传失败', error, file, fileList);
        proxy.$modal.msgError("文件上传失败");
        upload.open = false;
    },
    // æ–‡ä»¶ä¸Šä¼ è¿›åº¦æ”¹å˜æ—¶çš„回调
    onProgress: (event, file, fileList) => {
        console.log('上传进度', event, file, fileList);
        upload.isUploading = true;
    },
});
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    waterEquipmentListPage({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    }).catch(() => {
        tableLoading.value = false;
    })
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
/** å¯¼å…¥æŒ‰é’®æ“ä½œ */
function handleImport() {
    upload.title = "用水设备";
    upload.open = true;
    // æ¸…空上次上传的文件列表
    nextTick(() => {
        proxy.$refs["uploadRef"]?.clearFiles();
    });
}
function importTemplate() {
    proxy.download(
        "/waterRecord/export",
        {},
        '用水设备导入模版.xlsx'
    );
}
/** æäº¤ä¸Šä¼ æ–‡ä»¶ */
function submitFileForm() {
    proxy.$refs["uploadRef"].submit();
}
/** å¼¹æ¡†å…³é—­æ—¶æ¸…空文件列表 */
function handleDialogClose() {
    nextTick(() => {
        proxy.$refs["uploadRef"]?.clearFiles();
    });
}
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            tableLoading.value = true;
            waterEquipmentDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("删除成功");
                    getList();
                })
                .finally(() => {
                    tableLoading.value = false;
                });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/waterBill.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,181 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增</el-button>
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive, nextTick} from "vue";
import FormDia from "@/views/energyManagement/waterManagement/components/waterBillForm.vue";
import {ElMessageBox} from "element-plus";
import {waterBillDelete, waterBillListPage} from "@/api/energyManagement/waterManagement.js";
const { proxy } = getCurrentInstance();
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "name",
        width: 200,
    },
    {
        label: "规格型号",
        prop: "code",
        width: 200,
    },
    {
        label: "用水量",
        prop: "waterConsumption",
    },
    {
        label: "水费单价",
        prop: "waterPrice",
    },
    {
        label: "水费金额",
        prop: "waterBill",
        width:150
    },
    {
        label: "计费日期",
        prop: "billDate",
        width: 150,
    },
    {
        label: "用水类型",
        prop: "waterType",
        width:120
    },
    {
        dataType: "action",
        label: "操作",
        align: "center",
        fixed: 'right',
        operation: [
            {
                name: "编辑",
                type: "text",
                clickFun: (row) => {
                    openForm("edit", row);
                },
            },
        ],
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
const formDia = ref()
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    waterBillListPage({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    });
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "删除提示", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            tableLoading.value = true;
            waterBillDelete(ids)
                .then((res) => {
                    proxy.$modal.msgSuccess("删除成功");
                    getList();
                })
                .finally(() => {
                    tableLoading.value = false;
                });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/energyManagement/waterManagement/waterTrends.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">设备名称:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
            ></PIMTable>
        </div>
    </div>
</template>
<script setup>
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref, reactive} from "vue";
import {listPageByWaterTrend} from "@/api/energyManagement/waterManagement.js";
const data = reactive({
    searchForm: {
        name: "",
    },
});
const { searchForm } = toRefs(data);
const selectedRows = ref([]);
const tableColumn = ref([
    {
        label: "设备名称",
        prop: "name",
        width: 220,
    },
    {
        label: "规格型号",
        prop: "code",
        width: 220,
    },
    {
        label: "运行时间",
        prop: "runDate",
        width: 250,
    },
    {
        label: "昨日用水量",
        prop: "toDayNum",
    },
    {
        label: "本月平均水量",
        prop: "avgNum",
        width:150
    },
    {
        label: "趋势",
        prop: "trend",
        width: 220,
    },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    listPageByWaterTrend({ ...searchForm.value, ...page }).then((res) => {
        tableLoading.value = false;
        tableData.value = res.data.records;
        page.total = res.data.total;
    });
};
onMounted(() => {
    getList();
});
</script>
<style scoped>
</style>
src/views/equipmentManagement/gasTank/simple.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,566 @@
<template>
  <div class="app-container">
    <!-- é¡µé¢æ ‡é¢˜ -->
    <div class="page-header">
      <h2>重型罐式货车监控</h2>
      <div class="header-actions">
<!--        <el-button type="primary" @click="addTank">新增储罐</el-button>-->
<!--        <el-button @click="exportData">导出数据</el-button>-->
      </div>
    </div>
    <!-- å››ä¸ªä¸»è¦æ¨¡å— -->
    <div class="modules-container">
      <!-- 1. åŸºæœ¬ä¿¡æ¯æ¨¡å— -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>1. åŸºæœ¬ä¿¡æ¯</span>
                         <el-button type="text" @click="handleEditBasicInfo">编辑</el-button>
          </div>
        </template>
        <div class="info-grid">
          <div class="info-item">
            <label>储罐编号:</label>
            <span>{{ basicInfo.tankCode }}</span>
          </div>
          <div class="info-item">
            <label>储罐名称:</label>
            <span>{{ basicInfo.tankName }}</span>
          </div>
          <div class="info-item">
            <label>储罐类型:</label>
            <span>{{ basicInfo.tankType }}</span>
          </div>
          <div class="info-item">
            <label>设计压力:</label>
            <span>{{ basicInfo.designPressure }} MPa</span>
          </div>
          <div class="info-item">
            <label>工作压力:</label>
            <span>{{ basicInfo.workingPressure }} MPa</span>
          </div>
          <div class="info-item">
            <label>容积:</label>
            <span>{{ basicInfo.volume }} m³</span>
          </div>
        </div>
      </el-card>
      <!-- 2. ç›‘测参数模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>2. ç›‘测参数</span>
            <el-button type="text" @click="refreshMonitoring">刷新</el-button>
          </div>
        </template>
        <div class="monitoring-grid">
          <div class="monitor-item">
            <div class="monitor-label">压力</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.pressureStatus)">
              {{ monitoringData.pressure }} MPa
            </div>
            <div class="monitor-status">{{ monitoringData.pressureStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">温度</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.temperatureStatus)">
              {{ monitoringData.temperature }} â„ƒ
            </div>
            <div class="monitor-status">{{ monitoringData.temperatureStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">气体浓度</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.gasStatus)">
              {{ monitoringData.gasConcentration }} ppm
            </div>
            <div class="monitor-status">{{ monitoringData.gasStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
          <div class="monitor-item">
            <div class="monitor-label">流量</div>
            <div class="monitor-value" :class="getStatusClass(monitoringData.flowStatus)">
              {{ monitoringData.flow }} m³/h
            </div>
            <div class="monitor-status">{{ monitoringData.flowStatus === 'normal' ? '正常' : '异常' }}</div>
          </div>
        </div>
      </el-card>
      <!-- 3. å®‰å…¨è£…置模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>3. å®‰å…¨è£…ç½®</span>
            <el-button type="text" @click="checkSafetyDevices">检查</el-button>
          </div>
        </template>
        <div class="safety-grid">
          <div class="safety-item" v-for="device in safetyDevices" :key="device.name">
            <div class="device-info">
              <div class="device-name">{{ device.name }}</div>
              <div class="device-status" :class="device.status">
                {{ device.status === 'normal' ? '正常' : '异常' }}
              </div>
            </div>
          </div>
        </div>
      </el-card>
      <!-- 4. ç»´æŠ¤è®°å½•模块 -->
      <el-card class="module-card">
        <template #header>
          <div class="card-header">
            <span>4. ç»´æŠ¤è®°å½•</span>
            <el-button type="text" @click="addMaintenanceRecord">添加记录</el-button>
          </div>
        </template>
        <div class="maintenance-list">
          <div class="maintenance-item" v-for="record in maintenanceRecords" :key="record.id">
            <div class="record-header">
              <span class="record-date">{{ record.date }}</span>
              <el-tag :type="record.type === 'inspection' ? 'primary' : 'success'" size="small">
                {{ record.type === 'inspection' ? '检验' : '维护' }}
              </el-tag>
            </div>
            <div class="record-content">
              <div class="record-title">{{ record.title }}</div>
              <div class="record-desc">{{ record.description }}</div>
              <div class="record-operator">操作人:{{ record.operator }}</div>
            </div>
          </div>
        </div>
      </el-card>
    </div>
    <!-- ç¼–辑基本信息弹窗 -->
    <el-dialog v-model="basicInfoDialogVisible" title="编辑基本信息" width="600px">
      <el-form :model="editBasicInfo" label-width="120px">
        <el-form-item label="储罐编号">
          <el-input v-model="editBasicInfo.tankCode" />
        </el-form-item>
        <el-form-item label="储罐名称">
          <el-input v-model="editBasicInfo.tankName" />
        </el-form-item>
        <el-form-item label="储罐类型">
          <el-select v-model="editBasicInfo.tankType" style="width: 100%">
            <el-option label="液化气体储罐" value="液化气体储罐" />
            <el-option label="压力容器" value="压力容器" />
            <el-option label="常压储罐" value="常压储罐" />
          </el-select>
        </el-form-item>
        <el-form-item label="设计压力">
          <el-input-number v-model="editBasicInfo.designPressure" :precision="2" style="width: 100%" />
        </el-form-item>
        <el-form-item label="工作压力">
          <el-input-number v-model="editBasicInfo.workingPressure" :precision="2" style="width: 100%" />
        </el-form-item>
        <el-form-item label="容积">
          <el-input-number v-model="editBasicInfo.volume" :precision="2" style="width: 100%" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="basicInfoDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveBasicInfo">保存</el-button>
      </template>
    </el-dialog>
    <!-- æ·»åŠ ç»´æŠ¤è®°å½•å¼¹çª— -->
    <el-dialog v-model="maintenanceDialogVisible" title="添加维护记录" width="600px">
      <el-form :model="newMaintenanceRecord" label-width="120px">
        <el-form-item label="记录类型">
          <el-select v-model="newMaintenanceRecord.type" style="width: 100%">
            <el-option label="检验" value="inspection" />
            <el-option label="维护" value="maintenance" />
          </el-select>
        </el-form-item>
        <el-form-item label="标题">
          <el-input v-model="newMaintenanceRecord.title" />
        </el-form-item>
        <el-form-item label="描述">
          <el-input type="textarea" v-model="newMaintenanceRecord.description" :rows="3" />
        </el-form-item>
        <el-form-item label="操作人">
          <el-input v-model="newMaintenanceRecord.operator" />
        </el-form-item>
      </el-form>
      <template #footer>
        <el-button @click="maintenanceDialogVisible = false">取消</el-button>
        <el-button type="primary" @click="saveMaintenanceRecord">保存</el-button>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
// åŸºæœ¬ä¿¡æ¯
const basicInfo = reactive({
  tankCode: 'GT001',
  tankName: '液化气储罐A',
  tankType: '液化气体储罐',
  designPressure: 1.6,
  workingPressure: 0.8,
  volume: 100.5
})
// ç›‘测参数
const monitoringData = reactive({
  pressure: 0.8,
  pressureStatus: 'normal',
  temperature: 25.5,
  temperatureStatus: 'normal',
  gasConcentration: 0.1,
  gasStatus: 'normal',
  flow: 15.2,
  flowStatus: 'normal'
})
// å®‰å…¨è£…ç½®
const safetyDevices = ref([
  { name: '安全阀', status: 'normal' },
  { name: '压力传感器', status: 'normal' },
  { name: '温度传感器', status: 'normal' },
  { name: '气体检测器', status: 'normal' },
  { name: '爆破片', status: 'normal' },
  { name: '泄压装置', status: 'normal' }
])
// ç»´æŠ¤è®°å½•
const maintenanceRecords = ref([
  {
    id: 1,
    date: '2024-01-15',
    type: 'inspection',
    title: '年度检验',
    description: '按照TSG 21-2016标准进行年度检验,设备状态良好',
    operator: '张工程师'
  },
  {
    id: 2,
    date: '2024-02-20',
    type: 'maintenance',
    title: '安全阀维护',
    description: '更换安全阀密封圈,校准压力设定值',
    operator: '李技师'
  },
  {
    id: 3,
    date: '2024-03-10',
    type: 'inspection',
    title: '压力测试',
    description: '进行压力容器水压试验,符合设计要求',
    operator: '王检验员'
  }
])
// å¼¹çª—控制
const basicInfoDialogVisible = ref(false)
const maintenanceDialogVisible = ref(false)
// ç¼–辑表单数据
const editBasicInfo = reactive({ ...basicInfo })
const newMaintenanceRecord = reactive({
  type: 'inspection',
  title: '',
  description: '',
  operator: ''
})
// èŽ·å–çŠ¶æ€æ ·å¼ç±»
const getStatusClass = (status) => {
  return status === 'normal' ? 'status-normal' : 'status-warning'
}
// æ–°å¢žå‚¨ç½
const addTank = () => {
  ElMessage.success('新增储罐功能')
}
// å¯¼å‡ºæ•°æ®
const exportData = () => {
  ElMessage.success('导出成功')
}
// ç¼–辑基本信息
const handleEditBasicInfo = () => {
  Object.assign(editBasicInfo, basicInfo)
  basicInfoDialogVisible.value = true
}
// ä¿å­˜åŸºæœ¬ä¿¡æ¯
const saveBasicInfo = () => {
  Object.assign(basicInfo, editBasicInfo)
  basicInfoDialogVisible.value = false
  ElMessage.success('保存成功')
}
// åˆ·æ–°ç›‘测数据
const refreshMonitoring = () => {
  // æ¨¡æ‹Ÿæ•°æ®æ›´æ–°
  monitoringData.pressure = (Math.random() * 0.5 + 0.6).toFixed(2)
  monitoringData.temperature = (Math.random() * 10 + 20).toFixed(1)
  monitoringData.gasConcentration = (Math.random() * 0.2).toFixed(2)
  monitoringData.flow = (Math.random() * 10 + 10).toFixed(1)
  ElMessage.success('数据已刷新')
}
// æ£€æŸ¥å®‰å…¨è£…ç½®
const checkSafetyDevices = () => {
  // æ¨¡æ‹Ÿæ£€æŸ¥è¿‡ç¨‹
  safetyDevices.value.forEach(device => {
    device.status = Math.random() > 0.1 ? 'normal' : 'warning'
  })
  ElMessage.success('安全装置检查完成')
}
// æ·»åŠ ç»´æŠ¤è®°å½•
const addMaintenanceRecord = () => {
  newMaintenanceRecord.type = 'inspection'
  newMaintenanceRecord.title = ''
  newMaintenanceRecord.description = ''
  newMaintenanceRecord.operator = ''
  maintenanceDialogVisible.value = true
}
// ä¿å­˜ç»´æŠ¤è®°å½•
const saveMaintenanceRecord = () => {
  const record = {
    id: Date.now(),
    date: new Date().toISOString().split('T')[0],
    ...newMaintenanceRecord
  }
  maintenanceRecords.value.unshift(record)
  maintenanceDialogVisible.value = false
  ElMessage.success('记录添加成功')
}
// æ¨¡æ‹Ÿå®žæ—¶æ•°æ®æ›´æ–°
onMounted(() => {
  setInterval(() => {
    monitoringData.pressure = (Math.random() * 0.5 + 0.6).toFixed(2)
    monitoringData.temperature = (Math.random() * 10 + 20).toFixed(1)
    monitoringData.gasConcentration = (Math.random() * 0.2).toFixed(2)
    monitoringData.flow = (Math.random() * 10 + 10).toFixed(1)
  }, 5000)
})
</script>
<style lang="scss" scoped>
.app-container {
  padding: 20px;
  background: #f5f5f5;
  min-height: 100vh;
}
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  h2 {
    margin: 0;
    color: #303133;
  }
  .header-actions {
    display: flex;
    gap: 10px;
  }
}
.modules-container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}
.module-card {
  .card-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: bold;
    color: #303133;
  }
}
.info-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .info-item {
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background: #f8f9fa;
    border-radius: 4px;
    label {
      font-weight: bold;
      color: #606266;
    }
    span {
      color: #303133;
    }
  }
}
.monitoring-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .monitor-item {
    text-align: center;
    padding: 15px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 2px solid transparent;
    .monitor-label {
      font-size: 14px;
      color: #606266;
      margin-bottom: 8px;
    }
    .monitor-value {
      font-size: 20px;
      font-weight: bold;
      margin-bottom: 5px;
      &.status-normal {
        color: #67c23a;
      }
      &.status-warning {
        color: #e6a23c;
      }
    }
    .monitor-status {
      font-size: 12px;
      color: #909399;
    }
  }
}
.safety-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 15px;
  .safety-item {
    display: flex;
    align-items: center;
    padding: 15px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 2px solid transparent;
    .device-icon {
      margin-right: 15px;
    }
    .device-info {
      flex: 1;
      .device-name {
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .device-status {
        font-size: 12px;
        padding: 2px 8px;
        border-radius: 10px;
        display: inline-block;
        &.normal {
          background: #f0f9ff;
          color: #409eff;
        }
        &.warning {
          background: #fef7e0;
          color: #e6a23c;
        }
      }
    }
  }
}
.maintenance-list {
  max-height: 300px;
  overflow-y: auto;
  .maintenance-item {
    padding: 15px;
    border-bottom: 1px solid #ebeef5;
    margin-bottom: 10px;
    &:last-child {
      border-bottom: none;
      margin-bottom: 0;
    }
    .record-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 8px;
      .record-date {
        font-size: 14px;
        color: #909399;
      }
    }
    .record-content {
      .record-title {
        font-weight: bold;
        color: #303133;
        margin-bottom: 5px;
      }
      .record-desc {
        font-size: 14px;
        color: #606266;
        margin-bottom: 5px;
        line-height: 1.4;
      }
      .record-operator {
        font-size: 12px;
        color: #909399;
      }
    }
  }
}
// å“åº”式设计
@media (max-width: 1200px) {
  .modules-container {
    grid-template-columns: 1fr;
  }
}
@media (max-width: 768px) {
  .info-grid,
  .monitoring-grid,
  .safety-grid {
    grid-template-columns: 1fr;
  }
}
</style>
src/views/equipmentManagement/ledger/Form.vue
@@ -8,7 +8,7 @@
      </el-col>
      <el-col :span="12">
        <el-form-item label="规格型号" prop="deviceModel">
          <el-input v-model="form.deviceModel" placeholder="请输入规格型号" />
          <el-input v-model="form.deviceModel" :disabled="form.deviceModel != null ? true : false" placeholder="请输入规格型号" />
        </el-form-item>
      </el-col>
      <el-col :span="12">
src/views/equipmentManagement/ledger/index.vue
@@ -79,22 +79,17 @@
        @selection-change="handleSelectionChange"
        @pagination="changePage"
      >
        <template #operation="{ row }">
          <el-button type="primary" text @click="edit(row.id)" icon="editPen">
            ç¼–辑
          </el-button>
          <el-button
            type="danger"
            text
            icon="delete"
            @click="deleteRow(row.id)"
          >
            åˆ é™¤
          </el-button>
        </template>
      </PIMTable>
    </div>
    <Modal ref="modalRef" @success="getTableData"></Modal>
    <el-dialog v-model="qrDialogVisible" title="二维码" width="300px">
      <div style="text-align:center;">
        <img :src="qrCodeUrl" alt="二维码" style="width:200px;height:200px;" />
        <div style="margin:10px 0;">
          <el-button type="primary" @click="downloadQRCode">下载二维码图片</el-button>
        </div>
      </div>
    </el-dialog>
  </div>
</template>
@@ -106,6 +101,8 @@
import Modal from "./Modal.vue";
import { ElMessageBox, ElMessage } from "element-plus";
import dayjs from "dayjs";
import QRCode from "qrcode";
import { ref } from "vue";
defineOptions({
  name: "设备台账",
@@ -115,6 +112,10 @@
const multipleList = ref([]);
const { proxy } = getCurrentInstance();
const modalRef = ref();
const qrDialogVisible = ref(false);
const qrCodeUrl = ref("");
const qrRowData = ref(null);
const {
  filters,
  columns,
@@ -184,14 +185,29 @@
      align: "center",
      prop: "createTime",
    },
    {
      fixed: "right",
      label: "操作",
      dataType: "slot",
      slot: "operation",
      align: "center",
      width: "200px",
    },
        {
            dataType: "action",
            label: "操作",
            align: "center",
            fixed: 'right',
            width: 140,
            operation: [
                {
                    name: "编辑",
                    type: "text",
                    clickFun: (row) => {
                        edit(row.id)
                    },
                },
                {
                    name: "生成二维码",
                    type: "text",
                    clickFun: (row) => {
                        showQRCode(row)
                    },
                },
            ],
        },
  ]
);
@@ -253,6 +269,21 @@
    });
};
const showQRCode = async (row) => {
  // ä½ å¯ä»¥è‡ªå®šä¹‰äºŒç»´ç å†…容,比如 row.id æˆ– row.deviceName
  const qrContent = JSON.stringify(row); // æˆ– `${row.id}`
  qrCodeUrl.value = await QRCode.toDataURL(qrContent);
  qrRowData.value = row;
  qrDialogVisible.value = true;
};
const downloadQRCode = () => {
  const a = document.createElement("a");
  a.href = qrCodeUrl.value;
  a.download = `${qrRowData.value.deviceName || "二维码"}.png`;
  a.click();
};
onMounted(() => {
  filters.entryDate = [
    dayjs().format("YYYY-MM-DD"),
src/views/inventoryManagement/stockWarning/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1137 @@
<template>
  <div class="app-container">
    <!-- æœç´¢è¡¨å• -->
    <div class="search_form">
      <el-form :model="searchForm" :inline="true">
        <el-form-item label="储气罐名称:">
          <el-input v-model="searchForm.tankName" placeholder="请输入储气罐名称" clearable style="width: 200px" />
        </el-form-item>
        <el-form-item label="储气罐类型:">
          <el-select v-model="searchForm.tankType" placeholder="请选择储气罐类型" clearable style="width: 200px">
            <el-option label="液化气储罐" value="液化气储罐" />
            <el-option label="压缩气储罐" value="压缩气储罐" />
            <el-option label="天然气储罐" value="天然气储罐" />
            <el-option label="氧气储罐" value="氧气储罐" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警类型:">
          <el-select v-model="searchForm.warningType" placeholder="请选择预警类型" clearable style="width: 200px">
            <el-option label="气体不足" value="气体不足" />
            <el-option label="压力异常" value="压力异常" />
            <el-option label="温度异常" value="温度异常" />
            <el-option label="泄漏预警" value="泄漏预警" />
          </el-select>
        </el-form-item>
        <el-form-item label="预警级别:">
          <el-select v-model="searchForm.warningLevel" placeholder="请选择预警级别" clearable style="width: 200px">
            <el-option label="紧急" value="紧急" />
            <el-option label="重要" value="重要" />
            <el-option label="一般" value="一般" />
          </el-select>
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="handleQuery">搜索</el-button>
          <el-button @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>
    </div>
    <!-- æ•°æ®è¡¨æ ¼ -->
    <div class="table_list">
      <!-- æ“ä½œæŒ‰é’® -->
      <div class="table-operations">
        <el-button type="primary" @click="handleAdd">新增预警规则</el-button>
        <el-button type="success" @click="handleBatchProcess">批量处理</el-button>
        <el-button @click="handleExport">导出</el-button>
      </div>
      <el-table
        :data="tableData"
        border
        v-loading="tableLoading"
        @selection-change="handleSelectionChange"
        style="width: 100%"
        height="calc(100vh - 280px)"
      >
        <el-table-column align="center" type="selection" width="55" />
        <el-table-column align="center" label="序号" type="index" width="60" />
        <!-- åŸºç¡€ä¿¡æ¯å­—段 -->
        <el-table-column label="储气罐编码" prop="tankCode" width="120" show-overflow-tooltip />
        <el-table-column label="储气罐名称" prop="tankName" width="200" show-overflow-tooltip />
        <el-table-column label="储气罐类型" prop="tankType" width="120" show-overflow-tooltip />
        <el-table-column label="规格型号" prop="specificationModel" width="150" show-overflow-tooltip />
        <el-table-column label="容积(m³)" prop="volume" width="100" show-overflow-tooltip />
        <!-- åº“存相关字段 -->
        <el-table-column label="当前气体量" prop="currentGasLevel" width="120" show-overflow-tooltip>
          <template #default="scope">
            <span :class="getGasLevelClass(scope.row)">{{ scope.row.currentGasLevel }}%</span>
          </template>
        </el-table-column>
        <el-table-column label="安全气体量" prop="safetyGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最低气体量" prop="minGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="最高气体量" prop="maxGasLevel" width="120" show-overflow-tooltip />
        <el-table-column label="当前压力(MPa)" prop="currentPressure" width="140" show-overflow-tooltip />
        <!-- é¢„警规则字段 -->
        <el-table-column label="预警类型" prop="warningType" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningTypeTag(scope.row.warningType)">
              {{ scope.row.warningType }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警级别" prop="warningLevel" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-tag :type="getWarningLevelTag(scope.row.warningLevel)">
              {{ scope.row.warningLevel }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="预警阈值" prop="warningThreshold" width="100" show-overflow-tooltip />
        <el-table-column label="是否启用" prop="isEnabled" width="100" show-overflow-tooltip>
          <template #default="scope">
            <el-switch v-model="scope.row.isEnabled" @change="handleEnableChange(scope.row)" />
          </template>
        </el-table-column>
        <!-- æ—¶é—´ç›¸å…³å­—段 -->
        <el-table-column label="预警时间" prop="warningTime" width="150" show-overflow-tooltip />
        <el-table-column label="预警持续天数" prop="warningDuration" width="120" show-overflow-tooltip />
        <el-table-column label="最后更新时间" prop="lastUpdateTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计充装时间" prop="expectedRefillTime" width="150" show-overflow-tooltip />
        <el-table-column label="预计缺气时间" prop="expectedShortageTime" width="150" show-overflow-tooltip>
          <template #default="scope">
            <div v-if="scope.row.expectedShortageTime">
              <div v-if="getCountdown(scope.row.expectedShortageTime).isExpired" class="countdown-expired">
                <el-tag type="danger">已缺气</el-tag>
              </div>
              <div v-else class="countdown-timer">
                <span :class="getCountdownClass(scope.row.expectedShortageTime)">
                  {{ getCountdown(scope.row.expectedShortageTime).text }}
                </span>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
        <!-- æ“ä½œåˆ— -->
        <el-table-column fixed="right" label="操作" width="200" align="center">
          <template #default="scope">
            <el-button link type="primary" size="small" @click="handleEdit(scope.row)">编辑</el-button>
            <el-button link type="success" size="small" @click="handleProcess(scope.row)">处理</el-button>
            <el-button link type="danger" size="small" @click="handleDelete(scope.row)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>
      <!-- åˆ†é¡µ -->
      <pagination
        v-show="total > 0"
        :total="total"
        layout="total, sizes, prev, pager, next, jumper"
        :page="page.current"
        :limit="page.size"
        @pagination="paginationChange"
      />
    </div>
    <!-- æ–°å¢ž/编辑预警规则弹窗 -->
    <el-dialog
      v-model="dialogFormVisible"
      :title="operationType === 'add' ? '新增预警规则' : '编辑预警规则'"
      width="50%"
      @close="closeDialog"
    >
      <el-form :model="form" :rules="rules" ref="formRef" label-width="140px">
        <el-row :gutter="20">
          <!-- åŸºç¡€ä¿¡æ¯ -->
          <el-col :span="12">
            <el-form-item label="储气罐编码:" prop="tankCode">
              <el-input v-model="form.tankCode" placeholder="请输入储气罐编码" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="储气罐名称:" prop="tankName">
              <el-input v-model="form.tankName" placeholder="请输入储气罐名称" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="储气罐类型:" prop="tankType">
              <el-select v-model="form.tankType" placeholder="请选择储气罐类型" style="width: 100%">
                <el-option label="液化气储罐" value="液化气储罐" />
                <el-option label="压缩气储罐" value="压缩气储罐" />
                <el-option label="天然气储罐" value="天然气储罐" />
                <el-option label="氧气储罐" value="氧气储罐" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="规格型号:" prop="specificationModel">
              <el-input v-model="form.specificationModel" placeholder="请输入规格型号" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="容积(m³):" prop="volume">
              <el-input-number v-model="form.volume" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前气体量(%):" prop="currentGasLevel">
              <el-input-number v-model="form.currentGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- åº“存相关 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="安全气体量(%):" prop="safetyGasLevel">
              <el-input-number v-model="form.safetyGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="最低气体量(%):" prop="minGasLevel">
              <el-input-number v-model="form.minGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="最高气体量(%):" prop="maxGasLevel">
              <el-input-number v-model="form.maxGasLevel" :min="0" :max="100" :precision="1" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前压力(MPa):" prop="currentPressure">
              <el-input-number v-model="form.currentPressure" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- é¢„警规则 -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警类型:" prop="warningType">
              <el-select v-model="form.warningType" placeholder="请选择预警类型" style="width: 100%">
                <el-option label="气体不足" value="气体不足" />
                <el-option label="压力异常" value="压力异常" />
                <el-option label="温度异常" value="温度异常" />
                <el-option label="泄漏预警" value="泄漏预警" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警级别:" prop="warningLevel">
              <el-select v-model="form.warningLevel" placeholder="请选择预警级别" style="width: 100%">
                <el-option label="紧急" value="紧急" />
                <el-option label="重要" value="重要" />
                <el-option label="一般" value="一般" />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警阈值:" prop="warningThreshold">
              <el-input-number v-model="form.warningThreshold" :min="0" :precision="2" style="width: 100%" />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="是否启用:" prop="isEnabled">
              <el-switch v-model="form.isEnabled" />
            </el-form-item>
          </el-col>
        </el-row>
        <!-- æ—¶é—´ç›¸å…³ -->
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预警时间:" prop="warningTime">
              <el-date-picker
                v-model="form.warningTime"
                type="datetime"
                placeholder="请选择预警时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预计充装时间:" prop="expectedRefillTime">
              <el-date-picker
                v-model="form.expectedRefillTime"
                type="datetime"
                placeholder="请选择预计充装时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="20">
          <el-col :span="12">
            <el-form-item label="预计缺气时间:" prop="expectedShortageTime">
              <el-date-picker
                v-model="form.expectedShortageTime"
                type="datetime"
                placeholder="请选择预计缺气时间"
                style="width: 100%"
                value-format="YYYY-MM-DD HH:mm:ss"
              />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="预警规则描述:" prop="warningRule">
              <el-input
                v-model="form.warningRule"
                type="textarea"
                :rows="3"
                placeholder="请输入预警规则描述"
              />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button @click="closeDialog">取消</el-button>
          <el-button type="primary" @click="submitForm">确认</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
    <!-- ç¼ºæ°”预警弹框 -->
    <el-dialog
      v-model="shortageWarningVisible"
      title="⚠️ ç¼ºæ°”预警"
      width="400px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :show-close="false"
    >
      <div class="shortage-warning-content">
        <div class="warning-icon">
          <el-icon size="48" color="#f56c6c"><WarningFilled /></el-icon>
        </div>
        <div class="warning-message">
          <h3>{{ currentWarningTank.tankName }}</h3>
          <p>储气罐已缺气,请及时处理!</p>
          <p class="warning-details">
            å‚¨æ°”罐编码:{{ currentWarningTank.tankCode }}<br>
            å‚¨æ°”罐类型:{{ currentWarningTank.tankType }}<br>
            å½“前气体量:{{ currentWarningTank.currentGasLevel }}%
          </p>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="handleShortageWarning">立即处理</el-button>
          <el-button @click="closeShortageWarning">稍后处理</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { WarningFilled } from '@element-plus/icons-vue'
import pagination from '@/components/PIMTable/Pagination.vue'
// æ³¨é‡ŠæŽ‰API导入,使用假数据
// import {
//   getStockWarningPage,
//   addStockWarning,
//   updateStockWarning,
//   deleteStockWarning,
//   batchProcessStockWarning,
//   exportStockWarning,
//   toggleStockWarningStatus
// } from '@/api/inventoryManagement/stockWarning.js'
const { proxy } = getCurrentInstance()
// å“åº”式数据
const tableData = ref([])
const tableLoading = ref(false)
const selectedRows = ref([])
const dialogFormVisible = ref(false)
const operationType = ref('add')
const total = ref(0)
// ç¼ºæ°”预警相关
const shortageWarningVisible = ref(false)
const currentWarningTank = ref({})
const countdownTimer = ref(null)
// åˆ†é¡µå‚æ•°
const page = reactive({
  current: 1,
  size: 10
})
// æœç´¢è¡¨å•
const searchForm = reactive({
  tankName: '',
  tankType: '',
  warningType: '',
  warningLevel: ''
})
// è¡¨å•数据
const form = reactive({
  id: null,
  tankCode: '',
  tankName: '',
  tankType: '',
  specificationModel: '',
  volume: 0,
  currentGasLevel: 0,
  safetyGasLevel: 0,
  minGasLevel: 0,
  maxGasLevel: 0,
  currentPressure: 0,
  warningType: '',
  warningLevel: '',
  warningThreshold: 0,
  isEnabled: true,
  warningTime: '',
  warningDuration: 0,
  lastUpdateTime: '',
  expectedRefillTime: '',
  expectedShortageTime: '',
  warningRule: ''
})
// è¡¨å•验证规则
const rules = {
  tankCode: [{ required: true, message: '请输入储气罐编码', trigger: 'blur' }],
  tankName: [{ required: true, message: '请输入储气罐名称', trigger: 'blur' }],
  tankType: [{ required: true, message: '请选择储气罐类型', trigger: 'change' }],
  warningType: [{ required: true, message: '请选择预警类型', trigger: 'change' }],
  warningLevel: [{ required: true, message: '请选择预警级别', trigger: 'change' }],
  warningThreshold: [{ required: true, message: '请输入预警阈值', trigger: 'blur' }]
}
// èŽ·å–å€’è®¡æ—¶ä¿¡æ¯
const getCountdown = (expectedTime) => {
  if (!expectedTime) return { text: '-', isExpired: false }
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return { text: '已缺气', isExpired: true }
  }
  const days = Math.floor(diff / (1000 * 60 * 60 * 24))
  const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))
  const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
  if (days > 0) {
    return { text: `${days}天${hours}小时`, isExpired: false }
  } else if (hours > 0) {
    return { text: `${hours}小时${minutes}分钟`, isExpired: false }
  } else {
    return { text: `${minutes}分钟`, isExpired: false }
  }
}
// èŽ·å–å€’è®¡æ—¶æ ·å¼ç±»
const getCountdownClass = (expectedTime) => {
  if (!expectedTime) return ''
  const now = new Date().getTime()
  const expected = new Date(expectedTime).getTime()
  const diff = expected - now
  if (diff <= 0) {
    return 'countdown-expired'
  } else if (diff <= 24 * 60 * 60 * 1000) { // 24小时内
    return 'countdown-urgent'
  } else if (diff <= 7 * 24 * 60 * 60 * 1000) { // 7天内
    return 'countdown-warning'
  } else {
    return 'countdown-normal'
  }
}
// æ£€æŸ¥ç¼ºæ°”预警
const checkShortageWarnings = () => {
  tableData.value.forEach(tank => {
    if (tank.expectedShortageTime) {
      const countdown = getCountdown(tank.expectedShortageTime)
      if (countdown.isExpired && !tank.warningShown) {
        // æ ‡è®°å·²æ˜¾ç¤ºé¢„警,避免重复弹框
        tank.warningShown = true
        showShortageWarning(tank)
      }
    }
  })
}
// æ˜¾ç¤ºç¼ºæ°”预警弹框
const showShortageWarning = (tank) => {
  currentWarningTank.value = tank
  shortageWarningVisible.value = true
  // æ’­æ”¾æç¤ºéŸ³ï¼ˆå¯é€‰ï¼‰
  // const audio = new Audio('/path/to/warning-sound.mp3')
  // audio.play()
}
// å¤„理缺气预警
const handleShortageWarning = () => {
  ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// å¤„理缺气预警
const closeShortageWarning = () => {
  // ElMessage.success(`正在处理储气罐 ${currentWarningTank.value.tankName} çš„缺气问题`)
  shortageWarningVisible.value = false
  // è¿™é‡Œå¯ä»¥è°ƒç”¨å¤„理API
}
// ç”Ÿæˆå‡æ•°æ®
const generateMockData = () => {
  const mockData = [
    {
      id: 1,
      tankCode: 'TANK001',
      tankName: '液化气储罐A',
      tankType: '液化气储罐',
      specificationModel: 'LPG-5000L',
      volume: 5000,
      currentGasLevel: 15,
      safetyGasLevel: 30,
      minGasLevel: 10,
      maxGasLevel: 95,
      currentPressure: 2.5,
      warningType: '气体不足',
      warningLevel: '紧急',
      warningThreshold: 20,
      isEnabled: true,
      warningTime: '2024-01-15 08:30:00',
      warningDuration: 3,
      lastUpdateTime: '2024-01-15 10:00:00',
      expectedRefillTime: '2024-01-16 14:00:00',
      expectedShortageTime: '2024-01-15 18:30:00', // ä»Šå¤©ä¸‹åˆ6:30缺气
      warningRule: '当气体量低于20%时触发预警'
    },
    {
      id: 2,
      tankCode: 'TANK002',
      tankName: '压缩气储罐B',
      tankType: '压缩气储罐',
      specificationModel: 'COMP-3000L',
      volume: 3000,
      currentGasLevel: 45,
      safetyGasLevel: 25,
      minGasLevel: 15,
      maxGasLevel: 90,
      currentPressure: 8.2,
      warningType: '压力异常',
      warningLevel: '重要',
      warningThreshold: 10,
      isEnabled: true,
      warningTime: '2024-01-14 16:20:00',
      warningDuration: 2,
      lastUpdateTime: '2024-01-15 09:15:00',
      expectedRefillTime: '2024-01-17 09:00:00',
      expectedShortageTime: '2024-01-18 12:00:00', // 3天后缺气
      warningRule: '当压力超过8MPa时触发预警'
    },
    {
      id: 3,
      tankCode: 'TANK003',
      tankName: '天然气储罐C',
      tankType: '天然气储罐',
      specificationModel: 'NG-8000L',
      volume: 8000,
      currentGasLevel: 75,
      safetyGasLevel: 20,
      minGasLevel: 10,
      maxGasLevel: 95,
      currentPressure: 4.8,
      warningType: '温度异常',
      warningLevel: '一般',
      warningThreshold: 5,
      isEnabled: true,
      warningTime: '2024-01-13 11:45:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 08:45:00',
      expectedRefillTime: '2024-01-20 10:00:00',
      expectedShortageTime: '2024-01-22 15:30:00', // 7天后缺气
      warningRule: '当温度超过60°C时触发预警'
    },
    {
      id: 4,
      tankCode: 'TANK004',
      tankName: '氧气储罐D',
      tankType: '氧气储罐',
      specificationModel: 'O2-2000L',
      volume: 2000,
      currentGasLevel: 8,
      safetyGasLevel: 25,
      minGasLevel: 5,
      maxGasLevel: 90,
      currentPressure: 6.5,
      warningType: '泄漏预警',
      warningLevel: '紧急',
      warningThreshold: 15,
      isEnabled: true,
      warningTime: '2024-01-15 07:15:00',
      warningDuration: 4,
      lastUpdateTime: '2024-01-15 11:30:00',
      expectedRefillTime: '2024-01-15 16:00:00',
      expectedShortageTime: '2024-01-15 14:00:00', // ä»Šå¤©ä¸‹åˆ2点缺气
      warningRule: '当检测到气体泄漏时触发预警'
    },
    {
      id: 5,
      tankCode: 'TANK005',
      tankName: '液化气储罐E',
      tankType: '液化气储罐',
      specificationModel: 'LPG-6000L',
      volume: 6000,
      currentGasLevel: 35,
      safetyGasLevel: 30,
      minGasLevel: 15,
      maxGasLevel: 95,
      currentPressure: 3.2,
      warningType: '气体不足',
      warningLevel: '重要',
      warningThreshold: 20,
      isEnabled: false,
      warningTime: '2024-01-14 14:30:00',
      warningDuration: 2,
      lastUpdateTime: '2024-01-15 09:00:00',
      expectedRefillTime: '2024-01-19 08:00:00',
      expectedShortageTime: '2024-01-21 10:00:00', // 6天后缺气
      warningRule: '当气体量低于20%时触发预警'
    },
    {
      id: 6,
      tankCode: 'TANK006',
      tankName: '压缩气储罐F',
      tankType: '压缩气储罐',
      specificationModel: 'COMP-4000L',
      volume: 4000,
      currentGasLevel: 85,
      safetyGasLevel: 20,
      minGasLevel: 10,
      maxGasLevel: 90,
      currentPressure: 7.8,
      warningType: '压力异常',
      warningLevel: '一般',
      warningThreshold: 8,
      isEnabled: true,
      warningTime: '2024-01-12 09:20:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 08:30:00',
      expectedRefillTime: '2024-01-25 14:00:00',
      expectedShortageTime: '2024-01-28 16:00:00', // 13天后缺气
      warningRule: '当压力超过8MPa时触发预警'
    },
    {
      id: 7,
      tankCode: 'TANK007',
      tankName: '天然气储罐G',
      tankType: '天然气储罐',
      specificationModel: 'NG-10000L',
      volume: 10000,
      currentGasLevel: 92,
      safetyGasLevel: 15,
      minGasLevel: 8,
      maxGasLevel: 95,
      currentPressure: 5.2,
      warningType: '温度异常',
      warningLevel: '重要',
      warningThreshold: 6,
      isEnabled: true,
      warningTime: '2024-01-11 16:45:00',
      warningDuration: 1,
      lastUpdateTime: '2024-01-15 07:45:00',
      expectedRefillTime: '2024-01-30 09:00:00',
      expectedShortageTime: '2024-02-05 12:00:00', // 21天后缺气
      warningRule: '当温度超过60°C时触发预警'
    },
    {
      id: 8,
      tankCode: 'TANK008',
      tankName: '氧气储罐H',
      tankType: '氧气储罐',
      specificationModel: 'O2-1500L',
      volume: 1500,
      currentGasLevel: 12,
      safetyGasLevel: 30,
      minGasLevel: 8,
      maxGasLevel: 90,
      currentPressure: 4.5,
      warningType: '泄漏预警',
      warningLevel: '紧急',
      warningThreshold: 12,
      isEnabled: true,
      warningTime: '2024-01-15 06:30:00',
      warningDuration: 5,
      lastUpdateTime: '2024-01-15 12:15:00',
      expectedRefillTime: '2024-01-15 20:00:00',
      expectedShortageTime: '2024-01-15 17:30:00', // ä»Šå¤©ä¸‹åˆ5:30缺气
      warningRule: '当检测到气体泄漏时触发预警'
    }
  ]
  // æ ¹æ®æœç´¢æ¡ä»¶è¿‡æ»¤æ•°æ®
  let filteredData = mockData.filter(item => {
    if (searchForm.tankName && !item.tankName.includes(searchForm.tankName)) return false
    if (searchForm.tankType && item.tankType !== searchForm.tankType) return false
    if (searchForm.warningType && item.warningType !== searchForm.warningType) return false
    if (searchForm.warningLevel && item.warningLevel !== searchForm.warningLevel) return false
    return true
  })
  // åˆ†é¡µå¤„理
  const start = (page.current - 1) * page.size
  const end = start + page.size
  const paginatedData = filteredData.slice(start, end)
  return {
    records: paginatedData,
    total: filteredData.length
  }
}
// èŽ·å–åˆ—è¡¨æ•°æ®
const getList = async () => {
  tableLoading.value = true
  try {
    // æ¨¡æ‹Ÿç½‘络延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    const result = generateMockData()
    tableData.value = result.records
    total.value = result.total
    // æ£€æŸ¥ç¼ºæ°”预警
    checkShortageWarnings()
  } catch (error) {
    console.error('获取列表失败:', error)
    ElMessage.error('获取列表失败')
  } finally {
    tableLoading.value = false
  }
}
// æœç´¢
const handleQuery = () => {
  page.current = 1
  getList()
}
// é‡ç½®æœç´¢
const resetQuery = () => {
  Object.keys(searchForm).forEach(key => {
    searchForm[key] = ''
  })
  handleQuery()
}
// åˆ†é¡µå˜åŒ–
const paginationChange = (obj) => {
  page.current = obj.page
  page.size = obj.limit
  getList()
}
// è¡¨æ ¼é€‰æ‹©å˜åŒ–
const handleSelectionChange = (selection) => {
  selectedRows.value = selection
}
// æ–°å¢ž
const handleAdd = () => {
  operationType.value = 'add'
  resetForm()
  dialogFormVisible.value = true
}
// ç¼–辑
const handleEdit = (row) => {
  operationType.value = 'edit'
  Object.assign(form, row)
  dialogFormVisible.value = true
}
// å¤„理预警
const handleProcess = async (row) => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 300))
    ElMessage.success(`正在处理预警:${row.tankName}`)
    getList()
  } catch (error) {
    ElMessage.error('处理预警失败')
  }
}
// åˆ é™¤
const handleDelete = async (row) => {
  try {
    await ElMessageBox.confirm(`确定要删除预警规则:${row.tankName}吗?`, '提示', {
      confirmButtonText: '确定',
      cancelButtonText: '取消',
      type: 'warning'
    })
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 300))
    ElMessage.success('删除成功')
    getList()
  } catch (error) {
    if (error !== 'cancel') {
      ElMessage.error('删除失败')
    }
  }
}
// æ‰¹é‡å¤„理
const handleBatchProcess = async () => {
  if (selectedRows.value.length === 0) {
    ElMessage.warning('请选择要处理的预警')
    return
  }
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    ElMessage.success(`批量处理了 ${selectedRows.value.length} æ¡é¢„è­¦`)
    getList()
  } catch (error) {
    ElMessage.error('批量处理失败')
  }
}
// å¯¼å‡º
const handleExport = async () => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 800))
    // ç”Ÿæˆå¯¼å‡ºæ•°æ®
    const exportData = generateMockData().records
    const csvContent = generateCSV(exportData)
    // åˆ›å»ºä¸‹è½½é“¾æŽ¥
    const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
    const url = window.URL.createObjectURL(blob)
    const link = document.createElement('a')
    link.href = url
    link.download = `储气罐预警数据_${new Date().getTime()}.csv`
    link.click()
    window.URL.revokeObjectURL(url)
    ElMessage.success('导出成功')
  } catch (error) {
    ElMessage.error('导出失败')
  }
}
// ç”ŸæˆCSV内容
const generateCSV = (data) => {
  const headers = [
    '储气罐编码', '储气罐名称', '储气罐类型', '规格型号', '容积(m³)',
    '当前气体量(%)', '安全气体量(%)', '最低气体量(%)', '最高气体量(%)',
    '当前压力(MPa)', '预警类型', '预警级别', '预警阈值', '是否启用',
    '预警时间', '预警持续天数', '最后更新时间', '预计充装时间', '预计缺气时间', '预警规则描述'
  ]
  const csvRows = [headers.join(',')]
  data.forEach(item => {
    const row = [
      item.tankCode,
      item.tankName,
      item.tankType,
      item.specificationModel,
      item.volume,
      item.currentGasLevel,
      item.safetyGasLevel,
      item.minGasLevel,
      item.maxGasLevel,
      item.currentPressure,
      item.warningType,
      item.warningLevel,
      item.warningThreshold,
      item.isEnabled ? '是' : '否',
      item.warningTime,
      item.warningDuration,
      item.lastUpdateTime,
      item.expectedRefillTime,
      item.expectedShortageTime,
      item.warningRule
    ]
    csvRows.push(row.join(','))
  })
  return csvRows.join('\n')
}
// å¯ç”¨çŠ¶æ€å˜åŒ–
const handleEnableChange = async (row) => {
  try {
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 200))
    ElMessage.success(`${row.tankName} çš„启用状态已更新`)
  } catch (error) {
    ElMessage.error('状态更新失败')
    // æ¢å¤åŽŸçŠ¶æ€
    row.isEnabled = !row.isEnabled
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    await proxy.$refs.formRef.validate()
    // æ¨¡æ‹ŸAPI调用延迟
    await new Promise(resolve => setTimeout(resolve, 500))
    if (operationType.value === 'add') {
      ElMessage.success('新增成功')
    } else {
      ElMessage.success('编辑成功')
    }
    closeDialog()
    getList()
  } catch (error) {
    if (!error.errors) {
      ElMessage.error(operationType.value === 'add' ? '新增失败' : '编辑失败')
    }
  }
}
// å…³é—­å¼¹çª—
const closeDialog = () => {
  dialogFormVisible.value = false
  resetForm()
}
// é‡ç½®è¡¨å•
const resetForm = () => {
  Object.keys(form).forEach(key => {
    if (key === 'isEnabled') {
      form[key] = true
    } else if (typeof form[key] === 'number') {
      form[key] = 0
    } else {
      form[key] = ''
    }
  })
  proxy.$refs.formRef?.resetFields()
}
// èŽ·å–æ°”ä½“é‡æ ·å¼ç±»
const getGasLevelClass = (row) => {
  if (row.currentGasLevel < row.minGasLevel) {
    return 'text-danger'
  } else if (row.currentGasLevel > row.maxGasLevel) {
    return 'text-warning'
  }
  return 'text-success'
}
// èŽ·å–é¢„è­¦ç±»åž‹æ ‡ç­¾æ ·å¼
const getWarningTypeTag = (type) => {
  const typeMap = {
    '气体不足': 'danger',
    '压力异常': 'warning',
    '温度异常': 'info',
    '泄漏预警': 'danger'
  }
  return typeMap[type] || 'info'
}
// èŽ·å–é¢„è­¦çº§åˆ«æ ‡ç­¾æ ·å¼
const getWarningLevelTag = (level) => {
  const levelMap = {
    '紧急': 'danger',
    '重要': 'warning',
    '一般': 'info'
  }
  return levelMap[level] || 'info'
}
// å¯åŠ¨å€’è®¡æ—¶å®šæ—¶å™¨
const startCountdownTimer = () => {
  countdownTimer.value = setInterval(() => {
    checkShortageWarnings()
  }, 60000) // æ¯åˆ†é’Ÿæ£€æŸ¥ä¸€æ¬¡
}
// åœæ­¢å€’计时定时器
const stopCountdownTimer = () => {
  if (countdownTimer.value) {
    clearInterval(countdownTimer.value)
    countdownTimer.value = null
  }
}
// é¡µé¢åŠ è½½
onMounted(() => {
  getList()
  startCountdownTimer()
})
// é¡µé¢å¸è½½
onUnmounted(() => {
  stopCountdownTimer()
})
</script>
<style scoped lang="scss">
.app-container {
  padding: 20px;
  .table-operations {
    text-align: right;
    margin-bottom: 20px;
    .el-button {
      margin-right: 10px;
    }
  }
  .table_list {
    background: #fff;
    border-radius: 4px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  }
  .text-danger {
    color: #f56c6c;
    font-weight: bold;
  }
  .text-warning {
    color: #e6a23c;
    font-weight: bold;
  }
  .text-success {
    color: #67c23a;
    font-weight: bold;
  }
  .dialog-footer {
    text-align: right;
  }
  // å€’计时样式
  .countdown-timer {
    font-weight: bold;
  }
  .countdown-normal {
    color: #67c23a;
  }
  .countdown-warning {
    color: #e6a23c;
  }
  .countdown-urgent {
    color: #f56c6c;
    animation: blink 1s infinite;
  }
  .countdown-expired {
    color: #f56c6c;
    font-weight: bold;
  }
  @keyframes blink {
    0%, 50% { opacity: 1; }
    51%, 100% { opacity: 0.5; }
  }
  // ç¼ºæ°”预警弹框样式
  .shortage-warning-content {
    text-align: center;
    padding: 20px 0;
    .warning-icon {
      margin-bottom: 20px;
    }
    .warning-message {
      h3 {
        color: #f56c6c;
        margin-bottom: 10px;
      }
      p {
        margin-bottom: 10px;
        color: #606266;
      }
      .warning-details {
        background: #f5f7fa;
        padding: 15px;
        border-radius: 4px;
        text-align: left;
        font-size: 14px;
        line-height: 1.6;
      }
    }
  }
}
</style>
src/views/login.vue
@@ -181,7 +181,7 @@
<style lang='scss' scoped>
.login {
  height: 100%;
  background-image: url("../assets/indexViews/JZYJView.png");
  background-image: url("../assets/indexViews/HYSNView.png");
  background-size: cover;
  position: relative;
}
src/views/personnelManagement/payrollManagement/components/formDia.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,315 @@
<template>
  <div>
    <el-dialog
        v-model="dialogFormVisible"
        :title="operationType === 'add' ? '新增入职' : '编辑人员'"
        width="50%"
        @close="closeDia"
    >
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="月份:" prop="payDate">
                            <el-date-picker
                                v-model="form.payDate"
                                type="month"
                                value-format="YYYY-MM-DD"
                                format="YYYY-MM"
                                placeholder="请选择月份"
                                clearable
                                :disabled="operationType === 'edit'"
                                style="width: 100%"
                            />
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="姓名:" prop="staffId">
                            <el-select v-model="form.staffId" placeholder="请选择人员" style="width: 100%" @change="handleSelect" :disabled="operationType === 'edit'">
                                <el-option
                                    v-for="item in personList"
                                    :key="item.id"
                                    :label="item.staffName"
                                    :value="item.id"
                                />
                            </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="应出勤天数:" prop="shouldAttendedNum">
                            <el-input v-model="form.shouldAttendedNum" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="实际出勤天数:" prop="actualAttendedNum">
              <el-input v-model="form.actualAttendedNum" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="基本工资:" prop="basicSalary">
              <el-input v-model="form.basicSalary" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="岗位工资:" prop="postSalary">
              <el-input v-model="form.postSalary" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="入离职缺勤扣款:" prop="deductionAbsenteeism">
              <el-input v-model="form.deductionAbsenteeism" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="病假扣款:" prop="sickLeaveDeductions">
              <el-input v-model="form.sickLeaveDeductions" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="事假扣款:" prop="deductionPersonalLeave">
              <el-input v-model="form.deductionPersonalLeave" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="忘记打卡扣款:" prop="forgetClockDeduct">
              <el-input v-model="form.forgetClockDeduct" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="绩效得分:" prop="performanceScore">
              <el-input v-model="form.performanceScore" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="绩效工资:" prop="performancePay">
              <el-input v-model="form.performancePay" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="应发合计:" prop="payableWages">
              <el-input v-model="form.payableWages" placeholder="请输入" clearable type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="社保个人:" prop="socialSecurityIndividuals">
              <el-input v-model="form.socialSecurityIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="社保公司:" prop="socialSecurityCompanies">
                            <el-input v-model="form.socialSecurityCompanies" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="社保合计:" prop="socialSecurityTotal">
                            <el-input v-model="form.socialSecurityTotal" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公积金个人:" prop="providentFundIndividuals">
                            <el-input v-model="form.providentFundIndividuals" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="公积金公司:" prop="providentFundCompany">
                            <el-input v-model="form.providentFundCompany" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="公积金合计:" prop="providentFundTotal">
                            <el-input v-model="form.providentFundTotal" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="应税工资:" prop="taxableWaget">
                            <el-input v-model="form.taxableWaget" :precision="0" :step="1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row :gutter="30">
          <el-col :span="12">
            <el-form-item label="个人所得税:" prop="personalIncomeTax">
                            <el-input v-model="form.personalIncomeTax" :step="0.1" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="实发工资:" prop="actualWages">
                            <el-input v-model="form.actualWages" style="width: 100%" type="number"/>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import {ref} from "vue";
import {getStaffJoinInfo, getStaffOnJob, staffJoinAdd, staffJoinUpdate} from "@/api/personnelManagement/onboarding.js";
import {compensationAdd, compensationUpdate} from "@/api/personnelManagement/payrollManagement.js";
const { proxy } = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
const operationType = ref('')
const data = reactive({
  form: {
        payDate: "",
    staffId: "",
        name: "",
        shouldAttendedNum: "",
        actualAttendedNum: "",
        basicSalary: "",
        postSalary: "",
        deductionAbsenteeism: "",
        sickLeaveDeductions: "",
        deductionPersonalLeave: "",
        forgetClockDeduct: "",
        performanceScore: "",
        performancePay: "",
        payableWages: "",
        socialSecurityIndividuals: "",
        socialSecurityCompanies: "",
        socialSecurityTotal: "",
        providentFundIndividuals: "",
        providentFundCompany: "",
        providentFundTotal: "",
        taxableWaget: "",
        personalIncomeTax: "",
        actualWages: "",
  },
  rules: {
        payDate: [{ required: true, message: "请选择", trigger: "change" },],
        staffId: [{ required: true, message: "请选择", trigger: "change" },],
    staffName: [{ required: true, message: "请输入", trigger: "blur" }],
        shouldAttendedNum: [{ required: true, message: "请输入", trigger: "blur" }],
        actualAttendedNum: [{ required: true, message: "请输入", trigger: "blur" }],
        basicSalary: [{ required: true, message: "请输入", trigger: "blur" }],
        postSalary: [{ required: true, message: "请输入", trigger: "blur" }],
        deductionAbsenteeism: [{ required: true, message: "请输入", trigger: "blur" }],
        sickLeaveDeductions: [{ required: true, message: "请输入", trigger: "blur" }],
        deductionPersonalLeave: [{ required: true, message: "请输入", trigger: "blur" }],
        forgetClockDeduct: [{ required: true, message: "请输入", trigger: "blur" }],
        performanceScore: [{ required: true, message: "请输入", trigger: "blur" }],
        performancePay: [{ required: true, message: "请输入", trigger: "blur" }],
        payableWages: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityIndividuals: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityCompanies: [{ required: true, message: "请输入", trigger: "blur" }],
        socialSecurityTotal: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundIndividuals: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundCompany: [{ required: true, message: "请输入", trigger: "blur" }],
        providentFundTotal: [{ required: true, message: "请输入", trigger: "blur" }],
        taxableWaget: [{ required: true, message: "请输入", trigger: "blur" }],
        personalIncomeTax: [{ required: true, message: "请输入", trigger: "blur" }],
        actualWages: [{ required: true, message: "请输入", trigger: "blur" }],
  },
});
const { form, rules } = toRefs(data);
const personList = ref([]);
// æ‰“开弹框
const openDialog = (type, row) => {
  operationType.value = type;
  dialogFormVisible.value = true;
    getStaffOnJob().then(res => {
        personList.value = res.data
    })
    form.value = {}
  if (operationType.value === 'edit') {
    getStaffJoinInfo(row.id).then(res => {
            form.value = {...row}
            form.value.payDate = form.value.payDate + '-01'
    })
  }
}
const handleSelect = (value) => {
    console.log('value', value)
    const index = personList.value.findIndex(row => row.id === value)
    if (index > -1) {
        form.value.name = personList.value[index].staffName
    }
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.staffState = 1
      if (operationType.value === "add") {
                compensationAdd(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
                compensationUpdate(form.value).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      }
    }
  })
}
// è®¡ç®—合同年限
const calculateContractTerm = () => {
  if (form.value.contractStartTime && form.value.contractEndTime) {
    const startDate = new Date(form.value.contractStartTime);
    const endDate = new Date(form.value.contractEndTime);
    if (endDate > startDate) {
      // è®¡ç®—年份差
      const yearDiff = endDate.getFullYear() - startDate.getFullYear();
      const monthDiff = endDate.getMonth() - startDate.getMonth();
      const dayDiff = endDate.getDate() - startDate.getDate();
      let years = yearDiff;
      // å¦‚果结束日期的月日小于开始日期的月日,则减去1å¹´
      if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
        years = yearDiff - 1;
      }
      form.value.contractTerm = Math.max(0, years);
    } else {
      form.value.contractTerm = 0;
    }
  } else {
    form.value.contractTerm = 0;
  }
};
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
  emit('close')
};
defineExpose({
  openDialog,
});
</script>
<style scoped>
</style>
src/views/personnelManagement/payrollManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,291 @@
<template>
    <div class="app-container">
        <div class="search_form">
            <div>
                <span class="search_title">姓名:</span>
                <el-input
                    v-model="searchForm.name"
                    style="width: 240px"
                    placeholder="请输入姓名搜索"
                    @change="handleQuery"
                    clearable
                    :prefix-icon="Search"
                />
                <span class="search_title ml10">月份:</span>
                <el-date-picker
                    v-model="searchForm.payDateStr"
                    type="month"
                    @change="handleQuery"
                    value-format="YYYY-MM"
                    format="YYYY-MM"
                    placeholder="请选择月份"
                    style="width: 240px"
                    clearable
                />
                <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
                >搜索</el-button
                >
            </div>
            <div>
                <el-button type="primary" @click="openForm('add')">新增薪资</el-button>
<!--                <el-button @click="handleOut">导出</el-button>-->
                <el-button type="danger" plain @click="handleDelete">删除</el-button>
            </div>
        </div>
        <div class="table_list">
            <PIMTable
                rowKey="id"
                :column="tableColumn"
                :tableData="tableData"
                :page="page"
                :isSelection="true"
                @selection-change="handleSelectionChange"
                :tableLoading="tableLoading"
                @pagination="pagination"
                :total="page.total"
            ></PIMTable>
        </div>
        <form-dia ref="formDia" @close="handleQuery"></form-dia>
    </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import FormDia from "@/views/personnelManagement/payrollManagement/components/formDia.vue";
import {staffJoinDel} from "@/api/personnelManagement/onboarding.js";
import {ElMessageBox} from "element-plus";
import dayjs from "dayjs";
import {compensationDelete, compensationListPage} from "@/api/personnelManagement/payrollManagement.js";
const data = reactive({
    searchForm: {
        name: "",
        payDateStr: "",
    },
});
const { searchForm } = toRefs(data);
const tableColumn = ref([
    {
        label: "薪资月份",
        prop: "payDate",
    },
    {
        label: "姓名",
        prop: "name",
    },
    {
        label: "应出勤天数",
        prop: "shouldAttendedNum",
        width:100
    },
    {
        label: "实际出勤天数",
        prop: "actualAttendedNum",
        width:110
    },
    {
        label: "基本工资",
        prop: "basicSalary",
    },
    {
        label: "岗位工资",
        prop: "postSalary",
        width:100
    },
    {
        label: "入离职缺勤扣款",
        prop: "deductionAbsenteeism",
        width:130
    },
    {
        label: "病假扣款",
        prop: "sickLeaveDeductions",
        width:100
    },
    {
        label: "事假扣款",
        prop: "deductionPersonalLeave",
        width:100
    },
    {
        label: "忘记打卡扣款",
        prop: "forgetClockDeduct",
        width:110
    },
    {
        label: "绩效得分",
        prop: "performanceScore",
        width:150
    },
    {
        label: "绩效工资",
        prop: "performancePay",
        width: 120
    },
    {
        label: "应发合计",
        prop: "payableWages",
        width:150
    },
    {
        label: "社保个人",
        prop: "socialSecurityIndividuals",
    },
    {
        label: "社保公司",
        prop: "socialSecurityCompanies",
        width: 120
    },
    {
        label: "社保合计",
        prop: "socialSecurityTotal",
        width: 120
    },
    {
        label: "公积金个人",
        prop: "providentFundIndividuals",
        width: 120
    },
    {
        label: "公积金公司",
        prop: "providentFundCompany",
        width: 120
    },
    {
        label: "公积金合计",
        prop: "providentFundTotal",
        width: 120
    },
    {
        label: "应税工资",
        prop: "taxableWaget",
    },
    {
        label: "个人所得税",
        prop: "personalIncomeTax",
        width: 120
    },
    {
        label: "实发工资",
        prop: "actualWages",
        width: 120
    },
    {
        dataType: "action",
        label: "操作",
        align: "center",
        fixed: 'right',
        operation: [
            {
                name: "编辑",
                type: "text",
                clickFun: (row) => {
                    openForm("edit", row);
                },
            },
        ],
    },
]);
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const page = reactive({
    current: 1,
    size: 100,
    total: 0,
});
const formDia = ref()
const { proxy } = getCurrentInstance()
const handleDateChange = (value,type) => {
    searchForm.value.entryDateEnd = null
    searchForm.value.entryDateStart = null
    if(type === 1){
        if (value) {
            searchForm.value.entryDateStart = dayjs(value).format("YYYY-MM-DD");
        }
    }else{
        if (value) {
            searchForm.value.entryDateEnd = dayjs(value).format("YYYY-MM-DD");
        }
    }
    getList();
};
// æŸ¥è¯¢åˆ—表
/** æœç´¢æŒ‰é’®æ“ä½œ */
const handleQuery = () => {
    page.current = 1;
    getList();
};
const pagination = (obj) => {
    page.current = obj.page;
    page.size = obj.limit;
    getList();
};
const getList = () => {
    tableLoading.value = true;
    compensationListPage({...page, ...searchForm.value, staffState: 1}).then(res => {
        tableLoading.value = false;
        tableData.value = res.data.records
        page.total = res.data.total;
    }).catch(err => {
        tableLoading.value = false;
    })
};
// è¡¨æ ¼é€‰æ‹©æ•°æ®
const handleSelectionChange = (selection) => {
    selectedRows.value = selection;
};
// æ‰“开弹框
const openForm = (type, row) => {
    nextTick(() => {
        formDia.value?.openDialog(type, row)
    })
};
// åˆ é™¤
const handleDelete = () => {
    let ids = [];
    if (selectedRows.value.length > 0) {
        ids = selectedRows.value.map((item) => item.id);
    } else {
        proxy.$modal.msgWarning("请选择数据");
        return;
    }
    ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            compensationDelete(ids).then((res) => {
                proxy.$modal.msgSuccess("删除成功");
                getList();
            });
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
// å¯¼å‡º
const handleOut = () => {
    ElMessageBox.confirm("选中的内容将被导出,是否确认导出?", "导出", {
        confirmButtonText: "确认",
        cancelButtonText: "取消",
        type: "warning",
    })
        .then(() => {
            proxy.download("/staff/staffJoinLeaveRecord/export", {staffState: 1}, "人员入职.xlsx");
        })
        .catch(() => {
            proxy.$modal.msg("已取消");
        });
};
onMounted(() => {
    getList();
});
</script>
<style scoped></style>
src/views/qualityManagement/rawMaterialInspection/components/formDia.vue
@@ -54,7 +54,8 @@
          </el-col>
          <el-col :span="12">
            <el-form-item label="数量:" prop="quantity">
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入" clearable :precision="2"/>
              <el-input-number :step="0.01" :min="0" style="width: 100%" v-model="form.quantity" placeholder="请输入"
                               clearable :precision="2"/>
            </el-form-item>
          </el-col>
        </el-row>
@@ -67,8 +68,8 @@
          <el-col :span="12">
            <el-form-item label="检测结果:" prop="checkResult">
              <el-select v-model="form.checkResult">
                <el-option label="合格" value="合格" />
                <el-option label="不合格" value="不合格" />
                <el-option label="合格" value="合格"/>
                <el-option label="不合格" value="不合格"/>
              </el-select>
            </el-form-item>
          </el-col>
@@ -77,7 +78,7 @@
          <el-col :span="12">
            <el-form-item label="检验员:" prop="checkName">
              <el-input v-model="form.checkName" placeholder="请输入" clearable/>
            </el-form-item>
          </el-col>
          <el-col :span="12">
@@ -95,6 +96,22 @@
          </el-col>
        </el-row>
      </el-form>
      <div style="margin-bottom: 10px;text-align: right">
        <el-button type="danger" plain @click="handleDelete">删除</el-button>
      </div>
      <PIMTable
          rowKey="id"
          :column="tableColumn"
          :tableData="tableData"
          :tableLoading="tableLoading"
          :isSelection="true"
          @selection-change="handleSelectionChange"
          height="400"
      >
        <template #slot="{ row }">
          <el-input v-model="row.testValue" clearable/>
        </template>
      </PIMTable>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
@@ -110,7 +127,11 @@
import {getOptions} from "@/api/procurementManagement/procurementLedger.js";
import {productTreeList} from "@/api/basicData/product.js";
import {qualityInspectAdd, qualityInspectUpdate} from "@/api/qualityManagement/rawMaterialInspection.js";
const { proxy } = getCurrentInstance()
import {ElMessageBox} from "element-plus";
import {qualityInspectParamDel, qualityInspectParamInfo} from "@/api/qualityManagement/qualityInspectParam.js";
import {qualityInspectDetailByProductId} from "@/api/qualityManagement/metricMaintenance.js";
const {proxy} = getCurrentInstance()
const emit = defineEmits(['close'])
const dialogFormVisible = ref(false);
@@ -129,20 +150,48 @@
    checkResult: "",
  },
  rules: {
    checkTime: [{ required: false, message: "请输入", trigger: "blur" },],
    supplier: [{ required: true, message: "请输入", trigger: "blur" }],
    checkName: [{ required: false, message: "请输入", trigger: "blur" }],
    productId: [{ required: true, message: "请输入", trigger: "blur" }],
    model: [{ required: false, message: "请输入", trigger: "blur" }],
    unit: [{ required: false, message: "请输入", trigger: "blur" }],
    quantity: [{ required: true, message: "请输入", trigger: "blur" }],
    checkCompany: [{ required: false, message: "请输入", trigger: "blur" }],
    checkResult: [{ required: true, message: "请选择检测结果", trigger: "change" }],
    checkTime: [{required: false, message: "请输入", trigger: "blur"},],
    supplier: [{required: true, message: "请输入", trigger: "blur"}],
    checkName: [{required: false, message: "请输入", trigger: "blur"}],
    productId: [{required: true, message: "请输入", trigger: "blur"}],
    model: [{required: false, message: "请输入", trigger: "blur"}],
    unit: [{required: false, message: "请输入", trigger: "blur"}],
    quantity: [{required: true, message: "请输入", trigger: "blur"}],
    checkCompany: [{required: false, message: "请输入", trigger: "blur"}],
    checkResult: [{required: true, message: "请选择检测结果", trigger: "change"}],
  },
});
const { form, rules } = toRefs(data);
const tableColumn = ref([
  {
    label: "指标",
    prop: "parameterItem",
  },
  {
    label: "单位",
    prop: "unit",
  },
  {
    label: "标准值",
    prop: "standardValue",
  },
  {
    label: "内控值",
    prop: "controlValue",
  },
  {
    label: "检验值",
    prop: "testValue",
    dataType: 'slot',
    slot: 'slot',
  },
]);
const tableData = ref([]);
const tableLoading = ref(false);
const {form, rules} = toRefs(data);
const supplierList = ref([]);
const productOptions = ref([]);
const currentProductId = ref(0);
// æ‰“开弹框
const openDialog = (type, row) => {
@@ -154,6 +203,8 @@
  getProductOptions();
  if (operationType.value === 'edit') {
    form.value = {...row}
    currentProductId.value = row.productId || 0
    getQualityInspectParamList(row.id)
  }
}
const getProductOptions = () => {
@@ -162,7 +213,11 @@
  });
};
const getModels = (value) => {
  currentProductId.value = value
  form.value.productName = findNodeById(productOptions.value, value);
  if (currentProductId) {
    getList();
  }
};
const findNodeById = (nodes, productId) => {
  for (let i = 0; i < nodes.length; i++) {
@@ -178,9 +233,10 @@
  }
  return null; // æ²¡æœ‰æ‰¾åˆ°èŠ‚ç‚¹ï¼Œè¿”å›žnull
};
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item;
    const {id, children, ...rest} = item;
    const newItem = {
      ...rest,
      value: id, // å°† id æ”¹ä¸º value
@@ -188,22 +244,24 @@
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children);
    }
    return newItem;
  });
}
// æäº¤äº§å“è¡¨å•
const submitForm = () => {
  proxy.$refs.formRef.validate(valid => {
    if (valid) {
      form.value.inspectType = 0
      const data = {...form.value, qualityInspectParams: tableData.value}
      if (operationType.value === "add") {
        qualityInspectAdd(form.value).then(res => {
        qualityInspectAdd(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
      } else {
        qualityInspectUpdate(form.value).then(res => {
        qualityInspectUpdate(data).then(res => {
          proxy.$modal.msgSuccess("提交成功");
          closeDia();
        })
@@ -211,9 +269,50 @@
    }
  })
}
const handleSelectionChange = (selection) => {
  selectedRows.value = selection;
};
const handleDelete = () => {
  let ids = [];
  if (selectedRows.value.length > 0) {
    ids = selectedRows.value.map((item) => item.id);
  } else {
    proxy.$modal.msgWarning("请选择数据");
    return;
  }
  ElMessageBox.confirm("选中的内容将被删除,是否确认删除?", "导出", {
    confirmButtonText: "确认",
    cancelButtonText: "取消",
    type: "warning",
  })
      .then(() => {
        qualityInspectParamDel(ids).then((res) => {
          proxy.$modal.msgSuccess("删除成功");
          getList();
        });
      })
      .catch(() => {
        proxy.$modal.msg("已取消");
      });
};
const getList = () => {
  qualityInspectDetailByProductId(currentProductId.value).then(res => {
    tableData.value = res.data;
  })
}
const getQualityInspectParamList = (id) => {
  qualityInspectParamInfo(id).then(res => {
    tableData.value = res.data;
  })
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  tableData.value = []
  dialogFormVisible.value = false;
  emit('close')
};
src/views/qualityManagement/rawMaterialInspection/index.vue
@@ -11,11 +11,12 @@
            clearable
            :prefix-icon="Search"
        />
        <span  style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker  v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange" />
        <span style="margin-left: 10px" class="search_title">检测日期:</span>
        <el-date-picker v-model="searchForm.entryDate" value-format="YYYY-MM-DD" format="YYYY-MM-DD" type="daterange"
                        placeholder="请选择" clearable @change="changeDaterange"/>
        <el-button type="primary" @click="handleQuery" style="margin-left: 10px"
        >搜索</el-button
        >搜索
        </el-button
        >
      </div>
      <div>
@@ -40,18 +41,42 @@
    <InspectionFormDia ref="inspectionFormDia" @close="handleQuery"></InspectionFormDia>
    <FormDia ref="formDia" @close="handleQuery"></FormDia>
    <files-dia ref="filesDia" @close="handleQuery"></files-dia>
    <el-dialog v-model="dialogFormVisible" title="编辑检验员" width="70%"
               @close="closeDia">
      <el-form :model="form" label-width="140px" label-position="top" :rules="rules" ref="formRef">
        <el-form-item label="检验员:" prop="checkName">
          <el-select v-model="form.checkName" placeholder="请选择" clearable>
            <el-option v-for="item in userList" :key="item.nickName" :label="item.nickName"
                       :value="item.nickName"/>
          </el-select>
        </el-form-item>
      </el-form>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" @click="submitForm">确认</el-button>
          <el-button @click="closeDia">取消</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { Search } from "@element-plus/icons-vue";
import {Search} from "@element-plus/icons-vue";
import {onMounted, ref} from "vue";
import InspectionFormDia from "@/views/qualityManagement/rawMaterialInspection/components/inspectionFormDia.vue";
import FormDia from "@/views/qualityManagement/rawMaterialInspection/components/formDia.vue";
import {ElMessageBox} from "element-plus";
import {qualityInspectDel, qualityInspectListPage} from "@/api/qualityManagement/rawMaterialInspection.js";
import {
  downloadQualityInspect,
  qualityInspectDel,
  qualityInspectListPage, qualityInspectUpdate,
  submitQualityInspect
} from "@/api/qualityManagement/rawMaterialInspection.js";
import FilesDia from "@/views/qualityManagement/rawMaterialInspection/components/filesDia.vue";
import dayjs from "dayjs";
import {userListNoPage} from "@/api/system/user.js";
const data = reactive({
  searchForm: {
@@ -63,8 +88,11 @@
    entryDateStart: dayjs().format("YYYY-MM-DD"),
    entryDateEnd: dayjs().add(1, "day").format("YYYY-MM-DD"),
  },
  rules: {
    checkName: [{required: true, message: "请选择", trigger: "change"}],
  },
});
const { searchForm } = toRefs(data);
const {searchForm, rules} = toRefs(data);
const tableColumn = ref([
  {
    label: "检测日期",
@@ -107,12 +135,23 @@
    prop: "checkResult",
    dataType: "tag",
    formatType: (params) => {
      if (params == '不合格') {
      if (params === '不合格') {
        return "danger";
      } else if (params == '合格') {
      } else if (params === '合格') {
        return "success";
      } else {
        return null;
      }
    },
  },
  {
    label: "提交状态",
    prop: "inspectState",
    formatData: (params) => {
      if (params) {
        return "已提交";
      } else {
        return "未提交";
      }
    },
  },
@@ -121,7 +160,7 @@
    label: "操作",
    align: "center",
    fixed: "right",
    width: 190,
    width: 250,
    operation: [
      {
        name: "编辑",
@@ -131,17 +170,35 @@
        },
      },
      {
        name: "新增检验记录",
        type: "text",
        clickFun: (row) => {
          openInspectionForm("edit", row);
        },
      },
      {
        name: "附件",
        type: "text",
        clickFun: (row) => {
          openFilesFormDia(row);
        },
      },
      {
        name: "提交",
        type: "text",
        clickFun: (row) => {
          submit(row.id);
        },
      },
      {
        name: "分配检验员",
        type: "text",
        clickFun: (row) => {
          if (!row.checkName) {
            open(row)
          } else {
            proxy.$modal.msgError("检验员已存在");
          }
        },
      },
      {
        name: "下载",
        type: "text",
        clickFun: (row) => {
          downLoadFile(row);
        },
      },
    ],
@@ -150,15 +207,21 @@
const tableData = ref([]);
const selectedRows = ref([]);
const tableLoading = ref(false);
const userList = ref([]);
const dialogFormVisible = ref(false);
const form = ref({
  checkName: ""
});
const page = reactive({
  current: 1,
  size: 100,
  total: 0
});
const currentRow = ref(null)
const formDia = ref()
const filesDia = ref()
const inspectionFormDia = ref()
const { proxy } = getCurrentInstance()
const {proxy} = getCurrentInstance()
const changeDaterange = (value) => {
  searchForm.value.entryDateStart = undefined;
  searchForm.value.entryDateEnd = undefined;
@@ -181,7 +244,7 @@
};
const getList = () => {
  tableLoading.value = true;
  const params = { ...searchForm.value, ...page };
  const params = {...searchForm.value, ...page};
  params.entryDate = undefined
  qualityInspectListPage({...params, inspectType: 0}).then(res => {
    tableLoading.value = false;
@@ -253,6 +316,62 @@
        proxy.$modal.msg("已取消");
      });
};
// æä»·
const submit = async (id) => {
  const res = await submitQualityInspect({id: id})
  if (res.code === 200) {
    proxy.$modal.msgSuccess("提交成功");
    getList();
  }
}
// å…³é—­å¼¹æ¡†
const closeDia = () => {
  proxy.resetForm("formRef");
  dialogFormVisible.value = false;
};
const submitForm = () => {
  if (currentRow.value) {
    const data = {
      ...form.value,
      id: currentRow.value.id
    }
    qualityInspectUpdate(data).then(res => {
      proxy.$modal.msgSuccess("提交成功");
      closeDia();
      getList();
    })
  }
};
const open = async (row) => {
  let userLists = await userListNoPage();
  userList.value = userLists.data;
  currentRow.value = row
  dialogFormVisible.value = true
}
const downLoadFile = (row) => {
  downloadQualityInspect({id: row.id}).then(res => {
    // åˆ›å»º blob å¯¹è±¡
    const blob = new Blob([res.data], {type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'})
    const downloadUrl = window.URL.createObjectURL(blob)
    // åˆ›å»ºä¸´æ—¶ <a> æ ‡ç­¾è¿›è¡Œä¸‹è½½
    const link = document.createElement('a')
    link.href = downloadUrl
    link.download = '检验报告.docx' // è¿™é‡Œå’ŒåŽç«¯ä¸€è‡´
    document.body.appendChild(link)
    link.click()
    // æ¸…理
    document.body.removeChild(link)
    window.URL.revokeObjectURL(downloadUrl)
  })
};
onMounted(() => {
  getList();
});
vite.config.js
@@ -8,8 +8,8 @@
  const { VITE_APP_ENV } = env;
  const baseUrl =
    VITE_APP_ENV == "development"
      ? "http://192.168.1.147:7003" // å¼€å‘环境后端接口
      : "http://114.132.189.42:7003"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
      ? "http://114.132.189.42:8092" // å¼€å‘环境后端接口
      : "http://114.132.189.42:8092"; // ç”Ÿäº§çŽ¯å¢ƒåŽç«¯æŽ¥å£
  return {
    // éƒ¨ç½²ç”Ÿäº§çŽ¯å¢ƒå’Œå¼€å‘çŽ¯å¢ƒä¸‹çš„URL。