Browse Source

新增简报

BAI 10 giờ trước cách đây
mục cha
commit
85270cf71c

+ 306 - 1
package-lock.json

@@ -15,9 +15,12 @@
         "cesium": "^1.139.1",
         "echarts": "^6.0.0",
         "element-plus": "^2.13.5",
+        "file-saver": "^2.0.5",
+        "html2pdf.js": "^0.14.0",
         "mapbox-gl": "^3.24.0",
         "vue": "^3.5.25",
-        "vue-router": "^5.0.3"
+        "vue-router": "^5.0.3",
+        "xlsx": "^0.18.5"
       },
       "devDependencies": {
         "@playwright/test": "^1.60.0",
@@ -1714,12 +1717,25 @@
         "undici-types": "~7.18.0"
       }
     },
+    "node_modules/@types/pako": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmmirror.com/@types/pako/-/pako-2.0.4.tgz",
+      "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
+      "license": "MIT"
+    },
     "node_modules/@types/pbf": {
       "version": "3.0.5",
       "resolved": "https://registry.npmmirror.com/@types/pbf/-/pbf-3.0.5.tgz",
       "integrity": "sha512-j3pOPiEcWZ34R6a6mN07mUkM4o4Lwf6hPNt8eilOeZhTFbxFXmKhvXl9Y28jotFPaI1bpPDJsbCprUoNke6OrA==",
       "license": "MIT"
     },
+    "node_modules/@types/raf": {
+      "version": "3.4.3",
+      "resolved": "https://registry.npmmirror.com/@types/raf/-/raf-3.4.3.tgz",
+      "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/@types/supercluster": {
       "version": "7.1.3",
       "resolved": "https://registry.npmmirror.com/@types/supercluster/-/supercluster-7.1.3.tgz",
@@ -1978,6 +1994,15 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/adler-32": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
+      "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/arr-union": {
       "version": "3.1.0",
       "resolved": "https://registry.npmmirror.com/arr-union/-/arr-union-3.1.0.tgz",
@@ -2085,6 +2110,15 @@
         "proxy-from-env": "^1.1.0"
       }
     },
+    "node_modules/base64-arraybuffer": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+      "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6.0"
+      }
+    },
     "node_modules/birpc": {
       "version": "2.9.0",
       "resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
@@ -2132,6 +2166,26 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/canvg": {
+      "version": "3.0.11",
+      "resolved": "https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz",
+      "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@babel/runtime": "^7.12.5",
+        "@types/raf": "^3.4.0",
+        "core-js": "^3.8.3",
+        "raf": "^3.4.1",
+        "regenerator-runtime": "^0.13.7",
+        "rgbcolor": "^1.0.1",
+        "stackblur-canvas": "^2.0.0",
+        "svg-pathdata": "^6.0.3"
+      },
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/cesium": {
       "version": "1.139.1",
       "resolved": "https://registry.npmmirror.com/cesium/-/cesium-1.139.1.tgz",
@@ -2150,6 +2204,19 @@
         "node": ">=20.19.0"
       }
     },
+    "node_modules/cfb": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
+      "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "crc-32": "~1.2.0"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/cheap-ruler": {
       "version": "4.0.0",
       "resolved": "https://registry.npmmirror.com/cheap-ruler/-/cheap-ruler-4.0.0.tgz",
@@ -2171,6 +2238,15 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
+    "node_modules/codepage": {
+      "version": "1.15.0",
+      "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
+      "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/combined-stream": {
       "version": "1.0.8",
       "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -2195,6 +2271,39 @@
       "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
       "license": "MIT"
     },
+    "node_modules/core-js": {
+      "version": "3.49.0",
+      "resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.49.0.tgz",
+      "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/core-js"
+      }
+    },
+    "node_modules/crc-32": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
+      "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+      "license": "Apache-2.0",
+      "bin": {
+        "crc32": "bin/crc32.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/css-line-break": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz",
+      "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/csscolorparser": {
       "version": "1.0.3",
       "resolved": "https://registry.npmmirror.com/csscolorparser/-/csscolorparser-1.0.3.tgz",
@@ -2508,6 +2617,17 @@
       "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
       "license": "MIT"
     },
+    "node_modules/fast-png": {
+      "version": "6.4.0",
+      "resolved": "https://registry.npmmirror.com/fast-png/-/fast-png-6.4.0.tgz",
+      "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/pako": "^2.0.3",
+        "iobuffer": "^5.3.2",
+        "pako": "^2.1.0"
+      }
+    },
     "node_modules/fdir": {
       "version": "6.5.0",
       "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
@@ -2531,6 +2651,12 @@
       "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
       "license": "MIT"
     },
+    "node_modules/file-saver": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
+      "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+      "license": "MIT"
+    },
     "node_modules/follow-redirects": {
       "version": "1.15.11",
       "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
@@ -2567,6 +2693,15 @@
         "node": ">= 6"
       }
     },
+    "node_modules/frac": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
+      "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/fresh": {
       "version": "0.5.2",
       "resolved": "https://registry.npmmirror.com/fresh/-/fresh-0.5.2.tgz",
@@ -2786,6 +2921,30 @@
       "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
       "license": "MIT"
     },
+    "node_modules/html2canvas": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz",
+      "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+      "license": "MIT",
+      "dependencies": {
+        "css-line-break": "^2.1.0",
+        "text-segmentation": "^1.0.3"
+      },
+      "engines": {
+        "node": ">=8.0.0"
+      }
+    },
+    "node_modules/html2pdf.js": {
+      "version": "0.14.0",
+      "resolved": "https://registry.npmmirror.com/html2pdf.js/-/html2pdf.js-0.14.0.tgz",
+      "integrity": "sha512-yvNJgE/8yru2UeGflkPdjW8YEY+nDH5X7/2WG4uiuSCwYiCp8PZ8EKNiTAa6HxJ1NjC51fZSIEq6xld5CADKBQ==",
+      "license": "MIT",
+      "dependencies": {
+        "dompurify": "^3.3.1",
+        "html2canvas": "^1.0.0",
+        "jspdf": "^4.0.0"
+      }
+    },
     "node_modules/http-errors": {
       "version": "2.0.1",
       "resolved": "https://registry.npmmirror.com/http-errors/-/http-errors-2.0.1.tgz",
@@ -2840,6 +2999,12 @@
       "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
       "license": "ISC"
     },
+    "node_modules/iobuffer": {
+      "version": "5.4.0",
+      "resolved": "https://registry.npmmirror.com/iobuffer/-/iobuffer-5.4.0.tgz",
+      "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
+      "license": "MIT"
+    },
     "node_modules/is-extendable": {
       "version": "0.1.1",
       "resolved": "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz",
@@ -2938,6 +3103,23 @@
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jspdf": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmmirror.com/jspdf/-/jspdf-4.2.1.tgz",
+      "integrity": "sha512-YyAXyvnmjTbR4bHQRLzex3CuINCDlQnBqoSYyjJwTP2x9jDLuKDzy7aKUl0hgx3uhcl7xzg32agn5vlie6HIlQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@babel/runtime": "^7.28.6",
+        "fast-png": "^6.2.0",
+        "fflate": "^0.8.1"
+      },
+      "optionalDependencies": {
+        "canvg": "^3.0.11",
+        "core-js": "^3.6.0",
+        "dompurify": "^3.3.1",
+        "html2canvas": "^1.0.0-rc.5"
+      }
+    },
     "node_modules/kdbush": {
       "version": "4.0.2",
       "resolved": "https://registry.npmmirror.com/kdbush/-/kdbush-4.0.2.tgz",
@@ -3426,6 +3608,13 @@
       "integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
       "license": "MIT"
     },
+    "node_modules/performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmmirror.com/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/picocolors": {
       "version": "1.1.1",
       "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@@ -3613,6 +3802,16 @@
       "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==",
       "license": "ISC"
     },
+    "node_modules/raf": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmmirror.com/raf/-/raf-3.4.1.tgz",
+      "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "performance-now": "^2.1.0"
+      }
+    },
     "node_modules/range-parser": {
       "version": "1.2.1",
       "resolved": "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz",
@@ -3645,6 +3844,13 @@
         "url": "https://paulmillr.com/funding/"
       }
     },
+    "node_modules/regenerator-runtime": {
+      "version": "0.13.11",
+      "resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
+      "license": "MIT",
+      "optional": true
+    },
     "node_modules/regl": {
       "version": "1.6.1",
       "resolved": "https://registry.npmmirror.com/regl/-/regl-1.6.1.tgz",
@@ -3660,6 +3866,16 @@
         "protocol-buffers-schema": "^3.3.1"
       }
     },
+    "node_modules/rgbcolor": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/rgbcolor/-/rgbcolor-1.0.1.tgz",
+      "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+      "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.8.15"
+      }
+    },
     "node_modules/robust-predicates": {
       "version": "3.0.3",
       "resolved": "https://registry.npmmirror.com/robust-predicates/-/robust-predicates-3.0.3.tgz",
@@ -3884,6 +4100,28 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/ssf": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
+      "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "frac": "~1.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/stackblur-canvas": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmmirror.com/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+      "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=0.1.14"
+      }
+    },
     "node_modules/statuses": {
       "version": "2.0.2",
       "resolved": "https://registry.npmmirror.com/statuses/-/statuses-2.0.2.tgz",
@@ -3909,6 +4147,25 @@
       "integrity": "sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==",
       "license": "ISC"
     },
+    "node_modules/svg-pathdata": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+      "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/text-segmentation": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz",
+      "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+      "license": "MIT",
+      "dependencies": {
+        "utrie": "^1.0.2"
+      }
+    },
     "node_modules/tinyglobby": {
       "version": "0.2.15",
       "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -4049,6 +4306,15 @@
       "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==",
       "license": "MIT"
     },
+    "node_modules/utrie": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz",
+      "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+      "license": "MIT",
+      "dependencies": {
+        "base64-arraybuffer": "^1.0.2"
+      }
+    },
     "node_modules/viewport-mercator-project": {
       "version": "6.2.3",
       "resolved": "https://registry.npmmirror.com/viewport-mercator-project/-/viewport-mercator-project-6.2.3.tgz",
@@ -4289,6 +4555,45 @@
         "which": "bin/which"
       }
     },
+    "node_modules/wmf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
+      "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/word": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
+      "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/xlsx": {
+      "version": "0.18.5",
+      "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
+      "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "adler-32": "~1.3.0",
+        "cfb": "~1.2.1",
+        "codepage": "~1.15.0",
+        "crc-32": "~1.2.1",
+        "ssf": "~0.11.2",
+        "wmf": "~1.0.1",
+        "word": "~0.3.0"
+      },
+      "bin": {
+        "xlsx": "bin/xlsx.njs"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
     "node_modules/yaml": {
       "version": "2.8.2",
       "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz",

+ 4 - 1
package.json

@@ -19,9 +19,12 @@
     "cesium": "^1.139.1",
     "echarts": "^6.0.0",
     "element-plus": "^2.13.5",
+    "file-saver": "^2.0.5",
+    "html2pdf.js": "^0.14.0",
     "mapbox-gl": "^3.24.0",
     "vue": "^3.5.25",
-    "vue-router": "^5.0.3"
+    "vue-router": "^5.0.3",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@playwright/test": "^1.60.0",

BIN
src/assets/乌拉海沟水库简报模版.docx


+ 25 - 0
src/stores/briefArchiveStore.js

@@ -0,0 +1,25 @@
+// 每日简报归档存储(共享响应式数据)
+import { reactive } from 'vue'
+
+export const briefArchiveStore = reactive({
+  records: JSON.parse(localStorage.getItem('brief_archives') || '[]'),
+
+  addRecord(record) {
+    const now = new Date()
+    const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
+    const docNo = `乌海水库简报〔${now.getFullYear()}〕${String(this.records.length + 1).padStart(3, '0')}号`
+    this.records.unshift({
+      category: 'brief',
+      docNo,
+      name: `水库每日工作简报_${dateStr}`,
+      fileType: 'PDF',
+      pageCount: 1,
+      size: '—',
+      level: '普通',
+      uploader: record.dutyPerson || '管理员',
+      archiveDate: dateStr,
+      summary: record
+    })
+    localStorage.setItem('brief_archives', JSON.stringify(this.records))
+  }
+})

+ 8 - 1
src/views/admin/ArchiveFilesView.vue

@@ -185,6 +185,7 @@
 
 <script>
 import { Search, ArrowDown, Upload, UploadFilled, Download, Document } from '@element-plus/icons-vue'
+import { briefArchiveStore } from '../../stores/briefArchiveStore.js'
 
 export default {
   name: 'ArchiveFilesView',
@@ -209,6 +210,7 @@ export default {
       },
       selectedFile: null,
       categories: [
+        { key: 'brief', label: '工作简报', icon: '📰', count: 0 },
         { key: 'basic', label: '基础资料', icon: '📋', count: 42 },
         { key: 'design', label: '设计资料', icon: '📐', count: 56 },
         { key: 'construction', label: '施工资料', icon: '🏗️', count: 128 },
@@ -279,7 +281,12 @@ export default {
   },
   computed: {
     filteredFiles() {
-      let files = this.archiveFiles.filter(f => f.category === this.activeCategory)
+      let files
+      if (this.activeCategory === 'brief') {
+        files = [...briefArchiveStore.records]
+      } else {
+        files = this.archiveFiles.filter(f => f.category === this.activeCategory)
+      }
       if (this.searchText) {
         const kw = this.searchText.toLowerCase()
         files = files.filter(f => f.name.toLowerCase().includes(kw) || f.docNo.toLowerCase().includes(kw))

+ 727 - 235
src/views/mainPages/WorkspaceView.vue

@@ -137,8 +137,9 @@
 
         <!-- 首页内容 -->
         <template v-if="isHomePage">
+        <div class="home-grid">
         <!-- 核心水情数据卡片 -->
-        <section class="glass-card stats-section">
+        <section class="glass-card stats-section home-full">
           <div class="card-header">
             <span class="card-title">实时水情</span>
             <div class="card-actions">
@@ -166,207 +167,278 @@
           </div>
         </section>
 
-        <!-- 中间区域:预警 + 工单 + 设备状态 -->
-        <section class="middle-section">
-          <!-- 左侧:预警信息 + 待办工单 -->
-          <div class="middle-left">
-            <!-- 预警信息 -->
-            <div class="glass-card warning-panel">
-              <div class="card-header">
-                <span class="card-title">预警信息</span>
-                <el-badge :value="warningList.length" :max="99" class="warning-badge">
-                  <el-button type="danger" size="small" plain>查看全部</el-button>
-                </el-badge>
+        <!-- 每日简报 -->
+        <section class="glass-card daily-stats-card">
+          <div class="card-header">
+            <span class="card-title">每日简报</span>
+            <div class="card-actions">
+              <el-button type="primary" size="small" plain @click="exportDailyReport('excel')">
+                <el-icon><Upload /></el-icon> Excel
+              </el-button>
+              <el-button type="primary" size="small" plain @click="showPdfPreview">
+                <el-icon><Document /></el-icon> PDF
+              </el-button>
+              <el-button type="primary" size="small" plain @click="archiveBrief">
+                <el-icon><Folder /></el-icon> 归档
+              </el-button>
+            </div>
+          </div>
+          <div class="card-body">
+            <div class="daily-stats-list">
+              <div class="daily-stat-row">
+                <span class="ds-label">值班信息</span>
+                <span class="ds-value">{{ dailyStats.dutyPerson }} / {{ dailyStats.shiftTime }}</span>
               </div>
-              <div class="card-body">
-                <div class="warning-list">
-                  <div v-for="(item, index) in warningList" :key="index" 
-                       :class="['warning-item', `warning-${item.level}`]">
-                    <div class="warning-icon">
-                      <el-icon><Warning /></el-icon>
-                    </div>
-                    <div class="warning-content">
-                      <div class="warning-title">{{ item.title }}</div>
-                      <div class="warning-meta">
-                        <span>{{ item.location }}</span>
-                        <span>{{ item.time }}</span>
-                      </div>
-                    </div>
-                    <el-tag :type="getWarningType(item.level)" size="small" effect="dark">
-                      {{ item.levelText }}
-                    </el-tag>
-                  </div>
-                </div>
+              <div class="daily-stat-row">
+                <span class="ds-label">气象数据</span>
+                <span class="ds-value">{{ dailyStats.weather.temp }}°C {{ dailyStats.weather.desc }}</span>
               </div>
-            </div>
-
-            <!-- 月度指标统计 -->
-            <div class="glass-card stats-table-panel">
-              <div class="card-header">
-                <span class="card-title">月度指标统计</span>
-                <div class="card-actions">
-                  <el-button type="primary" size="small" plain>导出Excel</el-button>
-                  <el-button type="primary" size="small" plain>导出PDF</el-button>
-                </div>
+              <div class="daily-stat-row">
+                <span class="ds-label">库水位</span>
+                <span class="ds-value">
+                  <span class="tp-group" v-for="(wl, idx) in dailyStats.waterLevels" :key="idx">
+                    <span class="tp-time">{{ wl.time }}</span>
+                    <span class="tp-val">{{ wl.value }}m</span>
+                    <span v-if="idx < dailyStats.waterLevels.length - 1" class="tp-sep">|</span>
+                  </span>
+                </span>
               </div>
-              <div class="card-body">
-                <el-table :data="monthlyStats" stripe style="width: 100%" size="small">
-                  <el-table-column prop="name" label="统计项" width="120" />
-                  <el-table-column label="本月数值">
-                    <template #default="{ row }">
-                      <span class="value-highlight">{{ row.value }}</span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column prop="unit" label="单位" width="80" />
-                  <el-table-column label="环比变化" width="100">
-                    <template #default="{ row }">
-                      <span :class="['trend', row.trend > 0 ? 'trend-up' : 'trend-down']">
-                        {{ row.trend > 0 ? '+' : '' }}{{ row.trend }}%
-                      </span>
-                    </template>
-                  </el-table-column>
-                  <el-table-column label="趋势" width="80">
-                    <template #default="{ row }">
-                      <div class="mini-chart">
-                        <div class="chart-bar" :style="{ height: Math.abs(row.trend) + '%', background: row.trend > 0 ? '#52c41a' : '#ff4d4f' }"></div>
-                      </div>
-                    </template>
-                  </el-table-column>
-                </el-table>
+              <div class="daily-stat-row">
+                <span class="ds-label">雨量(8:00~次日8:00)</span>
+                <span class="ds-value">{{ dailyStats.rainfall }} mm</span>
               </div>
-            </div>
-
-            <!-- 待办工单 -->
-            <div class="glass-card todo-panel">
-              <div class="card-header">
-                <span class="card-title">待办工单</span>
-                <el-button type="primary" size="small" plain>查看全部</el-button>
+              <div class="daily-stat-row">
+                <span class="ds-label">出入库</span>
+                <span class="ds-value">
+                  最大 <b>{{ dailyStats.inout.max }}</b> |
+                  最小 <b>{{ dailyStats.inout.min }}</b> |
+                  平均 <b>{{ dailyStats.inout.avg }}</b> |
+                  日水量 <b>{{ dailyStats.inout.total }}</b> 万m³
+                </span>
               </div>
-              <div class="card-body">
-                <el-table :data="todoList" stripe style="width: 100%" size="small" header-row-class-name="table-header" height="150">
-                  <el-table-column prop="type" label="工单类型" width="100" />
-                  <el-table-column prop="location" label="关联点位" />
-                  <el-table-column prop="reportTime" label="上报时间" width="140" />
-                  <el-table-column prop="responsible" label="责任人" width="80" />
-                  <el-table-column label="状态" width="80">
-                    <template #default="{ row }">
-                      <el-tag :type="getStatusType(row.status)" size="small" effect="light" round>
-                        {{ row.status }}
-                      </el-tag>
-                    </template>
-                  </el-table-column>
-                  <el-table-column label="操作" width="70">
-                    <template #default>
-                      <el-button type="primary" link size="small">处理</el-button>
-                    </template>
-                  </el-table-column>
-                </el-table>
+              <div class="daily-stat-row">
+                <span class="ds-label">瞬时流量</span>
+                <span class="ds-value">
+                  <span class="tp-group" v-for="(flow, idx) in dailyStats.instantFlow" :key="idx">
+                    <span class="tp-time">{{ flow.time }}</span>
+                    <span class="tp-val">{{ flow.value }}</span>
+                    <span v-if="idx < dailyStats.instantFlow.length - 1" class="tp-sep">|</span>
+                  </span>
+                </span>
+              </div>
+              <div class="daily-stat-row">
+                <span class="ds-label">其他</span>
+                <span class="ds-value">
+                  降雨 {{ dailyStats.other.rainfall }}mm |
+                  水质 {{ dailyStats.other.waterQuality }} |
+                  管线压力 {{ dailyStats.other.pipelinePressure }}MPa
+                </span>
+              </div>
+              <div class="daily-stat-row">
+                <span class="ds-label">自定义检查</span>
+                <span class="ds-value check-list">
+                  <el-checkbox v-for="(item, idx) in dailyStats.checkItems" :key="idx" v-model="item.checked" size="small" @change="onCheckChange">
+                    {{ item.label }}
+                  </el-checkbox>
+                  <el-button type="primary" link size="small" @click="addCheckItem">
+                    <el-icon><Plus /></el-icon> 添加
+                  </el-button>
+                </span>
               </div>
             </div>
           </div>
+        </section>
 
-          <!-- 右侧:设备状态 + 今日调度 + 月度统计 -->
-          <div class="middle-right">
-            <!-- 设备运行状态 -->
-            <div class="glass-card device-panel">
-              <div class="card-header">
-                <span class="card-title">设备状态</span>
-                <el-button type="primary" size="small" link>设备管理</el-button>
-              </div>
-              <div class="device-stats">
-                <div class="device-stat-item" v-for="(item, index) in deviceStats" :key="index">
-                  <div class="device-stat-icon" :style="{ background: item.color }">
-                    <span>{{ item.icon }}</span>
-                  </div>
-                  <div class="device-stat-info">
-                    <span class="device-stat-value">{{ item.value }}</span>
-                    <span class="device-stat-label">{{ item.label }}</span>
-                  </div>
+        <!-- 预警信息 -->
+        <section class="glass-card warning-panel">
+          <div class="card-header">
+            <span class="card-title">预警信息</span>
+            <el-badge :value="warningList.length" :max="99" class="warning-badge">
+              <el-button type="danger" size="small" plain>查看全部</el-button>
+            </el-badge>
+          </div>
+          <div class="card-body">
+            <div class="warning-list">
+              <div v-for="(item, index) in warningList" :key="index" 
+                   :class="['warning-item', `warning-${item.level}`]">
+                <div class="warning-icon">
+                  <el-icon><Warning /></el-icon>
                 </div>
-              </div>
-              <div class="device-list">
-                <div v-for="(device, index) in deviceList" :key="index" class="device-item">
-                  <div class="device-info">
-                    <span class="device-name">{{ device.name }}</span>
-                    <span class="device-location">{{ device.location }}</span>
+                <div class="warning-content">
+                  <div class="warning-title">{{ item.title }}</div>
+                  <div class="warning-meta">
+                    <span>{{ item.location }}</span>
+                    <span>{{ item.time }}</span>
                   </div>
-                  <el-tag :type="getDeviceStatusType(device.status)" size="small" effect="light">
-                    {{ device.statusText }}
-                  </el-tag>
                 </div>
+                <el-tag :type="getWarningType(item.level)" size="small" effect="dark">
+                  {{ item.levelText }}
+                </el-tag>
               </div>
             </div>
+          </div>
+        </section>
 
-            <!-- 今日调度计划 -->
-            <div class="glass-card schedule-panel">
-              <div class="card-header">
-                <span class="card-title">今日调度</span>
-                <el-button type="primary" size="small" plain>调度管理</el-button>
-              </div>
-              <div class="card-body">
-                <div class="schedule-list">
-                  <div v-for="(item, index) in todaySchedule" :key="index" class="schedule-item">
-                    <div class="schedule-time">{{ item.time }}</div>
-                    <div class="schedule-content">
-                      <div class="schedule-title">{{ item.title }}</div>
-                      <div class="schedule-desc">{{ item.description }}</div>
-                    </div>
-                    <el-tag :type="getScheduleType(item.status)" size="small" effect="light">
-                      {{ item.statusText }}
-                    </el-tag>
+        <!-- 月度指标统计 -->
+        <section class="glass-card stats-table-panel">
+          <div class="card-header">
+            <span class="card-title">月度指标统计</span>
+            <div class="card-actions">
+              <el-button type="primary" size="small" plain>导出Excel</el-button>
+              <el-button type="primary" size="small" plain>导出PDF</el-button>
+            </div>
+          </div>
+          <div class="card-body">
+            <el-table :data="monthlyStats" stripe style="width: 100%" size="small">
+              <el-table-column prop="name" label="统计项" width="120" />
+              <el-table-column label="本月数值">
+                <template #default="{ row }">
+                  <span class="value-highlight">{{ row.value }}</span>
+                </template>
+              </el-table-column>
+              <el-table-column prop="unit" label="单位" width="80" />
+              <el-table-column label="环比变化" width="100">
+                <template #default="{ row }">
+                  <span :class="['trend', row.trend > 0 ? 'trend-up' : 'trend-down']">
+                    {{ row.trend > 0 ? '+' : '' }}{{ row.trend }}%
+                  </span>
+                </template>
+              </el-table-column>
+              <el-table-column label="趋势" width="80">
+                <template #default="{ row }">
+                  <div class="mini-chart">
+                    <div class="chart-bar" :style="{ height: Math.abs(row.trend) + '%', background: row.trend > 0 ? '#52c41a' : '#ff4d4f' }"></div>
                   </div>
-                </div>
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+        </section>
+
+        <!-- 设备运行状态 -->
+        <section class="glass-card device-panel">
+          <div class="card-header">
+            <span class="card-title">设备状态</span>
+            <el-button type="primary" size="small" link>设备管理</el-button>
+          </div>
+          <div class="device-stats">
+            <div class="device-stat-item" v-for="(item, index) in deviceStats" :key="index">
+              <div class="device-stat-icon" :style="{ background: item.color }">
+                <span>{{ item.icon }}</span>
+              </div>
+              <div class="device-stat-info">
+                <span class="device-stat-value">{{ item.value }}</span>
+                <span class="device-stat-label">{{ item.label }}</span>
               </div>
             </div>
+          </div>
+          <div class="device-list">
+            <div v-for="(device, index) in deviceList" :key="index" class="device-item">
+              <div class="device-info">
+                <span class="device-name">{{ device.name }}</span>
+                <span class="device-location">{{ device.location }}</span>
+              </div>
+              <el-tag :type="getDeviceStatusType(device.status)" size="small" effect="light">
+                {{ device.statusText }}
+              </el-tag>
+            </div>
+          </div>
+        </section>
 
+        <!-- 待办工单 -->
+        <section class="glass-card todo-panel">
+          <div class="card-header">
+            <span class="card-title">待办工单</span>
+            <el-button type="primary" size="small" plain>查看全部</el-button>
+          </div>
+          <div class="card-body">
+            <el-table :data="todoList" stripe style="width: 100%" size="small" header-row-class-name="table-header" height="150">
+              <el-table-column prop="type" label="工单类型" width="100" />
+              <el-table-column prop="location" label="关联点位" />
+              <el-table-column prop="reportTime" label="上报时间" width="140" />
+              <el-table-column prop="responsible" label="责任人" width="80" />
+              <el-table-column label="状态" width="80">
+                <template #default="{ row }">
+                  <el-tag :type="getStatusType(row.status)" size="small" effect="light" round>
+                    {{ row.status }}
+                  </el-tag>
+                </template>
+              </el-table-column>
+              <el-table-column label="操作" width="70">
+                <template #default>
+                  <el-button type="primary" link size="small">处理</el-button>
+                </template>
+              </el-table-column>
+            </el-table>
           </div>
         </section>
 
-        <!-- 底部:快捷功能 + 气象信息 -->
-        <section class="bottom-section">
-          <!-- 快捷功能 -->
-          <div class="glass-card quick-panel">
-            <div class="card-header">
-              <span class="card-title">快捷功能</span>
-            </div>
-            <div class="quick-grid">
-              <div class="quick-item" v-for="(item, index) in quickAccess" :key="index" @click="handleQuickAccess(item.name)">
-                <div class="quick-icon" :style="{ background: item.gradient }">
-                  <span>{{ item.icon }}</span>
+        <!-- 今日调度计划 -->
+        <section class="glass-card schedule-panel">
+          <div class="card-header">
+            <span class="card-title">今日调度</span>
+            <el-button type="primary" size="small" plain>调度管理</el-button>
+          </div>
+          <div class="card-body">
+            <div class="schedule-list">
+              <div v-for="(item, index) in todaySchedule" :key="index" class="schedule-item">
+                <div class="schedule-time">{{ item.time }}</div>
+                <div class="schedule-content">
+                  <div class="schedule-title">{{ item.title }}</div>
+                  <div class="schedule-desc">{{ item.description }}</div>
                 </div>
-                <span class="quick-label">{{ item.name }}</span>
+                <el-tag :type="getScheduleType(item.status)" size="small" effect="light">
+                  {{ item.statusText }}
+                </el-tag>
               </div>
             </div>
           </div>
+        </section>
 
-          <!-- 气象信息 -->
-          <div class="glass-card weather-panel">
-            <div class="card-header">
-              <span class="card-title">气象信息</span>
-              <span class="weather-update">更新于 {{ weatherData.updateTime }}</span>
+        <!-- 快捷功能 -->
+        <section class="glass-card quick-panel">
+          <div class="card-header">
+            <span class="card-title">快捷功能</span>
+          </div>
+          <div class="quick-grid">
+            <div class="quick-item" v-for="(item, index) in quickAccess" :key="index" @click="handleQuickAccess(item.name)">
+              <div class="quick-icon" :style="{ background: item.gradient }">
+                <span>{{ item.icon }}</span>
+              </div>
+              <span class="quick-label">{{ item.name }}</span>
             </div>
-            <div class="weather-content">
-              <div class="weather-main">
-                <div class="weather-icon">{{ weatherData.icon }}</div>
-                <div class="weather-info">
-                  <span class="weather-temp">{{ weatherData.temperature }}°C</span>
-                  <span class="weather-desc">{{ weatherData.description }}</span>
-                </div>
+          </div>
+        </section>
+
+        <!-- 实时天气 + 未来预报 -->
+        <section class="glass-card weather-current-panel">
+          <div class="card-header">
+            <span class="card-title">实时天气</span>
+            <span class="weather-update">更新于 {{ weatherData.updateTime }}</span>
+          </div>
+          <div class="weather-current-body">
+            <div class="weather-main">
+              <div class="weather-icon">{{ weatherData.icon }}</div>
+              <div class="weather-info">
+                <span class="weather-temp">{{ weatherData.temperature }}°C</span>
+                <span class="weather-desc">{{ weatherData.description }}</span>
               </div>
-              <div class="weather-details">
-                <div class="weather-detail-item">
-                  <span class="detail-label">湿度</span>
-                  <span class="detail-value">{{ weatherData.humidity }}%</span>
-                </div>
-                <div class="weather-detail-item">
-                  <span class="detail-label">风速</span>
-                  <span class="detail-value">{{ weatherData.windSpeed }}m/s</span>
-                </div>
-                <div class="weather-detail-item">
-                  <span class="detail-label">降雨概率</span>
-                  <span class="detail-value">{{ weatherData.rainProbability }}%</span>
-                </div>
+            </div>
+            <div class="weather-details">
+              <div class="weather-detail-item">
+                <span class="detail-label">湿度</span>
+                <span class="detail-value">{{ weatherData.humidity }}%</span>
               </div>
+              <div class="weather-detail-item">
+                <span class="detail-label">风速</span>
+                <span class="detail-value">{{ weatherData.windSpeed }}m/s</span>
+              </div>
+              <div class="weather-detail-item">
+                <span class="detail-label">降雨概率</span>
+                <span class="detail-value">{{ weatherData.rainProbability }}%</span>
+              </div>
+            </div>
+            <div class="weather-merged-forecast">
               <div class="weather-forecast">
                 <div v-for="(day, index) in weatherForecast" :key="index" class="forecast-item">
                   <span class="forecast-day">{{ day.day }}</span>
@@ -377,14 +449,72 @@
             </div>
           </div>
         </section>
+        </div>
         </template>
       </main>
     </div>
   </div>
+
+  <!-- 简报预览弹窗 -->
+  <el-dialog v-model="previewVisible" title="每日简报预览" width="800px" top="20px" :close-on-click-modal="false" destroy-on-close>
+    <div ref="previewContent" class="brief-preview-wrapper">
+      <div class="brief-paper" v-if="previewData">
+        <!-- 红头 -->
+        <div class="brief-red-header">乌拉海沟水库</div>
+        <div class="brief-doc-number">{{ previewDate.getFullYear() }}(第{{ String(previewDate.getFullYear()).slice(-2) }}号)文</div>
+        <!-- 红色分隔线 -->
+        <div class="brief-red-line"></div>
+        <!-- 标题 -->
+        <div class="brief-title">水库每日工作简报</div>
+        <div class="brief-body">
+          <p class="brief-paragraph"><b>一、水情概况</b></p>
+          <p class="brief-paragraph">
+            1. 定时库水位:{{ fmtTime(previewData.waterLevels[0].time) }}{{ previewData.waterLevels[0].value }}m、{{ fmtTime(previewData.waterLevels[1].time) }}{{ previewData.waterLevels[1].value }}m、{{ fmtTime(previewData.waterLevels[2].time) }}{{ previewData.waterLevels[2].value }}m、{{ fmtTime(previewData.waterLevels[3].time) }}{{ previewData.waterLevels[3].value }}m,今日水位整体平稳,处于正常蓄水区间,人畜饮水、农田灌溉库容保障充足。
+          </p>
+          <p class="brief-paragraph">
+            2. 降雨量:今日时段累计降雨量{{ previewData.rainfall }}mm。
+          </p>
+          <p class="brief-paragraph">
+            3. 出入库流量数据:瞬时流量({{ fmtTime(previewData.instantFlow[0].time) }}{{ previewData.instantFlow[0].value }}m³/s、{{ fmtTime(previewData.instantFlow[1].time) }}{{ previewData.instantFlow[1].value }}m³/s、{{ fmtTime(previewData.instantFlow[2].time) }}{{ previewData.instantFlow[2].value }}m³/s、{{ fmtTime(previewData.instantFlow[3].time) }}{{ previewData.instantFlow[3].value }}m³/s);日流量最大{{ previewData.inout.max }}m³/s、最小{{ previewData.inout.min }}m³/s、平均{{ previewData.inout.avg }}m³/s;今日总入库水量{{ (previewData.inout.total * 10000).toLocaleString() }}m³、总出库水量{{ (previewData.inout.total * 10000).toLocaleString() }}m³,水量收支平衡,供水调度稳定。
+          </p>
+          <p class="brief-paragraph"><b>二、供水水质及管线运行情况</b></p>
+          <p class="brief-paragraph">
+            1. 水质指标及评价:今日对库区水源开展常态化水质监测,水体水面整洁,无漂浮杂物、无外源污水汇入、无藻类异常繁殖。综合水质评价:本次所有监测指标均符合《地表水环境质量标准》Ⅲ类水质标准,水质整体优良。
+          </p>
+          <p class="brief-paragraph">
+            2. 管线压力运行情况:今日对库区供水主管、灌溉支管压力进行常态化监测,管网运行压力维持在{{ previewData.other.pipelinePressure }}MPa,处于安全合理运行区间,压力波动平稳、工况稳定。供水管网、输水管道无渗漏、无泄压、无堵管现象,阀门、接口等附属设施运行正常,输水供水效能良好。
+          </p>
+          <p class="brief-paragraph"><b>三、工程运维巡查</b></p>
+          <p class="brief-paragraph">
+            今日严格落实日常值守及常态化巡查制度,对大坝、输水渠道、供水管道、启闭设备、取水设施及监测设备开展全面排查。坝体无渗漏、裂缝、塌陷等异常,输水、供水通道畅通无淤积堵塞,各类设备运行稳定、监测数据完整。今日完成库区保洁、设备日常养护、渠道疏通等工作,工程设施整体运行良好,无隐患、无设备故障,供水灌溉保障能力稳定。
+          </p>
+          <p class="brief-paragraph"><b>四、其他</b></p>
+          <p class="brief-paragraph">
+            值班人员{{ previewData.dutyPerson }}全员在岗在位,实时监测水位、库容、出水流量,规范完善台账记录,按时完成信息报送。库区供水、管护物资储备充足、器材完好可用。常态化开展库区安全巡查管控,严禁违规垂钓、下水、逗留等危险行为,杜绝破坏供水灌溉设施行为,今日库区运行平稳,无安全隐患、无突发事件及安全事故,供水灌溉秩序正常。
+          </p>
+        </div>
+        <div class="brief-footer-info">
+          <p>报送:上级水利部门</p>
+          <p>抄送:水库管理处</p>
+          <p>日期:{{ previewDate.getFullYear() }}年{{ previewDate.getMonth() + 1 }}月{{ previewDate.getDate() }}日 星期{{ weekDayNames[previewDate.getDay()] }}</p>
+        </div>
+        <div class="brief-footer-line"></div>
+        <div class="brief-footer-note">系统自动生成,仅供参考</div>
+      </div>
+    </div>
+    <template #footer>
+      <el-button @click="previewVisible = false">取消</el-button>
+      <el-button type="primary" @click="confirmExportPdf">确认导出PDF</el-button>
+    </template>
+  </el-dialog>
 </template>
 
 <script>
-import { Search, Warning, VideoCamera } from '@element-plus/icons-vue'
+import { Search, Warning, VideoCamera, Upload, Document, Plus, Folder } from '@element-plus/icons-vue'
+import { ElMessage } from 'element-plus'
+import * as XLSX from 'xlsx'
+import html2pdf from 'html2pdf.js'
+import { briefArchiveStore } from '../../stores/briefArchiveStore.js'
 import DeviceMaintainView from '../admin/DeviceMaintainView.vue'
 import PatrolPlanView from '../admin/PatrolPlanView.vue'
 import PatrolRecordView from '../admin/PatrolRecordView.vue'
@@ -403,7 +533,7 @@ import SysLogView from '../admin/SysLogView.vue'
 
 export default {
   name: 'WorkspaceView',
-  components: { Search, Warning, VideoCamera, DeviceMaintainView, PatrolPlanView, PatrolRecordView, PatrolHiddenView, WaterRainView, HydroHistoryView, WaterResourceAllocationView, GateControlAdminView, BenefitSummaryView, ArchiveFilesView, ArchiveOrdersView, ArchiveLifecycleView, SysRoleView, SysUserView, SysLogView },
+  components: { Search, Warning, VideoCamera, Upload, Document, Plus, Folder, DeviceMaintainView, PatrolPlanView, PatrolRecordView, PatrolHiddenView, WaterRainView, HydroHistoryView, WaterResourceAllocationView, GateControlAdminView, BenefitSummaryView, ArchiveFilesView, ArchiveOrdersView, ArchiveLifecycleView, SysRoleView, SysUserView, SysLogView },
   data() {
     return {
       currentDate: '',
@@ -477,6 +607,43 @@ export default {
         { day: '明天', icon: '🌧️', high: 28, low: 20 },
         { day: '后天', icon: '☀️', high: 35, low: 24 }
       ],
+      // 每日实时数据统计
+      dailyStats: {
+        dutyPerson: '张三',
+        shiftTime: '08:00 ~ 18:00',
+        weather: { temp: 28, desc: '多云' },
+        waterLevels: [
+          { time: '02:00', value: '976.82' },
+          { time: '08:00', value: '977.15' },
+          { time: '14:00', value: '977.08' },
+          { time: '20:00', value: '976.95' }
+        ],
+        yesterdayWaterLevel: '976.50',
+        rainfall: 12.5,
+        inout: {
+          max: 15.6,
+          min: 3.2,
+          avg: 8.4,
+          total: 72.6
+        },
+        instantFlow: [
+          { time: '02:00', value: 6.8 },
+          { time: '08:00', value: 8.2 },
+          { time: '14:00', value: 10.5 },
+          { time: '20:00', value: 7.3 }
+        ],
+        other: {
+          rainfall: 12.5,
+          waterQuality: 'Ⅱ类',
+          pipelinePressure: 0.65
+        },
+        checkItems: [
+          { label: '大坝坝体巡查正常', checked: true },
+          { label: '闸门启闭设备正常', checked: true },
+          { label: '通信设备运行正常', checked: false },
+          { label: '排水沟渠畅通', checked: true }
+        ]
+      },
       // 待办工单
       todoList: [
         { type: '巡检待完成', location: '大坝坝顶', reportTime: '2024-01-15 09:00', responsible: '张三', status: '待处理' },
@@ -501,6 +668,12 @@ export default {
         { name: '隐患台账', icon: '📋', gradient: 'linear-gradient(135deg, #f3e8ff, #e9d5ff)' },
         { name: '报表导出', icon: '📊', gradient: 'linear-gradient(135deg, #fce7f3, #fbcfe8)' }
       ],
+      // 简报预览
+      previewVisible: false,
+      previewData: null,
+      previewDate: new Date(),
+      previewTime: '',
+      weekDayNames: ['日', '一', '二', '三', '四', '五', '六'],
       // 月度统计
       monthlyStats: [
         { name: '月度灌溉水量', value: '125.6', unit: '万m³', trend: 5.2 },
@@ -544,6 +717,13 @@ export default {
         cells.push({ name: label })
       }
       return cells
+    },
+    waterLevelChange() {
+      const current = parseFloat(this.dailyStats.waterLevels[2]?.value || 0)
+      const yesterday = parseFloat(this.dailyStats.yesterdayWaterLevel || 0)
+      const diff = (current - yesterday).toFixed(2)
+      const direction = diff >= 0 ? '涨' : '落'
+      return { diff: Math.abs(diff).toString(), direction }
     }
   },
   mounted() {
@@ -579,6 +759,178 @@ export default {
     },
     selectCamera(index) {
       this.activeCameraIndex = index
+    },
+    // 格式化时间:02:00 → 2时
+    fmtTime(timeStr) {
+      if (!timeStr) return ''
+      const h = parseInt(timeStr.split(':')[0], 10)
+      return h + '时'
+    },
+    // 计算日总水量(万m³ → m³)
+    dailyInoutTotal() {
+      const total = this.dailyStats.inout?.total || 0
+      return (total * 10000).toLocaleString()
+    },
+    // 导出每日简报
+    exportDailyReport(type) {
+      const data = this.dailyStats
+      const now = new Date()
+      const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
+
+      if (type === 'excel') {
+        this.exportToExcel(data, dateStr)
+      } else if (type === 'pdf') {
+        this.exportToPdf(data, dateStr)
+      }
+    },
+    // Excel 导出
+    exportToExcel(data, dateStr) {
+      const rows = [
+        ['项目', '内容'],
+        ['值班人员', data.dutyPerson],
+        ['交接班时间', data.shiftTime],
+        ['气温', `${data.weather.temp}°C`],
+        ['天气状况', data.weather.desc],
+        ['', ''],
+        ['库水位', ''],
+        ['02:00 水位', `${data.waterLevels[0]?.value || ''} m`],
+        ['08:00 水位', `${data.waterLevels[1]?.value || ''} m`],
+        ['14:00 水位', `${data.waterLevels[2]?.value || ''} m`],
+        ['20:00 水位', `${data.waterLevels[3]?.value || ''} m`],
+        ['', ''],
+        ['雨量(8:00~次日8:00)', `${data.rainfall} mm`],
+        ['', ''],
+        ['出入库', ''],
+        ['最大流量', `${data.inout.max} m³/s`],
+        ['最小流量', `${data.inout.min} m³/s`],
+        ['平均流量', `${data.inout.avg} m³/s`],
+        ['总出入库日水量', `${data.inout.total} 万m³`],
+        ['', ''],
+        ['瞬时流量', ''],
+        ['02:00 瞬时流量', `${data.instantFlow[0]?.value || ''} m³/s`],
+        ['08:00 瞬时流量', `${data.instantFlow[1]?.value || ''} m³/s`],
+        ['14:00 瞬时流量', `${data.instantFlow[2]?.value || ''} m³/s`],
+        ['20:00 瞬时流量', `${data.instantFlow[3]?.value || ''} m³/s`],
+        ['', ''],
+        ['其他', ''],
+        ['降雨', `${data.other.rainfall} mm`],
+        ['水质', data.other.waterQuality],
+        ['管线压力', `${data.other.pipelinePressure} MPa`],
+        ['', ''],
+        ['自定义检查', ''],
+        ...data.checkItems.map(item => [item.label, item.checked ? '✅' : '❌'])
+      ]
+
+      const wb = XLSX.utils.book_new()
+      const ws = XLSX.utils.aoa_to_sheet(rows)
+
+      // 设置列宽
+      ws['!cols'] = [{ wch: 25 }, { wch: 20 }]
+
+      XLSX.utils.book_append_sheet(wb, ws, '每日数据统计')
+      XLSX.writeFile(wb, `每日数据统计_${dateStr}.xlsx`)
+    },
+    // 显示简报预览
+    showPdfPreview() {
+      this.previewDate = new Date()
+      const now = new Date()
+      this.previewTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
+      this.previewData = JSON.parse(JSON.stringify(this.dailyStats))
+      this.previewVisible = true
+    },
+    // 归档简报
+    archiveBrief() {
+      briefArchiveStore.addRecord(this.dailyStats)
+      ElMessage.success('简报已归档至「资料档案 → 工作简报」')
+    },
+    // 从预览确认导出 PDF
+    confirmExportPdf() {
+      this.previewVisible = false
+      const data = this.previewData || this.dailyStats
+      const now = new Date()
+      const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`
+      this.exportToPdf(data, dateStr)
+    },
+    // PDF 导出
+    exportToPdf(data, dateStr) {
+      const checkItemsHtml = data.checkItems.map(item =>
+        `<tr><td style="padding:5px 10px;border:1px solid #d0d5dd;font-size:12px;">${item.label}</td><td style="padding:5px 10px;border:1px solid #d0d5dd;text-align:center;font-size:12px;">${item.checked ? '✅' : '⬜'}</td></tr>`
+      ).join('')
+
+      const waterLevelsHtml = data.waterLevels.map((wl, i) =>
+        `${wl.time}:${wl.value}m${i < data.waterLevels.length - 1 ? ';' : ''}`
+      ).join('')
+      const instantFlowHtml = data.instantFlow.map((f, i) =>
+        `${f.time}:${f.value} m³/s${i < data.instantFlow.length - 1 ? ';' : ''}`
+      ).join('')
+
+      const year = dateStr.slice(0, 4)
+      const docNum = `乌海水库〔${year}〕${year.slice(-2)}号`
+
+      const now = new Date()
+      const weekDayNames = ['日', '一', '二', '三', '四', '五', '六']
+      const weekDay = weekDayNames[now.getDay()]
+      const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`
+
+      const html = `
+        <div style="font-family: 'SimSun', 'Microsoft YaHei', serif; padding: 40px 50px; max-width: 800px; margin: 0 auto; line-height: 1.8;">
+          <div style="text-align:center;font-size:24px;font-weight:700;color:#cc0000;letter-spacing:8px;margin-bottom:2px;">乌拉海沟水库</div>
+          <div style="text-align:center;font-size:13px;color:#E80000;margin-bottom:6px;">${dateStr.slice(0,4)}(第${dateStr.slice(2,4)}号)文</div>
+          <div style="height:2px;background:#cc0000;margin:4px 0 14px;"></div>
+          <div style="text-align:center;font-size:18px;font-weight:700;color:#1e293b;margin-bottom:14px;">水库每日工作简报</div>
+          <div style="font-size:14px;line-height:2;color:#1e293b;">
+            <p style="margin:8px 0;"><b>一、水情概况</b></p>
+            <p style="margin:4px 0;text-indent:2em;">1. 定时库水位:${parseInt(data.waterLevels[0].time)}时${data.waterLevels[0].value}m、${parseInt(data.waterLevels[1].time)}时${data.waterLevels[1].value}m、${parseInt(data.waterLevels[2].time)}时${data.waterLevels[2].value}m、${parseInt(data.waterLevels[3].time)}时${data.waterLevels[3].value}m,今日水位整体平稳,处于正常蓄水区间,人畜饮水、农田灌溉库容保障充足。</p>
+            <p style="margin:4px 0;text-indent:2em;">2. 降雨量:今日时段累计降雨量${data.rainfall}mm。</p>
+            <p style="margin:4px 0;text-indent:2em;">3. 出入库流量数据:瞬时流量(${parseInt(data.instantFlow[0].time)}时${data.instantFlow[0].value}m³/s、${parseInt(data.instantFlow[1].time)}时${data.instantFlow[1].value}m³/s、${parseInt(data.instantFlow[2].time)}时${data.instantFlow[2].value}m³/s、${parseInt(data.instantFlow[3].time)}时${data.instantFlow[3].value}m³/s);日流量最大${data.inout.max}m³/s、最小${data.inout.min}m³/s、平均${data.inout.avg}m³/s;今日总入库水量${(data.inout.total * 10000).toLocaleString()}m³、总出库水量${(data.inout.total * 10000).toLocaleString()}m³,水量收支平衡,供水调度稳定。</p>
+            <p style="margin:8px 0;"><b>二、供水水质及管线运行情况</b></p>
+            <p style="margin:4px 0;text-indent:2em;">1. 水质指标及评价:今日对库区水源开展常态化水质监测,水体水面整洁,无漂浮杂物、无外源污水汇入、无藻类异常繁殖。综合水质评价:本次所有监测指标均符合《地表水环境质量标准》Ⅲ类水质标准,水质整体优良。</p>
+            <p style="margin:4px 0;text-indent:2em;">2. 管线压力运行情况:今日对库区供水主管、灌溉支管压力进行常态化监测,管网运行压力维持在${data.other.pipelinePressure}MPa,处于安全合理运行区间,压力波动平稳、工况稳定。供水管网、输水管道无渗漏、无泄压、无堵管现象,阀门、接口等附属设施运行正常,输水供水效能良好。</p>
+            <p style="margin:8px 0;"><b>三、工程运维巡查</b></p>
+            <p style="margin:4px 0;text-indent:2em;">今日严格落实日常值守及常态化巡查制度,对大坝、输水渠道、供水管道、启闭设备、取水设施及监测设备开展全面排查。坝体无渗漏、裂缝、塌陷等异常,输水、供水通道畅通无淤积堵塞,各类设备运行稳定、监测数据完整。今日完成库区保洁、设备日常养护、渠道疏通等工作,工程设施整体运行良好,无隐患、无设备故障,供水灌溉保障能力稳定。</p>
+            <p style="margin:8px 0;"><b>四、其他</b></p>
+            <p style="margin:4px 0;text-indent:2em;">值班人员${data.dutyPerson}全员在岗在位,实时监测水位、库容、出水流量,规范完善台账记录,按时完成信息报送。库区供水、管护物资储备充足、器材完好可用。常态化开展库区安全巡查管控,严禁违规垂钓、下水、逗留等危险行为,杜绝破坏供水灌溉设施行为,今日库区运行平稳,无安全隐患、无突发事件及安全事故,供水灌溉秩序正常。</p>
+          </div>
+          <div style="text-align:right;font-size:13px;color:#333;margin-top:20px;line-height:2;">
+            <p>报送:上级水利部门</p>
+            <p>抄送:水库管理处</p>
+            <p>日期:${dateStr} 星期${weekDay}</p>
+          </div>
+          <div style="height:2px;background:#cc0000;margin:10px 0 6px;"></div>
+          <div style="text-align:center;font-size:11px;color:#94a3b8;">系统自动生成,仅供参考</div>
+        </div>
+      `
+
+      const element = document.createElement('div')
+      element.innerHTML = html
+      document.body.appendChild(element)
+
+      const opt = {
+        margin: [12, 15],
+        filename: `每日简报_${dateStr}.pdf`,
+        image: { type: 'jpeg', quality: 0.98 },
+        html2canvas: { scale: 2, useCORS: true, letterRendering: true },
+        jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' },
+        pagebreak: { mode: ['avoid-all', 'css', 'legacy'] }
+      }
+
+      html2pdf().set(opt).from(element).save().then(() => {
+        document.body.removeChild(element)
+      })
+    },
+    // 检查项变更
+    onCheckChange() {
+      // 可以在此处持久化保存检查项状态
+    },
+    // 添加自定义检查项
+    addCheckItem() {
+      const label = prompt('请输入检查事项:')
+      if (label && label.trim()) {
+        this.dailyStats.checkItems.push({
+          label: label.trim(),
+          checked: false
+        })
+      }
     }
   }
 }
@@ -763,8 +1115,8 @@ export default {
   flex: 1;
   display: flex;
   flex-direction: column;
-  padding: 20px;
-  gap: 20px;
+  padding: 10px;
+  gap: 10px;
   overflow-y: auto;
   overflow-x: hidden;
 }
@@ -941,14 +1293,29 @@ export default {
 }
 .quick-label { font-size: 13px; font-weight: 500; color: #475569; }
 
-/* ==================== 中间区域布局 ==================== */
-.middle-section { display: flex; gap: 20px; min-height: 600px; flex-shrink: 0; }
-.middle-left { flex: 55; display: flex; flex-direction: column; gap: 20px; }
-.middle-right { flex: 45; display: flex; flex-direction: column; gap: 20px; }
-.warning-panel { min-height: 220px; }
-.todo-panel { flex: 1; min-height: 0; }
+/* ==================== 首页瀑布流布局 ==================== */
+.home-grid {
+  column-count: 3;
+  column-gap: 10px;
+  width: 100%;
+}
+.home-grid > .glass-card {
+  break-inside: avoid;
+  margin-bottom: 10px;
+}
+.home-grid > .home-full {
+  column-span: all;
+}
+.home-grid .quick-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 10px;
+  padding: 12px 16px;
+}
+.warning-panel { }
+.todo-panel { }
 .todo-panel .card-body { overflow: hidden; }
-.device-panel { min-height: 280px; }
+.device-panel { }
 .weather-panel { flex-shrink: 0; }
 .quick-panel { flex-shrink: 0; }
 
@@ -1091,45 +1458,50 @@ export default {
   color: #94a3b8;
 }
 
-/* ==================== 气象信息样式 ==================== */
-.weather-content {
+/* ==================== 实时天气样式 ==================== */
+.weather-current-body {
   padding: 16px 20px;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  flex: 1;
 }
-.weather-main {
+.weather-current-body .weather-main {
   display: flex;
   align-items: center;
   gap: 16px;
   margin-bottom: 16px;
 }
-.weather-icon {
+.weather-current-body .weather-main .weather-icon {
   font-size: 48px;
   line-height: 1;
 }
-.weather-info {
+.weather-current-body .weather-main .weather-info {
   display: flex;
   flex-direction: column;
 }
-.weather-temp {
+.weather-current-body .weather-main .weather-temp {
   font-size: 32px;
   font-weight: 700;
   color: #1e293b;
   line-height: 1.2;
 }
-.weather-desc {
+.weather-current-body .weather-main .weather-desc {
   font-size: 14px;
   color: #64748b;
 }
-.weather-update {
-  font-size: 12px;
-  color: #94a3b8;
-}
-.weather-details {
+.weather-current-body .weather-details {
   display: grid;
   grid-template-columns: repeat(3, 1fr);
   gap: 12px;
-  padding-bottom: 16px;
-  border-bottom: 1px solid rgba(226, 232, 240, 0.6);
-  margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid rgba(226, 232, 240, 0.5);
+  margin-bottom: 12px;
+}
+.weather-merged-forecast .weather-forecast {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 8px;
 }
 .weather-detail-item {
   display: flex;
@@ -1146,17 +1518,18 @@ export default {
   font-weight: 600;
   color: #334155;
 }
-.weather-forecast {
-  display: grid;
-  grid-template-columns: repeat(3, 1fr);
-  gap: 12px;
+.weather-update {
+  font-size: 12px;
+  color: #94a3b8;
 }
+
+/* ==================== 未来预报样式 ==================== */
 .forecast-item {
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 6px;
-  padding: 10px;
+  padding: 16px 10px;
   border-radius: 8px;
   background: rgba(248, 250, 252, 0.8);
 }
@@ -1166,34 +1539,19 @@ export default {
   font-weight: 500;
 }
 .forecast-icon {
-  font-size: 24px;
+  font-size: 28px;
 }
 .forecast-temp {
-  font-size: 12px;
+  font-size: 13px;
   color: #475569;
 }
 
 /* ==================== 今日调度样式 ==================== */
 .schedule-panel {
-  min-height: 200px;
 }
 .stats-table-panel {
-  min-height: 250px;
 }
 
-/* ==================== 底部区域(快捷功能 + 气象) ==================== */
-.bottom-section {
-  display: flex;
-  gap: 20px;
-  min-height: 200px;
-  flex-shrink: 0;
-}
-.bottom-section .quick-panel {
-  flex: 40;
-}
-.bottom-section .weather-panel {
-  flex: 60;
-}
 .schedule-list {
   display: flex;
   flex-direction: column;
@@ -1251,21 +1609,6 @@ export default {
   min-height: 4px;
   border-radius: 2px 2px 0 0;
 }
-
-/* ==================== 底部区域 ==================== */
-.bottom-section .card-header {
-  height: 48px;
-  padding: 0 20px;
-  border-bottom: 1px solid rgba(226, 232, 240, 0.6);
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.bottom-section .card-body {
-  flex: 1;
-  overflow: auto;
-  padding: 12px 16px;
-}
 .trend { font-weight: 600; font-size: 13px; }
 
 /* ==================== 滚动条美化 ==================== */
@@ -1389,4 +1732,153 @@ export default {
   font-size: 13px;
   color: #334155;
 }
+
+/* ==================== 每日实时数据统计卡片(紧凑单行) ==================== */
+.daily-stats-card {
+  flex-shrink: 0;
+  max-width: 860px;
+}
+.daily-stats-card .card-body {
+  padding: 4px 16px;
+}
+.daily-stats-list {
+  display: flex;
+  flex-direction: column;
+}
+.daily-stat-row {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 6px 0;
+  border-bottom: 1px solid rgba(226, 232, 240, 0.3);
+  min-height: 34px;
+}
+.daily-stat-row:last-child {
+  border-bottom: none;
+}
+.ds-label {
+  font-size: 12px;
+  font-weight: 600;
+  color: #64748b;
+  min-width: 120px;
+  flex-shrink: 0;
+}
+.ds-value {
+  font-size: 12px;
+  color: #1e293b;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  flex-wrap: wrap;
+  line-height: 1.5;
+}
+.ds-value b {
+  font-weight: 600;
+  color: #1d4ed8;
+}
+.tp-group {
+  display: inline-flex;
+  align-items: center;
+  gap: 3px;
+}
+.tp-time {
+  font-size: 11px;
+  color: #94a3b8;
+}
+.tp-val {
+  font-size: 12px;
+  font-weight: 600;
+  color: #1d4ed8;
+}
+.tp-sep {
+  color: #d0d5dd;
+  margin: 0 2px;
+}
+.check-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px 12px;
+}
+.check-list :deep(.el-checkbox__label) {
+    font-size: 12px;
+  }
+  
+  /* ==================== 简报预览弹窗样式 ==================== */
+  .brief-preview-wrapper {
+    background: #fff;
+    padding: 20px;
+    max-height: 75vh;
+    overflow-y: auto;
+  }
+  .brief-paper {
+    font-family: 'SimSun', 'Microsoft YaHei', serif;
+    max-width: 700px;
+    margin: 0 auto;
+    padding: 30px 35px;
+    background: #fff;
+    border: 1px solid #e2e8f0;
+    box-shadow: 0 2px 12px rgba(0,0,0,0.06);
+    line-height: 1.8;
+  }
+  .brief-red-header {
+    text-align: center;
+    font-size: 22px;
+    font-weight: 700;
+    color: #cc0000;
+    letter-spacing: 8px;
+    margin-bottom: 2px;
+  }
+  .brief-doc-number {
+    text-align: center;
+    font-size: 12px;
+    color: #E80000;
+    margin-bottom: 6px;
+  }
+  .brief-red-line {
+    height: 2px;
+    background: #cc0000;
+    margin: 4px 0 14px;
+  }
+  .brief-title {
+    text-align: center;
+    font-size: 18px;
+    font-weight: 700;
+    color: #1e293b;
+    margin-bottom: 14px;
+  }
+  .brief-body {
+    font-size: 14px;
+    color: #1e293b;
+    line-height: 2;
+  }
+  .brief-paragraph {
+    margin: 6px 0;
+    text-indent: 2em;
+  }
+  .brief-paragraph b {
+    font-weight: 700;
+    color: #1e293b;
+  }
+  .brief-footer-info {
+    text-align: right;
+    font-size: 13px;
+    color: #333;
+    margin-top: 20px;
+    line-height: 2;
+  }
+  .brief-footer-info p {
+    margin: 2px 0;
+  }
+  .brief-footer-line {
+    height: 2px;
+    background: #cc0000;
+    margin: 10px 0 6px;
+  }
+  .brief-footer-note {
+    text-align: center;
+    font-size: 11px;
+    color: #94a3b8;
+  }
+
 </style>
+

+ 86 - 69
系统改造任务清单.md

@@ -2,157 +2,170 @@
 
 > 基于客户会议纪要整理,2026-06-24
 
----
+***
 
 ## T1 — 首页(流域总览)添加天气预报模块
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                     |
+| -------- | -------------------------------------- |
+| **优先级**  | 高                                      |
 | **涉及文件** | `src/views/mainPages/OverviewView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                  |
 
 **需求说明:**
 流域总览页面(综合首页)左侧新增天气预报模块,展示内容包括:
+
 - 天气图标(晴/阴/雨/雪)
 - 当前温度
 - 湿度、风速
 - 逐小时/逐日预报信息
 
-**参考:** 工作台已有气象数据(WorkspaceView.vue 中 weatherData),可复用接口逻辑或 UI 样式。
+**参考:** 工作台已有气象数据(WorkspaceView\.vue 中 weatherData),可复用接口逻辑或 UI 样式。
 
----
+***
 
 ## T2 — 渗压监测设计线和警戒线随水位动态变化
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                     |
+| -------- | -------------------------------------- |
+| **优先级**  | 高                                      |
 | **涉及文件** | `src/components/DamSeepageSection.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                  |
 
 **需求说明:**
 当前设计线(designWaterLevel = 10.2m)和警戒线(warningWaterLevel = 11.5m)是固定硬编码值,客户要求:
+
 - 设计线和警戒线应根据当前实际水位**动态偏移**
 - 建立水位 → 设计线 / 警戒线的映射关系
   - 方案一:当前水位 ± 固定差值
   - 方案二:多段线性插值表
 
 **改造要点:**
+
 - DamSeepageSection.vue 中 `designControlPoints` 和 `warningControlPoints` 不再使用固定值
 - 根据 `currentWaterLevel` 实时计算对应控制点 Y 值
 - Canvas 绘制逻辑适配动态线
 
----
+***
 
 ## T3 — 核查并修正设备类型
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                              |
+| -------- | ----------------------------------------------- |
+| **优先级**  | 高                                               |
 | **涉及文件** | `src/views/mainPages/EngineeringSafetyView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                           |
 
 **需求说明:**
 客户指出系统中某些设备类型实际上并不存在,需要:
+
 1. 全面梳理设备清单(tree 数据)和设备类型
 2. 移除不存在的设备类型
 3. 修正设备清单数据
 
 **关联数据:**
-- EngineeringSafetyView.vue 中的设备清单树(tree / searchKey / filteredTree)
+
+- EngineeringSafetyView\.vue 中的设备清单树(tree / searchKey / filteredTree)
 - 设备类型枚举列表
 - 设备台账相关数据
 
----
+***
 
 ## T4 — 安全评估等级修改为参考值
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                              |
+| -------- | ----------------------------------------------- |
+| **优先级**  | 高                                               |
 | **涉及文件** | `src/views/mainPages/EngineeringSafetyView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                           |
 
 **需求说明:**
 当前显示 "综合安全等级 → 一类坝",客户指出系统只能提供**参考评估**,不能直接评定为一类坝(需专业机构评定)。
 
 **改造方案(二选一):**
+
 - 方案 A:等级名称改为 **"安全" / "健康" / "关注" / "危险"** 等参考性表述
 - 方案 B:保持等级显示,但在 UI 上标注 **"(参考值)"** 或 **"系统评估,仅供参考"**
 
 **影响位置:**
-- EngineeringSafetyView.vue 中 `<span class="safety-eval-grade grade-a">一类坝</span>` 所在区域
+
+- EngineeringSafetyView\.vue 中 `<span class="safety-eval-grade grade-a">一类坝</span>` 所在区域
 - 安全评估结论相关文案
 
----
+***
 
 ## T5 — 闸门控制改造(引水闸 + 放水闸)
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                        |
+| -------- | ----------------------------------------- |
+| **优先级**  | 高                                         |
 | **涉及文件** | `src/views/mainPages/GateControlView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                     |
 
 **需求说明:**
 闸门控制页面结构需要重新调整:
 
 **闸室划分:**
-| 闸室 | 说明 |
-|------|------|
+
+| 闸室    | 说明        |
+| ----- | --------- |
 | 引水口闸室 | 包含 3 个螺杆闸 |
-| 放水洞闸室 | 包含放水闸 |
+| 放水洞闸室 | 包含放水闸     |
 
 **右侧面板改动:**
+
 - ❌ 移除原有的 "1# 弧形钢闸门" 和 "2# 平面钢闸门"
 - ✅ 改为展示:
   - **引水闸(3 个螺杆闸)** — 分别展示开度、工况、安全状态
   - **放水闸** — 展示开度、工况、安全状态
 
 **左侧及中间部分:**
+
 - 调度计划与指令 → 保留
 - 闸室监控 → 根据新闸室结构调整
 - Cesium 地图 → 保留不变
 
----
+***
 
 ## T6 — 视频监控移入工作台
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                                                                                          |
+| -------- | ----------------------------------------------------------------------------------------------------------- |
+| **优先级**  | 高                                                                                                           |
 | **涉及文件** | `src/views/mainPages/VideoMonitorView.vue`、`src/views/mainPages/WorkspaceView.vue`、`src/views/HomeView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                                                                                       |
 
 **需求说明:**
 
 ### 前台大屏
-- 从 HomeView.vue 的 tab 导航中移除"视频监控"入口
+
+- 从 HomeView\.vue 的 tab 导航中移除"视频监控"入口
 - 原有视频监控位置(tab 切换)将被 T7 的"管线安监"替代
 
 ### 工作台
-- WorkspaceView.vue 的左侧菜单新增"视频监控"菜单项
+
+- WorkspaceView\.vue 的左侧菜单新增"视频监控"菜单项
 - 视频监控模块嵌入工作台内容区
 - **样式要求:** 工作台是蓝白 B 端风格,视频监控模块的 UI **必须与工作台风格统一**
   - 不可直接套用大屏深色科技风
   - 使用 Element Plus 组件体系
   - 配色、字体、间距与工作台一致
 
----
+***
 
 ## T7 — 原视频监控位置改为管线安监大屏
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                                                             |
+| -------- | ------------------------------------------------------------------------------ |
+| **优先级**  | 高                                                                              |
 | **涉及文件** | 新建 `src/views/mainPages/PipelineMonitorView.vue`(示例名)、`src/views/HomeView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                                                          |
 
 **需求说明:**
 原视频监控 tab 位置替换为"管线安监"大屏页面,整体保持大屏深色科技风设计。
 
 **页面布局:**
+
 ```
 ┌─────────────────────────────────────────┐
 │            管线安监(标题)               │
@@ -172,53 +185,57 @@
 ```
 
 **数据展示:**
+
 - 6 个管网压力监测点
 - 2 个流量监测点
 - 来水总量
 - 压力监测数据(实时值、趋势)
 - 流量监测数据(实时值、趋势)
 
----
+***
 
 ## T8 — 工作台首页新增每日实时数据统计卡片(支持导出简报)
 
-| 项目 | 内容 |
-|------|------|
-| **优先级** | 高 |
+| 项目       | 内容                                      |
+| -------- | --------------------------------------- |
+| **优先级**  | 高                                       |
 | **涉及文件** | `src/views/mainPages/WorkspaceView.vue` |
-| **状态** | ⏳ 待开发 |
+| **状态**   | ⏳ 待开发                                   |
 
 **需求说明:**
 工作台首页(首页 dashboard 区域)新增一个统计卡片模块,内容涵盖:
 
 ### 数据项
-| 数据类别 | 具体内容 |
-|----------|----------|
-| 值班信息 | 值班人员、交接班时间 |
-| 气象数据 | 气温、天气状况 |
-| 库水位 | 2点、8点、14点、20点水位(每6小时) |
-| 雨量 | 8:00 ~ 次日8:00 累计雨量 |
-| 出入库 | 最大流量、最小流量、平均流量、总出入库日水量 |
-| 瞬时流量 | 2点、8点、14点、20点瞬时流量(m³/s) |
-| 其他 | 降雨、水质、管线压力 |
-| 自定义检查 | 检查事项等自定义检查项 |
+
+| 数据类别  | 具体内容                    |
+| ----- | ----------------------- |
+| 值班信息  | 值班人员、交接班时间              |
+| 气象数据  | 气温、天气状况                 |
+| 库水位   | 2点、8点、14点、20点水位(每6小时)   |
+| 雨量    | 8:00 \~ 次日8:00 累计雨量     |
+| 出入库   | 最大流量、最小流量、平均流量、总出入库日水量  |
+| 瞬时流量  | 2点、8点、14点、20点瞬时流量(m³/s) |
+| 其他    | 降雨、水质、管线压力              |
+| 自定义检查 | 检查事项等自定义检查项             |
 
 ### 导出功能
+
 - 支持**导出生成简报**(PDF / Excel 格式)
 - 简报模板包含上述所有数据项
 - 可自定义简报标题和备注
 
----
+***
 
 ## 进度总览
 
-| 编号 | 任务 | 优先级 | 涉及端 | 状态 |
-|------|------|--------|--------|------|
-| T1 | 首页添加天气预报 | 高 | 前台大屏 | ⏳ |
-| T2 | 渗压监测线动态变化 | 高 | 前台大屏 | ⏳ |
-| T3 | 设备类型修正 | 高 | 全系统 | ⏳ |
-| T4 | 安全评估改为参考值 | 高 | 前台大屏 | ⏳ |
-| T5 | 闸门控制改造 | 高 | 前台大屏 | ⏳ |
-| T6 | 视频监控移入工作台 | 高 | 前台+后台 | ⏳ |
-| T7 | 管线安监大屏 | 高 | 前台大屏 | ⏳ |
-| T8 | 每日数据统计卡片+导出 | 高 | 后台工作台 | ⏳ |
+| 编号 | 任务          | 优先级 | 涉及端   | 状态    |
+| -- | ----------- | --- | ----- | ----- |
+| T1 | 首页添加天气预报    | 高   | 前台大屏  | ✅ 已完成 |
+| T2 | 渗压监测线动态变化   | 高   | 前台大屏  | ✅ 已完成 |
+| T3 | 设备类型修正      | 高   | 全系统   | ✅ 已完成 |
+| T4 | 安全评估改为参考值   | 高   | 前台大屏  | ✅ 已完成 |
+| T5 | 闸门控制改造      | 高   | 前台大屏  | ✅ 已完成 |
+| T6 | 视频监控移入工作台   | 高   | 前台+后台 | ✅ 已完成 |
+| T7 | 管线安监大屏      | 高   | 前台大屏  | ✅ 已完成 |
+| T8 | 每日数据统计卡片+导出 | 高   | 后台工作台 | ✅ 已完成 |
+