Merge origin/dev_项目管理 into dev_项目管理
resolve conflict in multiple/config.json: keep both new tenant configs
已添加22个文件
已复制1个文件
已重命名1个文件
已修改44个文件
| | |
| | | "VITE_BASE_API": "http://1.15.17.182:9048", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9049" |
| | | }, |
| | | "logo": "logo/XDRJ.png", |
| | | "logo": "logo/SHYY.png", |
| | | "favicon": "favicon/favicon.ico" |
| | | }, |
| | | "BTYX": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ²³å帮太ä¼éé£åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://1.15.17.182:9056", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9057" |
| | | }, |
| | | "logo": "logo/BTYXLogo.png", |
| | | "favicon": "favicon/BTYXfavicon.ico" |
| | | }, |
| | | "ZXZN": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ²³åæºè¯æºè½æºå¨äººæéå
¬å¸", |
| | | "VITE_BASE_API": "http://127.0.0.1:9001", |
| | | "VITE_JAVA_API": "http://127.0.0.1:9000" |
| | | }, |
| | | "logo": "logo/ZXZNLogo.png", |
| | | "favicon": "favicon/ZXZNfavicon.ico" |
| | | }, |
| | | "SDTX": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ²³ååé¼éä¿¡ç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.156.184:9001", |
| | | "VITE_JAVA_API": "http://36.213.156.184:9000" |
| | | }, |
| | | "logo": "logo/SDTXLogo.png", |
| | | "favicon": "favicon/SDTXfavicon.ico" |
| | | }, |
| | | "QXY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "强信å®çµå¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.134.154.10:9001", |
| | | "VITE_JAVA_API": "http://36.134.154.10:9000" |
| | | }, |
| | | "logo": "logo/QXYLogo.png", |
| | | "favicon": "favicon/QXYfavicon.ico" |
| | | }, |
| | | "HQJC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å强建æç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.134.77.64:9001", |
| | | "VITE_JAVA_API": "http://36.134.77.64:9000" |
| | | }, |
| | | "logo": "logo/HQJCLogo.png", |
| | | "favicon": "favicon/HQJCfavicon.ico" |
| | | }, |
| | | "XCDQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ææ¨çµå¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9001", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9002" |
| | | }, |
| | | "logo": "logo/XCDQLogo.png", |
| | | "favicon": "favicon/XCDQfavicon.ico" |
| | | }, |
| | | "BWSM": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å
«ç»´å贸管çç³»ç»", |
| | | "VITE_BASE_API": "http://1.15.17.182:9070", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9069" |
| | | }, |
| | | "logo": "logo/BWSMLogo.png", |
| | | "favicon": "favicon/BWSMfavicon.ico" |
| | | }, |
| | | "CKGM": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "宸康工贸管çç³»ç»", |
| | | "VITE_BASE_API": "http://1.15.17.182:9072", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9071" |
| | | }, |
| | | "logo": "logo/CKGMLogo.png", |
| | | "favicon": "favicon/CKGMfavicon.ico" |
| | | }, |
| | | "RFSY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ç丰çä¸ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://1.15.17.182:9076", |
| | | "VITE_JAVA_API": "http://1.15.17.182:9075" |
| | | }, |
| | | "logo": "logo/RFSYLogo.png", |
| | | "favicon": "favicon/RFSYfavicon.ico" |
| | | }, |
| | | "ZQSY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ³½æ·å®ä¸", |
| | | "VITE_BASE_API": "http://36.213.128.159:9000", |
| | | "VITE_JAVA_API": "http://36.213.128.159:9001" |
| | | }, |
| | | "logo": "logo/ZQSYLogo.png", |
| | | "favicon": "favicon/ZQSYfavicon.ico" |
| | | }, |
| | | "JXJH": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æµå¿æ±æµ·æ°´æ³¥å¶åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.201.20:9000", |
| | | "VITE_JAVA_API": "http://36.139.201.20:9001" |
| | | }, |
| | | "logo": "logo/JXJHLogo.png", |
| | | "favicon": "favicon/JXJHfavicon.ico" |
| | | }, |
| | | "YTJZ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "è±«æ³°å»ºçæææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.201.181:9000", |
| | | "VITE_JAVA_API": "http://36.139.201.181:9001" |
| | | }, |
| | | "logo": "logo/YTJZLogo.png", |
| | | "favicon": "favicon/YTJZfavicon.ico" |
| | | }, |
| | | "HYLQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "èªé¸è·¯æ¡¥å·¥ç¨æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.139.202.111:9000", |
| | | "VITE_JAVA_API": "http://36.139.202.111:9001" |
| | | }, |
| | | "logo": "logo/HYLQLogo.png", |
| | | "favicon": "favicon/HYLQfavicon.ico" |
| | | }, |
| | | "HJGC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ç¯å¢å·¥ç¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.139.202.111:9014", |
| | | "VITE_JAVA_API": "http://36.139.202.111:9013" |
| | | }, |
| | | "logo": "logo/HJGCLogo.png", |
| | | "favicon": "favicon/HJGCfavicon.ico" |
| | | }, |
| | | "QYHY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å¥ç¼æèºç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.139.202.111:9012", |
| | | "VITE_JAVA_API": "http://36.139.202.111:9011" |
| | | }, |
| | | "logo": "logo/QYHYLogo.png", |
| | | "favicon": "favicon/QYHYfavicon.ico" |
| | | }, |
| | | "QXY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "强信å®çµå¨äºä¸»æº", |
| | | "VITE_BASE_API": "http://36.134.154.10:9000", |
| | | "VITE_JAVA_API": "http://36.134.154.10:9001" |
| | | }, |
| | | "logo": "logo/QXYLogo.png", |
| | | "favicon": "favicon/QXYfavicon.ico" |
| | | }, |
| | | "HYJC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ææ´å»ºæ", |
| | | "VITE_BASE_API": "http://36.138.94.178:9000", |
| | | "VITE_JAVA_API": "http://36.138.94.178:9001" |
| | | }, |
| | | "logo": "logo/HYJCLogo.png", |
| | | "favicon": "favicon/HYJCfavicon.ico" |
| | | }, |
| | | "JHY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å±±è¥¿çæ¦ç¤¾å¿æååé£åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://223.15.233.27:9001", |
| | | "VITE_JAVA_API": "http://223.15.233.27:9002" |
| | | }, |
| | | "logo": "logo/JHYLogo.png", |
| | | "favicon": "favicon/JHYfavicon.ico" |
| | | }, |
| | | "XCDQ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ææ¨çµå¨ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9001", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9002" |
| | | }, |
| | | "logo": "logo/XCDQLogo.png", |
| | | "favicon": "favicon/XCDQfavicon.ico" |
| | | }, |
| | | "XHKS": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "é«é§ç¿å±±ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9078", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9077" |
| | | }, |
| | | "logo": "logo/XHKSLogo.png", |
| | | "favicon": "favicon/XHKSfavicon.ico" |
| | | }, |
| | | "QFSW": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "乾丰çç©ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9080", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9079" |
| | | }, |
| | | "logo": "logo/QFSWLogo.png", |
| | | "favicon": "favicon/QFSWfavicon.ico" |
| | | }, |
| | | "ZDJC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "æ£å¾·æ°å建æç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://36.133.45.183:9082", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9081" |
| | | }, |
| | | "logo": "logo/ZDJCLogo.png", |
| | | "favicon": "favicon/ZDJCfavicon.ico" |
| | | }, |
| | | "LSY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西ææç±å·¥è´¸æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.133.45.183:9084", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9083" |
| | | }, |
| | | "logo": "logo/LSYLogo.png", |
| | | "favicon": "favicon/LSYfavicon.ico" |
| | | }, |
| | | "HTHB": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西å®è
¾æ°åç¯ä¿ç§ææé责任å
¬å¸", |
| | | "VITE_BASE_API": "http://36.133.45.183:9090", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9089" |
| | | }, |
| | | "logo": "logo/HTHBLogo.png", |
| | | "favicon": "favicon/HTHBfavicon.ico" |
| | | }, |
| | | "YSS": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å±±è¥¿ç¾¿ç¥æåçç©ç§æå屿éå
¬å¸", |
| | | "VITE_BASE_API": "http://36.133.45.183:9086", |
| | | "VITE_JAVA_API": "http://36.133.45.183:9085" |
| | | }, |
| | | "logo": "logo/YSSLogo.png", |
| | | "favicon": "favicon/YSSfavicon.ico" |
| | | }, |
| | | "KYHG": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å±±è¥¿å¤æºåå·¥æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.137.13.29:9001", |
| | | "VITE_JAVA_API": "http://36.137.13.29:9000" |
| | | }, |
| | | "logo": "logo/KYHGLogo.png", |
| | | "favicon": "favicon/KYHGfavicon.ico" |
| | | }, |
| | | "JXSM": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "è¥å£å¿æ´é«åè´¸æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9001", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9002" |
| | | }, |
| | | "logo": "logo/JXSMLogo.png", |
| | | "favicon": "favicon/JXSMico.ico" |
| | | }, |
| | | "DCHX": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西大æå纤è¡ä»½æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9088", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9087" |
| | | }, |
| | | "logo": "logo/DCHXLogo.png", |
| | | "favicon": "favicon/DCHXfavicon.ico" |
| | | }, |
| | | "KHYY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "é¿æ²»å¸å¤çæ¶²åå¶é æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9094", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9093" |
| | | }, |
| | | "logo": "logo/KHYYLogo.png", |
| | | "favicon": "favicon/KHYYfavicon.ico" |
| | | }, |
| | | "XLGS": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "é¿æ²»å¸é«æµªä¾æ°´è®¾å¤æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9092", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9091" |
| | | }, |
| | | "logo": "logo/XLGSLogo.png", |
| | | "favicon": "favicon/XLGSfavicon.ico" |
| | | }, |
| | | "NY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西çè¯ºé¢¢æ°æææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9092", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9091" |
| | | }, |
| | | "logo": "logo/NYLogo.png", |
| | | "favicon": "favicon/NYfavicon.ico" |
| | | }, |
| | | "JFHY": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西éç¦æè¿åè´¸æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9100", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9099" |
| | | }, |
| | | "logo": "logo/JFHYLogo.png", |
| | | "favicon": "favicon/JFHYfavicon.ico" |
| | | }, |
| | | "BTW": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西æ¯ç¹ç»´ç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9102", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9101" |
| | | }, |
| | | "logo": "logo/BTWLogo.png", |
| | | "favicon": "favicon/BTWfavicon.ico" |
| | | }, |
| | | "BLSGR": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å®å¤å¸éæ¯æ ¼å°ç¾ç»å¶åæéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.134.76.148:9102", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9101" |
| | | }, |
| | | "logo": "logo/BLSGRLogo.png", |
| | | "favicon": "favicon/BLSGRfavicon.ico" |
| | | }, |
| | | "SDZZ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å¹³é¥å¿çè¾¾é¸é å", |
| | | "VITE_BASE_API": "http://36.134.76.148:9102", |
| | | "VITE_JAVA_API": "http://36.134.76.148:9101" |
| | | }, |
| | | "logo": "logo/SDZZLogo.png", |
| | | "favicon": "favicon/SDZZfavicon.ico" |
| | | }, |
| | | "QLMC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ç¥è¿ç§åºä¿¡æ¯ç®¡çç³»ç»", |
| | | "VITE_BASE_API": "http://114.132.189.42:9066", |
| | | "VITE_JAVA_API": "http://114.132.189.42:9060" |
| | | }, |
| | | "logo": "logo/QLMCLogo.png", |
| | | "favicon": "favicon/QLMCico.ico" |
| | | }, |
| | | "HYZC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "山西åäº¿ä¼æå»ºææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.180.217:9009", |
| | | "VITE_JAVA_API": "http://36.213.180.217:9008" |
| | | }, |
| | | "logo": "logo/HYZCLogo.png", |
| | | "favicon": "favicon/HYZCfavicon.ico" |
| | | }, |
| | | "OLD": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·æ¬§è±å¾·æ°ææç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://223.221.37.139:9000", |
| | | "VITE_JAVA_API": "http://223.221.37.139:9000" |
| | | }, |
| | | "logo": "logo/OLDLogo.png", |
| | | "favicon": "favicon/OLD.ico" |
| | | }, |
| | | "XJGM": { |
| | | "env": { |
| | |
| | | "logo": "logo/XJGMLogo.png", |
| | | "favicon": "favicon/XJGM.ico" |
| | | }, |
| | | |
| | | "RBHB": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "å
æçä¾èé¦ç¯ä¿ç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.180.217:9017", |
| | | "VITE_JAVA_API": "http://36.213.180.217:9018" |
| | | }, |
| | | "logo": "logo/RBHBLogo.png", |
| | | "favicon": "favicon/RBHB.ico" |
| | | }, |
| | | "OLDXCL": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·æ¬§è±å¾·æ°ææç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.90.123:9524", |
| | | "VITE_JAVA_API": "http://36.213.90.123:9525" |
| | | }, |
| | | "logo": "logo/OLDXCLLogo.png", |
| | | "favicon": "favicon/OLDXCLfavicon.ico" |
| | | }, |
| | | "YXHJ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·å½¦ç¥¥ç¯å¢ç§ææéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.90.123:9526", |
| | | "VITE_JAVA_API": "http://36.213.90.123:9527" |
| | | }, |
| | | "logo": "logo/YXHJLogo.png", |
| | | "favicon": "favicon/YXHJfavicon.ico" |
| | | }, |
| | | "ZXDZ": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·åæçµåè¡¡å¨æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.90.123:9528", |
| | | "VITE_JAVA_API": "http://36.213.90.123:9529" |
| | | }, |
| | | "logo": "logo/ZXDZLogo.png", |
| | | "favicon": "favicon/ZXDZfavicon.ico" |
| | | }, |
| | | "THZS": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·ç¹å¼åçç©èµåæ¶æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.90.123:9530", |
| | | "VITE_JAVA_API": "http://36.213.90.123:9531" |
| | | }, |
| | | "logo": "logo/THZSLogo.png", |
| | | "favicon": "favicon/THZSfavicon.ico" |
| | | }, |
| | | "JMGC": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "éæµ·é¦æ·¼å·¥ç¨æºæ¢°æéå
¬å¸", |
| | | "VITE_BASE_API": "http://36.213.90.123:9532", |
| | | "VITE_JAVA_API": "http://36.213.90.123:9533" |
| | | }, |
| | | "logo": "logo/JMGCLogo.png", |
| | | "favicon": "favicon/JMGCfavicon.ico" |
| | | }, |
| | | "ZGLT": { |
| | | "env": { |
| | | "VITE_APP_TITLE": "ä¸å½èé", |
| | | "VITE_BASE_API": "http://36.213.180.217:9017", |
| | | "VITE_JAVA_API": "http://36.213.180.217:9018" |
| | | }, |
| | | "logo": "logo/ZGLTLogo.png", |
| | | "favicon": "favicon/ZGLTico.ico" |
| | | }, |
| | | "logo": "/src/assets/logo/logo.png", |
| | | "favicon": "/public/favicon.ico" |
| | | } |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import request from '@/utils/request' |
| | | |
| | | // 顶鍿»è§ |
| | | export function getSafetyOverview() { |
| | | return request({ |
| | | url: '/safeDashboard/getOverview', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // ææ°å®å
¨å¨æ |
| | | export function getLatestSafeEvents(params) { |
| | | return request({ |
| | | url: '/safeDashboard/getLatestEvents', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // éæ£ç±»ååå¸ |
| | | export function getHiddenTypeDistribution() { |
| | | return request({ |
| | | url: '/safeDashboard/getHiddenTypeDistribution', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // è¿6个æéæ£è¶å¿ |
| | | export function getHiddenTrend() { |
| | | return request({ |
| | | url: '/safeDashboard/getHiddenTrend', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // å±é©æºé£é©ç级åå¸ |
| | | export function getHazardRiskDistribution() { |
| | | return request({ |
| | | url: '/safeDashboard/getHazardRiskDistribution', |
| | | method: 'get' |
| | | }) |
| | | } |
| | | |
| | | // å±é©æºç±»åæå |
| | | export function getHazardTypeRanking(params) { |
| | | return request({ |
| | | url: '/safeDashboard/getHazardTypeRanking', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | | |
| | | // éæ£æ´æ¹è´£ä»»äººæå |
| | | export function getHiddenRectifyRanking(params) { |
| | | return request({ |
| | | url: '/safeDashboard/getHiddenRectifyRanking', |
| | | method: 'get', |
| | | params |
| | | }) |
| | | } |
| | |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @click="goLink(scope.row, item.linkMethod)"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-if="!item.formatData">{{ |
| | | toDateOnly(scope.row[item.prop]) |
| | | }}</span> |
| | | </div> |
| | | <!-- é»è®¤çº¯å±ç¤ºæ°æ® --> |
| | | <div v-else |
| | | class="cell" |
| | | style="width: 100%"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-if="!item.formatData">{{ |
| | | toDateOnly(scope.row[item.prop]) |
| | | }}</span> |
| | | <span v-else>{{ |
| | | formatters(scope.row[item.prop], item.formatData) |
| | | toDateOnly(formatters(scope.row[item.prop], item.formatData)) |
| | | }}</span> |
| | | </div> |
| | | </template> |
| | |
| | | import pagination from "./Pagination.vue"; |
| | | import { computed, ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { toDateOnly } from "@/utils/dateDisplay"; |
| | | |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | import PageHeader from "@/components/PageHeader/index.vue"; |
| | | |
| | | import { getToken } from "@/utils/auth"; |
| | | import { toDateOnly } from "@/utils/dateDisplay"; |
| | | import { |
| | | calculateTaxExclusiveTotalPrice, |
| | | summarizeTable, |
| | |
| | | app.config.globalProperties.addDateRange = addDateRange; |
| | | app.config.globalProperties.selectDictLabel = selectDictLabel; |
| | | app.config.globalProperties.selectDictLabels = selectDictLabels; |
| | | app.config.globalProperties.formatDateOnly = toDateOnly; |
| | | app.config.globalProperties.javaApi = __BASE_API__; |
| | | app.config.globalProperties.HaveJson = (val) => { |
| | | return JSON.parse(JSON.stringify(val)); |
| | |
| | | }); |
| | | app._context.components.ElDialog.props.closeOnClickModal.default = false; |
| | | |
| | | // å表æ¶é´ç»ä¸åªå±ç¤ºå¹´ææ¥ï¼æªåç¬é
ç½® formatter çè¡¨æ ¼åèµ°è¿é |
| | | const tableColumnFormatter = |
| | | app._context.components.ElTableColumn?.props?.formatter; |
| | | if (tableColumnFormatter) { |
| | | const formatCell = (row, column, cellValue) => toDateOnly(cellValue); |
| | | if (typeof tableColumnFormatter === "function") { |
| | | app._context.components.ElTableColumn.props.formatter = { |
| | | type: Function, |
| | | default: formatCell, |
| | | }; |
| | | } else { |
| | | tableColumnFormatter.default = formatCell; |
| | | } |
| | | } |
| | | |
| | | app.mount("#app"); |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | /** |
| | | * å表å±ç¤ºç¨çæ¥æå¤çï¼å¸¦æ¶åç§çæ¥ææ¶é´åªä¿çå¹´ææ¥ï¼ |
| | | * æ¬æ¥å°±æ¯æ¥æãæ¶é´æå
¶å®å
容çå¼åæ ·è¿åã |
| | | */ |
| | | const DATE_TIME_PATTERN = |
| | | /^(\d{4})[-/å¹´](\d{1,2})[-/æ](\d{1,2})æ¥?[ T]\d{1,2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?$/; |
| | | |
| | | function pad2(value) { |
| | | return String(value).padStart(2, "0"); |
| | | } |
| | | |
| | | export function toDateOnly(value) { |
| | | if (value == null || value === "") return value; |
| | | |
| | | if (value instanceof Date) { |
| | | if (Number.isNaN(value.getTime())) return value; |
| | | return `${value.getFullYear()}-${pad2(value.getMonth() + 1)}-${pad2( |
| | | value.getDate() |
| | | )}`; |
| | | } |
| | | |
| | | if (typeof value !== "string") { |
| | | // 对象/æ°ç»æè¡¨æ ¼åææ¹å¼è½¬æææ¬ï¼é¿å
æéåç¬¦ä¸²ç´æ¥äº¤ç»æ¸²æå± |
| | | return typeof value === "object" ? String(value) : value; |
| | | } |
| | | |
| | | const text = value.trim(); |
| | | const matched = text.match(DATE_TIME_PATTERN); |
| | | if (!matched) return value; |
| | | |
| | | return `${matched[1]}-${pad2(matched[2])}-${pad2(matched[3])}`; |
| | | } |
| | |
| | | ? "0" + (date.getMonth() + 1)
|
| | | : date.getMonth() + 1;
|
| | | var day = date.getDate() < 10 ? "0" + date.getDate() : date.getDate();
|
| | | var hours = date.getHours() < 10 ? "0" + date.getHours() : date.getHours();
|
| | | var minutes =
|
| | | date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes();
|
| | | var seconds =
|
| | | date.getSeconds() < 10 ? "0" + date.getSeconds() : date.getSeconds();
|
| | | return (
|
| | | year + "-" + month + "-" + day + " " + hours + ":" + minutes + ":" + seconds
|
| | | );
|
| | | return year + "-" + month + "-" + day;
|
| | | }
|
| | |
|
| | | /**
|
| | |
| | | if (arguments.length === 0 || !time) {
|
| | | return null
|
| | | }
|
| | | const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
|
| | | const format = pattern || '{y}-{m}-{d}'
|
| | | let date
|
| | | if (typeof time === 'object') {
|
| | | date = time
|
| | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | const matched = String(dateTime).match( |
| | | /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/ |
| | | ) |
| | | if (!matched) return dateTime |
| | | return `${matched[1]}-${matched[2].padStart(2, '0')}-${matched[3].padStart(2, '0')}` |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | const matched = String(dateTime).match( |
| | | /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/ |
| | | ) |
| | | if (!matched) return dateTime |
| | | return `${matched[1]}-${matched[2].padStart(2, '0')}-${matched[3].padStart(2, '0')}` |
| | | } |
| | | |
| | | // æäº¤å®¡æ¹ |
| | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | const matched = String(dateTime).match( |
| | | /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/ |
| | | ) |
| | | if (!matched) return dateTime |
| | | return `${matched[1]}-${matched[2].padStart(2, '0')}-${matched[3].padStart(2, '0')}` |
| | | } |
| | | |
| | | // æäº¤åå¸ |
| | |
| | | // æ ¼å¼åæ¥ææ¶é´ |
| | | const formatDateTime = (dateTime) => { |
| | | if (!dateTime) return '' |
| | | return dateTime.replace(' ', '\n') |
| | | const matched = String(dateTime).match( |
| | | /^(\d{4})[-/](\d{1,2})[-/](\d{1,2})/ |
| | | ) |
| | | if (!matched) return dateTime |
| | | return `${matched[1]}-${matched[2].padStart(2, '0')}-${matched[3].padStart(2, '0')}` |
| | | } |
| | | |
| | | // 页é¢å è½½æ¶è·åæ°æ® |
| | |
| | | formatData: cell => { |
| | | if (!cell) return "-"; |
| | | try { |
| | | return dayjs(cell).format("YYYY-MM-DD HH:mm:ss"); |
| | | return dayjs(cell).format("YYYY-MM-DD"); |
| | | } catch { |
| | | return cell; |
| | | } |
| | |
| | | const resetQuery = () => { |
| | | queryParams.name = ''; |
| | | pagination.current = 1; |
| | | pagination.size = 10; |
| | | fetchListData(); |
| | | } |
| | | |
| | | // å页大尿¹å |
| | | const handleSizeChange = (size) => { |
| | | pagination.size = size; |
| | | pagination.current = 1; |
| | | const handleSizeChange = ({ page, limit }) => { |
| | | pagination.current = page; |
| | | pagination.size = limit; |
| | | fetchListData(); |
| | | } |
| | | |
| | |
| | | prop: "registrationDate", |
| | | label: "ç»è®°æ¥æ", |
| | | minWidth: 100, |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : "-"), |
| | | }, |
| | | { |
| | | fixed: "right", |
| | |
| | | label: "å®é
ä¿å
»æ¥æ", |
| | | align: "center", |
| | | prop: "maintenanceActuallyTime", |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : "-"), |
| | | }, |
| | | { |
| | | label: "ä¿å
ȍȾ", |
| | |
| | | }" |
| | | @pagination="changePage"> |
| | | <template #inboundDate="{ row }"> |
| | | {{ row.inboundDate ?? row.InboundDate ?? "" }} |
| | | {{ formatDateOnly(row.inboundDate ?? row.InboundDate ?? "") }} |
| | | </template> |
| | | </PIMTable> |
| | | </div> |
| | |
| | | /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <el-form-item label="å¯¹è´¦æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | placeholder="è¯·éæ©å¯¹è´¦æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | range-separator="è³" |
| | | clearable |
| | | style="width: 300px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="onSearch">æç´¢</el-button> |
| | |
| | | <FormDialog title="对账æç»" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail"> |
| | | <div class="statement-header"> |
| | | <h3>{{ currentSupplier }} åºä»å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | <p>å¯¹è´¦æ¥æ: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="statementMonth"> |
| | | <el-form-item label="å¯¹è´¦æ¥æ" prop="statementMonth"> |
| | | <el-date-picker |
| | | v-model="generateForm.statementMonth" |
| | | type="month" |
| | | placeholder="éæ©æä»½" |
| | | value-format="YYYY-MM" |
| | | type="date" |
| | | placeholder="éæ©å¯¹è´¦æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | style="width: 100%;" |
| | | @change="onStatementMonthChange" |
| | | /> |
| | |
| | | </el-form> |
| | | |
| | | <div v-if="statementDetailLoaded" class="purchase-section"> |
| | | <div v-if="purchaseData.length > 0" class="section-title">æ¬æéè´æ°æ®</div> |
| | | <div v-if="purchaseData.length > 0" class="section-title">å¯¹è´¦æ¥æéè´æ°æ®</div> |
| | | <el-table |
| | | v-if="purchaseData.length > 0" |
| | | ref="purchaseTableRef" |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | <el-empty v-else description="该ä¾åºåæ¬æææ æç»æ°æ®" :image-size="80" /> |
| | | <el-empty v-else description="该ä¾åºå彿¥ææ æç»æ°æ®" :image-size="80" /> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.openingBalance) }}</strong></span> |
| | |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.supplierId && generateForm.statementMonth && !purchaseLoading" class="empty-tip"> |
| | | <el-empty description="该ä¾åºåæ¬æææ éè´æ°æ®" /> |
| | | <el-empty description="该ä¾åºå彿¥ææ éè´æ°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | |
| | | |
| | | const filters = reactive({ |
| | | supplierId: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementNumber", width: "150" }, |
| | | { label: "ä¾åºå", prop: "supplierName", width: "180" }, |
| | | { label: "对账æé´", prop: "statementMonth", width: "150" }, |
| | | { label: "å¯¹è´¦æ¥æ", prop: "statementMonth", width: "150" }, |
| | | { label: "æåä½é¢", prop: "openingBalance", dataType: "slot", slot: "openingBalance" }, |
| | | { label: "æ¬æåºä»", prop: "currentPlan", dataType: "slot", slot: "currentPlan" }, |
| | | { label: "æ¬æä»æ¬¾", prop: "currentActually", dataType: "slot", slot: "currentActually" }, |
| | |
| | | if (filters.supplierId) { |
| | | result.customerId = filters.supplierId; |
| | | } |
| | | if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) { |
| | | result.statementMonth = filters.startMonth; |
| | | } else if (filters.startMonth) { |
| | | result.startMonth = filters.startMonth; |
| | | } |
| | | if (filters.endMonth && filters.startMonth !== filters.endMonth) { |
| | | result.endMonth = filters.endMonth; |
| | | if (Array.isArray(filters.dateRange) && filters.dateRange.length === 2) { |
| | | result.startDate = filters.dateRange[0]; |
| | | result.endDate = filters.dateRange[1]; |
| | | } |
| | | return result; |
| | | }; |
| | |
| | | |
| | | const resetFilters = () => { |
| | | filters.supplierId = ""; |
| | | filters.startMonth = ""; |
| | | filters.endMonth = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | |
| | | const viewDetail = (row) => { |
| | | const partnerId = row.customerId ?? row.supplierId; |
| | | if (!partnerId || !row.statementMonth) { |
| | | ElMessage.warning("缺å°ä¾åºåæå¯¹è´¦æä»½ï¼æ æ³æ¥è¯¢æç»"); |
| | | ElMessage.warning("缺å°ä¾åºåæå¯¹è´¦æ¥æï¼æ æ³æ¥è¯¢æç»"); |
| | | return; |
| | | } |
| | | |
| | |
| | | <el-option v-for="item in customerList" :key="item.id" :label="item.customerName" :value="item.id" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="对账æé´:"> |
| | | <el-date-picker v-model="filters.startMonth" type="month" placeholder="å¼å§æä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <span style="margin: 0 10px;">è³</span> |
| | | <el-date-picker v-model="filters.endMonth" type="month" placeholder="ç»ææä»½" value-format="YYYY-MM" style="width: 140px;" /> |
| | | <el-form-item label="å¯¹è´¦æ¥æ:"> |
| | | <el-date-picker |
| | | v-model="filters.dateRange" |
| | | type="daterange" |
| | | placeholder="è¯·éæ©å¯¹è´¦æ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | format="YYYY-MM-DD" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | range-separator="è³" |
| | | clearable |
| | | style="width: 300px;" |
| | | /> |
| | | </el-form-item> |
| | | <el-form-item> |
| | | <el-button type="primary" @click="getTableData">æç´¢</el-button> |
| | |
| | | <FormDialog title="对账æç»" v-model="detailDialogVisible" width="900px" @confirm="printDetail" @cancel="detailDialogVisible = false" operationType="detail"> |
| | | <div class="statement-header"> |
| | | <h3>{{ currentCustomer }} åºæ¶å¯¹è´¦å</h3> |
| | | <p>对账æé´: {{ currentPeriod }}</p> |
| | | <p>å¯¹è´¦æ¥æ: {{ currentPeriod }}</p> |
| | | </div> |
| | | <el-table :data="detailData" border style="width: 100%" v-loading="detailLoading"> |
| | | <el-table-column prop="date" label="æ¥æ" width="120" /> |
| | |
| | | </el-form-item> |
| | | </el-col> |
| | | <el-col :span="12"> |
| | | <el-form-item label="对账æä»½" prop="statementMonth"> |
| | | <el-date-picker v-model="generateForm.statementMonth" type="month" placeholder="éæ©æä»½" value-format="YYYY-MM" style="width: 100%;" @change="onStatementMonthChange" /> |
| | | <el-form-item label="å¯¹è´¦æ¥æ" prop="statementMonth"> |
| | | <el-date-picker v-model="generateForm.statementMonth" type="date" placeholder="éæ©å¯¹è´¦æ¥æ" value-format="YYYY-MM-DD" format="YYYY-MM-DD" style="width: 100%;" @change="onStatementMonthChange" /> |
| | | </el-form-item> |
| | | </el-col> |
| | | </el-row> |
| | | </el-form> |
| | | |
| | | <div v-if="statementDetailLoaded" class="sales-section"> |
| | | <div v-if="salesData.length > 0" class="section-title">æ¬æé宿°æ®</div> |
| | | <div v-if="salesData.length > 0" class="section-title">å¯¹è´¦æ¥æé宿°æ®</div> |
| | | <el-table |
| | | v-if="salesData.length > 0" |
| | | ref="salesTableRef" |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" /> |
| | | </el-table> |
| | | <el-empty v-else description="è¯¥å®¢æ·æ¬æææ æç»æ°æ®" :image-size="80" /> |
| | | <el-empty v-else description="该客æ·å½æ¥ææ æç»æ°æ®" :image-size="80" /> |
| | | |
| | | <div class="summary-row"> |
| | | <span>æåä½é¢: <strong class="text-primary">Â¥{{ formatMoney(generateForm.openingBalance) }}</strong></span> |
| | |
| | | </div> |
| | | |
| | | <div v-else-if="generateForm.customerId && generateForm.statementMonth && !salesLoading" class="empty-tip"> |
| | | <el-empty description="è¯¥å®¢æ·æ¬æææ é宿°æ®" /> |
| | | <el-empty description="该客æ·å½æ¥ææ é宿°æ®" /> |
| | | </div> |
| | | |
| | | <template #footer> |
| | |
| | | |
| | | const filters = reactive({ |
| | | customerId: "", |
| | | startMonth: "", |
| | | endMonth: "", |
| | | dateRange: [], |
| | | }); |
| | | |
| | | const pagination = reactive({ |
| | |
| | | const columns = [ |
| | | { label: "对账åå·", prop: "statementNumber", width: "150" }, |
| | | { label: "客æ·åç§°", prop: "customerName", width: "180" }, |
| | | { label: "对账æé´", prop: "statementMonth", width: "150" }, |
| | | { label: "å¯¹è´¦æ¥æ", prop: "statementMonth", width: "150" }, |
| | | { label: "æåä½é¢", prop: "openingBalance", dataType: "slot", slot: "openingBalance" }, |
| | | { label: "æ¬æåºæ¶", prop: "currentPlan", dataType: "slot", slot: "currentPlan" }, |
| | | { label: "æ¬ææ¶æ¬¾", prop: "currentActually", dataType: "slot", slot: "currentActually" }, |
| | |
| | | if (filters.customerId) { |
| | | result.customerId = filters.customerId; |
| | | } |
| | | if (filters.startMonth && filters.endMonth && filters.startMonth === filters.endMonth) { |
| | | result.statementMonth = filters.startMonth; |
| | | } else if (filters.startMonth) { |
| | | result.startMonth = filters.startMonth; |
| | | } |
| | | if (filters.endMonth && filters.startMonth !== filters.endMonth) { |
| | | result.endMonth = filters.endMonth; |
| | | if (Array.isArray(filters.dateRange) && filters.dateRange.length === 2) { |
| | | result.startDate = filters.dateRange[0]; |
| | | result.endDate = filters.dateRange[1]; |
| | | } |
| | | return result; |
| | | }; |
| | |
| | | |
| | | const resetFilters = () => { |
| | | filters.customerId = ""; |
| | | filters.startMonth = ""; |
| | | filters.endMonth = ""; |
| | | filters.dateRange = []; |
| | | pagination.currentPage = 1; |
| | | getTableData(); |
| | | }; |
| | |
| | | |
| | | const viewDetail = (row) => { |
| | | if (!row.customerId || !row.statementMonth) { |
| | | ElMessage.warning("缺å°å®¢æ·æå¯¹è´¦æä»½ï¼æ æ³æ¥è¯¢æç»"); |
| | | ElMessage.warning("缺å°å®¢æ·æå¯¹è´¦æ¥æï¼æ æ³æ¥è¯¢æç»"); |
| | | return; |
| | | } |
| | | |
| | |
| | | <li v-for="item in todoList" :key="item.id" class="todo-item"> |
| | | <div class="todo-head"> |
| | | <span class="todo-no">å¾
åç¼å·ï¼{{ item.approveId }}</span> |
| | | <span class="todo-time">{{ item.approveTime }}</span> |
| | | <span class="todo-time">{{ formatDateOnly(item.approveTime) }}</span> |
| | | </div> |
| | | <div class="todo-desc">é¨é¨ï¼{{ item.approveDeptName }}</div> |
| | | <div class="todo-desc">äºç±ï¼{{ item.approveReason }}</div> |
| | |
| | | </div> |
| | | <div class="plan-meta"> |
| | | <span>计å {{ item.planQty }}</span> |
| | | <span>交æ {{ item.deliveryDate }}</span> |
| | | <span>交æ {{ formatDateOnly(item.deliveryDate) }}</span> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | |
| | | label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip |
| | | /> |
| | | > |
| | | <template #default="scope"> |
| | | {{ formatDateOnly(scope.row.updateTime || scope.row.createTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" min-width="180" align="center"> |
| | | <template #default="scope"> |
| | | <el-button |
| | |
| | | <el-table-column label="å»ç»æ°é" prop="lockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="åºåé¢è¦æ°é" prop="warnNum" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ formatDateOnly(scope.row.updateTime || scope.row.createTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" min-width="90" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">é¢ç¨</el-button> |
| | |
| | | show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" |
| | | prop="updateTime" |
| | | show-overflow-tooltip /> |
| | | show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ formatDateOnly(scope.row.updateTime || scope.row.createTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" |
| | | label="æä½" |
| | | min-width="80" |
| | |
| | | <el-table-column label="åºåæ°é" prop="qualitity" show-overflow-tooltip /> |
| | | <el-table-column label="å»ç»æ°é" prop="lockedQuantity" show-overflow-tooltip /> |
| | | <el-table-column label="夿³¨" prop="remark" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip /> |
| | | <el-table-column label="æè¿æ´æ°æ¶é´" prop="updateTime" show-overflow-tooltip> |
| | | <template #default="scope"> |
| | | {{ formatDateOnly(scope.row.updateTime || scope.row.createTime) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column fixed="right" label="æä½" min-width="90" align="center"> |
| | | <template #default="scope"> |
| | | <el-button link type="primary" @click="showSubtractModal(scope.row)" :disabled="scope.row.unLockedQuantity === 0">é¢ç¨</el-button> |
| | |
| | | const leadingCols = moduleKey && INSTANCE_NO_SEARCH_MODULE_KEYS.has(moduleKey) ? [INSTANCE_NO_TABLE_COLUMN] : []; |
| | | |
| | | return computed(() => { |
| | | const formCols = getFormConfigFieldColumns(tableDataRef.value?.[0], { excludeKeys }); |
| | | // åºå®åå·²å±ç¤ºçåæ®µä¸åç± formConfig 卿åéå¤çæ |
| | | const fixedProps = new Set( |
| | | [...beforeFormColumns, ...extraColumns, ...afterFormColumns] |
| | | .map(c => c?.prop) |
| | | .filter(Boolean) |
| | | ); |
| | | const formCols = getFormConfigFieldColumns(tableDataRef.value?.[0], { |
| | | excludeKeys: new Set([...(excludeKeys || []), ...fixedProps]), |
| | | }); |
| | | return [ |
| | | ...leadingCols, |
| | | ...beforeFormColumns, |
| | |
| | | APPROVAL_MODULE_KEYS.WORK_HANDOVER, |
| | | APPROVAL_MODULE_KEYS.LEAVE, |
| | | APPROVAL_MODULE_KEYS.OVERTIME, |
| | | APPROVAL_MODULE_KEYS.OUT_OFFICE, |
| | | ]); |
| | | |
| | | export const INSTANCE_NO_TABLE_COLUMN = { |
| | |
| | | WORK_HANDOVER: "work_handover", |
| | | LEAVE: "leave", |
| | | OVERTIME: "overtime", |
| | | OUT_OFFICE: "out_office", |
| | | TRAVEL_REIMBURSE: "travel_reimburse", |
| | | COST_REIMBURSE: "cost_reimburse", |
| | | ENTERPRISE_NEWS: "enterprise_news", |
| | |
| | | [APPROVAL_MODULE_KEYS.WORK_HANDOVER]: 13, |
| | | [APPROVAL_MODULE_KEYS.LEAVE]: 14, |
| | | [APPROVAL_MODULE_KEYS.OVERTIME]: 15, |
| | | [APPROVAL_MODULE_KEYS.OUT_OFFICE]: 1, |
| | | [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: 16, |
| | | [APPROVAL_MODULE_KEYS.COST_REIMBURSE]: 17, |
| | | [APPROVAL_MODULE_KEYS.ENTERPRISE_NEWS]: 18, |
| | |
| | | businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.OVERTIME], |
| | | typeLabels: ["å ç", "å çç³è¯·", "å ç审æ¹"], |
| | | }, |
| | | [APPROVAL_MODULE_KEYS.OUT_OFFICE]: { |
| | | label: "å
¬åºç³è¯·", |
| | | approvalType: "out_office", |
| | | businessType: APPROVAL_BUSINESS_TYPE[APPROVAL_MODULE_KEYS.OUT_OFFICE], |
| | | typeLabels: ["å
¬åº", "å
¬åºç³è¯·", "å
¬åºç®¡ç"], |
| | | }, |
| | | [APPROVAL_MODULE_KEYS.TRAVEL_REIMBURSE]: { |
| | | label: "å·®æ
æ¥é", |
| | | approvalType: "travel_reimburse", |
| | |
| | | if (val == null || val === "") return ""; |
| | | if (Array.isArray(val) && val.length >= 3) { |
| | | const [y, m, d, h = 0, min = 0, s = 0] = val; |
| | | return dayjs(new Date(y, m - 1, d, h, min, s)).format("YYYY-MM-DD HH:mm:ss"); |
| | | return dayjs(new Date(y, m - 1, d, h, min, s)).format("YYYY-MM-DD"); |
| | | } |
| | | if (typeof val === "number") { |
| | | const d = val > 1e12 ? dayjs(val) : dayjs.unix(val); |
| | | return d.isValid() ? d.format("YYYY-MM-DD HH:mm:ss") : ""; |
| | | return d.isValid() ? d.format("YYYY-MM-DD") : ""; |
| | | } |
| | | const s = String(val).trim(); |
| | | if (!s) return ""; |
| | | const parsed = dayjs(s.includes("T") ? s : s.replace(/-/g, "/")); |
| | | return parsed.isValid() ? parsed.format("YYYY-MM-DD HH:mm:ss") : s; |
| | | return parsed.isValid() ? parsed.format("YYYY-MM-DD") : s; |
| | | } |
| | | |
| | | export function formatDisplayTime(val) { |
| | |
| | | }; |
| | | const kw = (searchForm?.keyword || "").trim(); |
| | | if (kw) params.templateName = kw; |
| | | if (searchForm?.businessType != null && searchForm.businessType !== "") { |
| | | params.businessType = searchForm.businessType; |
| | | } |
| | | if (searchForm?.enabledOnly) params.enabled = "1"; |
| | | return params; |
| | | } |
| | |
| | | |
| | | /> |
| | | |
| | | <span class="search_title" style="margin-left: 12px">模æ¿ç±»åï¼</span> |
| | | <el-select |
| | | v-model="searchForm.businessType" |
| | | placeholder="è¯·éæ©æ¨¡æ¿ç±»å" |
| | | clearable |
| | | filterable |
| | | style="width: 180px" |
| | | @change="handleQuery" |
| | | > |
| | | <el-option |
| | | v-for="opt in templateTypeOptions" |
| | | :key="`search-biz-type-${opt.value}`" |
| | | :label="opt.label" |
| | | :value="opt.value" |
| | | /> |
| | | </el-select> |
| | | <el-checkbox v-model="searchForm.enabledOnly" class="ml12" @change="handleQuery"> |
| | | |
| | | ä»
æ¾ç¤ºå¯ç¨ |
| | |
| | | import { ElMessage } from "element-plus"; |
| | | |
| | | import { computed, nextTick, onMounted, ref } from "vue"; |
| | | import { useRoute } from "vue-router"; |
| | | |
| | | import { userListNoPageByTenantId } from "@/api/system/user.js"; |
| | | |
| | |
| | | } = useApproveTemplate(); |
| | | |
| | | |
| | | |
| | | const route = useRoute(); |
| | | |
| | | const flowUserOptions = ref([]); |
| | | |
| | |
| | | |
| | | |
| | | |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | |
| | | loadUsers(); |
| | | |
| | | loadTemplateTypeOptions(); |
| | | const queryBusinessType = route.query.businessType; |
| | | |
| | | if (queryBusinessType != null && queryBusinessType !== "") { |
| | | searchForm.businessType = Number(queryBusinessType); |
| | | } |
| | | |
| | | await loadTemplateTypeOptions(); |
| | | |
| | | fetchTemplateList(); |
| | | |
| | |
| | | |
| | | const searchForm = reactive({ |
| | | keyword: "", |
| | | businessType: "", |
| | | enabledOnly: false, |
| | | }); |
| | | |
| | |
| | | |
| | | function resetSearch() { |
| | | searchForm.keyword = ""; |
| | | searchForm.businessType = ""; |
| | | searchForm.enabledOnly = false; |
| | | handleQuery(); |
| | | } |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <!--OA模åï¼å
¬åºç³è¯·--> |
| | | <template> |
| | | <div class="app-container"> |
| | | <div class="search_form mb20"> |
| | | <div> |
| | | <span class="search_title">审æ¹åå·ï¼</span> |
| | | <el-input |
| | | v-model="searchForm.instanceNo" |
| | | style="width: 220px" |
| | | placeholder="请è¾å
¥å®¡æ¹åå·" |
| | | clearable |
| | | @keyup.enter="onSearch" |
| | | /> |
| | | <span class="search_title" style="margin-left: 12px">ç³è¯·äººï¼</span> |
| | | <el-input |
| | | v-model="searchForm.applicantKeyword" |
| | | style="width: 220px" |
| | | placeholder="å§åæç¼å·" |
| | | clearable |
| | | :prefix-icon="Search" |
| | | @keyup.enter="onSearch" |
| | | /> |
| | | <el-button type="primary" style="margin-left: 10px" @click="onSearch">æç´¢</el-button> |
| | | <el-button @click="resetSearch">éç½®</el-button> |
| | | </div> |
| | | <div class="search_actions"> |
| | | <el-button type="primary" @click="openAddWithTemplate">æ°å¢å
¬åºç³è¯·</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="table_list"> |
| | | <PIMTable |
| | | rowKey="id" |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :page="page" |
| | | :isSelection="false" |
| | | :tableLoading="tableLoading" |
| | | @pagination="onPagination" |
| | | :total="page.total" |
| | | /> |
| | | </div> |
| | | |
| | | <ApprovalInstanceSubmitDialog |
| | | v-model="submitDialog.visible" |
| | | :title="submitDialogTitle" |
| | | :form="submitForm" |
| | | :rules="submitFormRules" |
| | | :fields="submitFormFields" |
| | | :active-template="activeTemplate" |
| | | :user-options="flowUserOptions" |
| | | :is-edit="isSubmitEdit" |
| | | :saving="submitSaving" |
| | | :form-ref="submitFormRef" |
| | | @submit="onSubmit" |
| | | /> |
| | | |
| | | <ApprovalTemplateBindDialog |
| | | v-model:visible="templateBindVisible" |
| | | :module-key="APPROVAL_MODULE_KEYS.OUT_OFFICE" |
| | | skip-form-confirm |
| | | @confirm="onTemplateBound" |
| | | @closed="onTemplateBindClosed" |
| | | /> |
| | | |
| | | <ApprovalInstanceDetailDialog |
| | | v-model="detailDialog.visible" |
| | | title="å
¬åºç³è¯·è¯¦æ
" |
| | | :row="detailRow" |
| | | @edit="openEditFromDetail" |
| | | /> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { Search } from "@element-plus/icons-vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { onMounted, reactive } from "vue"; |
| | | import ApprovalInstanceDetailDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceDetailDialog.vue"; |
| | | import ApprovalInstanceSubmitDialog from "../../ApproveManage/approve-shared/components/ApprovalInstanceSubmitDialog.vue"; |
| | | import ApprovalTemplateBindDialog from "../../ApproveManage/approve-shared/components/ApprovalTemplateBindDialog.vue"; |
| | | import { buildInstanceTableColumns } from "../../ApproveManage/approve-shared/approvalInstanceFormConfigTable.js"; |
| | | import { APPROVAL_MODULE_KEYS } from "../../ApproveManage/approve-shared/approvalModuleRegistry.js"; |
| | | import { useApprovalInstanceModule } from "../../ApproveManage/approve-shared/useApprovalInstanceModule.js"; |
| | | import { useFlowUserOptions } from "../../ApproveManage/approve-shared/useFlowUserOptions.js"; |
| | | |
| | | const searchForm = reactive({ |
| | | instanceNo: "", |
| | | applicantKeyword: "", |
| | | }); |
| | | |
| | | const mod = useApprovalInstanceModule({ |
| | | moduleKey: APPROVAL_MODULE_KEYS.OUT_OFFICE, |
| | | }); |
| | | |
| | | const { |
| | | tableData, |
| | | tableLoading, |
| | | page, |
| | | detailDialog, |
| | | detailRow, |
| | | submitDialog, |
| | | submitForm, |
| | | submitFormRef, |
| | | submitSaving, |
| | | isSubmitEdit, |
| | | activeTemplate, |
| | | submitFormFields, |
| | | submitFormRules, |
| | | submitDialogTitle, |
| | | templateBindVisible, |
| | | handleQuery, |
| | | initModuleList, |
| | | pagination, |
| | | openAddWithTemplate, |
| | | onTemplateBound, |
| | | onTemplateBindClosed, |
| | | openEditFromDetail, |
| | | submitInstanceForm, |
| | | buildTableActions, |
| | | } = mod; |
| | | |
| | | const { flowUserOptions, loadFlowUsers } = useFlowUserOptions(); |
| | | |
| | | const tableColumn = buildInstanceTableColumns(tableData, buildTableActions, { |
| | | moduleKey: APPROVAL_MODULE_KEYS.OUT_OFFICE, |
| | | }); |
| | | |
| | | function onSearch() { |
| | | handleQuery(searchForm); |
| | | } |
| | | |
| | | function resetSearch() { |
| | | searchForm.instanceNo = ""; |
| | | searchForm.applicantKeyword = ""; |
| | | onSearch(); |
| | | } |
| | | |
| | | function onPagination(obj) { |
| | | pagination(obj, searchForm); |
| | | } |
| | | |
| | | async function onSubmit() { |
| | | const ok = await submitInstanceForm({ skipValidate: true }); |
| | | if (ok) ElMessage.success(isSubmitEdit.value ? "ä¿®æ¹æå" : "æäº¤æå"); |
| | | } |
| | | |
| | | onMounted(async () => { |
| | | loadFlowUsers(); |
| | | await initModuleList(searchForm); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .mb20 { |
| | | margin-bottom: 20px; |
| | | } |
| | | .search_form { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | align-items: center; |
| | | justify-content: space-between; |
| | | gap: 12px; |
| | | } |
| | | .search_actions { |
| | | display: flex; |
| | | flex-wrap: wrap; |
| | | gap: 8px; |
| | | } |
| | | .search_title { |
| | | font-size: 14px; |
| | | color: var(--el-text-color-regular); |
| | | } |
| | | </style> |
| | |
| | | |
| | | const tableColumn = buildInstanceTableColumns(tableData, buildTableActions, { |
| | | moduleKey: APPROVAL_MODULE_KEYS.REGULAR, |
| | | beforeFormColumns: [ |
| | | { |
| | | label: "ç³è¯·äºº", |
| | | prop: "applicantName", |
| | | width: 120, |
| | | showOverflowTooltip: true, |
| | | }, |
| | | ], |
| | | }); |
| | | |
| | | function onSearch() { |
| | |
| | | clearable |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="åä¸" value="middle_school" /> |
| | | <el-option label="é«ä¸" value="high_school" /> |
| | | <el-option label="ä¸ä¸" value="technical_secondary" /> |
| | | <el-option label="ä¸ä¸å以ä¸" value="secondary" /> |
| | | <el-option label="大ä¸" value="junior_college" /> |
| | | <el-option label="æ¬ç§" value="bachelor" /> |
| | |
| | | prop: "phone", |
| | | width: 150, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人", |
| | | prop: "emergencyContact", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "ç´§æ¥è系人çµè¯", |
| | | prop: "emergencyContactPhone", |
| | | width: 150, |
| | | }, |
| | | // { |
| | | // label: "ååå¹´é", |
| | | // prop: "contractTerm", |
| | |
| | | clearable |
| | | style="width: 100%" |
| | | > |
| | | <el-option label="åä¸" value="middle_school" /> |
| | | <el-option label="é«ä¸" value="high_school" /> |
| | | <el-option label="ä¸ä¸" value="technical_secondary" /> |
| | | <el-option label="ä¸ä¸å以ä¸" value="secondary" /> |
| | | <el-option label="大ä¸" value="junior_college" /> |
| | | <el-option label="æ¬ç§" value="bachelor" /> |
| | |
| | | @close="auditDialogVisible = false" |
| | | @success="handleAuditSuccess" |
| | | /> |
| | | <el-dialog v-model="salaryDetailVisible" title="人åå·¥èµæç»" width="1100px"> |
| | | <div class="salary-detail-title"> |
| | | {{ salaryDetailRow.salaryTitle || "-" }}ï¼{{ salaryDetailRow.salaryMonth || "-" }}ï¼ |
| | | </div> |
| | | <el-table :data="salaryDetailRow.staffSalaryDetailList || []" border style="width: 100%"> |
| | | <el-table-column prop="staffName" label="å§å" width="90" /> |
| | | <el-table-column prop="deptName" label="é¨é¨" width="110" /> |
| | | <el-table-column prop="postName" label="å²ä½" width="110" /> |
| | | <el-table-column prop="basicSalary" label="åºæ¬å·¥èµ" width="100" align="right" /> |
| | | <el-table-column prop="pieceSalary" label="计件工èµ" width="100" align="right" /> |
| | | <el-table-column prop="otherIncome" label="å
¶ä»æ¶å
¥" width="100" align="right" /> |
| | | <el-table-column prop="socialPersonal" label="社ä¿(个人)" width="100" align="right" /> |
| | | <el-table-column prop="fundPersonal" label="å
¬ç§¯é(个人)" width="110" align="right" /> |
| | | <el-table-column prop="salaryTax" label="个ç¨" width="90" align="right" /> |
| | | <el-table-column prop="grossSalary" label="åºåå·¥èµ" width="110" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.grossSalary) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="deductSalary" label="æ£æ¬¾å计" width="100" align="right" /> |
| | | <el-table-column prop="netSalary" label="å®åå·¥èµ" width="110" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.netSalary) }}</template> |
| | | </el-table-column> |
| | | </el-table> |
| | | <template #footer> |
| | | <el-button @click="salaryDetailVisible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | <el-dialog v-model="issueRecordVisible" title="åæ¾è®°å½" width="820px" append-to-body> |
| | | <template v-if="issueRecordRow"> |
| | | <el-descriptions :column="2" border> |
| | | <el-descriptions-item label="å·¥èµä¸»é¢">{{ issueRecordRow.salaryTitle }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥èµæä»½">{{ issueRecordRow.salaryMonth }}</el-descriptions-item> |
| | | <el-descriptions-item label="å·¥èµæ»é¢">{{ formatAmount(issueRecordRow.totalSalary) }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ¾é¶è¡">{{ issueRecordRow.payBank || "-" }}</el-descriptions-item> |
| | | <el-descriptions-item label="åæ¾æ¶é´">{{ issueRecordRow.payTime || "-" }}</el-descriptions-item> |
| | | <el-descriptions-item label="å®¡æ ¸äºº">{{ issueRecordRow.auditUserName || "-" }}</el-descriptions-item> |
| | | <el-descriptions-item label="夿³¨" :span="2">{{ issueRecordRow.remark || "-" }}</el-descriptions-item> |
| | | </el-descriptions> |
| | | <div class="issue-record-title">åæ¾æç»ï¼{{ issueRecordDetails.length }} 人ï¼</div> |
| | | <el-table :data="issueRecordDetails" border size="small" max-height="360"> |
| | | <el-table-column prop="staffName" label="å§å" width="90" /> |
| | | <el-table-column prop="deptName" label="é¨é¨" width="110" /> |
| | | <el-table-column prop="postName" label="å²ä½" width="110" /> |
| | | <el-table-column label="åºåå·¥èµ" width="110" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.grossSalary) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="æ£æ¬¾å计" width="110" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.deductSalary) }}</template> |
| | | </el-table-column> |
| | | <el-table-column label="å®åå·¥èµ" width="110" align="right"> |
| | | <template #default="{ row }">{{ formatAmount(row.netSalary) }}</template> |
| | | </el-table-column> |
| | | <el-table-column prop="remark" label="夿³¨" show-overflow-tooltip /> |
| | | </el-table> |
| | | </template> |
| | | <template #footer> |
| | | <el-button @click="issueRecordVisible = false">å
³é</el-button> |
| | | </template> |
| | | </el-dialog> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | }, |
| | | { label: "å·¥èµæ»é¢", prop: "totalSalary", width: 120 }, |
| | | { label: "æ¯ä»é¶è¡", prop: "payBank", width: 120 }, |
| | | { label: "åæ¾æ¶é´", prop: "payTime", width: 160, formatData: (v) => v || "-" }, |
| | | { label: "å®¡æ ¸äºº", prop: "auditUserName", width: 110 }, |
| | | { label: "夿³¨", prop: "remark", minWidth: 120 }, |
| | | { |
| | |
| | | label: "æä½", |
| | | align: "center", |
| | | fixed: "right", |
| | | width: 180, |
| | | width: 240, |
| | | operation: [ |
| | | { |
| | | name: "æ¥çæç»", |
| | | type: "text", |
| | | clickFun: (row) => openSalaryDetail(row), |
| | | }, |
| | | { |
| | | name: "ç¼è¾", |
| | | type: "text", |
| | |
| | | type: "text", |
| | | disabled: (row) => Number(row?.status) !== 4, |
| | | clickFun: (row) => openIssue(row), |
| | | }, |
| | | { |
| | | name: "åæ¾è®°å½", |
| | | type: "text", |
| | | disabled: (row) => Number(row?.status) !== 5, |
| | | clickFun: (row) => openIssueRecord(row), |
| | | }, |
| | | ], |
| | | }, |
| | |
| | | const auditDialogVisible = ref(false); |
| | | const auditRow = ref(null); |
| | | const auditDiaRef = ref(null); |
| | | const salaryDetailVisible = ref(false); |
| | | const salaryDetailRow = ref({}); |
| | | const issueRecordVisible = ref(false); |
| | | const issueRecordRow = ref(null); |
| | | |
| | | const issueRecordDetails = computed(() => |
| | | Array.isArray(issueRecordRow.value?.staffSalaryDetailList) |
| | | ? issueRecordRow.value.staffSalaryDetailList |
| | | : [] |
| | | ); |
| | | |
| | | const formatAmount = (v) => (v == null || v === "" ? "-" : Number(v).toFixed(2)); |
| | | |
| | | const openIssueRecord = (row) => { |
| | | issueRecordRow.value = row || null; |
| | | issueRecordVisible.value = true; |
| | | }; |
| | | |
| | | const openSalaryDetail = (row) => { |
| | | salaryDetailRow.value = row || {}; |
| | | salaryDetailVisible.value = true; |
| | | }; |
| | | |
| | | const issueBankOptions = computed(() => { |
| | | const options = Array.isArray(bankSetting.value?.options) ? bankSetting.value.options : []; |
| | |
| | | .table_list { |
| | | margin-top: 20px; |
| | | } |
| | | .salary-detail-title { |
| | | margin-bottom: 12px; |
| | | font-weight: 600; |
| | | } |
| | | .issue-record-title { |
| | | margin: 16px 0 8px; |
| | | font-weight: 600; |
| | | font-size: 14px; |
| | | } |
| | | </style> |
| | |
| | | { |
| | | label: "ç»è®°æ¥æ", |
| | | prop: "registrationtDate", |
| | | formatData: cell => |
| | | cell ? dayjs(cell).format("YYYY-MM-DD HH:mm:ss") : "-", |
| | | formatData: cell => (cell ? dayjs(cell).format("YYYY-MM-DD") : "-"), |
| | | }, |
| | | ]); |
| | | const tableData = ref([]); |
| | |
| | | prefix-icon="Search" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <span class="search_title">ç产订åå·ï¼</span> |
| | | <el-input v-model="searchForm.productOrderNpsNo" |
| | | style="width: 240px" |
| | | placeholder="请è¾å
¥" |
| | | @change="handleQuery" |
| | | clearable |
| | | prefix-icon="Search" /> |
| | | </div> |
| | | <div class="search-item"> |
| | | <el-button type="primary" |
| | | @click="handleQuery">æç´¢</el-button> |
| | | </div> |
| | |
| | | const data = reactive({ |
| | | searchForm: { |
| | | workOrderNo: "", |
| | | productOrderNpsNo: "", |
| | | }, |
| | | }); |
| | | const { searchForm } = toRefs(data); |
| | |
| | | class="cell link" |
| | | style="width: 100%" |
| | | @click="goLink(scope.row, item.linkMethod)"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-if="!item.formatData">{{ |
| | | toDateOnly(scope.row[item.prop]) |
| | | }}</span> |
| | | </div> |
| | | <!-- é»è®¤çº¯å±ç¤ºæ°æ® --> |
| | | <div v-else |
| | | class="cell" |
| | | :class="item.className || ''" |
| | | style="width: 100%"> |
| | | <span v-if="!item.formatData">{{ scope.row[item.prop] }}</span> |
| | | <span v-if="!item.formatData">{{ |
| | | toDateOnly(scope.row[item.prop]) |
| | | }}</span> |
| | | <span v-else>{{ |
| | | formatters(scope.row[item.prop], item.formatData) |
| | | toDateOnly(formatters(scope.row[item.prop], item.formatData)) |
| | | }}</span> |
| | | </div> |
| | | </template> |
| | |
| | | import pagination from "../../../../components/PIMTable/Pagination.vue"; |
| | | import { ref, inject, getCurrentInstance } from "vue"; |
| | | import { ElMessage } from "element-plus"; |
| | | import { toDateOnly } from "@/utils/dateDisplay"; |
| | | |
| | | // è·åå
¨å±ç uploadHeader |
| | | const { proxy } = getCurrentInstance(); |
| | |
| | | <el-table-column label="èç³»æ¹å¼" prop="contact" width="140" /> |
| | | <el-table-column label="å馿¶é´" prop="feedbackTime" width="160"> |
| | | <template #default="scope"> |
| | | <span>{{ scope.row.feedbackTime }}</span> |
| | | <span>{{ formatDateOnly(scope.row.feedbackTime) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="é®é¢æè¿°" prop="problemDesc" show-overflow-tooltip /> |
| | |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isShowPagination="false" |
| | | height="400"> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isShowPagination="false" |
| | | height="400"> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | |
| | | :column="tableColumn" |
| | | :tableData="tableData" |
| | | :tableLoading="tableLoading" |
| | | :isShowPagination="false" |
| | | height="400"> |
| | | <template #slot="{ row }"> |
| | | <el-input v-model="row.testValue" |
| | |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ£æµåä½", |
| | | prop: "checkCompany", |
| | | width: 120, |
| | | }, |
| | | { |
| | | label: "æ£æµç»æ", |
| | | prop: "checkResult", |
| | | dataType: "tag", |
| | |
| | | > |
| | | <div class="todo-title">ç³è¯·ç±»åï¼{{ item.approveTypeName }}</div> |
| | | <div class="todo-division">ç³è¯·é¨é¨ï¼{{ item.approveDeptName }}</div> |
| | | <div class="todo-time">{{ item.approveTime }}</div> |
| | | <div class="todo-time">{{ formatDateOnly(item.approveTime) }}</div> |
| | | </div> |
| | | |
| | | </div> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å±é©æºé£é©ç级åå¸" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper"> |
| | | <div class="pie-background" :style="{ backgroundImage: `url(${roseBorderImg})` }"></div> |
| | | <Echarts |
| | | ref="echartsRef" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 320px" |
| | | /> |
| | | <div v-if="showEmpty" class="empty-tip">ææ æ°æ®</div> |
| | | </div> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, inject, watch } from 'vue' |
| | | import { getHazardRiskDistribution } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | import PanelHeader from '@/views/reportAnalysis/dataDashboard/components/PanelHeader.vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import roseBorderImg from '@/assets/BI/ç«ç°å¾è¾¹æ¡.png' |
| | | |
| | | const echartsRef = ref(null) |
| | | const riskRows = ref([]) |
| | | const loaded = ref(false) |
| | | const loading = ref(false) |
| | | const refreshFailed = ref(false) |
| | | |
| | | const pieColors = ['#ff5676', '#ffce64', '#43e8fc', '#82BAFF', '#D1E4F5'] |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' |
| | | } |
| | | |
| | | const total = computed(() => |
| | | riskRows.value.reduce((sum, item) => sum + (Number(item.value) || 0), 0) |
| | | ) |
| | | |
| | | const showEmpty = computed(() => loaded.value && !loading.value && total.value === 0) |
| | | |
| | | const objectData = computed(() => { |
| | | const map = {} |
| | | riskRows.value.forEach((item) => { |
| | | map[item.name] = item |
| | | }) |
| | | return map |
| | | }) |
| | | |
| | | const pieLegend = computed(() => ({ |
| | | orient: 'vertical', |
| | | top: 'center', |
| | | left: '50%', |
| | | itemGap: 30, |
| | | icon: 'circle', |
| | | data: riskRows.value.map((item, idx) => ({ |
| | | name: item.name, |
| | | icon: 'circle', |
| | | textStyle: { |
| | | fontSize: 18, |
| | | color: pieColors[idx % pieColors.length] |
| | | } |
| | | })), |
| | | formatter: function (name) { |
| | | const item = objectData.value[name] |
| | | if (!item) return name |
| | | return `{title|${name}}{value|${item.value}}{unit|å¤}{percent|${item.rate ?? '0.00'}}{unit|%}` |
| | | }, |
| | | textStyle: { |
| | | rich: { |
| | | value: { |
| | | color: '#43e8fc', |
| | | fontSize: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 30] |
| | | }, |
| | | unit: { |
| | | color: '#82baff', |
| | | fontSize: 14, |
| | | fontWeight: 600, |
| | | padding: [0, 50, 0, 0] |
| | | }, |
| | | percent: { |
| | | color: '#43e8fc', |
| | | fontSize: 18, |
| | | fontWeight: 600, |
| | | padding: [0, 10, 0, 0] |
| | | }, |
| | | title: { |
| | | fontSize: 18, |
| | | padding: [0, 0, 0, 0] |
| | | } |
| | | } |
| | | } |
| | | })) |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: '{a} <br/>{b}ï¼{c} å¤ï¼{d}%ï¼' |
| | | } |
| | | |
| | | const pieSeries = computed(() => [ |
| | | { |
| | | name: 'å±é©æºé£é©ç级', |
| | | type: 'pie', |
| | | radius: '70%', |
| | | center: ['20%', '50%'], |
| | | itemStyle: { |
| | | borderColor: '#0a1c3a', |
| | | borderWidth: 5 |
| | | }, |
| | | label: { |
| | | show: false |
| | | }, |
| | | minAngle: 15, |
| | | data: riskRows.value.map((item) => ({ |
| | | name: item.name, |
| | | value: Number(item.value) || 0, |
| | | rate: item.rate |
| | | })), |
| | | animationType: 'scale', |
| | | animationEasing: 'elasticOut' |
| | | } |
| | | ]) |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' } |
| | | } |
| | | |
| | | const loadData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | try { |
| | | const res = await getHazardRiskDistribution() |
| | | riskRows.value = Array.isArray(res?.data) ? res.data : [] |
| | | refreshFailed.value = false |
| | | loaded.value = true |
| | | } catch (e) { |
| | | console.error('è·åå±é©æºé£é©ç级åå¸å¤±è´¥:', e) |
| | | refreshFailed.value = true |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | position: relative; |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 370px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-113%, -50%); |
| | | width: 360px; |
| | | height: 360px; |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .empty-tip { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 44%; |
| | | width: 40%; |
| | | text-align: center; |
| | | font-size: 15px; |
| | | color: #8aa4c8; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .refresh-error { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 8px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="center-top"> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">éæ£æ»æ°</span> |
| | | <span class="card-value">{{ overviewValue(overview.hiddenTotal) }}</span> |
| | | <div class="card-sub"> |
| | | <span>å¾
æ´æ¹ {{ overviewValue(overview.hiddenPending) }}</span> |
| | | <span class="card-sub-divider">/</span> |
| | | <span>å·²æ´æ¹ {{ overviewValue(overview.hiddenRectified) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">å±é©æºæ»æ°</span> |
| | | <span class="card-value">{{ overviewValue(overview.hazardTotal) }}</span> |
| | | <div class="card-sub"> |
| | | <span>é大é£é© {{ overviewValue(overview.hazardMajor) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="徿 " class="card-icon" /> |
| | | <div class="card-content"> |
| | | <span class="card-label">äºæ
æ»æ°</span> |
| | | <span class="card-value">{{ overviewValue(overview.accidentTotal) }}</span> |
| | | <div class="card-sub"> |
| | | <span>æ¬å¹´ {{ overviewValue(overview.accidentThisYear) }}</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="徿 " class="event-icon" /> |
| | | <span class="event-title">å®å
¨å¨æ</span> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | |
| | | <div class="metrics-row"> |
| | | <div class="metric" v-for="item in metrics" :key="item.label"> |
| | | <span class="metric-value">{{ item.value }}</span> |
| | | <span class="metric-label">{{ item.label }}</span> |
| | | </div> |
| | | </div> |
| | | |
| | | <div class="event-content"> |
| | | <ul class="event-list" v-if="displayEvents.length > 0" ref="listRef"> |
| | | <li v-for="(item, index) in displayEvents" :key="index"> |
| | | <div class="event-line"> |
| | | <span class="event-cat" :class="catClass(item.category)">{{ item.category }}</span> |
| | | <span class="event-name">{{ item.title }}</span> |
| | | <span class="event-status">{{ item.statusText }}</span> |
| | | </div> |
| | | <div class="event-meta"> |
| | | <span>{{ item.location || 'â' }}</span> |
| | | <span>{{ item.eventTime }}</span> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else class="event-empty"> |
| | | {{ eventsFailed ? 'æ°æ®å 载失败ï¼60ç§åèªå¨éè¯' : 'ææ å®å
¨å¨æ' }} |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onBeforeUnmount, nextTick, inject, watch } from 'vue' |
| | | import { |
| | | getSafetyOverview, |
| | | getLatestSafeEvents |
| | | } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | |
| | | const overview = ref({ |
| | | hiddenTotal: 0, |
| | | hiddenPending: 0, |
| | | hiddenRectified: 0, |
| | | hazardTotal: 0, |
| | | hazardMajor: 0, |
| | | accidentTotal: 0, |
| | | accidentThisYear: 0, |
| | | trainingCount: 0, |
| | | trainingPersonTimes: 0, |
| | | planCount: 0, |
| | | certCount: 0 |
| | | }) |
| | | const overviewLoaded = ref(false) |
| | | const overviewFailed = ref(false) |
| | | |
| | | const events = ref([]) |
| | | const eventsFailed = ref(false) |
| | | |
| | | const listRef = ref(null) |
| | | const loading = ref(false) |
| | | |
| | | const refreshFailed = computed(() => overviewFailed.value || eventsFailed.value) |
| | | |
| | | const overviewValue = (val) => (overviewLoaded.value ? Number(val) || 0 : '--') |
| | | |
| | | const metrics = computed(() => [ |
| | | { label: 'å¹è®åºæ¬¡', value: overviewValue(overview.value.trainingCount) }, |
| | | { label: 'åè®äººæ¬¡', value: overviewValue(overview.value.trainingPersonTimes) }, |
| | | { label: 'åºæ¥é¢æ¡', value: overviewValue(overview.value.planCount) }, |
| | | { label: 'å®å
¨èµè´¨', value: overviewValue(overview.value.certCount) } |
| | | ]) |
| | | |
| | | const displayEvents = computed(() => { |
| | | if (events.value.length >= 4) { |
| | | return [...events.value, ...events.value] |
| | | } |
| | | return events.value |
| | | }) |
| | | |
| | | const catClass = (category) => { |
| | | if (category === 'äºæ
') return 'cat-accident' |
| | | if (category === 'å±é©ç©æé¢ç¨') return 'cat-material' |
| | | return 'cat-hidden' |
| | | } |
| | | |
| | | let rafId = null |
| | | |
| | | const stopScroll = () => { |
| | | if (rafId) { |
| | | cancelAnimationFrame(rafId) |
| | | rafId = null |
| | | } |
| | | } |
| | | |
| | | const startScroll = () => { |
| | | stopScroll() |
| | | nextTick(() => { |
| | | const el = listRef.value |
| | | if (!el || events.value.length < 4) return |
| | | const half = el.scrollHeight / 2 |
| | | if (half <= el.clientHeight) return |
| | | let position = 0 |
| | | const step = () => { |
| | | position += 0.4 |
| | | if (position >= half) { |
| | | position = 0 |
| | | el.scrollTop = 0 |
| | | } else { |
| | | el.scrollTop = position |
| | | } |
| | | rafId = requestAnimationFrame(step) |
| | | } |
| | | rafId = requestAnimationFrame(step) |
| | | }) |
| | | } |
| | | |
| | | const loadOverview = async () => { |
| | | try { |
| | | const res = await getSafetyOverview() |
| | | overview.value = { |
| | | hiddenTotal: 0, |
| | | hiddenPending: 0, |
| | | hiddenRectified: 0, |
| | | hazardTotal: 0, |
| | | hazardMajor: 0, |
| | | accidentTotal: 0, |
| | | accidentThisYear: 0, |
| | | trainingCount: 0, |
| | | trainingPersonTimes: 0, |
| | | planCount: 0, |
| | | certCount: 0, |
| | | ...(res?.data || {}) |
| | | } |
| | | overviewLoaded.value = true |
| | | overviewFailed.value = false |
| | | } catch (e) { |
| | | console.error('è·åå®å
¨æ»è§å¤±è´¥:', e) |
| | | overviewFailed.value = true |
| | | } |
| | | } |
| | | |
| | | const loadEvents = async () => { |
| | | try { |
| | | const res = await getLatestSafeEvents({ limit: 10 }) |
| | | stopScroll() |
| | | events.value = Array.isArray(res?.data) ? res.data : [] |
| | | eventsFailed.value = false |
| | | startScroll() |
| | | } catch (e) { |
| | | console.error('è·åææ°å®å
¨å¨æå¤±è´¥:', e) |
| | | eventsFailed.value = true |
| | | } |
| | | } |
| | | |
| | | const refreshData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | await Promise.allSettled([loadOverview(), loadEvents()]) |
| | | loading.value = false |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | refreshData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | refreshData() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | stopScroll() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .center-top { |
| | | flex: 1; |
| | | min-height: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .stat-card { |
| | | flex: 1; |
| | | display: flex; |
| | | align-items: center; |
| | | background-image: url('@/assets/BI/border@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | margin: 20px 20px 0 10px; |
| | | } |
| | | |
| | | .card-content { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 8px; |
| | | } |
| | | |
| | | .card-value { |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | background: linear-gradient(360deg, #008bfd 0%, #ffffff 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | } |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | color: rgba(208, 231, 255, 0.7); |
| | | } |
| | | |
| | | .card-sub { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 6px; |
| | | font-size: 14px; |
| | | color: #d0e7ff; |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .card-sub-divider { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | .event-info { |
| | | flex: 1; |
| | | min-height: 220px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | background-image: url('@/assets/BI/shijianmingchengbeijing@2x.png'); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 12px 20px 18px; |
| | | } |
| | | |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | position: relative; |
| | | } |
| | | |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 18px; |
| | | color: #fffffe; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .refresh-error { |
| | | margin-left: 12px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | |
| | | .metrics-row { |
| | | display: flex; |
| | | gap: 12px; |
| | | margin: 8px 0 12px; |
| | | } |
| | | |
| | | .metric { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | gap: 4px; |
| | | padding: 8px 0; |
| | | border-radius: 6px; |
| | | background: rgba(24, 93, 190, 0.18); |
| | | border: 1px solid rgba(64, 140, 235, 0.25); |
| | | } |
| | | |
| | | .metric-value { |
| | | font-size: 22px; |
| | | font-weight: 600; |
| | | color: #43e8fc; |
| | | } |
| | | |
| | | .metric-label { |
| | | font-size: 13px; |
| | | color: rgba(208, 231, 255, 0.75); |
| | | } |
| | | |
| | | .event-content { |
| | | flex: 1; |
| | | min-height: 0; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .event-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .event-list li { |
| | | padding: 8px 12px; |
| | | margin-bottom: 8px; |
| | | border-radius: 6px; |
| | | background: rgba(24, 93, 190, 0.12); |
| | | } |
| | | |
| | | .event-line { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .event-cat { |
| | | flex: none; |
| | | padding: 1px 8px; |
| | | border-radius: 10px; |
| | | font-size: 12px; |
| | | line-height: 18px; |
| | | } |
| | | |
| | | .cat-hidden { |
| | | color: #4ee4ff; |
| | | background: rgba(78, 228, 255, 0.15); |
| | | } |
| | | |
| | | .cat-accident { |
| | | color: #ff7f97; |
| | | background: rgba(255, 127, 151, 0.15); |
| | | } |
| | | |
| | | .cat-material { |
| | | color: #ffce64; |
| | | background: rgba(255, 206, 100, 0.15); |
| | | } |
| | | |
| | | .event-name { |
| | | flex: 1; |
| | | min-width: 0; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | white-space: nowrap; |
| | | font-size: 15px; |
| | | color: #fffffe; |
| | | } |
| | | |
| | | .event-status { |
| | | flex: none; |
| | | font-size: 13px; |
| | | color: rgba(208, 231, 255, 0.75); |
| | | } |
| | | |
| | | .event-meta { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | margin-top: 4px; |
| | | padding-left: 2px; |
| | | font-size: 12px; |
| | | color: rgba(184, 200, 224, 0.7); |
| | | } |
| | | |
| | | .event-empty { |
| | | height: 100%; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | font-size: 15px; |
| | | color: #8aa4c8; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="è¿6个æéæ£ææ¥ä¸æ´æ¹è¶å¿" /> |
| | | <div class="panel-item-customers"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="legend" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="chartOptions" |
| | | style="height: 380px" |
| | | /> |
| | | <div v-if="showEmpty" class="empty-tip">è¿6ä¸ªæææ éæ£æ°æ®</div> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '@/views/reportAnalysis/dataDashboard/components/PanelHeader.vue' |
| | | import { getHiddenTrend } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | |
| | | const chart = ref(null) |
| | | const trendData = ref([]) |
| | | const loaded = ref(false) |
| | | const loading = ref(false) |
| | | const refreshFailed = ref(false) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' |
| | | } |
| | | |
| | | const chartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' } |
| | | } |
| | | |
| | | const showEmpty = computed(() => { |
| | | if (!loaded.value || loading.value) return false |
| | | return trendData.value.every( |
| | | (item) => !Number(item.reportedCount) && !Number(item.completedCount) |
| | | ) |
| | | }) |
| | | |
| | | const categories = computed(() => |
| | | trendData.value.map((item) => { |
| | | const month = String(item.month || '') |
| | | return month.length >= 7 ? `${month.slice(5)}æ` : month |
| | | }) |
| | | ) |
| | | |
| | | const series = computed(() => [ |
| | | { |
| | | name: '䏿¥æ°', |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 1, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(0,164,237,0)' }, |
| | | { offset: 1, color: '#4EE4FF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: trendData.value.map((item) => Number(item.reportedCount) || 0) |
| | | }, |
| | | { |
| | | name: 'æ´æ¹å®ææ°', |
| | | type: 'bar', |
| | | barWidth: 18, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 1, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(38,255,203,0)' }, |
| | | { offset: 1, color: '#26FFCB' } |
| | | ] |
| | | } |
| | | }, |
| | | data: trendData.value.map((item) => Number(item.completedCount) || 0) |
| | | } |
| | | ]) |
| | | |
| | | const legend = { |
| | | show: true, |
| | | right: '4%', |
| | | top: '0%', |
| | | itemWidth: 10, |
| | | itemHeight: 10, |
| | | icon: 'circle', |
| | | textStyle: { color: '#B8C8E0', fontSize: 13 }, |
| | | data: ['䏿¥æ°', 'æ´æ¹å®ææ°'] |
| | | } |
| | | |
| | | const grid = { |
| | | left: '3%', |
| | | right: '4%', |
| | | bottom: '3%', |
| | | top: '18%', |
| | | containLabel: true |
| | | } |
| | | |
| | | const tooltip = { |
| | | trigger: 'axis', |
| | | axisPointer: { type: 'shadow' }, |
| | | formatter: function (params) { |
| | | let result = `${params[0].axisValueLabel}<br/>` |
| | | params.forEach((item) => { |
| | | result += `<div>${item.marker} ${item.seriesName}ï¼${item.value} 项</div>` |
| | | }) |
| | | return result |
| | | } |
| | | } |
| | | |
| | | const xAxis = computed(() => [ |
| | | { |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLine: { lineStyle: { color: 'rgba(255,255,255,0.2)' } }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: categories.value |
| | | } |
| | | ]) |
| | | |
| | | const yAxis = [ |
| | | { |
| | | type: 'value', |
| | | minInterval: 1, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | splitLine: { lineStyle: { color: 'rgba(255,255,255,0.08)' } } |
| | | } |
| | | ] |
| | | |
| | | const loadData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | try { |
| | | const res = await getHiddenTrend() |
| | | trendData.value = Array.isArray(res?.data) ? res.data : [] |
| | | refreshFailed.value = false |
| | | loaded.value = true |
| | | } catch (e) { |
| | | console.error('è·å鿣è¶å¿å¤±è´¥:', e) |
| | | refreshFailed.value = true |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | position: relative; |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 478px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .empty-tip { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 50%; |
| | | text-align: center; |
| | | font-size: 16px; |
| | | color: #8aa4c8; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .refresh-error { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 8px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="éæ£ç±»ååå¸" /> |
| | | <div class="panel-item-customers"> |
| | | <div class="pie-chart-wrapper" ref="pieWrapperRef"> |
| | | <div class="pie-background" ref="pieBackgroundRef"></div> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :legend="pieLegend" |
| | | :series="pieSeries" |
| | | :tooltip="pieTooltip" |
| | | :color="pieColors" |
| | | :options="pieOptions" |
| | | style="height: 100%" |
| | | /> |
| | | <div v-if="showEmpty" class="empty-tip">ææ æ°æ®</div> |
| | | </div> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, onBeforeUnmount, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '@/views/reportAnalysis/dataDashboard/components/PanelHeader.vue' |
| | | import { useChartBackground } from '@/hooks/useChartBackground.js' |
| | | import { getHiddenTypeDistribution } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | |
| | | const pieWrapperRef = ref(null) |
| | | const pieBackgroundRef = ref(null) |
| | | const chart = ref(null) |
| | | |
| | | const dataList = ref([]) |
| | | const loaded = ref(false) |
| | | const loading = ref(false) |
| | | const refreshFailed = ref(false) |
| | | |
| | | const pieColors = ['#26FFCB', '#24CBFF', '#35FBF4', '#2651FF', '#D1E4F5', '#5782F7', '#2F67EF', '#82BAFF'] |
| | | |
| | | const showEmpty = computed(() => loaded.value && !loading.value && dataList.value.length === 0) |
| | | |
| | | const dotRich = pieColors.reduce((acc, color, idx) => { |
| | | acc[`dot${idx}`] = { |
| | | width: 8, |
| | | height: 8, |
| | | borderRadius: 8, |
| | | backgroundColor: color, |
| | | align: 'center' |
| | | } |
| | | return acc |
| | | }, {}) |
| | | |
| | | const pieLegend = { |
| | | show: false, |
| | | data: [] |
| | | } |
| | | |
| | | const pieTooltip = { |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | return `${params.name}ï¼${params.value} 项ï¼${params.percent}%ï¼` |
| | | } |
| | | } |
| | | |
| | | const pieSeries = computed(() => { |
| | | if (!dataList.value.length) return [] |
| | | return [ |
| | | { |
| | | name: 'éæ£ç±»å', |
| | | type: 'pie', |
| | | radius: ['35%', '55%'], |
| | | center: ['50%', '50%'], |
| | | label: { |
| | | show: true, |
| | | position: 'outside', |
| | | color: '#fff', |
| | | fontSize: 12, |
| | | lineHeight: 18, |
| | | rich: { |
| | | ...dotRich, |
| | | parent: { fontSize: 14, fontWeight: 600, color: '#fff', lineHeight: 20, overflow: 'break' }, |
| | | child: { fontSize: 12, color: '#fff', lineHeight: 18 } |
| | | }, |
| | | formatter: function (params) { |
| | | const children = params?.data?.children || [] |
| | | const parentName = params?.data?.name || '' |
| | | const parentValue = Number(params?.data?.value) || 0 |
| | | const dotKey = `dot${(params?.dataIndex || 0) % pieColors.length}` |
| | | const parentLine = `{${dotKey}|} {parent|${parentName} (${parentValue}项)}` |
| | | if (!children.length) return parentLine |
| | | const childLines = children.map( |
| | | (c) => `{child|${c.name} ${Number(c.value) || 0}项}` |
| | | ) |
| | | return [parentLine, ...childLines].join('\n') |
| | | } |
| | | }, |
| | | labelLine: { |
| | | show: true, |
| | | length: 20, |
| | | length2: 20, |
| | | lineStyle: { |
| | | color: '#B8C8E0' |
| | | } |
| | | }, |
| | | itemStyle: { |
| | | color: function (params) { |
| | | return pieColors[params.dataIndex % pieColors.length] |
| | | } |
| | | }, |
| | | data: dataList.value |
| | | }, |
| | | { |
| | | type: 'pie', |
| | | radius: ['35%', '40%'], |
| | | center: ['50%', '50%'], |
| | | silent: true, |
| | | label: { show: false }, |
| | | labelLine: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(0, 127, 255, 0.25)' |
| | | }, |
| | | data: [1] |
| | | } |
| | | ] |
| | | }) |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '126%' |
| | | } |
| | | |
| | | const pieOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' } |
| | | } |
| | | |
| | | const { adjustBackgroundPosition, init: initBackground, cleanup: cleanupBackground } = useChartBackground({ |
| | | wrapperRef: pieWrapperRef, |
| | | backgroundRef: pieBackgroundRef, |
| | | offsetX: '-51.5%', |
| | | offsetY: '-39%', |
| | | watchData: dataList |
| | | }) |
| | | |
| | | const loadData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | try { |
| | | const res = await getHiddenTypeDistribution() |
| | | const items = res?.data || [] |
| | | dataList.value = items.map((it) => ({ |
| | | name: it.name || 'æªç¥ç±»å', |
| | | value: Number(it.value) || 0, |
| | | rate: it.rate, |
| | | children: Array.isArray(it.children) ? it.children.map((c) => ({ |
| | | name: c.name, |
| | | value: Number(c.value) || 0, |
| | | rate: c.rate |
| | | })) : [] |
| | | })) |
| | | refreshFailed.value = false |
| | | loaded.value = true |
| | | adjustBackgroundPosition() |
| | | } catch (e) { |
| | | console.error('è·åéæ£ç±»ååå¸å¤±è´¥:', e) |
| | | refreshFailed.value = true |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | initBackground() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | cleanupBackground() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | position: relative; |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 420px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .pie-chart-wrapper { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 320px; |
| | | background: transparent; |
| | | } |
| | | |
| | | .pie-background { |
| | | position: absolute; |
| | | width: 360px; |
| | | height: 360px; |
| | | background-image: url('@/assets/BI/ç«ç°å¾è¾¹æ¡.png'); |
| | | background-size: contain; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 1; |
| | | pointer-events: none; |
| | | left: 50%; |
| | | top: 50%; |
| | | transform: translate(-51.5%, -39%); |
| | | } |
| | | |
| | | .empty-tip { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 48%; |
| | | text-align: center; |
| | | font-size: 16px; |
| | | color: #8aa4c8; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .refresh-error { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 8px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="éæ£æ´æ¹è´£ä»»äººæå" /> |
| | | <div class="panel-item-customers"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="chartOptions" |
| | | style="height: 390px" |
| | | /> |
| | | <div v-if="showEmpty" class="empty-tip">ææ æ°æ®</div> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '@/views/reportAnalysis/dataDashboard/components/PanelHeader.vue' |
| | | import { getHiddenRectifyRanking } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | |
| | | const chart = ref(null) |
| | | const rows = ref([]) |
| | | const loaded = ref(false) |
| | | const loading = ref(false) |
| | | const refreshFailed = ref(false) |
| | | |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff' |
| | | } |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' |
| | | } |
| | | |
| | | const chartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' } |
| | | } |
| | | |
| | | const sortedRows = computed(() => |
| | | [...rows.value].sort((a, b) => (Number(a.value) || 0) - (Number(b.value) || 0)) |
| | | ) |
| | | |
| | | const showEmpty = computed( |
| | | () => loaded.value && !loading.value && rows.value.length === 0 |
| | | ) |
| | | |
| | | const dataNum = computed(() => |
| | | sortedRows.value.map((item, i) => { |
| | | const index = sortedRows.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | return { |
| | | value: Number(item.value) || 0, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: isTop3 ? '#ffdae1' : '#ecf3ff' }, |
| | | { offset: 0.07, color: isTop3 ? color[index] : color[3] }, |
| | | { offset: 1, color: isTop3 ? 'rgba(255, 86, 118, .1)' : 'rgba(125,174,255, .1)' } |
| | | ], |
| | | global: false |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0] |
| | | } |
| | | }) |
| | | ) |
| | | |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(1, ...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue * 1.25) |
| | | }) |
| | | |
| | | const tooltip = computed(() => ({ |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | const index = params[0]?.dataIndex ?? 0 |
| | | const row = sortedRows.value[index] || {} |
| | | return `${params[0]?.axisValueLabel || ''}<br/>å·²æ´æ¹ï¼${row.value ?? 0} 项<br/>å æ¯ï¼${row.rate ?? '0.00'}%` |
| | | } |
| | | })) |
| | | |
| | | const grid = { top: 0, left: '22%', right: '12%', bottom: 0 } |
| | | |
| | | const xAxis = [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false } |
| | | } |
| | | ] |
| | | |
| | | const yAxis = computed(() => [ |
| | | { |
| | | type: 'category', |
| | | data: sortedRows.value.map((item) => item.name), |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right' |
| | | } |
| | | } |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false } |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: sortedRows.value.map((item) => Number(item.value) || 0), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const row = sortedRows.value[index] || {} |
| | | const rank = sortedRows.value.length - index |
| | | const isTop3 = rank < 4 |
| | | return `{a${isTop3 ? rank : ''}|${row.rate ?? '0.00'} }{b${isTop3 ? rank : ''}|%}` |
| | | }, |
| | | rich: { |
| | | a: { fontSize: 18, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | a1: { fontSize: 18, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | a2: { fontSize: 18, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | a3: { fontSize: 18, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | b: { fontSize: 12, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | b1: { fontSize: 12, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | b2: { fontSize: 12, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | b3: { fontSize: 12, color: '#e8ed66', verticalAlign: 'bottom' } |
| | | } |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false } |
| | | } |
| | | ]) |
| | | |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'å·²æ´æ¹', |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value |
| | | }, |
| | | { |
| | | name: 'èæ¯', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 22, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | data: bgData.value |
| | | }, |
| | | { |
| | | name: 'å·²æ´æ¹æ¸å', |
| | | type: 'bar', |
| | | barWidth: 22, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(255, 218, 220)' }, |
| | | { offset: 0.07, color: 'rgba(255, 86, 118)' }, |
| | | { offset: 1, color: 'rgba(255, 86, 118, 0)' } |
| | | ], |
| | | global: false |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | data: dataNum.value |
| | | } |
| | | ]) |
| | | |
| | | const loadData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | try { |
| | | const res = await getHiddenRectifyRanking({ limit: 5 }) |
| | | rows.value = Array.isArray(res?.data) ? res.data : [] |
| | | refreshFailed.value = false |
| | | loaded.value = true |
| | | } catch (e) { |
| | | console.error('è·åéæ£æ´æ¹è´£ä»»äººæå失败:', e) |
| | | refreshFailed.value = true |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | position: relative; |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .empty-tip { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 50%; |
| | | text-align: center; |
| | | font-size: 16px; |
| | | color: #8aa4c8; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .refresh-error { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 8px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="å±é©æºç±»åæå" /> |
| | | <div class="panel-item-customers"> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :series="series" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="chartOptions" |
| | | style="height: 390px" |
| | | /> |
| | | <div v-if="showEmpty" class="empty-tip">ææ æ°æ®</div> |
| | | <span v-if="refreshFailed" class="refresh-error">å·æ°å¤±è´¥</span> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, computed, onMounted, inject, watch } from 'vue' |
| | | import Echarts from '@/components/Echarts/echarts.vue' |
| | | import PanelHeader from '@/views/reportAnalysis/dataDashboard/components/PanelHeader.vue' |
| | | import { getHazardTypeRanking } from '@/api/reportAnalysis/safetyDashboard.js' |
| | | |
| | | const chart = ref(null) |
| | | const rows = ref([]) |
| | | const loaded = ref(false) |
| | | const loading = ref(false) |
| | | const refreshFailed = ref(false) |
| | | |
| | | const aircraft = |
| | | 'path://M107.000,71.000 C104.936,71.000 102.665,70.806 100.273,70.467 C94.592,76.922 86.275,81.000 77.000,81.000 C70.794,81.000 65.020,79.170 60.172,76.029 C66.952,74.165 72.647,69.714 76.173,63.817 C69.821,61.362 64.063,58.593 60.000,56.039 L60.000,52.813 C70.456,53.950 80.723,55.000 83.000,55.000 C88.972,55.000 93.000,53.723 93.000,50.000 C93.000,47.071 89.222,45.000 83.000,45.000 C80.723,45.000 70.456,46.050 60.000,47.187 L60.000,43.989 C64.057,41.431 69.807,38.644 76.168,36.173 C72.641,30.281 66.948,25.834 60.172,23.971 C65.020,20.830 70.794,19.000 77.000,19.000 C86.270,19.000 94.584,23.074 100.265,29.524 C102.647,29.191 104.918,29.000 107.000,29.000 C129.644,29.000 148.000,50.000 148.000,50.000 C148.000,50.000 129.644,71.000 107.000,71.000 ZM113.000,38.000 C106.373,38.000 101.000,43.373 101.000,50.000 C101.000,56.627 106.373,62.000 113.000,62.000 C119.627,62.000 125.000,56.627 125.000,50.000 C125.000,43.373 119.627,38.000 113.000,38.000 ZM113.000,56.000 C109.686,56.000 107.000,53.314 107.000,50.000 C107.000,46.686 109.686,44.000 113.000,44.000 C116.314,44.000 119.000,46.686 119.000,50.000 C119.000,53.314 116.314,56.000 113.000,56.000 ZM110.500,19.000 C109.567,19.000 108.763,18.483 108.334,17.726 C100.231,9.857 89.187,5.000 77.000,5.000 C64.813,5.000 53.769,9.857 45.666,17.726 C45.237,18.483 44.433,19.000 43.500,19.000 C42.119,19.000 41.000,17.881 41.000,16.500 C41.000,15.847 41.256,15.259 41.665,14.813 L41.575,14.718 C50.629,5.628 63.156,-0.000 77.000,-0.000 C90.844,-0.000 103.371,5.628 112.425,14.718 L112.335,14.813 C112.744,15.259 113.000,15.847 113.000,16.500 C113.000,17.881 111.881,19.000 110.500,19.000 ZM53.000,49.484 C61.406,48.626 77.810,47.000 81.345,47.000 C87.353,47.000 91.000,48.243 91.000,50.000 C91.000,52.234 87.111,53.000 81.345,53.000 C77.810,53.000 61.406,51.374 53.000,50.516 L53.000,49.484 ZM53.000,47.000 L9.000,50.000 L53.000,53.000 L53.000,56.000 L-0.000,50.000 L53.000,44.000 L53.000,47.000 ZM43.500,81.000 C44.433,81.000 45.237,81.517 45.666,82.274 C53.769,90.143 64.813,95.000 77.000,95.000 C89.187,95.000 100.231,90.143 108.334,82.274 C108.763,81.517 109.567,81.000 110.500,81.000 C111.881,81.000 113.000,82.119 113.000,83.500 C113.000,84.153 112.744,84.741 112.335,85.187 L112.425,85.282 C103.371,94.372 90.844,100.000 77.000,100.000 C63.156,100.000 50.629,94.372 41.575,85.282 L41.665,85.187 C41.256,84.741 41.000,84.153 41.000,83.500 C41.000,82.119 42.119,81.000 43.500,81.000 Z' |
| | | |
| | | const color = { |
| | | 0: '#ff5676', |
| | | 1: '#ffd83e', |
| | | 2: '#fbff94', |
| | | 3: '#7daeff' |
| | | } |
| | | |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '100%' |
| | | } |
| | | |
| | | const chartOptions = { |
| | | backgroundColor: 'transparent', |
| | | textStyle: { color: '#B8C8E0' } |
| | | } |
| | | |
| | | const sortedRows = computed(() => |
| | | [...rows.value].sort((a, b) => (Number(a.value) || 0) - (Number(b.value) || 0)) |
| | | ) |
| | | |
| | | const showEmpty = computed( |
| | | () => loaded.value && !loading.value && rows.value.length === 0 |
| | | ) |
| | | |
| | | const dataNum = computed(() => |
| | | sortedRows.value.map((item, i) => { |
| | | const index = sortedRows.value.length - i - 1 |
| | | const isTop3 = index < 3 |
| | | return { |
| | | value: Number(item.value) || 0, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: isTop3 ? '#ffdae1' : '#ecf3ff' }, |
| | | { offset: 0.07, color: isTop3 ? color[index] : color[3] }, |
| | | { offset: 1, color: isTop3 ? 'rgba(255, 86, 118, .1)' : 'rgba(125,174,255, .1)' } |
| | | ], |
| | | global: false |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | symbol: isTop3 ? aircraft : 'none', |
| | | symbolPosition: 'end', |
| | | symbolSize: [30, 25], |
| | | symbolOffset: [35, 0] |
| | | } |
| | | }) |
| | | ) |
| | | |
| | | const bgData = computed(() => { |
| | | const maxValue = Math.max(1, ...dataNum.value.map((v) => v.value)) |
| | | return dataNum.value.map(() => maxValue * 1.25) |
| | | }) |
| | | |
| | | const tooltip = computed(() => ({ |
| | | trigger: 'axis', |
| | | textStyle: { fontSize: '100%' }, |
| | | formatter: function (params) { |
| | | const index = params[0]?.dataIndex ?? 0 |
| | | const row = sortedRows.value[index] || {} |
| | | return `${params[0]?.axisValueLabel || ''}<br/>æ°éï¼${row.value ?? 0} å¤<br/>å æ¯ï¼${row.rate ?? '0.00'}%` |
| | | } |
| | | })) |
| | | |
| | | const grid = { top: 0, left: '22%', right: '12%', bottom: 0 } |
| | | |
| | | const xAxis = [ |
| | | { |
| | | splitLine: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { show: false }, |
| | | axisTick: { show: false } |
| | | } |
| | | ] |
| | | |
| | | const yAxis = computed(() => [ |
| | | { |
| | | type: 'category', |
| | | data: sortedRows.value.map((item) => item.name), |
| | | axisLabel: { |
| | | formatter: (value) => { |
| | | if (!value) return '' |
| | | const maxLen = 6 |
| | | if (value.length <= maxLen) return `{a|${value}}` |
| | | const lines = [] |
| | | for (let i = 0; i < value.length; i += maxLen) { |
| | | lines.push(value.slice(i, i + maxLen)) |
| | | } |
| | | return lines.map((line) => `{a|${line}}`).join('\n') |
| | | }, |
| | | rich: { |
| | | a: { |
| | | width: 120, |
| | | fontSize: 14, |
| | | color: '#fff', |
| | | padding: [5, 4, 5, 0], |
| | | align: 'right' |
| | | } |
| | | } |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false } |
| | | }, |
| | | { |
| | | type: 'category', |
| | | data: sortedRows.value.map((item) => Number(item.value) || 0), |
| | | axisLabel: { |
| | | formatter: (params, index) => { |
| | | const row = sortedRows.value[index] || {} |
| | | const rank = sortedRows.value.length - index |
| | | const isTop3 = rank < 4 |
| | | return `{a${isTop3 ? rank : ''}|${row.rate ?? '0.00'} }{b${isTop3 ? rank : ''}|%}` |
| | | }, |
| | | rich: { |
| | | a: { fontSize: 18, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | a1: { fontSize: 18, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | a2: { fontSize: 18, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | a3: { fontSize: 18, color: '#e8ed66', verticalAlign: 'bottom' }, |
| | | b: { fontSize: 12, color: '#98bfff', verticalAlign: 'bottom' }, |
| | | b1: { fontSize: 12, color: '#ff7f97', verticalAlign: 'bottom' }, |
| | | b2: { fontSize: 12, color: '#ffce64', verticalAlign: 'bottom' }, |
| | | b3: { fontSize: 12, color: '#e8ed66', verticalAlign: 'bottom' } |
| | | } |
| | | }, |
| | | axisLine: { show: false }, |
| | | axisTick: { show: false }, |
| | | splitLine: { show: false } |
| | | } |
| | | ]) |
| | | |
| | | const series = computed(() => [ |
| | | { |
| | | name: 'æ°é', |
| | | z: 6, |
| | | type: 'pictorialBar', |
| | | data: dataNum.value |
| | | }, |
| | | { |
| | | name: 'èæ¯', |
| | | z: 6, |
| | | type: 'bar', |
| | | barWidth: 22, |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: 'rgba(255,255,255,.1)', |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | data: bgData.value |
| | | }, |
| | | { |
| | | name: 'æ°éæ¸å', |
| | | type: 'bar', |
| | | barWidth: 22, |
| | | barGap: '-100%', |
| | | tooltip: { show: false }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 1, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 0, |
| | | colorStops: [ |
| | | { offset: 0, color: 'rgba(255, 218, 220)' }, |
| | | { offset: 0.07, color: 'rgba(255, 86, 118)' }, |
| | | { offset: 1, color: 'rgba(255, 86, 118, 0)' } |
| | | ], |
| | | global: false |
| | | }, |
| | | barBorderRadius: [0, 20, 20, 0] |
| | | }, |
| | | data: dataNum.value |
| | | } |
| | | ]) |
| | | |
| | | const loadData = async () => { |
| | | if (loading.value) return |
| | | loading.value = true |
| | | try { |
| | | const res = await getHazardTypeRanking({ limit: 8 }) |
| | | rows.value = Array.isArray(res?.data) ? res.data : [] |
| | | refreshFailed.value = false |
| | | loaded.value = true |
| | | } catch (e) { |
| | | console.error('è·åå±é©æºç±»åæå失败:', e) |
| | | refreshFailed.value = true |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | const safetyDashboardRefreshTick = inject('safetyDashboardRefreshTick', null) |
| | | if (safetyDashboardRefreshTick) { |
| | | watch(safetyDashboardRefreshTick, () => { |
| | | loadData() |
| | | }) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | loadData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .panel-item-customers { |
| | | position: relative; |
| | | border: 1px solid #1a58b0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | box-sizing: border-box; |
| | | } |
| | | |
| | | .empty-tip { |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | top: 50%; |
| | | text-align: center; |
| | | font-size: 16px; |
| | | color: #8aa4c8; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .refresh-error { |
| | | position: absolute; |
| | | right: 16px; |
| | | top: 8px; |
| | | font-size: 12px; |
| | | color: #ff9a9a; |
| | | } |
| | | </style> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/> |
| | | </svg> |
| | | <svg v-else width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> |
| | | </svg> |
| | | </button> |
| | | |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">å®å
¨æ°æ®é©¾é©¶è±</div> |
| | | </div> |
| | | |
| | | <div class="dashboard-content"> |
| | | <div class="left-panel"> |
| | | <LeftTop /> |
| | | <LeftBottom /> |
| | | </div> |
| | | |
| | | <div class="center-panel"> |
| | | <CenterTop /> |
| | | <CenterBottom /> |
| | | </div> |
| | | |
| | | <div class="right-panel"> |
| | | <RightTop /> |
| | | <RightBottom /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted, onBeforeUnmount, nextTick, provide } from 'vue' |
| | | import LeftTop from './components/left-top.vue' |
| | | import LeftBottom from './components/left-bottom.vue' |
| | | import CenterTop from './components/center-top.vue' |
| | | import CenterBottom from './components/center-bottom.vue' |
| | | import RightTop from './components/right-top.vue' |
| | | import RightBottom from './components/right-bottom.vue' |
| | | |
| | | const isFullscreen = ref(false) |
| | | const scaleRatio = ref(1) |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | const DASHBOARD_REFRESH_MS = 60 * 1000 |
| | | const safetyDashboardRefreshTick = ref(0) |
| | | provide('safetyDashboardRefreshTick', safetyDashboardRefreshTick) |
| | | let dashboardPollTimer = null |
| | | |
| | | const calculateScale = () => { |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | } |
| | | |
| | | const handleResize = () => { |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.scale-container') |
| | | if (!element) return |
| | | if (!isFullscreen.value) { |
| | | if (element.requestFullscreen) { |
| | | element.requestFullscreen() |
| | | } else if (element.webkitRequestFullscreen) { |
| | | element.webkitRequestFullscreen() |
| | | } else if (element.msRequestFullscreen) { |
| | | element.msRequestFullscreen() |
| | | } |
| | | } else { |
| | | if (document.exitFullscreen) { |
| | | document.exitFullscreen() |
| | | } else if (document.webkitExitFullscreen) { |
| | | document.webkitExitFullscreen() |
| | | } else if (document.msExitFullscreen) { |
| | | document.msExitFullscreen() |
| | | } |
| | | } |
| | | } |
| | | |
| | | const handleFullscreenChange = () => { |
| | | const fullscreenElement = |
| | | document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = !!(fullscreenElement && fullscreenElement.classList.contains('scale-container')) |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 200) |
| | | } |
| | | |
| | | onMounted(() => { |
| | | nextTick(() => { |
| | | calculateScale() |
| | | }) |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | dashboardPollTimer = setInterval(() => { |
| | | safetyDashboardRefreshTick.value++ |
| | | }, DASHBOARD_REFRESH_MS) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (dashboardPollTimer) { |
| | | clearInterval(dashboardPollTimer) |
| | | dashboardPollTimer = null |
| | | } |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | height: calc(100vh - 84px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | .scale-container:fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .scale-container:-webkit-full-screen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .scale-container:-ms-fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 46px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | letter-spacing: 4px; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | | position: absolute; |
| | | top: 10px; |
| | | left: 20px; |
| | | width: 40px; |
| | | height: 40px; |
| | | background: rgba(0, 20, 60, 0.8); |
| | | border: 1px solid rgba(0, 212, 255, 0.3); |
| | | border-radius: 6px; |
| | | color: #00d4ff; |
| | | cursor: pointer; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | transition: all 0.3s; |
| | | z-index: 10000; |
| | | } |
| | | |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 30, 90, 0.9); |
| | | border-color: rgba(0, 212, 255, 0.5); |
| | | } |
| | | |
| | | .dashboard-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 86px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .center-panel, |
| | | .right-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .left-panel, |
| | | .right-panel { |
| | | flex: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | width: 520px; |
| | | } |
| | | |
| | | .center-panel { |
| | | flex: 1.5; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | } |
| | | </style> |
| | |
| | | const getList = () => { |
| | | tableLoading.value = true; |
| | | userListNoPage({}).then(res => { |
| | | console.log("res", res.data); |
| | | tableData.value = res.data; |
| | | tableDataCopy.value = res.data; |
| | | // è¶
级管çåè´¦å·ä¸æ¯å¹è®å¯¹è±¡ï¼ä¸åºç°å¨å¹è®è®°å½äººåå表é |
| | | const list = (res.data || []).filter(item => item.userId !== 1); |
| | | tableData.value = list; |
| | | tableDataCopy.value = list; |
| | | if (tableData.value.length > 0) { |
| | | customerId.value = tableData.value[0].userId; |
| | | receiptPaymentList(customerId.value); |
| | |
| | | <template #default="scope"> |
| | | <div> |
| | | <div v-if="scope.row.shippingDate"> |
| | | {{ scope.row.shippingDate }} |
| | | {{ formatDateOnly(scope.row.shippingDate) }} |
| | | </div> |
| | | <el-tag v-else type="info">- </el-tag> |
| | | </div> |
| | |
| | | </el-table-column> |
| | | <el-table-column prop="validPeriod" label="æææ" width="200"> |
| | | <template #default="scope"> |
| | | {{ scope.row.startDate }} è³ {{ scope.row.endDate }} |
| | | {{ formatDateOnly(scope.row.startDate) }} è³ {{ formatDateOnly(scope.row.endDate) }} |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="status" label="ç¶æ" width="80"> |
| | |
| | | <el-table-column label="çæ¬å·" prop="version" align="center" width="120"/> |
| | | <el-table-column label="å建æ¶é´" prop="createTime" align="center" width="170"> |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.createTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span> |
| | | <span>{{ parseTime(scope.row.createTime) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="æ´æ°æ¶é´" prop="updateTime" align="center" width="170"> |
| | | <template #default="scope"> |
| | | <span>{{ parseTime(scope.row.updateTime, "{y}-{m}-{d} {h}:{i}:{s}") }}</span> |
| | | <span>{{ parseTime(scope.row.updateTime) }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column label="å建人" prop="createUser" align="center" width="100"/> |