提交 8d534f1e authored 作者: coderBryanFu's avatar coderBryanFu

Merge branch 'pre' of http://8.140.26.4:10003/caijian/risk-monitor into fk-dev

流水线 #315 已通过 于阶段
in 1 分 37 秒
......@@ -1750,9 +1750,9 @@
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.50.2.tgz",
"integrity": "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
"integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==",
"cpu": [
"arm"
],
......@@ -1764,9 +1764,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.50.2.tgz",
"integrity": "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz",
"integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==",
"cpu": [
"arm64"
],
......@@ -1778,9 +1778,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.50.2.tgz",
"integrity": "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz",
"integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==",
"cpu": [
"arm64"
],
......@@ -1792,9 +1792,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.50.2.tgz",
"integrity": "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz",
"integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==",
"cpu": [
"x64"
],
......@@ -1806,9 +1806,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.50.2.tgz",
"integrity": "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz",
"integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==",
"cpu": [
"arm64"
],
......@@ -1820,9 +1820,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.50.2.tgz",
"integrity": "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz",
"integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==",
"cpu": [
"x64"
],
......@@ -1834,9 +1834,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.50.2.tgz",
"integrity": "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz",
"integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==",
"cpu": [
"arm"
],
......@@ -1848,9 +1848,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.50.2.tgz",
"integrity": "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz",
"integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==",
"cpu": [
"arm"
],
......@@ -1862,9 +1862,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.50.2.tgz",
"integrity": "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz",
"integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==",
"cpu": [
"arm64"
],
......@@ -1876,9 +1876,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.50.2.tgz",
"integrity": "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz",
"integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==",
"cpu": [
"arm64"
],
......@@ -1890,9 +1890,23 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.50.2.tgz",
"integrity": "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz",
"integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz",
"integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==",
"cpu": [
"loong64"
],
......@@ -1904,9 +1918,23 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.50.2.tgz",
"integrity": "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz",
"integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz",
"integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==",
"cpu": [
"ppc64"
],
......@@ -1918,9 +1946,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.50.2.tgz",
"integrity": "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz",
"integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==",
"cpu": [
"riscv64"
],
......@@ -1932,9 +1960,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.50.2.tgz",
"integrity": "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz",
"integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==",
"cpu": [
"riscv64"
],
......@@ -1946,9 +1974,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.50.2.tgz",
"integrity": "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz",
"integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==",
"cpu": [
"s390x"
],
......@@ -1960,9 +1988,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.50.2.tgz",
"integrity": "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz",
"integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==",
"cpu": [
"x64"
],
......@@ -1974,9 +2002,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.50.2.tgz",
"integrity": "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz",
"integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==",
"cpu": [
"x64"
],
......@@ -1987,10 +2015,24 @@
"linux"
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz",
"integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.50.2.tgz",
"integrity": "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz",
"integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==",
"cpu": [
"arm64"
],
......@@ -2002,9 +2044,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.50.2.tgz",
"integrity": "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz",
"integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==",
"cpu": [
"arm64"
],
......@@ -2016,9 +2058,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.50.2.tgz",
"integrity": "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz",
"integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==",
"cpu": [
"ia32"
],
......@@ -2029,10 +2071,24 @@
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz",
"integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.50.2.tgz",
"integrity": "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz",
"integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==",
"cpu": [
"x64"
],
......@@ -2551,9 +2607,9 @@
}
},
"node_modules/anymatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
......@@ -2650,7 +2706,7 @@
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
......@@ -2704,14 +2760,14 @@
}
},
"node_modules/axios": {
"version": "1.12.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz",
"integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==",
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
"integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^2.1.0"
}
},
"node_modules/balanced-match": {
......@@ -2795,9 +2851,9 @@
}
},
"node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -2873,7 +2929,7 @@
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
......@@ -3073,7 +3129,7 @@
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
......@@ -3789,7 +3845,7 @@
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
......@@ -3832,9 +3888,9 @@
"license": "Apache-2.0"
},
"node_modules/diff": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/diff/-/diff-5.2.0.tgz",
"integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz",
"integrity": "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
......@@ -3860,9 +3916,9 @@
"license": "MIT"
},
"node_modules/dompurify": {
"version": "3.3.0",
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz",
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz",
"integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
......@@ -3870,7 +3926,7 @@
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
......@@ -3963,7 +4019,7 @@
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
......@@ -3972,7 +4028,7 @@
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
......@@ -3981,7 +4037,7 @@
},
"node_modules/es-object-atoms": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"license": "MIT",
"dependencies": {
......@@ -3993,7 +4049,7 @@
},
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
......@@ -4304,9 +4360,9 @@
}
},
"node_modules/form-data": {
"version": "4.0.4",
"resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.4.tgz",
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
......@@ -4391,7 +4447,7 @@
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
......@@ -4415,7 +4471,7 @@
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
......@@ -4486,9 +4542,9 @@
"license": "BSD"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz",
"integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
......@@ -4496,9 +4552,9 @@
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
......@@ -4698,7 +4754,7 @@
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
......@@ -4748,7 +4804,7 @@
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
......@@ -4760,7 +4816,7 @@
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
......@@ -4904,9 +4960,9 @@
}
},
"node_modules/immutable": {
"version": "5.1.3",
"resolved": "https://registry.npmmirror.com/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz",
"integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==",
"dev": true,
"license": "MIT"
},
......@@ -5141,9 +5197,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
......@@ -5275,15 +5331,15 @@
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT"
},
"node_modules/lodash-unified": {
......@@ -5328,9 +5384,9 @@
}
},
"node_modules/markdown-it": {
"version": "14.1.0",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.1.0.tgz",
"integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
"integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1",
......@@ -5403,7 +5459,7 @@
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
......@@ -5953,9 +6009,9 @@
}
},
"node_modules/micromatch/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
......@@ -5967,7 +6023,7 @@
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
......@@ -5976,7 +6032,7 @@
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
......@@ -5987,13 +6043,13 @@
}
},
"node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
......@@ -6406,9 +6462,9 @@
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
"engines": {
......@@ -6664,10 +6720,13 @@
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
......@@ -6750,9 +6809,9 @@
}
},
"node_modules/readdirp/node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true,
"license": "MIT",
"engines": {
......@@ -6900,9 +6959,9 @@
"license": "Unlicense"
},
"node_modules/rollup": {
"version": "4.50.2",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.50.2.tgz",
"integrity": "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w==",
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz",
"integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -6916,27 +6975,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.50.2",
"@rollup/rollup-android-arm64": "4.50.2",
"@rollup/rollup-darwin-arm64": "4.50.2",
"@rollup/rollup-darwin-x64": "4.50.2",
"@rollup/rollup-freebsd-arm64": "4.50.2",
"@rollup/rollup-freebsd-x64": "4.50.2",
"@rollup/rollup-linux-arm-gnueabihf": "4.50.2",
"@rollup/rollup-linux-arm-musleabihf": "4.50.2",
"@rollup/rollup-linux-arm64-gnu": "4.50.2",
"@rollup/rollup-linux-arm64-musl": "4.50.2",
"@rollup/rollup-linux-loong64-gnu": "4.50.2",
"@rollup/rollup-linux-ppc64-gnu": "4.50.2",
"@rollup/rollup-linux-riscv64-gnu": "4.50.2",
"@rollup/rollup-linux-riscv64-musl": "4.50.2",
"@rollup/rollup-linux-s390x-gnu": "4.50.2",
"@rollup/rollup-linux-x64-gnu": "4.50.2",
"@rollup/rollup-linux-x64-musl": "4.50.2",
"@rollup/rollup-openharmony-arm64": "4.50.2",
"@rollup/rollup-win32-arm64-msvc": "4.50.2",
"@rollup/rollup-win32-ia32-msvc": "4.50.2",
"@rollup/rollup-win32-x64-msvc": "4.50.2",
"@rollup/rollup-android-arm-eabi": "4.60.1",
"@rollup/rollup-android-arm64": "4.60.1",
"@rollup/rollup-darwin-arm64": "4.60.1",
"@rollup/rollup-darwin-x64": "4.60.1",
"@rollup/rollup-freebsd-arm64": "4.60.1",
"@rollup/rollup-freebsd-x64": "4.60.1",
"@rollup/rollup-linux-arm-gnueabihf": "4.60.1",
"@rollup/rollup-linux-arm-musleabihf": "4.60.1",
"@rollup/rollup-linux-arm64-gnu": "4.60.1",
"@rollup/rollup-linux-arm64-musl": "4.60.1",
"@rollup/rollup-linux-loong64-gnu": "4.60.1",
"@rollup/rollup-linux-loong64-musl": "4.60.1",
"@rollup/rollup-linux-ppc64-gnu": "4.60.1",
"@rollup/rollup-linux-ppc64-musl": "4.60.1",
"@rollup/rollup-linux-riscv64-gnu": "4.60.1",
"@rollup/rollup-linux-riscv64-musl": "4.60.1",
"@rollup/rollup-linux-s390x-gnu": "4.60.1",
"@rollup/rollup-linux-x64-gnu": "4.60.1",
"@rollup/rollup-linux-x64-musl": "4.60.1",
"@rollup/rollup-openbsd-x64": "4.60.1",
"@rollup/rollup-openharmony-arm64": "4.60.1",
"@rollup/rollup-win32-arm64-msvc": "4.60.1",
"@rollup/rollup-win32-ia32-msvc": "4.60.1",
"@rollup/rollup-win32-x64-gnu": "4.60.1",
"@rollup/rollup-win32-x64-msvc": "4.60.1",
"fsevents": "~2.3.2"
}
},
......@@ -8066,9 +8129,9 @@
}
},
"node_modules/vite": {
"version": "5.4.20",
"resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.20.tgz",
"integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==",
"version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true,
"license": "MIT",
"dependencies": {
......@@ -8214,9 +8277,9 @@
"license": "MIT"
},
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true,
"license": "ISC",
"bin": {
......
......@@ -178,7 +178,7 @@ export function getSearchBlurb(params) {
})
}
// 获取相关事件getRelatedEvents
// 获取相关事件
export function getRelatedEvents(params) {
return request({
method: 'GET',
......@@ -186,8 +186,16 @@ export function getRelatedEvents(params) {
params
})
}
// 获取相关行政举措
export function getSearchAOMeasures(params) {
return request({
method: 'GET',
url: `/api/marketsearchDetails/getSearchAOMeasures`,
params
})
}
// 获取事件脉络 getSearchContext
// 获取事件脉络
export function getSearchContext(params) {
return request({
method: 'GET',
......
......@@ -126,11 +126,25 @@ export function getAreaType() {
})
}
/**
* 资助项目列表:领域、年份用逗号拼接为一个查询参数(arealist=1,2,3&yearlist=2025,2024)
*/
function normalizeProjectListQueryParams(params) {
const next = { ...(params || {}) }
if (Array.isArray(next.arealist)) {
next.arealist = next.arealist.filter((v) => v !== undefined && v !== null && v !== "").join(",")
}
if (Array.isArray(next.yearlist)) {
next.yearlist = next.yearlist.filter((v) => v !== undefined && v !== null && v !== "").join(",")
}
return next
}
//资助体系v2.0:资助项目列表分页
export function getProjectListNew(params) {
return request({
method: 'GET',
url: `/api/fund/getProjectListNew`,
params
params: normalizeProjectListQueryParams(params)
})
}
<template>
<div class="layout-container">
<div class="layout-main">
<div class="layout-main-center">
<div class="report-header">
<div class="report-title">政令原文</div>
<el-switch v-model="isHighlight" @change="handleHighlight" />
<div class="switch-label switch-label-left">高亮实体</div>
<el-switch v-model="isTranslate" @change="handleTranslate" />
<div class="switch-label">译文显示</div>
<div class="btn" @click="emits('download')">
<div class="icon icon-gap-4">
<img src="./assets/icons/download.png" alt="" />
</div>
<div class="text">下载</div>
</div>
<div class="btn" @click="handleFindWord('open')">
<div class="icon icon-gap-6">
<img src="./assets/icons/search.png" alt="" />
</div>
<div class="text">查找</div>
</div>
<div class="find-word-box" v-if="findWordBox">
<div class="find-word-input">
<el-input v-model="findWordTxt" placeholder="查找原文内容" @input="handleUpdateWord" />
</div>
<div class="find-word-limit">{{ findWordNum }}/{{ findWordMax }}</div>
<div class="find-word-icon" @click="handleFindWord('last')">
<el-icon><ArrowUp /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('next')">
<el-icon><ArrowDown /></el-icon>
</div>
<div class="find-word-icon" @click="handleFindWord('close')">
<el-icon><Close /></el-icon>
</div>
</div>
</div>
<div :class="['report-main', {'is-highlight': isHighlight}]">
<div v-if="!formatData.length" class="no-content">暂无数据</div>
<el-scrollbar v-else height="100%">
<div v-for="(item, index) in formatData" :key="index" class="content-row">
<div class="content-en" :class="{ 'only-show': !isTranslate }">
<template v-for="(node, num) in item.contentEn" :key="num">
<el-icon v-if="node.icon && isHighlight" color="#508fd4" size="14"><Search /></el-icon>
<span v-else-if="node.point" class="find-light-point"></span>
<span v-else-if="node.fill" :class="node.class" @click="handleHighLight(node)">{{ node.txt }}</span>
<span v-else :class="node.class">{{ node.txt }}</span>
</template>
</div>
<div class="content-ch" v-if="isTranslate">
<template v-for="(node, num) in item.contentCh" :key="num">
<el-icon v-if="node.icon && isHighlight" color="#508fd4" size="14"><Search /></el-icon>
<span v-else-if="node.point" class="find-light-point"></span>
<span v-else-if="node.fill" :class="node.class" @click="handleHighLight(node)">{{ node.txt }}</span>
<span v-else :class="node.class">{{ node.txt }}</span>
</template>
</div>
</div>
</el-scrollbar>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { nextTick, ref } from "vue";
import { debounce } from "lodash";
import { extractTextEntity } from "@/api/intelligent";
const emits = defineEmits(["download"]);
const isHighlight = ref(true); // 是否高亮实体
const isTranslate = ref(true); // 是否显示译文
const handleTranslate = () => {
if (findWordTxt.value?.trim()) handleUpdateWord()
}
const findWordTxt = ref("");
const findWordBox = ref(false);
const findWordNum = ref(0);
const findWordMax = ref(0);
const formatData = ref([])
const handleUpdateWord = debounce(() => {
findWordNum.value = 0;
findWordMax.value = 0;
formatData.value = originData.map(item => {
return {
contentEn: onHighlightedText(item.contentEn, keywordEn),
contentCh: isTranslate.value ? onHighlightedText(item.contentCh, keywordCh) : [],
}
})
if (findWordMax.value) {
findWordNum.value = 1;
nextTick(updateActiveHighlight)
}
}, 300);
function handleFindWord(event) {
switch (event) {
case "open":
findWordBox.value = true;
break;
case "last":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === 1 ? findWordMax.value : findWordNum.value - 1;
updateActiveHighlight();
}
break;
case "next":
if (findWordMax.value > 1) {
findWordNum.value = findWordNum.value === findWordMax.value ? 1 : findWordNum.value + 1;
updateActiveHighlight();
}
break;
case "close":
findWordBox.value = false;
findWordTxt.value = "";
handleUpdateWord();
break;
}
}
const updateActiveHighlight = () => {
if (!findWordNum.value) return
const spans = document.querySelectorAll("span.find-light-point");
spans.forEach((span, index) => {
if (index + 1 === findWordNum.value) span.scrollIntoView({});
});
}
// 政令原文信息
let originData = []
let keywordCh = []
let keywordEn = []
const setOriginalData = (data) => {
originData = []
let num = Math.max(data.content.length, data.contentEn.length)
for (let i = 0; i < num; i++) {
let obj = {
contentCh: data.content[i] || "",
contentEn: data.contentEn[i] || "",
}
originData.push(obj);
}
let allEn = ""
let allCh = ""
formatData.value = originData.map(item => {
allEn += item.contentEn + "。"
allCh += item.contentCh + "。"
return {
contentEn: [{txt: item.contentEn, class: ""}],
contentCh: [{txt: item.contentCh, class: ""}]
}
})
extractTextEntity(allEn || "").then(res => {
if (res?.result?.length) keywordEn = res.result || []
originData.forEach((item, index) => {
formatData.value[index].contentEn = onHighlightedText(item.contentEn, keywordEn);
})
})
extractTextEntity(allCh || "").then(res => {
if (res?.result?.length) keywordCh = res.result || []
if (isTranslate.value) {
originData.forEach((item, index) => {
formatData.value[index].contentCh = onHighlightedText(item.contentCh, keywordCh);
})
}
})
}
defineExpose({ setOriginalData })
// 文本分割
const onHighlightedText = (text, nounList) => {
if (text === "") return []
// 获取区间(合并相邻/重叠),并自动过滤空关键词
const getIntervals = (str, keywords) => {
const intervals = [];
for (const kw of keywords) {
if (!kw) continue; // 跳过空字符串,防止无限循环
let idx = 0;
while ((idx = str.indexOf(kw, idx)) !== -1) {
intervals.push({ start: idx, end: idx + kw.length });
idx += kw.length;
}
}
if (!intervals.length) return [];
intervals.sort((a, b) => a.start - b.start);
const merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
const prev = merged[merged.length - 1];
const curr = intervals[i];
if (curr.start <= prev.end) {
prev.end = Math.max(prev.end, curr.end);
} else {
merged.push(curr);
}
}
return merged;
};
// 处理空搜索文本
let textTrim = findWordTxt.value.trim() || "";
const searchIntervals = textTrim ? getIntervals(text, [textTrim]) : [];
const nounIntervals = getIntervals(text, nounList.map(n => n.text_span));
// 收集分割点
const points = new Set([0, text.length]);
[...searchIntervals, ...nounIntervals].forEach(({ start, end }) => {
points.add(start);
points.add(end);
});
const sortedPoints = Array.from(points).sort((a, b) => a - b);
const list = [];
for (let i = 0; i < sortedPoints.length - 1; i++) {
const start = sortedPoints[i];
const end = sortedPoints[i + 1];
if (start === end) continue;
const fragment = text.slice(start, end);
const findLight = searchIntervals.some(s => start >= s.start && end <= s.end);
const highLight = nounIntervals.some(n => start >= n.start && end <= n.end);
const classes = [];
if (findLight) classes.push("find-light");
if (highLight) classes.push("high-light");
const textItem = { txt: fragment, class: classes.join(" ") };
if (highLight) {
const nounInterval = nounIntervals.find(n => start >= n.start && end <= n.end);
if (nounInterval) {
textItem.fill = text.slice(nounInterval.start, nounInterval.end);
}
}
list.push(textItem);
// 实体区间结束后添加图标
const iconAdded = new Set();
for (const nounInterval of nounIntervals) {
const key = `${nounInterval.start}-${nounInterval.end}`;
if (end === nounInterval.end && !iconAdded.has(key)) {
list.push({ icon: true, class: "" });
iconAdded.add(key);
break;
}
}
// 搜索区间结束后添加锚点
const pointAdded = new Set();
for (const searchInterval of searchIntervals) {
const key = `${searchInterval.start}-${searchInterval.end}`;
if (end === searchInterval.end && !pointAdded.has(key)) {
findWordMax.value++;
list.push({ point: true, class: "" });
pointAdded.add(key);
break;
}
}
}
return list;
};
// 点击高亮实体
import { useGotoSearchResults } from "@/router/modules/comprehensiveSearch";
const gotoSearchResults = useGotoSearchResults();
const handleHighLight = (node) => {
gotoSearchResults(node.fill, '')
}
</script>
<style lang="scss" scoped>
.is-highlight .high-light {
color: var(--color-primary-100);
cursor: pointer;
}
.find-light {
background-color: #ffff00;
}
.layout-container {
width: 100%;
height: 100%;
background-color: #f7f8f9;
.layout-main {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
.layout-main-center {
width: 1600px;
background-color: white;
padding: 0 60px;
flex: auto;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
.report-header {
height: 80px;
display: flex;
align-items: center;
border-bottom: solid 1px rgba(234, 236, 238, 1);
margin: 0 20px 10px;
position: relative;
.find-word-box {
position: absolute;
top: -50px;
right: 0px;
width: 430px;
height: 60px;
border: 1px solid rgba(230, 231, 232, 1);
background-color: white;
border-radius: 6px;
display: flex;
align-items: center;
.find-word-input {
flex: auto;
}
.find-word-limit {
border-right: solid 1px rgba(230, 231, 232, 1);
color: #5f656c;
padding-right: 16px;
}
.find-word-icon {
padding: 10px 12px;
margin: 0 2px;
cursor: pointer;
}
}
.report-title {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 20px;
line-height: 20px;
font-weight: 700;
flex: 1;
}
.btn {
margin-left: 10px;
width: 88px;
height: 32px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 6px;
background: rgba(255, 255, 255, 1);
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
.text {
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 14px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.icon {
width: 16px;
height: 16px;
font-size: 0px;
img {
width: 100%;
height: 100%;
}
}
.icon-gap-4 {
margin-right: 4px;
}
.icon-gap-6 {
margin-right: 6px;
}
}
}
.report-main {
flex: auto;
height: 20px;
padding: 10px 0;
:deep(.el-scrollbar) {
height: 100%;
}
.no-content {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 20px;
font-weight: 400;
}
.content-row {
display: flex;
width: 100%;
padding: 0 20px;
min-height: 100px;
gap: 80px;
.content-en,
.content-ch {
width: 50%;
flex: auto;
padding-bottom: 40px;
box-sizing: border-box;
font-size: 16px;
line-height: 1.8;
color: #3b414b;
font-family: Microsoft YaHei;
text-align: justify;
white-space: pre-wrap;
}
.only-show {
padding-bottom: 10px;
}
}
}
}
}
}
.switch-label {
margin-left: 6px;
}
.switch-label-left {
margin-right: 10px;
}
:deep(.el-scrollbar__bar.is-vertical) {
right: 0px;
width: 4px;
background: transparent;
border-radius: 2px;
& > div {
background: #c5c7c9;
opacity: 1;
}
& > div:hover {
background: #505357;
}
}
</style>
......@@ -70,7 +70,9 @@ router.beforeEach((to, from, next) => {
if (to.meta.dynamicTitle) {
console.log('to', to);
const storageKey = to.meta.titleStorageKey || "curTabName";
document.title = window.sessionStorage.getItem(storageKey) || to.meta.title;
// 新开页签时 sessionStorage 不共享,优先用 query 带过来的 title/name
const queryTitle = (to.query && (to.query.title || to.query.name)) ? String(to.query.title || to.query.name) : "";
document.title = queryTitle || window.sessionStorage.getItem(storageKey) || to.meta.title;
} else {
document.title = to.meta.title
......
......@@ -88,7 +88,8 @@ const decreeRoutes = [
component: Institution,
meta: {
title: "行政机构主页",
dynamicTitle: true
dynamicTitle: true,
titleStorageKey: "institutionTabName"
}
},
{
......
// Bill layout & visual tokens
// 统一用于 bill 板块布局改造,后续可并入全局设计变量体系
// spacing
$bill-space-8: 8px;
$bill-space-12: 12px;
$bill-space-16: 16px;
$bill-space-24: 24px;
$bill-space-30: 30px;
// layout
$bill-column-gap: 16px;
$bill-left-column-width: 1064px;
$bill-right-column-width: 520px;
// typography
$bill-title-font-size: 16px;
$bill-title-font-weight: 600;
$bill-body-font-size: 14px;
$bill-body-font-weight: 400;
// surface
$bill-panel-radius: 8px;
$bill-panel-border-color: #e8ecf3;
$bill-panel-shadow: 0 2px 8px rgba(15, 35, 95, 0.06);
$bill-panel-bg: #ffffff;
// text & color
$bill-text-primary: #1f2a44;
$bill-text-secondary: #5b6b88;
$bill-text-muted: #8b97ad;
$bill-color-accent: #2f66ff;
$bill-color-success: #19be6b;
$bill-color-warning: #ff9f43;
$bill-color-danger: #f56c6c;
# Bill 板块布局统一改造计划
> 目标:统一 `@src/views/bill` 下页面布局与视觉风格,解决“底部 30px 留白不稳定”问题,并沉淀可复用布局组件,便于后续新会话直接接续执行。
## 1. 背景与问题
当前 `bill` 板块存在以下共性问题:
1. 页面布局实现不统一(外层滚动、内层滚动、固定高度混用)。
2. 多数页面为“左右两列 + 多卡片”结构,但容器命名、间距、宽高策略不一致。
3. `30px` 底部留白在不同页面生效机制不同,容易出现:
- 某页不生效
- 增加占位后挤压布局
- Header(Tab/操作按钮)受影响
4. 样式硬编码较多(固定宽高、局部 margin/padding),复用性低。
## 2. 总体改造策略
采用“**分层统一 + 渐进迁移**”策略,避免一次性大改风险:
- **第一层:板块承载层统一**
- 统一滚动、内容宽度、底部安全留白机制。
- **第二层:页面布局骨架统一**
- 抽象“页面壳 + 双列布局 + 卡片容器”组件。
- **第三层:业务页迁移**
- 先迁移典型页,再批量替换。
## 3. 分阶段计划
### 阶段 A:基线确认与止血(进行中)
- 修复当前试错引入的结构问题(例如模板闭合错误、布局挤压)。
- 保证关键页面可用:
- `background`
- `processAnalysis`
- `introdoction`
### 阶段 B:新增通用布局组件(进行中)
#### 完成备注(2026-04-02)
- [x] 第一步已完成并通过验收:完成布局组件目录与基础文件脚手架
- `src/views/bill/components/layout/BillPageShell.vue`
- `src/views/bill/components/layout/BillTwoColumn.vue`
- `src/views/bill/components/layout/BillPanel.vue`
- `src/views/bill/components/layout/index.js`
- [x] 第二步已完成并通过验收:实现 `BillPageShell` 滚动容器与底部 `30px` 安全留白机制
- [x] 第三步已完成并通过验收:实现 `BillTwoColumn` 统一双列布局(1064/520 + gap 16 + 响应式堆叠)
- [x] 第四步已完成并通过验收:实现 `BillPanel` 统一卡片结构与基础样式(header/body/footer + 可配置边框/圆角/阴影)
- [x] 第五步已完成并通过验收:`bill-tokens.scss` 接入 `BillPageShell/BillTwoColumn/BillPanel` 并替换关键样式硬编码
- [x] 第六步已完成并通过验收:试点页 `background/index.vue` 已迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第六步修正项(验收反馈已落实):
- 修正 1:移除 `.right-panel``margin-left/margin-right`,避免右列偏移与原样式不一致
- 修正 2:`BillPageShell` 不再使用内部滚动容器;页面滚动回到外层 `layout-main`,确保 Header 与内容一起滚动
- [x] 第七步已完成并通过验收:`introdoction/index.vue` 迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第七步修正项(验收反馈已落实):
- 修正 1:移除 `.introduction-wrap-right``margin-left/margin-right`,消除右列偏移
- 修正 2:在 `.introduction-wrap-content` 增加 `margin-bottom: 30px`,确保外层滚动时可见底部留白
- [x] 第八步已完成并通过验收:`template/index.vue` 迁移到统一布局骨架(`BillPageShell + BillTwoColumn`
- [x] 第八步注意点(已沉淀):
- 删除 `page-bottom-gap` 占位节点,避免与统一留白策略重复
- 在双列容器 `.content-row` 增加 `margin-bottom: 30px`,适配“外层 `layout-main` 滚动”模式
- 迁移后需检查未使用变量并清理,避免 lint 噪音影响后续页面迁移
- [x] 第九步已完成并通过验收:`deepDig/processOverview/index.vue` 迁移到统一页面壳(`BillPageShell`
- [x] 第九步注意点(已沉淀):
- 保留页面内部横向滚动容器(`.main`),仅替换页面根壳,避免时间轴布局回归风险
- 删除 `page-bottom-gap`,改用页面容器底部留白(`margin-bottom: 30px`)适配外层滚动
- [x] 第十步已完成并通过验收:`deepDig/processAnalysis/index.vue` 迁移到统一页面壳(`BillPageShell`
- [x] 第十步注意点(已沉淀):
- 保持原双列业务结构与内部滚动策略不变,仅替换页面根壳
- 页面底部留白统一为容器 `margin-bottom: 30px`,与外层 `layout-main` 滚动机制一致
新增组件建议放在:`src/views/bill/components/layout/`
#### 计划新增组件索引
1. `src/views/bill/components/layout/BillPageShell.vue`
- 作用:统一页面根容器、内容区、底部留白(30px)机制。
2. `src/views/bill/components/layout/BillTwoColumn.vue`
- 作用:统一左右列布局(默认 1064 / 520)、列间距、响应式策略。
3. `src/views/bill/components/layout/BillPanel.vue`
- 作用:统一卡片容器(header/body/footer)结构、间距、基础样式。
4. `src/views/bill/components/layout/index.js`
- 作用:导出布局组件,便于页面统一引入。
> 可选(后续):
> - `BillPanelGroup.vue`(同列多卡片统一间距)
> - `BillPageSection.vue`(统一页面内 section 标题与辅助动作区)
### 阶段 C:样式 Token 统一(计划)
新增:`src/styles/bill-tokens.scss`(或接入现有变量体系)
- 间距:`8/12/16/24/30`
- 列间距:`16`
- 标题字号/字重
- 卡片边框、阴影、圆角
- 统一颜色 token(主文字、次文字、强调色)
### 阶段 D:页面迁移(计划)
按“先典型、后批量”执行:
1. 试点页(先迁移)
- `background/index.vue`(典型双列 + 卡片 + 多交互)
2. 法案概况组
- `introdoction/index.vue`
- `template/index.vue`
- `background/index.vue`
3. 深度挖掘组
- `deepDig/processOverview/index.vue`
- `deepDig/processAnalysis/index.vue`
- `deepDig/poliContribution/index.vue`
4. 影响分析组
- `influence/industry/index.vue`
- `influence/scientificResearch/index.vue`
- `influence/index.vue`(承载层)
### 阶段 E:收口与回归(计划)
- 清理临时样式补丁(历史试错 `gap/margin-bottom` 等)。
- 回归检查:
1. 所有页面底部留白可见
2. 无横向滚动条
3. Header 不丢失
4. 双列不挤压
## 4. 现有页面索引(改造范围)
### 4.1 板块承载与导航层
- `src/views/bill/billLayout/index.vue`
- `src/views/bill/billLayout/components/BillHeader.vue`
- `src/views/bill/index.vue`
- `src/views/bill/deepDig/index.vue`
- `src/views/bill/influence/index.vue`
### 4.2 法案概况(Bill)
- `src/views/bill/introdoction/index.vue`
- `src/views/bill/background/index.vue`
- `src/views/bill/template/index.vue`
- `src/views/bill/billOriginalText/index.vue`
### 4.3 深度挖掘(DeepDig)
- `src/views/bill/deepDig/processOverview/index.vue`
- `src/views/bill/deepDig/processAnalysis/index.vue`
- `src/views/bill/deepDig/poliContribution/index.vue`
### 4.4 影响分析(Influence)
- `src/views/bill/influence/industry/index.vue`
- `src/views/bill/influence/scientificResearch/index.vue`
- `src/views/bill/influence/ProgressForecast/index.vue`
### 4.5 其他相关页面
- `src/views/bill/relevantCircumstance/index.vue`
- `src/views/bill/versionCompare/index.vue`
- `src/views/bill/billHome/index.vue`
## 5. 迁移原则(执行时必须遵守)
1. 不改变业务逻辑(API、状态、路由行为)
2. 先替换布局骨架,再替换样式细节
3. 每次只迁移少量页面,保证可回滚
4. 对“固定高度 + 内部滚动”页面优先保守改造
5. 若页面是横向 flex 主体,禁止直接插入会改变横向布局的占位节点
## 6. 验收标准
1. `@bill` 目标页面滚动到底统一可见 `30px` 留白
2. 页面核心布局(左右列、卡片)不变形
3. `BillHeader` 的 Tab 与功能按钮稳定显示
4. 不新增横向滚动条
5. 关键路由刷新后视觉一致
## 7. 新会话接续建议
新会话可直接按以下顺序继续:
1. 先读取本文件:
- `src/views/bill/BILL_LAYOUT_UNIFICATION_PLAN.md`
2. 先完成组件脚手架:
- `BillPageShell.vue`
- `BillTwoColumn.vue`
- `BillPanel.vue`
3. 首个迁移页:
- `src/views/bill/background/index.vue`
4. 迁移完成后逐页扩展到 `introdoction/template/processOverview/processAnalysis`
---
最后更新:2026-04-02
<template>
<div class="background-wrap">
<div class="background-wrap-left">
<AnalysisBox class="left-box left-box--background" title="立法背景" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box1Btn1Type" plain @click="handleClickBox1Btn(1)">涉华背景</el-button>
<el-button :type="box1Btn2Type" plain @click="handleClickBox1Btn(2)">全部背景</el-button>
</div>
</template>
<div class="box1-main" v-loading="backgroundLoading">
<div class="box1-main-center">
<div class="box1-main-item" v-for="item in backgroundDisplayList" :key="item.id">
<div class="id">{{ item.displayIndex }}</div>
<div class="title">{{ item.backgroundTitle }}</div>
<div class="share">
<img src="./assets/icons/open.png" alt="打开" />
<BillPageShell class="background-page">
<BillTwoColumn class="background-content" :stack-on-narrow="false">
<template #left>
<div class="background-wrap-left">
<AnalysisBox class="left-box left-box--background" title="立法背景" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box1Btn1Type" plain @click="handleClickBox1Btn(1)">涉华背景</el-button>
<el-button :type="box1Btn2Type" plain @click="handleClickBox1Btn(2)">全部背景</el-button>
</div>
</template>
<div class="box1-main" v-loading="backgroundLoading">
<div class="box1-main-center">
<div class="box1-main-item" v-for="item in backgroundDisplayList" :key="item.id">
<div class="id">{{ item.displayIndex }}</div>
<div class="title">{{ item.backgroundTitle }}</div>
<div class="share">
<img src="./assets/icons/open.png" alt="打开" />
</div>
</div>
</div>
<div class="box1-main-footer">
<div class="info">
{{ totalText }}
</div>
<el-pagination
background
layout="prev, pager, next"
:total="total"
v-model:current-page="currentPage"
@current-change="handleGetBillBackground"
/>
</div>
</div>
</div>
<div class="box1-main-footer">
<div class="info">
{{ totalText }}
</div>
<el-pagination background layout="prev, pager, next" :total="total" v-model:current-page="currentPage"
@current-change="handleGetBillBackground" />
</div>
</div>
</AnalysisBox>
</AnalysisBox>
<AnalysisBox class="left-box left-box--event" title="相关事件" :showAllBtn="false" :devTip="true">
<div class="box2-main">
<div class="box2-main-item" v-for="item in eventDisplayList" :key="item.id" @click="handleClickEvent(item)">
<div class="left">
<img :src="item.imgSrc" @error="handleNewsImgError" alt="" />
</div>
<div class="center">
<CommonPrompt :content="item.sjbt">
<div class="title">{{ item.sjbt }}</div>
</CommonPrompt>
<CommonPrompt :content="item.sjnr">
<div class="content">{{ item.sjnr }}</div>
</CommonPrompt>
<AnalysisBox class="left-box left-box--event" title="相关事件" :showAllBtn="false" :devTip="true">
<div class="box2-main">
<div class="box2-main-item" v-for="item in eventDisplayList" :key="item.id" @click="handleClickEvent(item)">
<div class="left">
<img :src="item.imgSrc" @error="handleNewsImgError" alt="" />
</div>
<div class="center">
<CommonPrompt :content="item.sjbt">
<div class="title">{{ item.sjbt }}</div>
</CommonPrompt>
<CommonPrompt :content="item.sjnr">
<div class="content">{{ item.sjnr }}</div>
</CommonPrompt>
</div>
<div class="right">{{ item.sjsj }}</div>
</div>
</div>
<div class="right">{{ item.sjsj }}</div>
</div>
</div>
</AnalysisBox>
</div>
<AnalysisBox class="right-panel" title="议员相关性分析" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box2Btn1Type" plain @click="handleClickBox2Btn(1)">赞成议员</el-button>
<el-button :type="box2Btn2Type" plain @click="handleClickBox2Btn(2)">反对议员</el-button>
</AnalysisBox>
</div>
</template>
<div class="background-wrap-right-main">
<div class="right-box1">
<div class="right-box1-main-top">
<el-icon class="nav-icon" size="20" :color="prevIconColor" @click="handlePrev">
<CaretLeft />
</el-icon>
<div class="user-list-container">
<div class="user-list-wrapper" :style="userListWrapperStyle">
<div class="user-box" v-for="item in personList" :key="item.id" @click="handleClickUser(item)">
<div class="img-box">
<img :src="item.image" alt="" />
<div class="icon1">
<img :src="item.icon" alt="" />
</div>
<div class="icon2">
<img :src="item.icon1" alt="" />
<template #right>
<AnalysisBox class="right-panel" title="议员相关性分析" :showAllBtn="false" :devTip="true">
<template #header-btn>
<div class="header-btn-box">
<el-button :type="box2Btn1Type" plain @click="handleClickBox2Btn(1)">赞成议员</el-button>
<el-button :type="box2Btn2Type" plain @click="handleClickBox2Btn(2)">反对议员</el-button>
</div>
</template>
<div class="background-wrap-right-main">
<div class="right-box1">
<div class="right-box1-main-top">
<el-icon class="nav-icon" size="20" :color="prevIconColor" @click="handlePrev">
<CaretLeft />
</el-icon>
<div class="user-list-container">
<div class="user-list-wrapper" :style="userListWrapperStyle">
<div class="user-box" v-for="item in personList" :key="item.id" @click="handleClickUser(item)">
<div class="img-box">
<img :src="item.image" alt="" />
<div class="icon1">
<img :src="item.icon" alt="" />
</div>
<div class="icon2">
<img :src="item.icon1" alt="" />
</div>
</div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div>
</div>
<CommonPrompt :content="item.name">
<div class="name">{{ item.name }}</div>
</CommonPrompt>
</div>
<el-icon class="nav-icon" size="20" :color="nextIconColor" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle" @wordClick="handleWordClick" />
</div>
</div>
<el-icon class="nav-icon" size="20" :color="nextIconColor" @click="handleNext">
<CaretRight />
</el-icon>
</div>
<div class="right-box1-main-bottom">
<WordCloudMap :data="wordCloudData" :selectedName="selectedIndustryName" shape="circle"
@wordClick="handleWordClick" />
</div>
</div>
<div class="right-box2">
<div class="right-box2-header">
<div class="title">
<span class="title-active">"{{ selectedIndustryName }}"</span>涉及议员动态 >
</div>
</div>
<div class="right-box2-center">
<div class="user-box" v-for="item in aboutUserList" :key="item.id" @click="handleClickUser(item)">
<div class="user-left">
<div class="img-box">
<img :src="item.img" alt="" />
<div class="right-box2">
<div class="right-box2-header">
<div class="title">
<span class="title-active">"{{ selectedIndustryName }}"</span>涉及议员动态 >
</div>
</div>
<div class="user-right">
<div class="name">{{ item.name }}</div>
<CommonPrompt :content="item.content">
<div class="content">{{ item.content }}</div>
</CommonPrompt>
<div class="right-box2-center">
<div class="user-box" v-for="item in aboutUserList" :key="item.id" @click="handleClickUser(item)">
<div class="user-left">
<div class="img-box">
<img :src="item.img" alt="" />
</div>
</div>
<div class="user-right">
<div class="name">{{ item.name }}</div>
<CommonPrompt :content="item.content">
<div class="content">{{ item.content }}</div>
</CommonPrompt>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
</div>
</AnalysisBox>
</template>
</BillTwoColumn>
</BillPageShell>
</template>
<script setup>
......@@ -119,6 +130,7 @@ import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
import WordCloudMap from "./WordCloudMap.vue";
import CommonPrompt from "../commonPrompt/index.vue";
import { BillPageShell, BillTwoColumn } from "../components/layout";
import userIcon from "./assets/icons/user-icon.png";
import userIcon1 from "./assets/icons/user-icon1.png";
import userIcon2 from "./assets/icons/user-icon2.png";
......@@ -400,16 +412,31 @@ onMounted(() => {
}
}
.background-wrap {
.background-page {
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
}
.background-content {
width: 100%;
height: auto;
box-sizing: border-box;
align-items: flex-start;
:deep(.bill-two-column__left) {
width: 1064px;
}
:deep(.bill-two-column__right) {
width: 520px;
margin-right: 18px;
}
.background-wrap-left {
width: 1064px;
margin-top: 16px;
margin-bottom: 30px;
.left-box {
width: 1064px;
......@@ -600,9 +627,8 @@ onMounted(() => {
}
}
.right-panel {
margin-right: 18px;
margin-left: 16px;
margin-top: 16px;
width: 520px;
max-height: 824px;
......
......@@ -61,7 +61,7 @@
</div>
<div class="right-box-bottom" v-if="showActions">
<div class="btn2" @click="emit('open-analysis', 'forsee')">
<div class="btn2" v-if="showForsee" @click="emit('open-analysis', 'forsee')">
<div class="icon">
<img :src="btnIconForsee" alt="" />
</div>
......@@ -108,6 +108,10 @@ const props = defineProps({
showActions: {
type: Boolean,
default: true
},
showForsee: {
type: Boolean,
default: true
}
});
......@@ -154,9 +158,7 @@ const emit = defineEmits(["tab-click", "open-analysis"]);
}
.header-main {
position: sticky;
top: 0;
z-index: 1000;
position: relative;
width: 100%;
background-color: #fff;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.05);
......
......@@ -4,7 +4,8 @@
<div class="layout-main">
<BillHeader :billInfo="billInfoGlobal" :defaultLogo="USALogo" :tabs="mainHeaderBtnList"
:activeTitle="activeTitle" :showTabs="showHeaderTabs" :showActions="showHeaderActions"
@tab-click="handleClickMainHeaderBtn" @open-analysis="handleAnalysisClick" />
:showForsee="showForseeAction" @tab-click="handleClickMainHeaderBtn"
@open-analysis="handleAnalysisClick" />
<div class="layout-main-center">
<router-view />
......@@ -14,10 +15,10 @@
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import { ref, onMounted, watch, computed } from "vue";
import router from "@/router";
import { useRoute } from "vue-router";
import { getBillInfoGlobal } from "@/api/bill";
import { getBillInfoGlobal, getBillInfo } from "@/api/bill";
import BillHeader from "./components/BillHeader.vue";
const route = useRoute();
......@@ -77,6 +78,25 @@ const activeTitle = ref("法案概况");
const showHeaderTabs = ref(true);
const showHeaderActions = ref(true);
const billBasicInfo = ref({});
const getBillBasicInfoFn = async () => {
const billId = route.query.billId;
if (!billId) return;
try {
const res = await getBillInfo({ id: billId });
billBasicInfo.value = res?.data || {};
} catch (error) {
console.error(error);
billBasicInfo.value = {};
}
};
const showForseeAction = computed(() => {
const stageList = Array.isArray(billBasicInfo.value?.stageList) ? billBasicInfo.value.stageList : [];
const latestStage = stageList.length ? String(stageList[stageList.length - 1] || "").trim() : "";
return !latestStage.includes("完成立法");
});
const getActiveTitleByRoutePath = path => {
if (path.startsWith("/billLayout/deepDig")) return "深度挖掘";
if (path.startsWith("/billLayout/influence")) return "影响分析";
......@@ -132,6 +152,7 @@ const handleAnalysisClick = analysisType => {
onMounted(() => {
getBillInfoGlobalFn();
getBillBasicInfoFn();
// 以当前路由为准,避免 sessionStorage 造成高亮错乱
syncHeaderStateFromRoute();
// 兜底:如果未来出现未知路由且有缓存,再用缓存
......@@ -143,6 +164,14 @@ watch(
() => route.path,
() => {
syncHeaderStateFromRoute();
}
);
watch(
() => route.query.billId,
() => {
getBillInfoGlobalFn();
getBillBasicInfoFn();
},
{ immediate: true }
);
......@@ -160,12 +189,12 @@ watch(
width: 100%;
height: 100vh;
overflow-y: auto;
overflow-x: hidden;
.layout-main-center {
// height: calc(100% - 137px);
width: 1600px;
max-width: 100%;
margin: 0 auto;
padding-bottom: 20px;
}
}
}
......
<template>
<div class="bill-page-shell" :class="shellClass" :style="shellStyle">
<div class="bill-page-shell__content" :class="contentClass">
<slot />
</div>
</div>
</template>
<script>
const BILL_DEFAULT_BOTTOM_SAFE_SPACE = 30
export default {
name: 'BillPageShell',
props: {
bottomSafeSpace: {
type: Number,
default: BILL_DEFAULT_BOTTOM_SAFE_SPACE
},
shellClass: {
type: [String, Array, Object],
default: ''
},
contentClass: {
type: [String, Array, Object],
default: ''
}
},
computed: {
shellStyle() {
return {
paddingBottom: `${this.bottomSafeSpace}px`
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-page-shell {
width: 100%;
min-height: 0;
box-sizing: border-box;
}
.bill-page-shell__content {
width: 100%;
color: $bill-text-primary;
}
</style>
<template>
<section class="bill-panel" :class="panelClass" :style="panelStyle">
<header
v-if="$slots.header"
class="bill-panel__header"
:class="headerClass"
:style="sectionStyle"
>
<slot name="header" />
</header>
<div class="bill-panel__body" :class="bodyClass" :style="sectionStyle">
<slot />
</div>
<footer
v-if="$slots.footer"
class="bill-panel__footer"
:class="footerClass"
:style="sectionStyle"
>
<slot name="footer" />
</footer>
</section>
</template>
<script>
const BILL_DEFAULT_PANEL_PADDING = 16
const BILL_DEFAULT_PANEL_RADIUS = 8
export default {
name: 'BillPanel',
props: {
panelClass: {
type: [String, Array, Object],
default: ''
},
headerClass: {
type: [String, Array, Object],
default: ''
},
bodyClass: {
type: [String, Array, Object],
default: ''
},
footerClass: {
type: [String, Array, Object],
default: ''
},
padding: {
type: Number,
default: BILL_DEFAULT_PANEL_PADDING
},
borderRadius: {
type: Number,
default: BILL_DEFAULT_PANEL_RADIUS
},
bordered: {
type: Boolean,
default: true
},
shadow: {
type: Boolean,
default: false
},
background: {
type: String,
default: '#fff'
}
},
computed: {
panelStyle() {
return {
background: this.background,
borderRadius: `${this.borderRadius}px`,
border: this.bordered ? '1px solid #e8ecf3' : 'none',
boxShadow: this.shadow ? '0 2px 8px rgba(15, 35, 95, 0.06)' : 'none'
}
},
sectionStyle() {
return {
padding: `${this.padding}px`
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-panel {
width: 100%;
box-sizing: border-box;
color: $bill-text-primary;
}
.bill-panel__header,
.bill-panel__body,
.bill-panel__footer {
width: 100%;
box-sizing: border-box;
}
.bill-panel__header {
padding-bottom: $bill-space-12;
}
.bill-panel__footer {
padding-top: $bill-space-12;
}
.bill-panel__header:empty,
.bill-panel__footer:empty {
display: none;
}
</style>
<template>
<div class="bill-two-column" :class="wrapperClass" :style="wrapperStyle">
<div class="bill-two-column__left" :style="leftStyle">
<slot name="left" />
</div>
<div class="bill-two-column__right" :style="rightStyle">
<slot name="right" />
</div>
</div>
</template>
<script>
const BILL_DEFAULT_LEFT_WIDTH = 1064
const BILL_DEFAULT_RIGHT_WIDTH = 520
const BILL_DEFAULT_COLUMN_GAP = 16
const BILL_DEFAULT_MIN_STACK_WIDTH = 1660
export default {
name: 'BillTwoColumn',
props: {
leftWidth: {
type: Number,
default: BILL_DEFAULT_LEFT_WIDTH
},
rightWidth: {
type: Number,
default: BILL_DEFAULT_RIGHT_WIDTH
},
gap: {
type: Number,
default: BILL_DEFAULT_COLUMN_GAP
},
minStackWidth: {
type: Number,
default: BILL_DEFAULT_MIN_STACK_WIDTH
},
stackOnNarrow: {
type: Boolean,
default: true
},
wrapperClass: {
type: [String, Array, Object],
default: ''
}
},
computed: {
shouldStack() {
if (!this.stackOnNarrow || typeof window === 'undefined') {
return false
}
return window.innerWidth < this.minStackWidth
},
wrapperStyle() {
if (this.shouldStack) {
return {
display: 'flex',
flexDirection: 'column',
gap: `${this.gap}px`
}
}
return {
display: 'grid',
gridTemplateColumns: `${this.leftWidth}px ${this.rightWidth}px`,
columnGap: `${this.gap}px`,
alignItems: 'start'
}
},
leftStyle() {
if (this.shouldStack) {
return {
width: '100%'
}
}
return {
minWidth: '0'
}
},
rightStyle() {
if (this.shouldStack) {
return {
width: '100%'
}
}
return {
minWidth: '0'
}
}
}
}
</script>
<style scoped lang="scss">
@import '@/styles/bill-tokens.scss';
.bill-two-column {
width: 100%;
min-width: 0;
color: $bill-text-primary;
}
.bill-two-column__left,
.bill-two-column__right {
min-width: 0;
}
</style>
import BillPageShell from './BillPageShell.vue'
import BillTwoColumn from './BillTwoColumn.vue'
import BillPanel from './BillPanel.vue'
export {
BillPageShell,
BillTwoColumn,
BillPanel
}
<template>
<div class="wrap">
<BillPageShell class="wrap">
<div class="left">
<div class="box1">
<!-- <div class="box-header">
......@@ -31,7 +31,7 @@
</div>
</div>
</div> -->
<AnalysisBox title="典型阶段耗时">
<AnalysisBox title="典型阶段耗时分析">
<div class="analysis-ai-wrapper analysis-ai-wrapper--box1">
<div class="box1-main" :class="{ 'box1-main--full': !timeFooterText }">
<div class="box1-main-center" id="chart1"></div>
......@@ -48,7 +48,7 @@
</div>
</div>
<div v-if="!aiPaneVisible.box1" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" />
<TipTab class="analysis-ai-tip" :text="'与历史同类法案的典型阶段耗时对比分析,数据来源:美国国会官网'"/>
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box1')" />
</div>
<div v-if="aiPaneVisible.box1" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box1')">
......@@ -105,7 +105,7 @@
</div>
</div>
<div v-if="!aiPaneVisible.box2" class="analysis-ai-tip-row">
<TipTab class="analysis-ai-tip" />
<TipTab class="analysis-ai-tip" :text="'与历史同类法案的修正案次数对比分析,数据来源:美国国会官网'"/>
<AiButton class="analysis-ai-tip-action" @mouseenter="handleShowAiPane('box2')" />
</div>
<div v-if="aiPaneVisible.box2" class="analysis-ai-pane" @mouseleave="handleHideAiPane('box2')">
......@@ -716,7 +716,7 @@
</AnalysisBox>
</div>
</div>
</div>
</BillPageShell>
</template>
<script setup>
......@@ -728,6 +728,7 @@ import { getChartAnalysis } from "@/api/aiAnalysis/index";
import TipTab from "@/components/base/TipTab/index.vue";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import { BillPageShell } from "../../components/layout";
import icon1 from "./assets/images/icon1.png";
import icon2 from "./assets/images/icon2.png";
......@@ -1160,7 +1161,7 @@ onMounted(async () => {
min: "最小次数",
current: "该法案修正案数量"
};
let chart2 = getBoxPlotChcart(chartData2.value, "", countLabels);
let chart2 = getBoxPlotChcart(chartData2.value, "", countLabels);
setChart(chart2, "chart2");
});
</script>
......@@ -1168,6 +1169,8 @@ onMounted(async () => {
<style lang="scss" scoped>
.wrap {
display: flex;
margin-bottom: 30px;
.box-header {
height: 56px;
......
<template>
<div class="process-overview-wrap">
<BillPageShell class="process-overview-wrap">
<AnalysisBox title="流程概要" :showAllBtn="false">
<div class="main">
<div class="left" :style="{ width: boardWidth + 'px' }">
......@@ -194,7 +194,7 @@
:position="dialogPos"
@close="handleClickDetail(false)"
/>
</div>
</BillPageShell>
</template>
<script setup>
......@@ -202,6 +202,7 @@ import { ref, onMounted, computed, nextTick } from "vue";
import { getBillDyqkSummary } from "@/api/bill";
import CommonPrompt from "../../commonPrompt/index.vue";
import ProcessOverviewDetailDialog from "../../ProcessOverviewDetailDialog.vue";
import { BillPageShell } from "../../components/layout";
const actionList = ref([]);
const isShowDetailDialog = ref(false);
......@@ -499,6 +500,7 @@ const updateRightTop = () => {
width: 1600px;
height: 848px;
margin-top: 16px;
margin-bottom: 30px;
position: relative;
.main {
......@@ -799,20 +801,13 @@ const updateRightTop = () => {
.text {
width: 240px;
max-width: 100%;
color: rgb(59, 65, 75);
font-size: 16px;
line-height: 24px;
display: -webkit-box;
line-clamp: 2;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
height: 48px;
}
:deep(.text-ellipsis) {
white-space: normal !important;
}
}
}
......
<template>
<div class="introduction-wrap">
<BillPageShell class="introduction-wrap">
<WarningPane v-if="riskSignal" class="risk-signal-pane-top" :warnningLevel="riskSignal.riskLevel"
:warnningContent="riskSignal.riskContent" />
<div class="introduction-wrap-content">
<div class="introduction-wrap-left">
<BillTwoColumn class="introduction-wrap-content" :stack-on-narrow="false">
<template #left>
<div class="introduction-wrap-left">
<div class="introduction-wrap-left-box1">
<AnalysisBox title="基本信息" :showAllBtn="false">
......@@ -69,10 +70,12 @@
</div>
</AnalysisBox>
</div>
</div>
<div class="introduction-wrap-right">
<AnalysisBox title="提出人" :showAllBtn="false">
<div class="introduction-wrap-right-main">
</div>
</template>
<template #right>
<div class="introduction-wrap-right">
<AnalysisBox title="提出人" :showAllBtn="false">
<div class="introduction-wrap-right-main">
<div class="right-main-box1">
<div class="name-box">
<div class="person-box">
......@@ -139,10 +142,11 @@
</div>
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</AnalysisBox>
</div>
</template>
</BillTwoColumn>
</BillPageShell>
</template>
<script setup>
......@@ -151,6 +155,7 @@ import { useRoute, useRouter } from "vue-router";
import WordCloudMap from "./WordCloudMap.vue";
import STimeline from "./STimeline.vue";
import WarningPane from "@/components/base/WarningPane/index.vue";
import { BillPageShell, BillTwoColumn } from "../components/layout";
import { getBillInfo, getBillPerson, getBillEvent, getBillDyqk } from "@/api/bill";
import { getPersonSummaryInfo } from "@/api/common/index";
import defaultAvatar from "../assets/images/default-icon1.png";
......@@ -338,6 +343,14 @@ onMounted(() => {
height: auto;
display: flex;
flex-direction: column;
box-sizing: border-box;
.page-bottom-gap {
height: 30px;
width: 100%;
flex-shrink: 0;
pointer-events: none;
}
.progress-header-btns {
display: flex;
......@@ -432,6 +445,7 @@ onMounted(() => {
.introduction-wrap-content {
display: flex;
margin-bottom: 30px;
}
.introduction-wrap-left {
......@@ -497,7 +511,6 @@ onMounted(() => {
flex-wrap: wrap;
align-items: center;
width: 700px;
min-height: 40px;
gap: 8px;
.right1-item {
......@@ -769,8 +782,6 @@ onMounted(() => {
.introduction-wrap-right {
margin-top: 16px;
margin-left: 16px;
margin-right: 18px;
width: 520px;
height: 845px;
......
<template>
<div class="temp-wrap">
<BillPageShell class="temp-wrap">
<div class="tools-row">
<div class="tools-row-left">
<el-select v-model="curBill" placeholder="请选择版本" @change="handleChangeBill"
......@@ -39,8 +39,9 @@
</div>
</div>
</div>
<div class="content-row">
<div class="side">
<BillTwoColumn class="content-row" :left-width="520" :right-width="1064" :stack-on-narrow="false">
<template #left>
<div class="side">
<div class="side-box side-box-domain">
<AnalysisBox title="涉及领域" width="520px" height="415px" v-loading="domainLoading">
<div class="chart-ai-wrap">
......@@ -93,8 +94,10 @@
</div>
</AnalysisBox>
</div>
</div>
<div class="terms">
</div>
</template>
<template #right>
<div class="terms">
<div class="terms-switch">
<span class="terms-switch-label">高亮实体</span>
<el-switch
......@@ -103,6 +106,10 @@
active-text="开"
inactive-text="关"
/>
<span v-if="termsEntityLoading" class="terms-entity-loading">
<el-icon class="is-loading"><Loading /></el-icon>
实体识别中(剩余 {{ termsEntityPendingCount }} 条)
</span>
<span class="terms-switch-divider"></span>
<span class="terms-switch-label">显示原文</span>
<el-switch
......@@ -112,7 +119,7 @@
inactive-text="关"
/>
</div>
<AnalysisBox title="主要条款" :showAllBtn="false" v-loading="termsLoading">
<AnalysisBox title="主要条款" :showAllBtn="false" height="auto" v-loading="termsLoading">
<div class="left-main">
<div class="left-main-item" v-for="(term, index) in displayTermsList" :key="getTermKey(term, index)">
<div class="term-body">
......@@ -120,11 +127,23 @@
<div class="term-main">
<div class="term-row term-row-cn">
<div class="term-no-cn">{{ term.tkxh }}条.</div>
<div class="term-content-cn" v-html="getTermContentHtml(term, 'cn')"></div>
<div class="term-content-cn">
<IntelligentEntityText
:text="term?.fynr || ''"
:entities="termsHighlight ? getTermEntities(term, 'cn') : []"
@on-entity-click="e => gotoSearchResults(e.text_span, '')"
/>
</div>
</div>
<div class="term-row term-row-en" v-if="termsShowOriginal">
<div class="term-no-en">Sec.{{ term.tkxh }}</div>
<div class="term-content-en" v-html="getTermContentHtml(term, 'en')"></div>
<div class="term-content-en">
<IntelligentEntityText
:text="term?.ywnr || ''"
:entities="termsHighlight ? getTermEntities(term, 'en') : []"
@on-entity-click="e => gotoSearchResults(e.text_span, '')"
/>
</div>
</div>
</div>
</div>
......@@ -144,16 +163,17 @@
</div>
</div>
</AnalysisBox>
</div>
</div>
</div>
</div>
</template>
</BillTwoColumn>
</BillPageShell>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed, watch } from "vue";
import { useRoute, onBeforeRouteLeave } from "vue-router";
import * as echarts from "echarts";
import { Search } from "@element-plus/icons-vue";
import { Search, Loading } from "@element-plus/icons-vue";
import getPieChart from "./utils/piechart";
import { getBillContentId, getBillContentTk, getBillContentXzfs, getBillHyly } from "@/api/bill";
import { getChartAnalysis } from "@/api/aiAnalysis/index";
......@@ -162,9 +182,12 @@ import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import { MUTICHARTCOLORS } from "@/common/constant";
import { extractTextEntity } from "@/api/intelligent/index";
import { useGotoSearchResults } from "@/router/modules/comprehensiveSearch";
import IntelligentEntityText from "@/components/base/texts/IntelligentEntityText.vue";
import { BillPageShell, BillTwoColumn } from "../components/layout";
const route = useRoute();
const gotoSearchResults = useGotoSearchResults();
const pageAbortController = new AbortController();
const isRequestCanceled = error => {
......@@ -186,6 +209,8 @@ const stopCurrentPageRequests = () => {
hylyRequestToken.value += 1;
entityRequestToken.value += 1;
termsLoading.value = false;
termsEntityLoading.value = false;
termsEntityPendingCount.value = 0;
limitLoading.value = false;
domainLoading.value = false;
aiPaneLoading.value = { domain: false, limit: false };
......@@ -214,16 +239,8 @@ const termsShowOriginal = ref(true);
const entityRequestToken = ref(0);
const termEntityCache = ref(new Map());
const escapeHtml = value => {
const str = String(value ?? "");
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
};
const termsEntityLoading = ref(false);
const termsEntityPendingCount = ref(0);
const normalizeEntities = entities => {
const list = Array.isArray(entities) ? entities : [];
......@@ -237,69 +254,23 @@ const normalizeEntities = entities => {
.filter(item => item.text_span);
};
const getEntityRanges = (text, entities) => {
const ranges = [];
const rawText = String(text ?? "");
if (!rawText) return ranges;
const list = normalizeEntities(entities).sort((a, b) => b.text_span.length - a.text_span.length);
for (const ent of list) {
let startIndex = 0;
while (startIndex < rawText.length) {
const idx = rawText.indexOf(ent.text_span, startIndex);
if (idx === -1) break;
ranges.push({ start: idx, end: idx + ent.text_span.length, ent });
startIndex = idx + ent.text_span.length;
}
}
ranges.sort((a, b) => a.start - b.start || b.end - a.end);
const merged = [];
let lastEnd = 0;
for (const r of ranges) {
if (r.start < lastEnd) continue;
merged.push(r);
lastEnd = r.end;
}
return merged;
};
const buildHighlightedHtml = (text, entities, enableHighlight) => {
const rawText = String(text ?? "");
if (!rawText) return "";
const safeText = escapeHtml(rawText).replace(/\n/g, "<br />");
if (!enableHighlight) return safeText;
const ranges = getEntityRanges(rawText, entities);
if (!ranges.length) return safeText;
let html = "";
let cursor = 0;
for (const r of ranges) {
if (cursor < r.start) {
html += escapeHtml(rawText.slice(cursor, r.start));
}
const spanText = rawText.slice(r.start, r.end);
const type = escapeHtml(r.ent?.type ?? "");
html += `<span class="term-entity" data-entity-type="${type}">${escapeHtml(spanText)}</span>`;
cursor = r.end;
}
if (cursor < rawText.length) {
html += escapeHtml(rawText.slice(cursor));
}
return html.replace(/\n/g, "<br />");
};
const getTermEntityKey = (term, lang) => {
const baseKey = getTermKey(term, -1);
return `${baseKey}__${lang}`;
};
const ensureEntitiesForTerms = async terms => {
if (!termsHighlight.value) return;
if (!termsHighlight.value) {
termsEntityLoading.value = false;
termsEntityPendingCount.value = 0;
return;
}
const list = Array.isArray(terms) ? terms : [];
if (!list.length) return;
if (!list.length) {
termsEntityLoading.value = false;
termsEntityPendingCount.value = 0;
return;
}
const currentToken = ++entityRequestToken.value;
......@@ -314,30 +285,40 @@ const ensureEntitiesForTerms = async terms => {
tasks.push({ key: enKey, text: term.ywnr });
}
}
if (!tasks.length) return;
if (!tasks.length) {
termsEntityLoading.value = false;
termsEntityPendingCount.value = 0;
return;
}
try {
const results = await Promise.all(
tasks.map(async item => {
const res = await extractTextEntity(item.text, { signal: getPageSignal() });
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
return { key: item.key, entities };
})
);
if (currentToken !== entityRequestToken.value) return;
for (const r of results) {
termEntityCache.value.set(r.key, r.entities);
termsEntityLoading.value = true;
termsEntityPendingCount.value = tasks.length;
const fetchOne = async item => {
try {
const res = await extractTextEntity(item.text, { signal: getPageSignal() });
if (currentToken !== entityRequestToken.value) return;
const entities = normalizeEntities(res?.result ?? res?.data?.result ?? res?.data ?? res);
const nextCache = new Map(termEntityCache.value);
nextCache.set(item.key, entities);
termEntityCache.value = nextCache;
} catch (error) {
if (currentToken !== entityRequestToken.value) return;
} finally {
if (currentToken !== entityRequestToken.value) return;
termsEntityPendingCount.value = Math.max(0, termsEntityPendingCount.value - 1);
if (termsEntityPendingCount.value === 0) {
termsEntityLoading.value = false;
}
}
} catch (error) {
if (currentToken !== entityRequestToken.value) return;
}
};
await Promise.all(tasks.map(item => fetchOne(item)));
};
const getTermContentHtml = (term, lang) => {
const raw = lang === "en" ? term?.ywnr : term?.fynr;
const getTermEntities = (term, lang) => {
const key = getTermEntityKey(term, lang);
const entities = termEntityCache.value.get(key) || [];
return buildHighlightedHtml(raw, entities, termsHighlight.value);
return termEntityCache.value.get(key) || [];
};
const tkRequestToken = ref(0);
......@@ -347,10 +328,6 @@ const hylyRequestToken = ref(0);
const mainTermsList = ref([]);
const domainFooterText = ref("");
const limitFooterText = ref("");
const btnActiveIndex = ref(1);
const handleSelectBtn = index => {
btnActiveIndex.value = index;
};
const getTermKey = (term, index) => {
return term?.ywid ?? term?.id ?? term?.tkxh ?? index;
......@@ -576,7 +553,16 @@ const handleChangeCheckbox = val => {
handleGetBillHyly();
};
/** 法案布局主区域可滚动容器(.layout-main)滚回顶部 */
const scrollBillLayoutMainToTop = () => {
const mainEl = document.querySelector(".layout-main");
if (mainEl) {
mainEl.scrollTo({ top: 0, behavior: "smooth" });
}
};
const handleCurrentChange = val => {
scrollBillLayoutMainToTop();
currentPage.value = val;
handleGetBillContentTk(checkedValue.value ? "Y" : "N");
};
......@@ -925,8 +911,11 @@ onBeforeUnmount(() => {
.content-row {
display: flex;
align-items: flex-start;
margin-bottom: 30px;
}
.box-header {
display: flex;
position: relative;
......@@ -988,9 +977,7 @@ onBeforeUnmount(() => {
.terms {
margin-top: 16px;
margin-left: 16px;
width: 1064px;
height: 1232px;
position: relative;
.terms-switch {
......@@ -1007,6 +994,14 @@ onBeforeUnmount(() => {
color: var(--text-primary-65-color);
}
.terms-entity-loading {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--text-primary-65-color);
}
.terms-switch-divider {
display: inline-block;
width: 1px;
......@@ -1022,17 +1017,21 @@ onBeforeUnmount(() => {
}
}
:deep(.analysis-box-wrapper) {
height: auto;
}
:deep(.wrapper-main) {
display: flex;
flex-direction: column;
height: calc(100% - 45px);
gap:16px;
height: auto;
min-height: 0;
overflow: visible;
gap: 16px;
}
.left-main {
flex: 1;
min-height: 0;
overflow-y: auto;
flex: 0 0 auto;
display: flex;
flex-direction: column;
gap: 12px;
......@@ -1112,12 +1111,12 @@ onBeforeUnmount(() => {
line-height: 24px;
color: var(--text-primary-80-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.35);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.25);
:deep(.entity-link) {
color: var(--color-primary-100);
}
:deep(.entity-link:hover) {
cursor: pointer;
}
}
......@@ -1128,12 +1127,12 @@ onBeforeUnmount(() => {
line-height: 24px;
color: var(--text-primary-65-color);
:deep(.term-entity) {
display: inline;
padding: 0 2px;
border-radius: 4px;
background: rgba(255, 213, 79, 0.28);
box-shadow: inset 0 0 0 1px rgba(255, 193, 7, 0.2);
:deep(.entity-link) {
color: var(--color-primary-100);
}
:deep(.entity-link:hover) {
cursor: pointer;
}
}
......@@ -1196,6 +1195,7 @@ onBeforeUnmount(() => {
display: flex;
align-items: center;
justify-content: center;
padding-left: 20px;
}
.overview-tip-action {
......
......@@ -26,7 +26,7 @@
</div>
<div class="layout-main-center">
<BaseDecreeOriginal :report-data="reportData" @download="handleDownload" />
<newOriginal ref="refNewOriginal" @download="handleDownload"></newOriginal>
</div>
</div>
</div>
......@@ -38,7 +38,7 @@ import { useRoute } from "vue-router";
import { ElMessage } from "element-plus";
import { getDecreeSummary } from "@/api/decree/introduction";
import { getDecreeReport } from "@/api/decree/introduction";
import BaseDecreeOriginal from "@/components/base/DecreeOriginal/index.vue";
import newOriginal from "@/components/base/DecreeOriginal/newOriginal.vue";
const route = useRoute();
let pdfUrl = "";
......@@ -98,26 +98,16 @@ const handleGetSummary = async () => {
};
// 获取报告原文 - 修改为获取分段数组
const reportData = ref([]);
const refNewOriginal = ref(null);
const handleGetReport = async () => {
try {
const res = await getDecreeReport({id: route.query.id});
console.log("报告原文", res);
if (res.code === 200 && res.data) {
if (res.code === 200) {
pdfUrl = res.data.pdfUrl;
const originData = [];
let num = Math.max(res.data.content.length, res.data.contentEn.length)
for (let i = 0; i < num; i++) {
let obj = {
content: res.data.content[i] || "",
contentEn: res.data.contentEn[i] || "",
num: i + 1,
}
originData.push(obj);
}
reportData.value = JSON.parse(JSON.stringify(originData));
refNewOriginal.value.setOriginalData({...res.data});
}
} catch (error) { }
} catch (error) {}
};
onMounted(() => {
......
<template>
<AnalysisBox :title="props.title" :showAllBtn="false" height="auto">
<div class="box-main">
<el-empty v-if="!props.listData?.length" description="暂无数据" :image-size="200" />
<div v-for="(item, index) in props.listData" :key="index" class="box-item">
<div class="item-tag">行政令</div>
<div class="item-right">
<div class="item-tag" :class="getClassName(item)">{{ item.TYPE }}</div>
<div class="item-right" @click="onNavigateTo(item)">
<div class="item-head">
<div class="item-name one-line-ellipsis">{{ item.name }}</div>
<div class="item-time">{{ item.time }}</div>
<div class="item-name one-line-ellipsis text-click-hover">{{ item.NAME }}</div>
<div class="item-time">{{ item.DATE }}</div>
</div>
<div class="item-text one-line-ellipsis">{{ item.text }}</div>
<div class="item-text one-line-ellipsis">{{ item.SUMMARY }}</div>
</div>
</div>
</div>
......@@ -16,6 +17,8 @@
</template>
<script setup lang="ts" name="RelatedEvent">
import router from "@/router";
import { useGotoNewsDetail } from '@/router/modules/news';
const props = defineProps({
listData: {
......@@ -28,6 +31,43 @@ const props = defineProps({
}
})
const getClassName = (item: any) => {
switch (item.TYPE) {
case "行政令":
return "theme1"
case "新闻":
return "theme2"
case "法案":
return "theme3"
default:
return "theme1"
}
}
// 跳转相关事件
const gotoNewsDetail = useGotoNewsDetail()
const onNavigateTo = (item: any) => {
switch (item.TYPE) {
case "行政令":
let route1 = router.resolve({
path: "/decreeLayout",
query: { id: item.ID }
});
window.open(route1.href, "_blank");
break;
case "新闻":
gotoNewsDetail(item.ID)
break;
case "法案":
let route2 = router.resolve({
path: "/billLayout",
query: { billId: item.ID }
});
window.open(route2.href, "_blank");
break;
}
}
</script>
<style scoped lang="scss">
......@@ -47,10 +87,9 @@ const props = defineProps({
text-align: center;
margin-right: 16px;
margin-top: 7px;
color: var(--color-yellow-100);
background-color: var(--color-yellow-10);
}
.item-right {
cursor: pointer;
width: 20px;
flex: auto;
line-height: 30px;
......@@ -77,4 +116,17 @@ const props = defineProps({
border-bottom: 1px solid var(--bg-black-5);
}
}
.theme1 {
background-color: var(--color-yellow-10);
color: var(--color-yellow-100);
}
.theme2 {
background-color: var(--color-green-10);
color: var(--color-green-100);
}
.theme3 {
background-color: var(--color-orange-10);
color: var(--color-orange-100);
}
</style>
\ No newline at end of file
<template>
<AnalysisBox :title="props.title" :showAllBtn="false" height="auto">
<div class="box-main">
<el-empty v-if="!props.listData?.length" description="暂无数据" :image-size="100" />
<div v-for="(item, index) in props.listData" :key="index" class="box-item">
<div class="item-icon">
<img src="@/views/marketAccessRestrictions/singleCaseLayout/assets/images/icon_affiche.png" alt="">
</div>
<div class="item-right">
<div class="item-time">{{ item.time }}</div>
<div class="item-text">{{ item.content }}</div>
</div>
</div>
</div>
</AnalysisBox>
</template>
<script setup lang="ts" name="SurveyAffiche">
const props = defineProps({
listData: {
type: Array as any,
default: () => ([])
},
title: {
type: String,
default: ""
}
})
</script>
<style scoped lang="scss">
.box-main {
padding: 0 16px 16px;
.box-item {
border-top: 1px solid var(--bg-black-5);
padding: 6px;
display: flex;
.item-icon {
width: 15px;
height: 15px;
font-size: 0px;
margin-right: 16px;
margin-top: 7px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
width: 20px;
flex: auto;
font-family: Source Han Sans CN;
font-size: 16px;
line-height: 30px;
.item-time {
font-weight: bold;
color: var(--text-primary-80-color);
}
.item-text {
color: var(--text-primary-65-color);
}
}
}
.box-item:last-child {
border-bottom: 1px solid var(--bg-black-5);
}
}
</style>
\ No newline at end of file
<template>
<div class="view-box">
<el-empty v-if="!props.surveyList?.length" description="当前条件下暂无数据" :image-size="200" />
<el-empty v-if="!props.surveyList?.length" description="暂无数据" :image-size="100" />
<div class="timeline-item" v-for="(item, index) in props.surveyList" :key="item.searchid" @click="onNavigateToDetail(item)">
<div class="timeline-date">
<div class="date-text">{{ item.searchdatezh.slice(0,4) }}</div>
......@@ -16,14 +16,12 @@
<div class="timeline-content-card">
<div class="item-head">
<div :class="`item-tag tag-${item.sortcode}`">{{ item.sortcode }}</div>
<div class="item-name">{{ item.searchname }}</div>
<div class="item-name one-line-ellipsis">{{ item.searchname }}</div>
<div class="item-state">
<span class="dot"></span> {{ item.casestatus }}
</div>
</div>
<div class="card-body">
{{ item.content }}
</div>
<div class="card-body">{{ item.content }}</div>
<div class="card-footer">
<div class="footer-left-tags">
<AreaTag v-for="(name, num) in item.searchArea" :key="num" :tagName="name"></AreaTag>
......@@ -129,7 +127,7 @@ const onNavigateToDetail = item => {
.timeline-content-card {
width: 20px;
flex: auto;
padding: 2px 16px 0;
padding: 4px 16px 0;
margin-bottom: 30px;
&:hover .item-head .item-name {
......
......@@ -145,7 +145,7 @@
<div class="box-header-right">
<el-select v-model="box6SelectedYear" @change="handleChangeBox6Year" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box6YearList" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
</template>
......@@ -168,12 +168,12 @@
<template #header-right>
<div class="box-header-right">
<div class="box7-header-right-select-box">
<el-select v-model="box7SelectedSurvey" @change="handleChangeBox7Survey" placeholder="选择调查"
<el-select v-model="box7SelectedSurvey" @change="handleBox7()" placeholder="选择调查"
style="width: 120px">
<el-option v-for="item in box7SurveyList" :key="item.value" :label="item.label"
<el-option v-for="item in box8SurveyList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
<el-select v-model="box7SelectedYear" @change="handleChangeBox7Year" placeholder="选择时间"
<el-select v-model="box7SelectedYear" @change="handleBox7()" placeholder="选择时间"
style="width: 120px">
<el-option v-for="item in box7YearList" :key="item.value" :label="item.label"
:value="item.value" />
......@@ -197,7 +197,7 @@
</template>
<template #header-right>
<div class="box-header-right">
<el-select v-model="box8SelectedSurvey" @change="handleChangeBox8Survey" placeholder="选择调查"
<el-select v-model="box8SelectedSurvey" @change="handleBox8()" placeholder="选择调查"
style="width: 120px">
<el-option v-for="item in box8SurveyList" :key="item.value" :label="item.label"
:value="item.value" />
......@@ -216,8 +216,7 @@
<div class="home-main-footer">
<DivideHeader id="position4" class="divide-header" :titleText="'资源库'"></DivideHeader>
<div class="home-main-footer-header">
<SourceTabList :sourceTabList="categoryList" :activeSouceTabId="activeCateId" @clickTab="handleClickCate">
</SourceTabList>
<SourceTabList :sourceTabList="categoryList" :activeSouceTabId="activeCateId" @clickTab="handleClickCate"></SourceTabList>
<div class="select-box">
<el-select v-model="isSort" placeholder="发布时间" style="width: 166px" @change="handlePxChange">
<template #prefix>
......@@ -232,60 +231,52 @@
</div>
<div class="home-main-footer-main">
<div class="left">
<div class="left-box1">
<div class="left-box1-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
</div>
<div class="left-box1-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears"
@change="handleChangeCheckedSurveyYears">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox>
<el-checkbox v-for="year in surveyYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }}
</el-checkbox>
</el-checkbox-group>
<!-- 科技领域 -->
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "科技领域" }}</div>
</div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
<div class="left-box2">
<div class="left-box2-header">
<div class="icon"></div>
<div class="title">{{ "科技领域" }}</div>
<!-- 发布时间 -->
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "发布时间" }}</div>
</div>
<div class="left-box2-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleChangeCheckedAreas">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in areaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
<div class="left-main">
<el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="check-item" v-for="item in surveyYearList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="left-box2">
<div class="left-box2-header">
<div class="icon"></div>
<div class="title">{{ "发布机构" }}</div>
<!-- 发布机构 -->
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "发布机构" }}</div>
</div>
<div class="left-box2-main">
<el-checkbox-group class="checkbox-group" v-model="checkedInsList" @change="handleChangeCheckedIns">
<el-checkbox class="filter-checkbox" label="全部机构"> 全部机构 </el-checkbox>
<el-checkbox v-for="area in insList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
<div class="left-main">
<el-checkbox-group class="check-list" v-model="checkedOrgList" @change="handleChangeCheckedIns">
<el-checkbox class="check-item" v-for="item in surveyOrgList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<div class="left-box2">
<div class="left-box2-header">
<div class="icon"></div>
<div class="title">{{ "受调查国家/地区" }}</div>
<!-- 受调查国家/地区 -->
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "受调查国家/地区" }}</div>
</div>
<div class="left-box2-main">
<el-checkbox-group class="checkbox-group" v-model="checkedCountryList"
@change="handleChangeCheckedCountry">
<el-checkbox class="filter-checkbox" label="全部"> 全部 </el-checkbox>
<el-checkbox v-for="area in countryList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
<div class="left-main">
<el-checkbox-group class="check-list" v-model="checkedCountryList" @change="handleCheckedCountriesChange">
<el-checkbox class="check-item" v-for="item in surveyCountryList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
......@@ -316,7 +307,7 @@
</template>
<script setup>
import { onMounted, ref, nextTick } from "vue";
import { onMounted, ref, nextTick, reactive } from "vue";
import LeftBtn from "@/components/base/pageBtn/LeftBtn.vue";
import RightBtn from "@/components/base/pageBtn/RightBtn.vue";
......@@ -347,8 +338,6 @@ import {
getSearchResult,
getSearchAllOrg,
getSearchAllCountry,
getStatAreaCompanyList,
getCompanyPlace
} from "@/api/marketAccessRestrictions/index";
import { getRiskSignal, getNews, getRemarks } from "@/api/common/index";
import { ElMessage } from "element-plus";
......@@ -379,21 +368,6 @@ const handleToPosi = id => {
}
};
const formatDate = (dateStr, dateType) => {
if (!dateStr) return "";
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
if (dateType === 'year') {
return year
} else {
return `${month.toString().padStart(2, "0")}月${day.toString().padStart(2, "0")}日`
}
// return `${year}年${month.toString().padStart(2, "0")}月${day.toString().padStart(2, "0")}日`;
}
let containerRef = ref(null);
// 首页分类
const sortInfo = ref([{}]);
......@@ -726,32 +700,6 @@ const handleBox5 = async () => {
// 制裁领域分布
const box6SelectedYear = ref("2025");
const box6YearList = ref([
{
label: "2025",
value: "2025"
},
{
label: "2024",
value: "2024"
},
{
label: "2023",
value: "2023"
},
{
label: "2022",
value: "2022"
},
{
label: "2021",
value: "2021"
},
{
label: "2020",
value: "2020"
}
]);
const handleChangeBox6Year = () => {
handleBox6();
};
......@@ -846,59 +794,18 @@ const handleBox6 = async () => {
};
// 受调查国家分布
const box7SurveyList = ref([
{
label: "337调查",
value: "337"
},
{
label: "301调查",
value: "301"
},
{
label: "232调查",
value: "232"
}
]);
const box7SelectedSurvey = ref("337");
const box7YearList = ref([
{
label: "2025",
value: "2025"
},
{
label: "2024",
value: "2024"
},
{
label: "2023",
value: "2023"
},
{
label: "2022",
value: "2022"
},
{
label: "2021",
value: "2021"
},
{
label: "2020",
value: "2020"
}
{ label: "2025", value: "2025" },
{ label: "2024", value: "2024" },
{ label: "2023", value: "2023" },
{ label: "2022", value: "2022" },
{ label: "2021", value: "2021" },
{ label: "2020", value: "2020" },
]);
const box7SelectedYear = ref("2025");
const handleChangeBox7Survey = () => {
handleBox7();
};
const handleChangeBox7Year = () => {
handleBox7();
};
const box7Data = ref({
title: [
],
const box7Data = reactive({
title: [],
data: []
});
......@@ -911,13 +818,13 @@ const handleGetBox7Data = async () => {
const res = await getSearchCountry(params);
console.log("受调查国家分布", res);
if (res.code === 200 && res.data) {
box7Data.value.title = res.data.map(item => {
box7Data.title = res.data.map(item => {
return {
name: item.COUNTRY,
img: item.COUNTRYIMAGE
};
});
box7Data.value.data = res.data.map(item => {
box7Data.data = res.data.map(item => {
return item.NUM;
});
}
......@@ -928,35 +835,21 @@ const handleGetBox7Data = async () => {
const handleBox7 = async () => {
await handleGetBox7Data();
const box7Chart = getBarChart(box7Data.value.title, box7Data.value.data);
const box7Chart = getBarChart(box7Data.title, box7Data.data);
setChart(box7Chart, "box7Chart");
};
const box8SurveyList = ref([
{
label: "337调查",
value: "337"
},
{
label: "301调查",
value: "301"
},
{
label: "232调查",
value: "232"
}
{ label: "337调查", value: "337" },
{ label: "301调查", value: "301" },
{ label: "232调查", value: "232" },
]);
const box8SelectedSurvey = ref("337");
// 调查结果分布
const box8Data = ref([
]);
const box8SelectedSurvey = ref("337");
const box8Data = ref([]);
const handleGetBox8Data = async () => {
const params = {
// searchId: '',
sortCode: box8SelectedSurvey.value
};
try {
const res = await getSearchResult(params);
const res = await getSearchResult({sortCode: box8SelectedSurvey.value});
console.log("调查结果分布", res);
if (res.code === 200 && res.data) {
box8Data.value = res.data.map(item => {
......@@ -968,23 +861,16 @@ const handleGetBox8Data = async () => {
} else {
box8Data.value = []
}
} catch (error) {
console.error(error);
}
};
const handleBox8 = async () => {
await handleGetBox8Data()
const box8Chart = getPieChart(box8Data.value);
setChart(box8Chart, "box8Chart");
}
const handleChangeBox8Survey = () => {
handleBox8();
};
// 资源库
const pageSize = ref(10);
const currentPage = ref(1);
......@@ -992,234 +878,132 @@ const totalDiscussNum = ref(0);
const handleCurrentChange = page => {
handleToPosi("position4")
currentPage.value = page;
handleGetSurveyList();
handleFetchSurveyList();
};
const categoryList = ref([
{
name: "全部调查",
id: ""
},
{
name: "301调查",
id: "301"
},
{
name: "232调查",
id: "232"
},
{
name: "337调查",
id: "337"
}
{ name: "全部调查", id: "" },
{ name: "337调查", id: "337" },
{ name: "301调查", id: "301" },
{ name: "232调查", id: "232" },
]);
const activeCateId = ref("");
const handleClickCate = item => {
activeCateId.value = item.id;
handleGetInsList()
handleGetSurveyList();
currentPage.value = 1;
handleFetchSurveyList();
};
const releaseTimeList = ref([
{
label: "按发布时间倒序",
value: true
},
{
label: "按发布时间升序",
value: false
}
{ label: "按发布时间倒序", value: true },
{ label: "按发布时间升序", value: false }
]);
const isSort = ref(true); // true 倒序 false 升序
// const handleSwithSort = () => {
// isSort.value = !isSort.value;
// };
const handlePxChange = val => {
handleGetSurveyList()
const isSort = ref(true);
const handlePxChange = () => {
currentPage.value = 1;
handleFetchSurveyList();
};
const surveyYearList = ref([
{
name: "2025年",
id: "2025"
},
{
name: "2024年",
id: "2024"
},
{
name: "2023年",
id: "2023"
},
{
name: "2022年",
id: "2022"
},
{
name: "2021年",
id: "2021"
}
{ name: "全部时间", id: "" },
{ name: "2025年", id: "2025" },
{ name: "2024年", id: "2024" },
{ name: "2023年", id: "2023" },
{ name: "2022年", id: "2022" },
{ name: "2021年", id: "2021" }
]);
const checkedSurveyYears = ref(['全部时间']);
const handleChangeCheckedSurveyYears = (val) => {
if (val.includes("全部时间") && val.length > 1) {
if (val[val.length - 1] === "全部时间") {
checkedSurveyYears.value = ["全部时间"];
} else {
checkedSurveyYears.value = val.filter(item => item !== "全部时间");
}
} else if (val.length === 0) {
checkedSurveyYears.value = ["全部时间"];
const checkedYearList = ref(['']);
const handleCheckedYearsChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedYearList.value = event.filter(item => item !== "");
} else {
checkedYearList.value = [""];
}
handleGetSurveyList();
currentPage.value = 1;
handleFetchSurveyList();
};
const areaList = ref([
]);
const checkedAreaList = ref(['全部领域']);
const handleGetAreaList = async () => {
// 科技领域过滤
const surveyAreaList = ref([]);
const checkedAreaList = ref(['']);
const handleGetSearchAllArea = async () => {
try {
const res = await getHylyList();
console.log("行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data.map(item => {
return {
name: item.name,
id: item.id
};
});
console.log("areaList", areaList.value);
if (res.code === 200) {
surveyAreaList.value = res.data.map(item => ({ name: item.name, id: item.id }));
}
} catch (error) { }
} catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
};
const handleChangeCheckedAreas = (val) => {
// console.log('val',val);
if (val.includes("全部领域") && val.length > 1) {
if (val[val.length - 1] === "全部领域") {
checkedAreaList.value = ["全部领域"];
} else {
checkedAreaList.value = val.filter(item => item !== "全部领域");
}
} else if (val.length === 0) {
checkedAreaList.value = ["全部领域"];
const handleCheckedAreasChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedAreaList.value = event.filter(item => item !== "");
} else {
checkedAreaList.value = [""];
}
handleGetSurveyList();
currentPage.value = 1;
handleFetchSurveyList();
};
// 发布机构
const insList = ref([
{
name: "商务部",
id: "54"
},
{
name: "国际贸易委员会",
id: "262"
},
{
name: "贸易代表办公室",
id: "491"
}
]);
const checkedInsList = ref(['全部机构']);
// 发布机构过滤
const surveyOrgList = ref([]);
const checkedOrgList = ref(['']);
const handleGetInsList = async () => {
// const params = {
// sortCode: activeCateId.value
// }
try {
const res = await getSearchAllOrg()
console.log('发布机构', res);
if (res.code === 200 && res.data) {
insList.value = res.data.map(item => {
return {
name: item.ORGNAME,
id: item.ORGID
}
})
// checkedInsList.value = [insList.value[0].id]
if (res.code === 200) {
surveyOrgList.value = res.data.map(item => ({ name: item.ORGNAME, id: item.ORGID }))
}
} catch (error) {
}
} catch (error) {}
surveyOrgList.value.unshift({ name: "全部机构", id: "" });
}
const handleChangeCheckedIns = (val) => {
// console.log(checkedAreaList.value);
if (val.includes("全部机构") && val.length > 1) {
if (val[val.length - 1] === "全部机构") {
checkedInsList.value = ["全部机构"];
} else {
checkedInsList.value = val.filter(item => item !== "全部机构");
}
} else if (val.length === 0) {
checkedInsList.value = ["全部机构"];
const handleChangeCheckedIns = (event) => {
if (event.length && event[event.length-1] !== "") {
checkedOrgList.value = event.filter(item => item !== "");
} else {
checkedOrgList.value = [""];
}
handleGetSurveyList();
currentPage.value = 1;
handleFetchSurveyList();
};
// 受调查国家/地区
const countryList = ref([
]);
const checkedCountryList = ref(['全部']);
const surveyCountryList = ref([]);
const checkedCountryList = ref(['']);
const handleGetAllSearchCountry = async () => {
try {
const res = await getSearchAllCountry()
console.log('受调查国家/地区', res);
if (res.code && res.data) {
countryList.value = res.data.map(item => {
return {
name: item.COUNTRYNAME,
id: item.COUNTRYID
}
})
// checkedCountryList.value = [countryList.value[0].id]
if (res.code === 200) {
surveyCountryList.value = res.data.map(item => ({ name: item.COUNTRYNAME, id: item.COUNTRYID }))
}
} catch (error) {
}
} catch (error) {}
surveyCountryList.value.unshift({ name: "全部", id: "" });
}
handleGetAllSearchCountry()
const handleChangeCheckedCountry = (val) => {
if (val.includes("全部") && val.length > 1) {
if (val[val.length - 1] === "全部") {
checkedCountryList.value = ["全部"];
} else {
checkedCountryList.value = val.filter(item => item !== "全部");
}
} else if (val.length === 0) {
checkedCountryList.value = ["全部"];
const handleCheckedCountriesChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedCountryList.value = event.filter(item => item !== "");
} else {
checkedCountryList.value = [""];
}
handleGetSurveyList();
currentPage.value = 1;
handleFetchSurveyList();
};
const surveyInfoList = ref([]);
const handleGetSurveyList = async () => {
const handleFetchSurveyList = async () => {
const params = {
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
sortCode: activeCateId.value ? activeCateId.value : null,
publishYear: checkedSurveyYears.value[0] === '全部时间' ? null : checkedSurveyYears.value.toString(),
Area: checkedAreaList.value[0] === '全部领域' ? null : checkedAreaList.value.toString(),
publishOrg: checkedInsList.value[0] === '全部机构' ? null : checkedInsList.value.toString(),
searchCountry: checkedCountryList.value[0] === '全部' ? null : checkedCountryList.value.toString(),
sortCode: activeCateId.value || null,
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
publishOrg: checkedOrgList.value.join(',') || null,
searchCountry: checkedCountryList.value.join(',') || null,
sortField: "date",
sortOrder: isSort.value ? "desc" : "asc"
};
// console.log(params);
try {
const res = await getSurveyList(params);
console.log("调查列表", res);
......@@ -1254,9 +1038,10 @@ onMounted(async () => {
handleBox6();
handleBox7();
handleBox8();
await handleGetInsList()
await handleGetAreaList();
handleGetSurveyList();
handleGetAllSearchCountry()
handleGetInsList()
handleGetSearchAllArea();
handleFetchSurveyList();
});
</script>
......@@ -2293,67 +2078,44 @@ onMounted(async () => {
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.left-box1 {
margin-top: 17px;
// height: 220px;
.left-box1-header {
display: flex;
.icon {
.check-box {
margin-top: 18px;
.check-head {
position: relative;
margin-bottom: 12px;
&::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 8px;
height: 16px;
margin-top: 4px;
border-radius: 2px 2px 0 0;
background: var(--color-main-active);
height: 100%;
background: var(--color-primary-100);
border-radius: 0 2px 2px 0;
}
.title {
height: 2px;
margin-left: 17px;
color: var(--color-main-active);
font-family: Source Han Sans CN;
.head-name {
margin-left: 25px;
color: var(--color-primary-100);
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
}
.left-box1-main {
margin-top: 10px;
padding-left: 20px;
}
}
.left-box2 {
margin-top: 17px;
// height: 260px;
.left-box2-header {
display: flex;
.icon {
width: 8px;
height: 16px;
margin-top: 4px;
border-radius: 2px 2px 0 0;
background: var(--color-main-active);
}
.title {
height: 2px;
margin-left: 17px;
color: var(--color-main-active);
line-height: 16px;
font-family: Source Han Sans CN;
font-size: 16px;
font-weight: 700;
line-height: 24px;
font-weight: bold;
}
}
.left-box2-main {
margin-top: 10px;
padding-left: 20px;
.check-list {
padding: 0 10px 0 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0 12px;
.check-item {
width: 100%;
height: 32px;
:deep(.el-checkbox__label) {
font-family: Source Han Sans CN;
font-size: 16px;
color: var(--text-primary-65-color);
}
}
}
}
}
......
......@@ -32,39 +32,26 @@
<div class="wrapper-main">
<div class="left">
<!-- 科技领域 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in surveyAreaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "科技领域" }}</div>
</div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
<!-- 发布时间 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "发布时间" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears" @change="handleCheckedYearsChange">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox>
<el-checkbox v-for="year in displayedYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }}
<el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="check-item" v-for="item in surveyYearList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
<div v-if="surveyYearList.length > 6" class="expand-btn" @click="isYearExpanded = !isYearExpanded">
{{ isYearExpanded ? "收起" : "更早" }}
<el-icon>
<ArrowUp v-if="isYearExpanded" />
<ArrowDown v-else />
</el-icon>
</div>
</el-checkbox-group>
</div>
</div>
......@@ -98,8 +85,8 @@
</template>
<script setup>
import { ref, onMounted, watch, computed } from "vue";
import { Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue";
import { ref, onMounted, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions";
import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue"
......@@ -116,42 +103,49 @@ const handleSwithSort = () => {
// 科技领域过滤
const surveyAreaList = ref([]);
const checkedAreaList = ref([]);
const checkAllAreas = ref(true);
const isIndeterminateAreas = ref(false);
const handleCheckAllAreasChange = val => {
checkedAreaList.value = val ? surveyAreaList.value.map(a => a.id) : [];
isIndeterminateAreas.value = false;
const checkedAreaList = ref(['']);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "232" });
if (res.code === 200) {
surveyAreaList.value = res.data.map(item => ({ name: item.AREANAME, id: item.AREACODE }));
}
} catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
};
const handleCheckedAreasChange = value => {
const checkedCount = value.length;
checkAllAreas.value = checkedCount === surveyAreaList.value.length;
isIndeterminateAreas.value = checkedCount > 0 && checkedCount < surveyAreaList.value.length;
const handleCheckedAreasChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedAreaList.value = event.filter(item => item !== "");
} else {
checkedAreaList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
// 发布时间过滤
const surveyYearList = ref([]);
const checkedSurveyYears = ref([]);
const checkAllYears = ref(true);
const isIndeterminateYears = ref(false);
const isYearExpanded = ref(false);
const displayedYearList = computed(() => {
if (isYearExpanded.value) return surveyYearList.value;
return surveyYearList.value.slice(0, 6);
});
const handleCheckAllYearsChange = val => {
checkedSurveyYears.value = val ? surveyYearList.value.map(y => y.id) : [];
isIndeterminateYears.value = false;
const checkedYearList = ref(['']);
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "232" });
if (res.code === 200) {
let allYear = res.data.sort((a, b) => (b-a));
let beforeYear = allYear.slice(6).join(',');
surveyYearList.value = allYear.slice(0, 6).map(item => ({ name: item + "年", id: item }));
if (beforeYear) surveyYearList.value.push({ name: "更早", id: beforeYear });
}
} catch (error) {}
surveyYearList.value.unshift({ name: "全部时间", id: "" });
};
const handleCheckedYearsChange = value => {
const checkedCount = value.length;
checkAllYears.value = checkedCount === surveyYearList.value.length;
isIndeterminateYears.value = checkedCount > 0 && checkedCount < surveyYearList.value.length;
const handleCheckedYearsChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedYearList.value = event.filter(item => item !== "");
} else {
checkedYearList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
// 数据列表
......@@ -169,8 +163,8 @@ const handleFetchSurveyList = async () => {
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
sortCode: "232",
publishYear: checkAllYears.value ? "" : checkedSurveyYears.value.toString(),
Area: checkAllAreas.value ? "" : checkedAreaList.value.toString(),
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
caseStatus: filterStage.value,
keywords: searchText.value,
sortField: "date",
......@@ -199,39 +193,12 @@ const handleSearch = () => {
};
// 监听过滤条件
watch([checkedSurveyYears, checkedAreaList, isSort, filterStage, filterParty, filterReason], () => {
watch([isSort, filterStage, filterParty, filterReason], () => {
if (isInitializing.value) return;
currentPage.value = 1;
handleFetchSurveyList();
});
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "232" });
if (res.code === 200 && res.data) {
surveyAreaList.value = res.data.map(item => ({
name: item.AREANAME,
id: item.AREACODE
}));
handleCheckAllAreasChange(true);
}
} catch (error) {}
};
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "232" });
if (res.code === 200 && res.data) {
const sortedYears = res.data.sort((a, b) => b - a);
surveyYearList.value = sortedYears.map(item => ({
name: item + "年",
id: item
}));
handleCheckAllYearsChange(true);
}
} catch (error) {}
};
onMounted(async () => {
await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]);
isInitializing.value = false;
......@@ -306,58 +273,50 @@ onMounted(async () => {
.left {
width: 360px;
min-height: 560px;
min-height: 300px;
height: fit-content;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: #fff;
.left-box {
margin-top: 17px;
.left-header {
display: flex;
align-items: center;
.icon {
.check-box {
margin-top: 18px;
.check-head {
position: relative;
margin-bottom: 12px;
&::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 8px;
height: 16px;
background: var(--color-main-active);
height: 100%;
background: var(--color-primary-100);
border-radius: 0 2px 2px 0;
}
.title {
margin-left: 17px;
color: var(--color-main-active);
.head-name {
margin-left: 25px;
color: var(--color-primary-100);
font-size: 16px;
font-weight: 700;
line-height: 16px;
font-family: Source Han Sans CN;
font-weight: bold;
}
}
}
.checkbox-group {
padding: 10px 0 0 25px;
.filter-checkbox {
width: 130px;
margin-bottom: 8px;
height: 32px;
:deep(.el-checkbox__label) {
font-size: 16px;
color: #5f656c;
}
}
.expand-btn {
color: var(--color-main-active);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-top: 4px;
.el-icon {
margin-left: 4px;
.check-list {
padding: 0 10px 0 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0 12px;
.check-item {
width: 100%;
height: 32px;
:deep(.el-checkbox__label) {
font-family: Source Han Sans CN;
font-size: 16px;
color: var(--text-primary-65-color);
}
}
}
}
......
......@@ -21,39 +21,26 @@
<div class="wrapper-main">
<div class="left">
<!-- 科技领域 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in surveyAreaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "科技领域" }}</div>
</div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
<!-- 发布时间 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "发布时间" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears" @change="handleCheckedYearsChange">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox>
<el-checkbox v-for="year in displayedYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }}
<el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="check-item" v-for="item in surveyYearList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
<div v-if="surveyYearList.length > 6" class="expand-btn" @click="isYearExpanded = !isYearExpanded">
{{ isYearExpanded ? "收起" : "更早" }}
<el-icon>
<ArrowUp v-if="isYearExpanded" />
<ArrowDown v-else />
</el-icon>
</div>
</el-checkbox-group>
</div>
</div>
......@@ -78,8 +65,8 @@
</template>
<script setup>
import { ref, onMounted, watch, computed } from "vue";
import { Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue";
import { ref, onMounted, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList } from "@/api/marketAccessRestrictions";
import SurveyHistory from "@/views/marketAccessRestrictions/com/SurveyHistory.vue"
......@@ -88,46 +75,51 @@ const handleSwithSort = () => {
isSort.value = !isSort.value;
};
const surveyYearList = ref([]);
const checkedSurveyYears = ref([]);
const isYearExpanded = ref(false);
const displayedYearList = computed(() => {
if (isYearExpanded.value) {
return surveyYearList.value;
}
return surveyYearList.value.slice(0, 6);
});
const checkAllYears = ref(false);
const isIndeterminateYears = ref(false);
const handleCheckAllYearsChange = (val) => {
checkedSurveyYears.value = val ? surveyYearList.value.map(y => y.id) : [];
isIndeterminateYears.value = false;
// 科技领域过滤
const surveyAreaList = ref([]);
const checkedAreaList = ref(['']);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "301" });
if (res.code === 200) {
surveyAreaList.value = res.data.map(item => ({ name: item.AREANAME, id: item.AREACODE }));
}
} catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
};
const handleCheckedYearsChange = (value) => {
const checkedCount = value.length;
checkAllYears.value = checkedCount === surveyYearList.value.length;
isIndeterminateYears.value = checkedCount > 0 && checkedCount < surveyYearList.value.length;
const handleCheckedAreasChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedAreaList.value = event.filter(item => item !== "");
} else {
checkedAreaList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
const surveyAreaList = ref([]);
const checkedAreaList = ref([]);
const checkAllAreas = ref(false);
const isIndeterminateAreas = ref(false);
const handleCheckAllAreasChange = (val) => {
checkedAreaList.value = val ? surveyAreaList.value.map(a => a.id) : [];
isIndeterminateAreas.value = false;
// 发布时间过滤
const surveyYearList = ref([]);
const checkedYearList = ref(['']);
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "301" });
if (res.code === 200) {
let allYear = res.data.sort((a, b) => (b-a));
let beforeYear = allYear.slice(6).join(',');
surveyYearList.value = allYear.slice(0, 6).map(item => ({ name: item + "年", id: item }));
if (beforeYear) surveyYearList.value.push({ name: "更早", id: beforeYear });
}
} catch (error) {}
surveyYearList.value.unshift({ name: "全部时间", id: "" });
};
const handleCheckedAreasChange = (value) => {
const checkedCount = value.length;
checkAllAreas.value = checkedCount === surveyAreaList.value.length;
isIndeterminateAreas.value = checkedCount > 0 && checkedCount < surveyAreaList.value.length;
const handleCheckedYearsChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedYearList.value = event.filter(item => item !== "");
} else {
checkedYearList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
const totalDiscussNum = ref(0);
......@@ -145,8 +137,8 @@ const handleFetchSurveyList = async () => {
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
sortCode: "301",
publishYear: checkAllYears.value ? "" : checkedSurveyYears.value.toString(),
Area: checkAllAreas.value ? "" : checkedAreaList.value.toString(),
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
// keywords: searchText.value,
sortField: "date",
sortOrder: isSort.value ? "asc" : "desc"
......@@ -173,60 +165,11 @@ const handleSearch = () => {
handleFetchSurveyList();
};
watch(
[checkedSurveyYears, checkedAreaList, isSort],
() => {
if (isInitializing.value) return;
currentPage.value = 1;
handleFetchSurveyList();
},
{
deep: true
}
);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({
sortCode: '301'
});
if(res.code === 200 && res.data) {
surveyAreaList.value = res.data.map(item => {
return {
name: item.AREANAME,
id: item.AREACODE
};
});
// 默认选中全部
checkAllAreas.value = true;
handleCheckAllAreasChange(true);
}
} catch (error) {
}
}
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({
sortCode: '301'
});
if(res.code === 200 && res.data) {
// 排序并格式化
const sortedYears = res.data.sort((a, b) => b - a);
surveyYearList.value = sortedYears.map(item => {
return {
name: item + '年',
id: item
};
});
// 默认选中全部
checkAllYears.value = true;
handleCheckAllYearsChange(true);
}
} catch (error) {
}
}
watch([isSort], () => {
if (isInitializing.value) return;
currentPage.value = 1;
handleFetchSurveyList();
});
onMounted(async () => {
await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear()]);
......@@ -309,58 +252,50 @@ onMounted(async () => {
.left {
width: 360px;
min-height: 560px;
min-height: 300px;
height: fit-content;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: #fff;
.left-box {
margin-top: 17px;
.left-header {
display: flex;
align-items: center;
.icon {
.check-box {
margin-top: 18px;
.check-head {
position: relative;
margin-bottom: 12px;
&::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 8px;
height: 16px;
background: var(--color-main-active);
height: 100%;
background: var(--color-primary-100);
border-radius: 0 2px 2px 0;
}
.title {
margin-left: 17px;
color: var(--color-main-active);
.head-name {
margin-left: 25px;
color: var(--color-primary-100);
font-size: 16px;
font-weight: 700;
line-height: 16px;
font-family: Source Han Sans CN;
font-weight: bold;
}
}
}
.checkbox-group {
padding: 10px 0 0 25px;
.filter-checkbox {
width: 130px;
margin-bottom: 8px;
height: 32px;
:deep(.el-checkbox__label) {
font-size: 16px;
color: #5f656c;
}
}
.expand-btn {
color: var(--color-main-active);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-top: 4px;
.el-icon {
margin-left: 4px;
.check-list {
padding: 0 10px 0 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0 12px;
.check-item {
width: 100%;
height: 32px;
:deep(.el-checkbox__label) {
font-family: Source Han Sans CN;
font-size: 16px;
color: var(--text-primary-65-color);
}
}
}
}
......
......@@ -33,54 +33,38 @@
<div class="wrapper-main">
<div class="left">
<!-- 科技领域 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "科技领域" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="filter-checkbox" label="全部领域"> 全部领域 </el-checkbox>
<el-checkbox v-for="area in surveyAreaList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
</el-checkbox>
</el-checkbox-group>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "科技领域" }}</div>
</div>
<el-checkbox-group class="check-list" v-model="checkedAreaList" @change="handleCheckedAreasChange">
<el-checkbox class="check-item" v-for="item in surveyAreaList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
<!-- 发布时间 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "发布时间" }}</div>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "发布时间" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedSurveyYears" @change="handleCheckedYearsChange">
<el-checkbox class="filter-checkbox" label="全部时间"> 全部时间 </el-checkbox>
<el-checkbox v-for="year in displayedYearList" :key="year.id" :label="year.id" class="filter-checkbox">
{{ year.name }}
<el-checkbox-group class="check-list" v-model="checkedYearList" @change="handleCheckedYearsChange">
<el-checkbox class="check-item" v-for="item in surveyYearList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
<div v-if="surveyYearList.length > 6" class="expand-btn" @click="isYearExpanded = !isYearExpanded">
{{ isYearExpanded ? "收起" : "更早" }}
<el-icon>
<ArrowUp v-if="isYearExpanded" />
<ArrowDown v-else />
</el-icon>
</div>
</el-checkbox-group>
</div>
</div>
<!-- 受调查国家/地区 -->
<div class="left-box">
<div class="left-header">
<div class="icon"></div>
<div class="title">{{ "受调查国家/地区" }}</div>
<div class="check-box">
<div class="check-head">
<div class="head-name">{{ "受调查国家/地区" }}</div>
</div>
<div class="left-main">
<el-checkbox-group class="checkbox-group" v-model="checkedCountryList"
@change="handleCheckedCountriesChange">
<el-checkbox class="filter-checkbox" label="全部"> 全部 </el-checkbox>
<el-checkbox v-for="area in surveyCountryList" :key="area.id" :label="area.id" class="filter-checkbox">
{{ area.name }}
<el-checkbox-group class="check-list" v-model="checkedCountryList" @change="handleCheckedCountriesChange">
<el-checkbox class="check-item" v-for="item in surveyCountryList" :key="item.id" :label="item.id">
{{ item.name }}
</el-checkbox>
</el-checkbox-group>
</div>
......@@ -103,8 +87,8 @@
</template>
<script setup>
import { ref, onMounted, watch, computed } from "vue";
import { Search, ArrowDown, ArrowUp } from "@element-plus/icons-vue";
import { ref, onMounted, watch } from "vue";
import { Search } from "@element-plus/icons-vue";
import { getSearchAllArea, getSearchAllYear, getSurveyList, getSearchAllCountry } from "@/api/marketAccessRestrictions";
import AnalysisBox from "@/components/base/boxBackground/analysisBox.vue"
......@@ -131,61 +115,71 @@ const releaseTimeList = ref([
// 科技领域过滤
const surveyAreaList = ref([]);
const checkedAreaList = ref(['全部领域']);
const handleCheckedAreasChange = val => {
if (val.includes("全部领域") && val.length > 1) {
if (val[val.length - 1] === "全部领域") {
checkedAreaList.value = ["全部领域"];
} else {
checkedAreaList.value = val.filter(item => item !== "全部领域");
const checkedAreaList = ref(['']);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "337" });
if (res.code === 200) {
surveyAreaList.value = res.data.map(item => ({ name: item.AREANAME, id: item.AREACODE }));
}
} else if (val.length === 0) {
checkedAreaList.value = ["全部领域"];
} catch (error) {}
surveyAreaList.value.unshift({ name: "全部领域", id: "" });
};
const handleCheckedAreasChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedAreaList.value = event.filter(item => item !== "");
} else {
checkedAreaList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
// 发布时间过滤
const surveyYearList = ref([]);
const checkedSurveyYears = ref(['全部时间']);
const isYearExpanded = ref(false);
const displayedYearList = computed(() => {
if (isYearExpanded.value) return surveyYearList.value;
return surveyYearList.value.slice(0, 6);
});
const handleCheckedYearsChange = value => {
// const checkedCount = value.length;
// checkAllYears.value = checkedCount === surveyYearList.value.length;
// isIndeterminateYears.value = checkedCount > 0 && checkedCount < surveyYearList.value.length;
if (val.includes("全部时间") && val.length > 1) {
if (val[val.length - 1] === "全部时间") {
checkedSurveyYears.value = ["全部时间"];
} else {
checkedSurveyYears.value = val.filter(item => item !== "全部时间");
const checkedYearList = ref(['']);
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "337" });
if (res.code === 200) {
let allYear = res.data.sort((a, b) => (b-a));
let beforeYear = allYear.slice(6).join(',');
surveyYearList.value = allYear.slice(0, 6).map(item => ({ name: item + "年", id: item }));
if (beforeYear) surveyYearList.value.push({ name: "更早", id: beforeYear });
}
} else if (val.length === 0) {
checkedSurveyYears.value = ["全部时间"];
} catch (error) {}
surveyYearList.value.unshift({ name: "全部时间", id: "" });
};
const handleCheckedYearsChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedYearList.value = event.filter(item => item !== "");
} else {
checkedYearList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
// 受调查国家/地区过滤
const surveyCountryList = ref([]);
const checkedCountryList = ref(['全部']);
const handleCheckedCountriesChange = val => {
if (val.includes("全部") && val.length > 1) {
if (val[val.length - 1] === "全部") {
checkedCountryList.value = ["全部"];
} else {
checkedCountryList.value = val.filter(item => item !== "全部");
const checkedCountryList = ref(['']);
const handleGetSearchAllCountry = async () => {
try {
const res = await getSearchAllCountry();
if (res.code === 200) {
surveyCountryList.value = res.data.map(item => ({ name: item.COUNTRYNAME, id: item.COUNTRYID }));
}
} else if (val.length === 0) {
checkedCountryList.value = ["全部"];
} catch (error) {}
surveyCountryList.value.unshift({ name: "全部", id: "" });
};
const handleCheckedCountriesChange = event => {
if (event.length && event[event.length-1] !== "") {
checkedCountryList.value = event.filter(item => item !== "");
} else {
checkedCountryList.value = [""];
}
currentPage.value = 1;
handleFetchSurveyList();
};
// 数据列表
......@@ -203,9 +197,9 @@ const handleFetchSurveyList = async () => {
currentPage: currentPage.value - 1,
pageSize: pageSize.value,
sortCode: "337",
publishYear: checkedSurveyYears.value[0] === '全部时间' ? null : checkedSurveyYears.value.toString(),
Area: checkedAreaList.value[0] === '全部领域' ? null : checkedAreaList.value.toString(),
searchCountry: checkedCountryList.value[0] === '全部' ? null : checkedCountryList.value.toString(),
publishYear: checkedYearList.value.join(',') || null,
Area: checkedAreaList.value.join(',') || null,
searchCountry: checkedCountryList.value.join(',') || null,
caseStatus: filterStage.value ? filterStage.value : null,
keywords: searchText.value ? searchText.value : null,
sortField: "date",
......@@ -234,52 +228,11 @@ const handleSearch = () => {
};
// 监听过滤条件
watch(
[checkedSurveyYears, checkedAreaList, checkedCountryList, isSort, filterStage, filterParty, filterReason],
() => {
if (isInitializing.value) return;
currentPage.value = 1;
handleFetchSurveyList();
}
);
const handleGetSearchAllArea = async () => {
try {
const res = await getSearchAllArea({ sortCode: "337" });
if (res.code === 200 && res.data) {
surveyAreaList.value = res.data.map(item => ({
name: item.AREANAME,
id: item.AREACODE
}));
handleCheckAllAreasChange(true);
}
} catch (error) { }
};
const handleGetSearchAllYear = async () => {
try {
const res = await getSearchAllYear({ sortCode: "337" });
if (res.code === 200 && res.data) {
const sortedYears = res.data.sort((a, b) => b - a);
surveyYearList.value = sortedYears.map(item => ({
name: item + "年",
id: item
}));
}
} catch (error) { }
};
const handleGetSearchAllCountry = async () => {
try {
const res = await getSearchAllCountry();
if (res.code === 200 && res.data) {
surveyCountryList.value = res.data.map(item => ({
name: item.COUNTRYNAME,
id: item.COUNTRYID
}));
}
} catch (error) { }
};
watch([isSort, filterStage, filterParty, filterReason], () => {
if (isInitializing.value) return;
currentPage.value = 1;
handleFetchSurveyList();
});
onMounted(async () => {
await Promise.all([handleGetSearchAllArea(), handleGetSearchAllYear(), handleGetSearchAllCountry()]);
......@@ -360,58 +313,50 @@ onMounted(async () => {
.left {
width: 360px;
min-height: 560px;
min-height: 300px;
height: fit-content;
padding-bottom: 20px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: #fff;
.left-box {
margin-top: 17px;
.left-header {
display: flex;
align-items: center;
.icon {
.check-box {
margin-top: 18px;
.check-head {
position: relative;
margin-bottom: 12px;
&::before {
content: "";
position: absolute;
top: 0px;
left: 0px;
width: 8px;
height: 16px;
background: var(--color-main-active);
height: 100%;
background: var(--color-primary-100);
border-radius: 0 2px 2px 0;
}
.title {
margin-left: 17px;
color: var(--color-main-active);
font-size: 16px;
font-weight: 700;
}
}
}
.checkbox-group {
padding: 10px 0 0 25px;
.filter-checkbox {
width: 130px;
margin-bottom: 8px;
height: 32px;
:deep(.el-checkbox__label) {
.head-name {
margin-left: 25px;
color: var(--color-primary-100);
font-size: 16px;
color: #5f656c;
line-height: 16px;
font-family: Source Han Sans CN;
font-weight: bold;
}
}
.expand-btn {
color: var(--color-main-active);
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
margin-top: 4px;
.el-icon {
margin-left: 4px;
.check-list {
padding: 0 10px 0 25px;
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-gap: 0 12px;
.check-item {
width: 100%;
height: 32px;
:deep(.el-checkbox__label) {
font-family: Source Han Sans CN;
font-size: 16px;
color: var(--text-primary-65-color);
}
}
}
}
......
......@@ -100,7 +100,12 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { getSearchBlurb, getSearchContext, getSearchConclusion } from "@/api/marketAccessRestrictions";
import {
getSearchBlurb,
getSearchContext,
getSearchConclusion,
getRelatedEvents,
} from "@/api/marketAccessRestrictions";
import RelatedEvent from "@/views/marketAccessRestrictions/com/RelatedEvent.vue";
import SurveyConclusion from "@/views/marketAccessRestrictions/com/SurveyConclusion.vue";
......@@ -181,26 +186,21 @@ const handleGetSearchConclusion = async () => {
};
// 相关行政举措
const eventList = ref([
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
])
const eventList = ref([])
const handleGetRelatedEvents = async () => {
try {
const res = await getRelatedEvents({
searchId: route.query.searchId
});
if(res.code === 200) eventList.value = res.data || [];
} catch (error) {
console.error("获取相关行政举措失败", error);
}
};
onMounted(() => {
handleGetSearchBlurb();
handleGetRelatedEvents();
handleGetSearchContext();
handleGetSearchConclusion();
});
......
<template>
<div class="wrapper">
<div class="left">
<div class="box1" v-loading="leftLoading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">基本信息</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<AnalysisBox title="基本信息" :showAllBtn="false" height="auto">
<div class="box1-main">
<div class="box1-item">
<div class="box1-item-left">{{ "启动时间:" }}</div>
......@@ -34,14 +25,8 @@
<div class="box1-item">
<div class="box1-item-left">{{ "调查领域:" }}</div>
<div class="box1-item-right2">
<div
class="tag"
:class="{ tag1: tag.type === 1, tag2: tag.type === 2 }"
v-for="(tag, index) in surveyAreaList"
:key="index"
>
{{ tag.name }}
</div>
<AreaTag v-for="(item, num) in surveyAreaList" :key="num" :tagName="item.name"></AreaTag>
<div v-if="!surveyAreaList?.length">-</div>
</div>
</div>
<div class="box1-item">
......@@ -68,50 +53,22 @@
</div>
</div>
</div>
</div>
<div class="box2" v-loading="leftLoading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">调查公告</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box2-main">
<div class="box2-item" v-for="(item, index) in box2Data" :key="index">
<div class="box2-item-left">
<img src="./assets/images/box2-icon.png" alt="" />
</div>
<div class="box2-item-right">
<div class="time">{{ item.time }}</div>
<div class="content">{{ item.content }}</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
<SurveyAffiche title="调查公告" :listData="box2Data"></SurveyAffiche>
</div>
<div class="right">
<div class="box3" v-loading="box3Loading">
<div class="box-header">
<AnalysisBox :showAllBtn="false" height="auto">
<template #custom-title>
<div class="btn-box">
<div
class="btn"
<div class="btn"
:class="{ btnActive: box3BtnActive === item }"
v-for="(item, index) in box3BtnList"
:key="index"
v-for="(item, index) in box3BtnList" :key="index"
@click="handleClickBox3Btn(item)"
>
{{ item }}
</div>
</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
</template>
<div class="box3-main">
<div class="box3-main1" v-if="box3BtnActive === '事件脉络'">
<div class="box3-main1-item" v-for="(item, index) in box3Data1" :key="index">
......@@ -123,10 +80,10 @@
</div>
<div class="right">
<div class="header">
<div class="time">{{ item.time }}</div>
<div class="title">{{ item.title }}</div>
<div class="time">{{ item.CONTTIME }}</div>
<div class="title">{{ item.CONTNODE }}</div>
</div>
<div class="content">{{ item.content }}</div>
<div class="content">{{ item.CONTDESC }}</div>
</div>
</div>
</div>
......@@ -151,60 +108,27 @@
</div>
</div>
</div>
</div>
<div class="box4" v-loading="box4Loading">
<div class="box-header">
<div class="header-left"></div>
<div class="title">相关事件</div>
<div class="header-right">
<div class="icon">
<img src="@/assets/icons/box-header-icon3.png" alt="" />
</div>
</div>
</div>
<div class="box4-main">
<div class="box4-main-item" v-for="(item, index) in box4Data" :key="index">
<div class="item-left">
<div class="item-left-tag" :class="{
tag1: item.tag.type == '新闻',
tag2: item.tag.type == '行政令',
tag3: item.tag.type == '法案',
}">
{{ item.tag.name }}
</div>
</div>
<div class="item-right">
<div class="item-right-header">
<div class="item-right-title">{{ item.title }}</div>
<div class="item-right-time">
{{ item.time }}
</div>
</div>
<div class="item-right-content">{{ item.content }}</div>
</div>
</div>
</div>
</div>
</AnalysisBox>
<RelatedEvent title="相关事件" :listData="eventList"></RelatedEvent>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { getSearchBlurb, getRelatedEvents, getSearchContext, getSearchMeasures } from "@/api/marketAccessRestrictions";
import {
getSearchBlurb,
getRelatedEvents,
getSearchContext,
getSearchMeasures,
} from "@/api/marketAccessRestrictions";
import RelatedEvent from "@/views/marketAccessRestrictions/com/RelatedEvent.vue";
import SurveyAffiche from "@/views/marketAccessRestrictions/com/SurveyAffiche.vue";
const route = useRoute();
const leftLoading = ref(false);
const box3Loading = ref(false);
const box4Loading = ref(false);
const baseInfo = ref({});
const surveyAreaList = ref([]);
const box2Data = ref([]);
const box3Data1 = ref([]);
const box3Data2 = ref([]);
const box4Data = ref([]);
const box3BtnList = ref(["事件脉络", "报复性措施"]);
const box3BtnActive = ref("事件脉络");
......@@ -213,8 +137,8 @@ const handleClickBox3Btn = btn => {
box3BtnActive.value = btn;
};
const box2Data = ref([]);
const handleGetSearchBlurb = async () => {
leftLoading.value = true;
try {
const res = await getSearchBlurb({
searchId: route.query.searchId,
......@@ -234,89 +158,43 @@ const handleGetSearchBlurb = async () => {
// 调查公告
if (data.progress) {
box2Data.value = data.progress.map(p => ({
time: p.PROGRESSDATE,
content: p.PROGRESSSTAGE
}));
// 事件脉络
box3Data1.value = data.progress.map(p => ({
time: p.PROGRESSDATE,
title: p.PROGRESSSTAGE,
content: ""
}));
}
// 报复性措施
if (data.SORTMEASURE) {
box3Data2.value = [
{
title: "报复性措施",
data: [{ content: data.SORTMEASURE }]
}
];
box2Data.value = data.progress.map(p => ({ time: p.PROGRESSDATE, content: p.PROGRESSSTAGE }));
}
}
} catch (error) {
console.error("获取调查详情失败", error);
} finally {
leftLoading.value = false;
}
};
// 相关事件
const eventList = ref([]);
const handleGetRelatedEvents = async () => {
box4Loading.value = true;
try {
const res = await getRelatedEvents({
searchId: route.query.searchId
});
if(res.code === 200 && res.data) {
box4Data.value = res.data.map(item => ({
title: item.NAME,
content: item.SUMMARY,
time: item.DATE,
tag: {
type: item.TYPE,
name: item.TYPE
}
}));
}
if(res.code === 200) eventList.value = res.data || [];
} catch (error) {
console.error("获取相关事件失败", error);
} finally {
box4Loading.value = false;
}
};
// 事件脉络
const box3Data1 = ref([]);
const handleGetSearchContext = async () => {
box3Loading.value = true;
try {
const res = await getSearchContext({
searchId: route.query.searchId
});
if(res.code === 200 && res.data) {
console.log(res.data)
box3Data1.value = res.data.map(item => ({
time: item.CONTTIME,
title: item.CONTNODE,
content: item.CONTDESC
}));
}
const res = await getSearchContext({ searchId: route.query.searchId });
console.log('事件脉络', res.data)
if(res.code === 200) box3Data1.value = res.data;
} catch (error) {
console.error("获取事件脉络失败", error);
} finally {
box3Loading.value = false;
}
}
// 报复性措施
const box3Data2 = ref([]);
const handleGetSearchMeasures = async () => {
// box3Loading is shared with Context for now if they are called together
// or we can just set it true here as well
box3Loading.value = true;
try {
const res = await getSearchMeasures({
searchId: route.query.searchId
});
const res = await getSearchMeasures({ searchId: route.query.searchId });
if(res.code === 200 && res.data) {
box3Data2.value = res.data.map(item => ({
title: item.TITLE,
......@@ -325,8 +203,6 @@ const handleGetSearchMeasures = async () => {
}
} catch (error) {
console.error("获取报复性措施失败", error);
} finally {
box3Loading.value = false;
}
};
onMounted(() => {
......@@ -342,473 +218,242 @@ onMounted(() => {
width: 1600px;
margin: 20px auto;
display: flex;
.box-header {
height: 56px;
.left {
width: 520px;
display: flex;
position: relative;
.header-left {
margin-top: 20px;
width: 8px;
height: 16px;
border-radius: 0 4px 4px 0;
background: var(--color-main-active);
}
.title {
margin-left: 14px;
margin-top: 16px;
flex-direction: column;
gap: 16px;
}
.right {
width: 20px;
flex: auto;
display: flex;
flex-direction: column;
gap: 16px;
margin-left: 16px;
}
}
.box1-main {
padding: 0 22px;
.box1-item {
display: flex;
margin-bottom: 16px;
.box1-item-left {
width: 100px;
height: 24px;
line-height: 24px;
color: var(--color-main-active);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 18px;
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-top: 3px;
margin-bottom: 3px;
}
.btn-box {
position: absolute;
top: 17px;
left: 36px;
display: flex;
gap: 20px;
height: 26px;
.btn {
height: 26px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
cursor: pointer;
}
.btnActive {
color: var(--color-main-active);
border: 1px solid var(--color-main-active);
font-weight: bold;
background: rgba(231, 243, 255, 1);
}
.box1-item-right {
width: 346px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
.header-right {
position: absolute;
top: 14px;
right: 12px;
.box1-item-right1 {
display: flex;
justify-content: flex-end;
gap: 4px;
gap: 6px;
align-items: center;
.icon {
width: 28px;
height: 28px;
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
.box1-item-right2 {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.box1-item-right3 {
width: 346px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.box1-item-right4 {
width: 346px;
}
}
.left {
width: 520px;
margin-bottom: 20px;
.box1 {
margin-top: 16px;
width: 520px;
height: 561px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box1-main {
margin-top: 6px;
margin-left: 22px;
.box1-item {
display: flex;
margin-bottom: 16px;
.box1-item-left {
width: 100px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
margin-top: 3px;
margin-bottom: 3px;
}
.box1-item-right {
width: 346px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
.box1-item-right1 {
display: flex;
gap: 6px;
align-items: center;
.icon {
width: 20px;
height: 20px;
img {
width: 100%;
height: 100%;
}
}
.text {
height: 24px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
}
.box1-item-right2 {
margin-top: 2px;
height: 24px;
display: flex;
gap: 8px;
.tag {
height: 24px;
padding: 0 8px;
box-sizing: border-box;
border-radius: 4px;
text-align: center;
line-height: 24px;
}
.tag1 {
color: rgba(19, 168, 168, 1);
border: 1px solid rgba(135, 232, 222, 1);
background: rgba(230, 255, 251, 1);
}
.tag2 {
color: rgba(22, 119, 255, 1);
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
}
.tag3 {
color: rgba(114, 46, 209, 1);
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
}
}
.box1-item-right3 {
width: 346px;
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.box1-item-right4 {
width: 346px;
}
.btn-box {
display: flex;
align-items: center;
height: 100%;
gap: 20px;
.btn {
height: 26px;
padding: 0 8px;
box-sizing: border-box;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: rgba(255, 255, 255, 1);
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 18px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
cursor: pointer;
}
.btnActive {
color: var(--color-main-active);
border: 1px solid var(--color-main-active);
font-weight: bold;
background: rgba(231, 243, 255, 1);
}
}
.box3-main {
border-top: 1px solid rgba(234, 236, 238, 1);
padding: 15px 20px;
.box3-main1 {
.box3-main1-item {
margin-bottom: 20px;
display: flex;
.left {
width: 10px;
.point {
width: 10px;
height: 10px;
img {
width: 100%;
height: 100%;
}
}
.line {
width: 2px;
height: 130%;
margin-left: 4px;
background: #e6e7e8;
}
}
}
.box2 {
width: 520px;
height: 815px;
margin-top: 16px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box2-main {
height: 749px;
border-top: 1px solid rgba(234, 236, 238, 1);
.box2-item {
width: 483px;
// height: 108px;
margin: 0 auto;
.right {
margin-left: 17px;
.header {
height: 24px;
display: flex;
gap: 16px;
padding: 12px 8px 8px 8px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.box2-item-left {
margin-top: 4px;
width: 15px;
height: 15px;
img {
width: 100%;
height: 100%;
}
}
.box2-item-right {
.time {
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
.content {
width: 435px;
// height: 60px;
margin-top: 4px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
}
}
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.content {
border-top: 1px solid #eaecee;
margin-top: 10px;
margin-bottom: 36px;
padding: 10px 0;
width: 971px;
max-height: 60px;
min-height: 0;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
.right {
width: 1064px;
margin-left: 16px;
.box3 {
margin-top: 16px;
width: 1064px;
// height: 410px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
.box3-main {
margin-top: 8px;
margin: 10px 0;
border-top: 1px solid rgba(234, 236, 238, 1);
padding: 15px 20px;
.box3-main1 {
.box3-main1-item {
// height: 140px;
margin-bottom: 20px;
display: flex;
.left {
width: 10px;
.point {
width: 10px;
height: 10px;
img {
width: 100%;
height: 100%;
}
}
.line {
width: 2px;
height: 130%;
margin-left: 4px;
background: #e6e7e8;
}
}
.right {
margin-left: 17px;
.header {
height: 24px;
display: flex;
gap: 16px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.content {
border-top: 1px solid #eaecee;
margin-top: 10px;
margin-bottom: 36px;
padding: 10px 0;
width: 971px;
max-height: 60px;
min-height: 0;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
}
}
}
.box3-main2 {
.box3-main2-item {
margin-bottom: 16px;
.box3-main2-item-header {
width: 1020px;
height: 48px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1dpx solid rgba(234, 236, 238, 1);
background: rgba(247, 248, 249, 1);
display: flex;
justify-content: space-between;
align-items: center;
.title {
margin-left: 12px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.box3-main2 {
.box3-main2-item {
margin-top: 16px;
.box3-main2-item-header {
width: 1020px;
height: 48px;
box-sizing: border-box;
border-bottom: 1px solid rgba(234, 236, 238, 1);
border-top: 1dpx solid rgba(234, 236, 238, 1);
background: rgba(247, 248, 249, 1);
display: flex;
justify-content: space-between;
align-items: center;
.title {
margin-left: 12px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Bold;
font-size: 18px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: justify;
}
.more {
margin-right: 22px;
height: 14px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 7px;
.icon {
width: 12px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 12px;
font-weight: 400;
line-height: 14px;
letter-spacing: 0px;
text-align: justify;
}
}
}
.box3-main2-item-content {
.item {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
padding: 12px 23px 12px 44px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.more {
margin-right: 22px;
height: 14px;
display: flex;
justify-content: flex-end;
align-items: center;
gap: 7px;
.icon {
width: 12px;
height: 12px;
img {
width: 100%;
height: 100%;
}
}
.text {
color: rgba(5, 95, 194, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 12px;
font-weight: 400;
line-height: 14px;
letter-spacing: 0px;
text-align: justify;
}
}
}
}
.box4 {
margin-top: 16px;
margin-bottom: 35px;
width: 1064px;
// height: 714px;
border-radius: 10px;
box-shadow: 0px 0px 15px 0px rgba(60, 87, 126, 0.2);
background: rgba(255, 255, 255, 1);
padding-bottom: 18px;
.box4-main {
width: 1014px;
margin: 10px auto;
.box4-main-item {
width: 1014px;
padding: 11px 0;
display: flex;
.box3-main2-item-content {
.item {
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-style: Regular;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
padding: 12px 23px 12px 44px;
border-bottom: 1px solid rgba(234, 236, 238, 1);
.item-left {
width: 90px;
display: flex;
.item-left-tag {
width: 80px;
height: 28px;
padding: 0px 8px;
border-radius: 20px;
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: center;
padding: 0 8px;
}
.tag1 {
color: rgba(19, 168, 168, 1);
border: 1px solid rgba(135, 232, 222, 1);
background: rgba(230, 255, 251, 1);
}
.tag2 {
color: rgba(22, 119, 255, 1);
border: 1px solid rgba(145, 202, 255, 1);
background: rgba(230, 244, 255, 1);
}
.tag3 {
color: rgba(114, 46, 209, 1);
border: 1px solid rgba(211, 173, 247, 1);
background: rgba(249, 240, 255, 1);
}
}
.item-right {
width: 924px;
.item-right-header {
width: 924px;
display: flex;
justify-content: space-between;
.item-right-title {
width: 800px;
height: 24px;
color: rgba(59, 65, 75, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
.item-right-time {
width: 124px;
height: 24px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
letter-spacing: 0px;
text-align: left;
}
}
.item-right-content {
margin-top: 4px;
width: 928px;
height: 30px;
color: rgba(95, 101, 108, 1);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 30px;
letter-spacing: 0px;
text-align: justify;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}
}
......
......@@ -30,37 +30,13 @@
<div class="box1-item">
<div class="box1-item-left">{{ "调查领域:" }}</div>
<div class="box1-item-right1">
<div class="tag" v-for="(item, index) in baseInfo.areaData" :key="index">{{ item }}</div>
<div v-if="!baseInfo.areaData || !baseInfo.areaData.length">-</div>
</div>
</div>
<div class="box1-item">
<div class="box1-item-left">{{ "案件进展:" }}</div>
<div class="box1-item-right2">
<div class="box1-item-right2-item" v-for="(item, index) in processList" :key="index">
<div class="icon">
<img src="./assets/images/icon1.png" alt="" />
</div>
<div class="time">{{ item.time }}</div>
<div class="title">{{ item.title }}</div>
</div>
<AreaTag v-for="(item, num) in baseInfo.areaData" :key="num" :tagName="item"></AreaTag>
<div v-if="!baseInfo.areaData?.length">-</div>
</div>
</div>
</div>
</AnalysisBox>
<AnalysisBox title="调查公告" :showAllBtn="false" height="auto">
<div class="box4-main">
<div v-for="(item, index) in afficheList" :key="index" class="box4-item">
<div class="item-icon">
<img src="@/views/marketAccessRestrictions/singleCaseLayout/assets/images/icon_affiche.png" alt="">
</div>
<div class="item-right">
<div class="item-time">{{ item.time }}</div>
<div class="item-text">{{ item.text }}</div>
</div>
</div>
</div>
</AnalysisBox>
<SurveyAffiche title="调查公告" :listData="processList"></SurveyAffiche>
</div>
<div class="right">
<AnalysisBox title="原告信息" :showAllBtn="false" height="auto">
......@@ -99,19 +75,20 @@
<script setup>
import { ref, onMounted } from "vue";
import { useRoute } from "vue-router";
import { getSearchBlurb } from "@/api/marketAccessRestrictions";
import { getSearchBlurb , getRelatedEvents} from "@/api/marketAccessRestrictions";
import AiTips from "@/views/marketAccessRestrictions/com/AiTips.vue";
import RelatedEvent from "@/views/marketAccessRestrictions/com/RelatedEvent.vue";
import SurveyAffiche from "@/views/marketAccessRestrictions/com/SurveyAffiche.vue";
const tips = `Pantech是韩国的一家通信设备公司,曾经是手机制造商,但现在可能已转型为专利持有实体。这类公司常被称为"非执业实体"(NPE)或"专利断言实体"(PAE),通过专利授权和诉讼获取收益。这些企业曾经是行业龙头,但因科技和市场形态巨变,加上自身改革步伐缓慢,经营状况每况愈下。卖掉实体业务部门后,留下来的是高达几万件的专利。`
const route = useRoute();
const loading = ref(false);
const baseInfo = ref({});
const processList = ref([]);
const caseList = ref([]);
// 调查概况
const processList = ref([]);
const handleGetSearchBlurb = async () => {
loading.value = true;
try {
......@@ -126,10 +103,7 @@ const handleGetSearchBlurb = async () => {
// 案件进展
if (data.progress) {
processList.value = data.progress.map(p => ({
time: p.PROGRESSDATE,
title: p.PROGRESSSTAGE
}));
processList.value = data.progress.map(p => ({ time: p.PROGRESSDATE, content: p.PROGRESSSTAGE }));
}
// 被告信息分组
......@@ -157,51 +131,22 @@ const handleGetSearchBlurb = async () => {
loading.value = false;
};
// 调查公告
const afficheList = ref([
{
time: "2025-03-30",
text: "美国ITC正式对外国制造的半导体器件及其下游产品和组件启动337调查"
},
{
time: "2025-03-30",
text: "美国ITC正式对外国制造的半导体器件及其下游产品和组件启动337调查"
},
{
time: "2025-03-30",
text: "美国ITC正式对外国制造的半导体器件及其下游产品和组件启动337调查"
},
{
time: "2025-03-30",
text: "美国ITC正式对外国制造的半导体器件及其下游产品和组件启动337调查"
},
{
time: "2025-03-30",
text: "美国ITC正式对外国制造的半导体器件及其下游产品和组件启动337调查"
},
]);
// 相关事件
const eventList = ref([
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
{
name: "某些特定外国制造的半导体器件及其下游产品和组件;委员会最终裁定未违反第337条的通知;调查终止",
text: "特此通知,2025 年 2 月 18 日,根据 1930 年《关税法》第 337 条(经修订)已代表爱尔兰 Longitude Licensing Ltd.和爱尔",
time: "2025年4月15日"
},
])
const eventList = ref([])
const handleGetRelatedEvents = async () => {
try {
const res = await getRelatedEvents({
searchId: route.query.searchId
});
if(res.code === 200) eventList.value = res.data || [];
} catch (error) {
console.error("获取相关事件失败", error);
}
};
onMounted(() => {
handleGetSearchBlurb();
handleGetRelatedEvents()
});
</script>
......@@ -346,59 +291,6 @@ onMounted(() => {
display: flex;
flex-wrap: wrap;
gap: 8px;
.tag {
margin-top: 3px;
margin-bottom: 3px;
height: 24px;
box-sizing: border-box;
border: 1px solid rgba(135, 232, 222, 1);
border-radius: 4px;
background: rgba(230, 255, 251, 1);
line-height: 24px;
text-align: center;
padding: 0 8px;
color: rgba(19, 168, 168, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
}
}
.box1-item-right2 {
.box1-item-right2-item {
width: 332px;
height: 36px;
display: flex;
.icon {
width: 10px;
height: 10px;
margin-left: 17px;
margin-top: 10px;
img {
width: 100%;
height: 100%;
}
}
.time {
height: 24px;
margin-left: 16px;
margin-top: 6px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 700;
line-height: 24px;
}
.title {
height: 24px;
margin-left: 16px;
margin-top: 6px;
color: var(--color-main-active);
font-family: Microsoft YaHei;
font-size: 16px;
font-weight: 400;
line-height: 24px;
}
}
}
}
}
......@@ -513,41 +405,4 @@ onMounted(() => {
}
}
}
.box4-main {
padding: 0 16px 16px;
.box4-item {
border-top: 1px solid var(--bg-black-5);
padding: 6px;
display: flex;
.item-icon {
width: 15px;
height: 15px;
font-size: 0px;
margin-right: 16px;
margin-top: 7px;
img {
width: 100%;
height: 100%;
}
}
.item-right {
width: 20px;
flex: auto;
font-family: Source Han Sans CN;
font-size: 16px;
line-height: 30px;
.item-time {
font-weight: bold;
color: var(--text-primary-80-color);
}
.item-text {
color: var(--text-primary-65-color);
}
}
}
.box4-item:last-child {
border-bottom: 1px solid var(--bg-black-5);
}
}
</style>
......@@ -23,8 +23,8 @@
</div>
</div> -->
<NewsList :newsList="leftList" @more-click="handleToMoreNews" img="newsImage" title="newsTitle"
content="newsContent" from="from" />
<MessageBubble :messageList="rightList" @more-click="handleToSocialDetail" source="orgName" content="remarks"
content="newsContent" from="from" @item-click="item => gotoNewsDetail(item.newsId)" />
<MessageBubble :messageList="rightList" @person-click="handleToSocialDetail" source="orgName" content="remarks"
name="personName" imageUrl="personImage">
</MessageBubble>
<!-- <div class="right-box">
......@@ -56,6 +56,7 @@
import NewsList from "@/components/base/newsList/index.vue";
import { ref, onMounted } from "vue";
import { useGotoNewsDetail } from "@/router/modules/news";
import {
getSocialMediaInfo, getNews
} from "@/api/scientificFunding/overview";
......@@ -171,15 +172,19 @@ const handleToMoreNews = () => {
};
// 查看社交媒体详情
const handleToSocialDetail = item => {
const personId = item?.personId || item?.id;
if (!personId) return;
const route = router.resolve({
path: "/characterPage",
query: {
personId: item.id
personId
}
});
window.open(route.href, "_blank");
};
const gotoNewsDetail = useGotoNewsDetail();
onMounted(async () => {
handleNews()
handleSocialMediaInfo()
......
......@@ -21,23 +21,30 @@
<div class="left-center-main-ul">
<ul>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">投资主体:</span>
<span class="ul-content">美国国家科学基金会</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">发布日期:</span>
<span class="ul-content">{{ itemData.publicationDate }}</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">资助经费:</span>
<span class="ul-content">{{ itemData.amount }}</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">涉及领域:</span>
<span class="ul-pie cl1" v-for="value in itemData.toOrgNameList">{{ value }}</span>
<span class="ul-pie cl1">
<AreaTag v-for="(val, idx) in itemData.areaList" :key="idx" :tagName="val" />
</span>
</li>
<li>
<img src="./assets/icon-black.png" alt="" class="li-img" />
<span class="ul-title">资助对象:</span>
<span class="ul-content">{{ itemData.fromOrgNameList.join(',') }}</span>
</li>
......@@ -97,42 +104,7 @@ import {
import router from "@/router";
const list = ref([
{
id: 1,
title: "特别重大",
content: "NSF宣布新的“新兴技术体验式学习”计划资...",
time: "一天前"
},
{
id: 2,
title: "一般风险",
content: "美国NASA公布NIAC计划2025年度第一轮资助",
time: "一天前"
},
{
id: 3,
title: "特别重大",
content: "美国NASA公布“早期创新计划”2026年资助...",
time: "一天前"
},
{
id: 4,
title: "重大风险",
content: '美国NIH冻结多所顶尖大学资金引发广泛争议"',
time: "一天前"
},
{
id: 5,
title: "重大风险",
content: "美国NIH终止哥伦比亚大学研究项目拨款引发...",
time: "一天前"
},
{
id: 6,
title: "特别重大",
content: "美国DARPA资助可调控生物功能微系统技术开发",
time: "一天前"
}
]);
//// 获取风险信号
......@@ -286,7 +258,7 @@ onMounted(async () => {
height: 175px;
.left-center-main-title {
margin-left: 19px;
margin-left: 22px;
margin-bottom: 17px;
font-size: 20px;
font-weight: 700;
......@@ -305,6 +277,20 @@ onMounted(async () => {
width: 100%;
height: 24px;
margin-bottom: 12px;
display: flex;
.li-img {
width: 4px;
height: 4px;
margin-right: 18px;
margin-top: 10px;
img {
width: 100%;
height: 100%;
display: block;
}
}
.ul-title {
display: inline-block;
......@@ -326,19 +312,14 @@ onMounted(async () => {
}
.ul-pie {
display: inline-block;
display: flex;
gap: 8px;
box-sizing: border-box;
padding: 2px 8px;
border: 1px solid;
border-radius: 4px;
flex-direction: row;
margin-right: 8px;
}
.cl1 {
border-color: rgba(186, 224, 255, 1);
background-color: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1);
}
.cl2 {
border-color: rgba(255, 163, 158, 1);
......
......@@ -5,20 +5,34 @@
<div class="left-title">
<img src="./assets/icon01.png" alt="" />
<div class="tit">资助领域分布情况</div>
<div :class="radio1 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="changeradio1()">
<div :class="radio1 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="setRadio1(true)">
资助经费
</div>
<div :class="radio1 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="changeradio1()">
<div :class="radio1 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="setRadio1(false)">
资助项目
</div>
<el-select v-model="value1" placeholder="Select" class="select" style=" right: 31px;">
<el-select v-model="value1" placeholder="Select" class="select" style=" right: 31px;"
@change="handleLeft1YearChange">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="left-main">
<div class="left-main-echarts" ref="leftChartRef" v-show="radio1 === true">资助经费</div>
<div class="left-main-echarts" ref="leftChartRef1" v-show="radio1 === false">资助项目</div>
<div class="left-main-echarts" ref="leftChartRef" v-show="radio1 === true"></div>
<div class="left-main-echarts" ref="leftChartRef1" v-show="radio1 === false"></div>
<el-empty v-show="!hasLeft1ChartData && !isLeft1Loading" class="datasub-el-empty" description="暂无数据"
:image-size="100" />
<div class="source" v-show="hasLeft1ChartData">
<TipTab :text="'资助领域分布情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasLeft1ChartData">
<div class="btn-box" v-if="!isShowAiContentLeft1" @mouseenter="handleSwitchAiLeft1(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiLeft1(false)">
<AiPane :aiContent="aiContentLeft1" />
</div>
</div>
</div>
</div>
<div class="left">
......@@ -31,6 +45,18 @@
</div>
<div class="left-main1">
<div class="left-sankey-echarts" ref="leftSankeyRef"></div>
<el-empty v-show="!hasLeft2ChartData" class="datasub-el-empty" description="暂无数据" :image-size="100" />
<div class="source" v-show="hasLeft2ChartData">
<TipTab :text="'机构资助领域情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasLeft2ChartData">
<div class="btn-box" v-if="!isShowAiContentLeft2" @mouseenter="handleSwitchAiLeft2(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiLeft2(false)">
<AiPane :aiContent="aiContentLeft2" />
</div>
</div>
</div>
</div>
</div>
......@@ -39,33 +65,58 @@
<div class="right-title">
<img src="./assets/icon02.png" alt="" />
<div class="tit">资助经费变化情况</div>
<div :class="radio2 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="changeradio2()">
<div :class="radio2 === true ? 'btn-select' : 'btn'" style=" right:250px;" @click="setRadio2(true)">
资助经费
</div>
<div :class="radio2 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="changeradio2()">
<div :class="radio2 === false ? 'btn-select' : 'btn'" style=" right: 150px;" @click="setRadio2(false)">
资助项目
</div>
<el-select v-model="value" placeholder="Select" class="select">
<el-select v-model="value" placeholder="Select" class="select" @change="handleRight1RangeChange">
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="right-main">
<div class="right-main-echarts" ref="rightChartRef" v-show="radio2 === true"></div>
<div class="right-main-echarts" ref="rightChartRef1" v-show="radio2 === false"></div>
<div class="right-main-tit">亿美元</div>
<div class="right-main-tit" v-show="hasRight1ChartData">亿美元</div>
<el-empty v-show="!hasRight1ChartData && !isRight1Loading" class="datasub-el-empty" description="暂无数据"
:image-size="100" />
<div class="source" v-show="hasRight1ChartData">
<TipTab :text="'资助经费变化情况,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasRight1ChartData">
<div class="btn-box" v-if="!isShowAiContentRight1" @mouseenter="handleSwitchAiRight1(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiRight1(false)">
<AiPane :aiContent="aiContentRight1" />
</div>
</div>
</div>
</div>
<div class="right">
<div class="right-title">
<img src="./assets/icon04.png" alt="" />
<div class="tit">项目资助强度分布</div>
<el-select v-model="value3" placeholder="Select" class="select">
<el-select v-model="value3" placeholder="Select" class="select" @change="handleRight2YearChange">
<el-option v-for="item in options1" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<div class="right-main1">
<div class="right-boxplot-echarts" ref="boxplotChartRef"></div>
<div class="right-main1-tit">单位:亿美元</div>
<div class="right-main1-tit" v-show="hasRight2ChartData">单位:亿美元</div>
<el-empty v-show="!hasRight2ChartData" class="datasub-el-empty" description="暂无数据" :image-size="100" />
<div class="source" v-show="hasRight2ChartData">
<TipTab :text="'项目资助强度分布,数据来源:美国国会官网'" />
</div>
<div class="chart-box" v-show="hasRight2ChartData">
<div class="btn-box" v-if="!isShowAiContentRight2" @mouseenter="handleSwitchAiRight2(true)">
<AiButton />
</div>
<div class="content-box" v-else @mouseleave="handleSwitchAiRight2(false)">
<AiPane :aiContent="aiContentRight2" />
</div>
</div>
</div>
</div>
</div>
......@@ -73,13 +124,31 @@
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import { ref, onMounted, onBeforeUnmount, nextTick, computed } from "vue";
import { getChartAnalysis } from "@/api/aiAnalysis";
import AiButton from "@/components/base/Ai/AiButton/index.vue";
import AiPane from "@/components/base/Ai/AiPane/index.vue";
import TipTab from "@/views/thinkTank/TipTab/index.vue";
import {
findFundField, findCountryProjectAreaList, getCountryFundingChange, getCountryFundProjectChange, getOrgFundsArea, getOrgFundStrength
} from "@/api/scientificFunding/overview";
import * as echarts from "echarts";
const isNonEmptyArray = (v) => Array.isArray(v) && v.length > 0;
// 兼容后端多种返回结构:[] / {data:[]} / {content:[]} 等
const extractArrayData = (res) => {
const d = res?.data;
if (Array.isArray(d)) return d;
if (Array.isArray(d?.data)) return d.data;
if (Array.isArray(d?.content)) return d.content;
if (Array.isArray(d?.list)) return d.list;
return [];
};
const isSuccessCode = (res) => Number(res?.code) === 200;
const value = ref(10);
......@@ -107,54 +176,153 @@ const options1 = [
label: "2024年"
}
];
//获取当前时间x年前的日期
function getDateYearsAgo(years) {
// 获取当前日期
const currentDate = new Date();
// 计算指定年数之前的日期
const pastDate = new Date(currentDate.getFullYear() - years, currentDate.getMonth(), currentDate.getDate());
// 格式化日期为 "YYYY-MM-DD" 的形式
const year = pastDate.getFullYear();
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
}
const normalizeYearParam = (val) => {
const y = Number(val);
return Number.isFinite(y) ? y : val;
};
/** 资助经费变化情况:固定时间窗起点 */
const buildStartDateByRange = (rangeYears) => {
const n = Number(rangeYears);
if (n === 10) return "2015-01-01";
if (n === 5) return "2020-01-01";
// 兜底:保持原逻辑的“年初”
const y = new Date().getFullYear() - (Number.isFinite(n) ? n : 0);
return `${y}-01-01`;
};
const radio1 = ref(true)
const changeradio1 = () => {
radio1.value = !radio1.value
}
const setRadio1 = (val) => {
if (radio1.value === val) return;
radio1.value = val;
// 切换模式:先清空当前模式数据与图表,再按当前年份重新拉数
if (val) {
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} else {
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
}
handleLeft1YearChange();
// 切换显示后强制当前图表 resize,确保 canvas 尺寸稳定
nextTick(() => {
if (radio1.value) {
if (leftChart) leftChart.resize();
} else {
if (leftChart1) leftChart1.resize();
}
});
};
const value1 = ref(2025);
const leftChartRef = ref(null);
const leftChartRef1 = ref(null);
const left1RawFund = ref([]);
const left1RawProject = ref([]);
const left1FundLoading = ref(false);
const left1ProjLoading = ref(false);
const hasLeft1ChartData = computed(() => {
return radio1.value ? isNonEmptyArray(left1RawFund.value) : isNonEmptyArray(left1RawProject.value);
});
const isLeft1Loading = computed(() => (radio1.value ? left1FundLoading.value : left1ProjLoading.value));
let left1FundReqSeq = 0;
let left1ProjReqSeq = 0;
const handleLeft1YearChange = () => {
// 根据当前“资助经费/资助项目”切换状态请求对应接口
if (radio1.value) {
handleGetFundField();
} else {
handleFindCountryProjectAreaList();
}
};
// 资助体系v2.0:资助领域分布情况:资助经费
const handleGetFundField = async () => {
const seq = ++left1FundReqSeq;
left1FundLoading.value = true;
try {
let params = {
year: value1.value
year: normalizeYearParam(value1.value)
}
const res = await findFundField(params);
if (seq !== left1FundReqSeq) return;
console.log("资助领域分布情况", res);
if (res.code === 200 && res.data) {
initLeftDonut(res.data, true)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left1RawFund.value = list;
if (list.length) {
await nextTick();
initLeftDonut(list, true)
} else if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} else {
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
}
} catch (error) {
if (seq !== left1FundReqSeq) return;
console.error("获取资助领域分布情况error", error);
left1RawFund.value = [];
if (leftChart) {
leftChart.dispose();
leftChart = null;
}
} finally {
if (seq === left1FundReqSeq) {
left1FundLoading.value = false;
}
}
};
//资助体系v2.0:资助领域分布情况:资助项目
const handleFindCountryProjectAreaList = async () => {
const seq = ++left1ProjReqSeq;
left1ProjLoading.value = true;
try {
let params = {
year: value1.value
year: normalizeYearParam(value1.value)
}
const res = await findCountryProjectAreaList(params);
if (seq !== left1ProjReqSeq) return;
console.log("资助领域分布情况", res);
if (res.code === 200 && res.data) {
initLeftDonut(res.data, false)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left1RawProject.value = list;
if (list.length) {
await nextTick();
initLeftDonut(list, false)
} else if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
} else {
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
}
} catch (error) {
if (seq !== left1ProjReqSeq) return;
console.error("获取资助领域分布情况error", error);
left1RawProject.value = [];
if (leftChart1) {
leftChart1.dispose();
leftChart1 = null;
}
} finally {
if (seq === left1ProjReqSeq) {
left1ProjLoading.value = false;
}
}
};
// 资助领域分布情况
......@@ -208,68 +376,179 @@ const initLeftDonut = (rawData, show) => {
};
if (show == true) {
if (leftChart) leftChart.dispose();
leftChart = echarts.init(leftChartRef.value);
leftChart.setOption(option);
nextTick(() => leftChart && leftChart.resize());
} else {
if (leftChart1) leftChart1.dispose();
leftChart1 = echarts.init(leftChartRef1.value);
leftChart1.setOption(option);
nextTick(() => leftChart1 && leftChart1.resize());
}
};
const rightChartRef = ref(null);
const rightChartRef1 = ref(null);
const right1RawFund = ref([]);
const right1RawProject = ref([]);
const right1FundLoading = ref(false);
const right1ProjLoading = ref(false);
const hasRight1ChartData = computed(() => {
return radio2.value ? isNonEmptyArray(right1RawFund.value) : isNonEmptyArray(right1RawProject.value);
});
const isRight1Loading = computed(() => (radio2.value ? right1FundLoading.value : right1ProjLoading.value));
const radio2 = ref(true)
const changeradio2 = () => {
radio2.value ? handlegetCountryFundingChange() : handlegetCountryFundProjectChange()
radio2.value = !radio2.value
}
const handleRight1RangeChange = () => {
// 根据当前“资助经费/资助项目”切换状态请求对应接口
if (radio2.value) {
handlegetCountryFundingChange();
} else {
handlegetCountryFundProjectChange();
}
};
const setRadio2 = (val) => {
if (radio2.value === val) return;
radio2.value = val;
// 切换模式:先清空当前模式数据与图表,再按当前时间窗重新拉数
if (val) {
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} else {
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
}
handleRight1RangeChange();
// 切换显示后强制当前图表 resize,确保 canvas 尺寸稳定
nextTick(() => {
if (radio2.value) {
if (rightChart) rightChart.resize();
} else {
if (rightChart1) rightChart1.resize();
}
});
};
// 资助体系v2.0:资助经费变化情况:资助经费
const handlegetCountryFundingChange = async () => {
try {
right1FundLoading.value = true;
let params = {
startDate: getDateYearsAgo(value.value)
startDate: buildStartDateByRange(value.value)
}
const res = await getCountryFundingChange(params);
console.log("资助经费变化情况", res);
if (res.code === 200 && res.data) {
initRightLine(res.data, true)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right1RawFund.value = list;
if (list.length) {
await nextTick();
initRightLine(list, true)
} else if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} else {
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
}
} catch (error) {
console.error("获取资助经费变化情况error", error);
right1RawFund.value = [];
if (rightChart) {
rightChart.dispose();
rightChart = null;
}
} finally {
right1FundLoading.value = false;
}
};
//资助体系v2.0:资助经费变化情况:资助项目
const handlegetCountryFundProjectChange = async () => {
try {
right1ProjLoading.value = true;
let params = {
startDate: getDateYearsAgo(value.value)
startDate: buildStartDateByRange(value.value)
}
const res = await getCountryFundProjectChange(params);
console.log("资助项目变化情况", res);
if (res.code === 200 && res.data) {
initRightLine(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right1RawProject.value = list;
if (list.length) {
await nextTick();
initRightLine(list)
} else if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
} else {
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
}
} catch (error) {
console.error("获取资助项目变化情况error", error);
right1RawProject.value = [];
if (rightChart1) {
rightChart1.dispose();
rightChart1 = null;
}
} finally {
right1ProjLoading.value = false;
}
};
//项目资助强度分布
const value3 = ref(2025);
const boxplotChartRef = ref(null);
const right2RawStrength = ref([]);
const hasRight2ChartData = computed(() => isNonEmptyArray(right2RawStrength.value));
const handleRight2YearChange = () => {
handlegetOrgFundStrength();
};
const handlegetOrgFundStrength = async () => {
try {
let params = {
year: value3.value
year: normalizeYearParam(value3.value)
}
const res = await getOrgFundStrength(params);
console.log("项目资助强度分布", res);
if (res.code === 200 && res.data) {
initBoxPlot(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
right2RawStrength.value = list;
if (list.length) {
await nextTick();
initBoxPlot(list)
} else if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
} else {
right2RawStrength.value = [];
if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
}
} catch (error) {
console.error("获取项目资助强度分布error", error);
right2RawStrength.value = [];
if (boxplotChart) {
boxplotChart.dispose();
boxplotChart = null;
}
}
};
//项目资助强度分布
......@@ -420,11 +699,152 @@ const initBoxPlot = (data) => {
let leftChart;
let leftChart1;
// let rightChart;
// let rightChart1;
let rightChart;
let rightChart1;
let leftSankey;
let boxplotChart;
// ------- AI 解读(刷新后默认展开,行为对齐智库概览) -------
const isShowAiContentLeft1 = ref(true);
const isShowAiContentLeft2 = ref(true);
const isShowAiContentRight1 = ref(true);
const isShowAiContentRight2 = ref(true);
const aiContentLeft1 = ref("");
const aiContentLeft2 = ref("");
const aiContentRight1 = ref("");
const aiContentRight2 = ref("");
const isAiLoadingLeft1 = ref(false);
const isAiLoadingLeft2 = ref(false);
const isAiLoadingRight1 = ref(false);
const isAiLoadingRight2 = ref(false);
const appendAiInterpretationChunk = (targetRef, chunk, loadingText = "解读生成中…") => {
if (!chunk) return;
const current = String(targetRef.value || "");
const base = current === loadingText ? "" : current;
targetRef.value = base + String(chunk);
};
const getInterpretationTextFromChartResponse = (res) => {
const list = res?.data;
const first = Array.isArray(list) ? list[0] : null;
return (
first?.["解读"] ||
first?.["interpretation"] ||
first?.["analysis"] ||
first?.["content"] ||
""
);
};
const fetchChartInterpretationOnce = async (payload, targetRef, loadingRef) => {
if (loadingRef.value) return;
const hasValidContent =
targetRef.value &&
targetRef.value !== "解读生成中…" &&
targetRef.value !== "解读加载失败" &&
targetRef.value !== "暂无图表数据";
if (hasValidContent) return;
loadingRef.value = true;
targetRef.value = "解读生成中…";
try {
const res = await getChartAnalysis(
{ text: JSON.stringify(payload) },
{
onChunk: (chunk) => appendAiInterpretationChunk(targetRef, chunk)
}
);
const text = getInterpretationTextFromChartResponse(res);
targetRef.value = text || targetRef.value || "未返回有效解读内容";
} catch (e) {
console.error("图表解读请求失败", e);
targetRef.value = "解读加载失败";
} finally {
loadingRef.value = false;
}
};
const buildPayloadLeft1 = () => {
const raw = radio1.value ? left1RawFund.value : left1RawProject.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "分布图",
name: radio1.value ? "资助领域分布情况-资助经费" : "资助领域分布情况-资助项目",
data: raw
};
};
const buildPayloadLeft2 = () => {
const raw = left2RawSankey.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "桑基图",
name: "机构资助领域情况",
data: raw
};
};
const buildPayloadRight1 = () => {
const raw = radio2.value ? right1RawFund.value : right1RawProject.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "折线图",
name: radio2.value ? "资助经费变化情况-资助经费" : "资助经费变化情况-资助项目",
data: raw
};
};
const buildPayloadRight2 = () => {
const raw = right2RawStrength.value;
if (!Array.isArray(raw) || raw.length === 0) return null;
return {
type: "箱线图",
name: "项目资助强度分布",
data: raw
};
};
const handleSwitchAiLeft1 = async (val) => {
isShowAiContentLeft1.value = val;
if (!val) return;
const payload = buildPayloadLeft1();
if (!payload) {
aiContentLeft1.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentLeft1, isAiLoadingLeft1);
};
const handleSwitchAiLeft2 = async (val) => {
isShowAiContentLeft2.value = val;
if (!val) return;
const payload = buildPayloadLeft2();
if (!payload) {
aiContentLeft2.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentLeft2, isAiLoadingLeft2);
};
const handleSwitchAiRight1 = async (val) => {
isShowAiContentRight1.value = val;
if (!val) return;
const payload = buildPayloadRight1();
if (!payload) {
aiContentRight1.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentRight1, isAiLoadingRight1);
};
const handleSwitchAiRight2 = async (val) => {
isShowAiContentRight2.value = val;
if (!val) return;
const payload = buildPayloadRight2();
if (!payload) {
aiContentRight2.value = "暂无图表数据";
return;
}
await fetchChartInterpretationOnce(payload, aiContentRight2, isAiLoadingRight2);
};
//资助经费变化情况
......@@ -463,8 +883,10 @@ const initRightLine = (data, show) => {
name: orgName,
type: "line",
data: values,
symbol: "none",
showSymbol: false,
smooth: true,
symbol: "emptyCircle",
showSymbol: true,
symbolSize: 6,
endLabel: {
show: true,
formatter: orgName, // 只显示 orgName
......@@ -522,30 +944,55 @@ const initRightLine = (data, show) => {
};
if (show == true) {
let rightChart = echarts.init(rightChartRef.value);
if (rightChart) rightChart.dispose();
rightChart = echarts.init(rightChartRef.value);
rightChart.setOption(option);
nextTick(() => rightChart && rightChart.resize());
} else {
let rightChart1 = echarts.init(rightChartRef1.value);
if (rightChart1) rightChart1.dispose();
rightChart1 = echarts.init(rightChartRef1.value);
rightChart1.setOption(option);
nextTick(() => rightChart1 && rightChart1.resize());
}
};
const leftSankeyRef = ref(null);
const value2 = ref(2025);
const left2RawSankey = ref([]);
const hasLeft2ChartData = computed(() => isNonEmptyArray(left2RawSankey.value));
// 机构资助领域情况
const handleGetOrgFundsArea = async () => {
try {
let params = {
year: value2.value
year: normalizeYearParam(value2.value)
}
const res = await getOrgFundsArea(params);
console.log("机构资助领域情况", res);
if (res.code === 200 && res.data) {
initLeftSankey(res.data)
if (isSuccessCode(res)) {
const list = extractArrayData(res);
left2RawSankey.value = list;
if (list.length) {
await nextTick();
initLeftSankey(list)
} else if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
} else {
left2RawSankey.value = [];
if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
}
} catch (error) {
console.error("获取机构资助领域情况error", error);
left2RawSankey.value = [];
if (leftSankey) {
leftSankey.dispose();
leftSankey = null;
}
}
};
//机构资助领域情况
......@@ -638,6 +1085,7 @@ const initLeftSankey = (data) => {
};
leftSankey.setOption(option);
nextTick(() => leftSankey && leftSankey.resize());
};
......@@ -650,13 +1098,21 @@ const initLeftSankey = (data) => {
// };
onMounted(() => {
handleGetFundField()
handleFindCountryProjectAreaList()
handlegetCountryFundingChange()
handlegetCountryFundProjectChange()
handleGetOrgFundsArea()
handlegetOrgFundStrength()
// 刷新后 AiPane 默认展开:先给出“解读生成中…”占位,再在数据到位后触发解读请求
aiContentLeft1.value = "解读生成中…";
aiContentLeft2.value = "解读生成中…";
aiContentRight1.value = "解读生成中…";
aiContentRight2.value = "解读生成中…";
// 先拉数据;每块数据到位后立即触发一次 AI 解读(不必等其它块完成)
void handleGetFundField().then(() => handleSwitchAiLeft1(true));
void handleFindCountryProjectAreaList();
void handlegetCountryFundingChange().then(() => handleSwitchAiRight1(true));
void handlegetCountryFundProjectChange();
void handleGetOrgFundsArea().then(() => handleSwitchAiLeft2(true));
void handlegetOrgFundStrength().then(() => handleSwitchAiRight2(true));
});
// onBeforeUnmount(() => {
// window.removeEventListener("resize", handleResize);
......@@ -746,18 +1202,24 @@ onMounted(() => {
.left-main {
width: 792px;
height: 412px;
padding: 52px 60px 78px 61px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.left-main-echarts {
width: 780px;
height: 350px;
width: 100%;
height: 100%;
}
}
.left-main1 {
width: 792px;
height: 412px;
padding: 30px 30px 30px 30px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.left-sankey-echarts {
width: 100%;
......@@ -825,14 +1287,16 @@ onMounted(() => {
.right-main {
width: 792px;
height: 421px;
padding: 40px 5px 30px 22px;
height: 412px;
box-sizing: border-box;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
.right-main-echarts {
/* 矢量 476 */
width: 780px;
height: 350px;
width: 100%;
height: 100%;
}
.right-main-tit {
......@@ -849,15 +1313,16 @@ onMounted(() => {
.right-main1 {
width: 792px;
height: 421px;
padding: 20px 20px;
height: 412px;
/* 对齐智库概览-数据总览内边距 */
padding: 24px 24px 65px 24px;
position: relative;
box-sizing: border-box;
.right-boxplot-echarts {
width: 100%;
height: 100%;
min-height: 300px;
height: 323px;
}
.right-main1-tit {
......@@ -876,6 +1341,50 @@ onMounted(() => {
}
}
/* 数据总览内:TipTab 与 AI 解读(尽量复用智库概览的定位) */
.source {
position: absolute;
left: 24px;
bottom: 21px;
z-index: 2;
}
.chart-box {
position: absolute;
right: 0px;
bottom: 18px;
width: 74px;
height: 28px;
z-index: 3;
.btn-box {
width: 74px;
height: 28px;
}
.content-box {
width: 792px;
position: absolute;
right: 0;
bottom: -18px;
}
}
.datasub-el-empty {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
margin: 0;
z-index: 5;
:deep(.el-empty__description) {
margin-top: 8px;
}
}
.btn {
position: absolute;
top: 11px;
......
......@@ -6,7 +6,7 @@
{{ item.orgName }}
</div>
</div>
<div class="select-box">
<div class="reslib-sort-box">
<div class="paixu-btn" @click="handleSwithSort()">
<div class="icon1">
<img v-if="sort" src="@/assets/icons/shengxu1.png" alt="" />
......@@ -21,40 +21,39 @@
</div>
<div class="main">
<div class="left">
<div class="left-ti1"></div>
<div class="left-ti2"></div>
<!-- <div class="left-title">项目经费</div>
<div class="left-content">
<div v-for="(item, i) in dataList" :key="item.id" class="left-item">
<input type="checkbox" checked />{{ item.name }}
<div class="select-box">
<div class="header">
<div class="icon"></div>
<div class="title">科技领域</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" :model-value="selectedAreaListModel"
@change="handleAreaGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_AREA">
{{ RESOURCE_FILTER_ALL_AREA }}
</el-checkbox>
<el-checkbox v-for="research in areaList" :key="research.id" class="filter-checkbox" :label="research.id">
{{ research.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div> -->
<div class="left-title cl1">涉及领域</div>
<div class="left-content">
<el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" class="all-checkbox"
@change="handleCheckAllChange">
全部领域
</el-checkbox>
<el-checkbox v-for="research in areaList" :key="research.id" v-model="selectedAreaList" :label="research.id"
@change="handleCheckedAreaChange()" class="filter-checkbox">
{{ research.name }}
</el-checkbox>
<!-- <div v-for="(item, i) in areaList" :key="item.id" class="left-item">
<input type="checkbox" :checked="i === 0" />{{ item.name }}
</div> -->
</div>
<div class="left-title cl1">发布时间</div>
<div class="left-content">
<el-checkbox v-model="checkAllTime" class="all-checkbox" :indeterminate="isIndeterminateTime"
@change="handleCheckAllChangeTime">
全部时间
</el-checkbox>
<el-checkbox-group v-model="selectedPubTimeList">
<el-checkbox v-for="time in pubTimeList" :key="time.id" :label="time.id" class="filter-checkbox"
@change="handleCheckedAreaChangeTime()">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
<div class="select-box">
<div class="header">
<div class="icon"></div>
<div class="title">发布时间</div>
</div>
<div class="select-main">
<el-checkbox-group class="checkbox-group" :model-value="selectedPubTimeListModel"
@change="handleTimeGroupChange">
<el-checkbox class="filter-checkbox all-checkbox" :label="RESOURCE_FILTER_ALL_TIME">
{{ RESOURCE_FILTER_ALL_TIME }}
</el-checkbox>
<el-checkbox v-for="time in pubTimeList" :key="time.id" class="filter-checkbox" :label="time.id">
{{ time.name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</div>
<div class="right">
......@@ -68,29 +67,22 @@
<div class="right-item-title">{{ item.projectName }}</div>
<div class="right-item-content">{{ item.abstractContent }}</div>
<div class="right-item-pie">
<div v-for="(pie, i) in item.areaList" :key="i" class="right-item-pie-item" :class="{
cl1: pie === '新材料',
cl2: pie === '人工智能',
cl3: pie === '量子科技',
cl4: pie === '能源',
cl5: pie === '生物科技',
cl6: pie === '航空航天'
}">
{{ pie }}
</div>
<AreaTag v-for="(val, idx) in item.areaList" :key="idx" :tagName="val" />
</div>
<div class="right-item-time">{{ item.publicationDate }}</div>
<div class="right-item-money"
:style="{ color: item.amount <= 1000 ? 'rgba(232, 189, 11, 1)' : item.amount <= 10000 ? 'rgba(255, 149, 77, 1)' : 'rgba(206, 79, 81, 1)' }">
{{ '$' + item.amount + '万' }}</div>
</div>
<div class="page">
<div class="count">{{ total }}</div>
<el-pagination v-model:current-page="currentPage" :page-size="pageSize" :total="total"
layout="prev, pager, next" background @current-change="handlePageChange" />
</div>
</div>
</div>
</div>
</div>
......@@ -101,49 +93,35 @@ import { ref, onMounted } from "vue";
import {
getProjectListNew, geFundSourceOrg, getAreaType
} from "@/api/scientificFunding/overview";
import {
RESOURCE_FILTER_ALL_AREA,
RESOURCE_FILTER_ALL_TIME,
RESOURCE_FILTER_EARLIER,
normalizeExclusiveAllOption,
stripAllAreaForRequest,
stripAllTimeForRequest,
isSelectionCoveringAllOptions,
expandEarlierNumericYears
} from "@/views/thinkTank/utils/resourceLibraryFilters";
/** 领域字典与接口 arealist 使用数字 id(1、2、3…) */
const normalizeAreaId = (id) => {
const n = Number(id);
return Number.isFinite(n) ? n : id;
};
/** 请求用:仅保留合法整数领域 id */
const toAreaIdListForRequest = (ids) => {
const list = Array.isArray(ids) ? ids : [];
return list
.map((id) => normalizeAreaId(id))
.filter((id) => typeof id === "number" && Number.isInteger(id));
};
const navList = ref([]);
const activeItem = ref("");
const areaList = ref([
{
id: 1,
name: "全部领域"
},
{
id: 2,
name: "人工智能"
},
{
id: 3,
name: "集成电路"
},
{
id: 4,
name: "通信网络"
},
{
id: 5,
name: "量子科技"
},
{
id: 6,
name: "能源"
},
{
id: 7,
name: "生物科技"
},
{
id: 8,
name: "航空航天"
},
{
id: 9,
name: "海洋"
}
]);
const areaList = ref([]);
// 来源机构列表
......@@ -160,89 +138,84 @@ const handleGeFundSourceOrg = async () => {
}
};
const checkAll = ref(false);
const isIndeterminate = ref(true);
const selectedAreaList = ref([]);
const handleCheckAllChange = val => {
// console.log(val, "handleCheckAllChange");
if (val) {
isIndeterminate.value = false;
selectedAreaList.value.length !== areaList.value.length
? (selectedAreaList.value = areaList.value.map(obj => obj.id))
: "";
} else {
selectedAreaList.value = [];
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
const selectedAreaListModel = ref([RESOURCE_FILTER_ALL_AREA]);
const selectedPubTimeListModel = ref([RESOURCE_FILTER_ALL_TIME]);
const handleAreaGroupChange = (val) => {
selectedAreaListModel.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_AREA).map((item) =>
item === RESOURCE_FILTER_ALL_AREA ? item : normalizeAreaId(item)
);
handleGetProjectListNew();
};
const handleCheckedAreaChange = () => {
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console.log(selectedAreaList.value, "当前选中的领域");
selectedAreaList.value.length !== areaList.value.length
? (isIndeterminate.value = true)
: ((checkAll.value = true), (isIndeterminate.value = false));
const handleTimeGroupChange = (val) => {
selectedPubTimeListModel.value = normalizeExclusiveAllOption(val, RESOURCE_FILTER_ALL_TIME);
handleGetProjectListNew();
};
const pubTimeList = ref([
{
id: 2025,
name: "2025"
name: "2025"
},
{
id: 2024,
name: "2024"
name: "2024"
},
{
id: 2023,
name: "2023"
name: "2023"
},
{
id: 2022,
name: "2022"
name: "2022"
},
{
id: 2021,
name: "2021"
name: "2021年"
},
{
id: RESOURCE_FILTER_EARLIER,
name: RESOURCE_FILTER_EARLIER
}
// {
// id: "更早时间",
// name: "更早时间"
// }
]);
const selectedPubTimeList = ref([""]);
const checkAllTime = ref(false);
const isIndeterminateTime = ref(true);
const sort = ref(false);
const handleSwithSort = () => {
sort.value = !sort.value;
handleGetProjectListNew();
};
const handleCheckAllChangeTime = val => {
// console.log(val, "handleCheckAllChange");
if (val) {
isIndeterminateTime.value = false;
selectedPubTimeList.value.length !== pubTimeList.value.length
? (selectedPubTimeList.value = pubTimeList.value.map(obj => obj.id))
: "";
} else {
selectedPubTimeList.value = [];
/** 选择「全部时间」时,yearlist 传 2000~2025 逐年 */
const YEAR_ALL_RANGE_START = 2000;
const YEAR_ALL_RANGE_END = 2025;
const buildYearlistForRequest = (selectedTimeModel) => {
const strippedTime = stripAllTimeForRequest(selectedTimeModel);
// 仅勾选「全部时间」、未选具体年份时,传 2000~2025 逐年
if (strippedTime.length === 0) {
const out = [];
for (let y = YEAR_ALL_RANGE_START; y <= YEAR_ALL_RANGE_END; y += 1) {
out.push(String(y));
}
return out;
}
// selectedAreaList.value = val ? areaList : []
// isIndeterminate.value = false
handleGetProjectListNew();
const hasEarlier = strippedTime.includes(RESOURCE_FILTER_EARLIER);
const numericOnly = strippedTime.filter((id) => id !== RESOURCE_FILTER_EARLIER);
const strippedNums = numericOnly
.map((id) => Number(id))
.filter((n) => Number.isInteger(n));
// 勾选「更早」:展开为 2000~2020,并与已选数字年合并(可与其他年份同时选)
if (hasEarlier) {
const yearsSet = new Set(expandEarlierNumericYears());
strippedNums.forEach((n) => yearsSet.add(n));
return [...yearsSet]
.sort((a, b) => a - b)
.map((n) => String(n));
}
return strippedNums.map((n) => String(n));
};
const handleCheckedAreaChangeTime = () => {
// console.log(selectedAreaList.value, "handleCheckedAreaChange");
console.log(selectedPubTimeList.value, "当前选中的时间");
selectedPubTimeList.value.length !== pubTimeList.value.length
? (isIndeterminateTime.value = true)
: ((checkAllTime.value = true), (isIndeterminateTime.value = false));
const sort = ref(false);
const handleSwithSort = () => {
sort.value = !sort.value;
handleGetProjectListNew();
};
......@@ -252,7 +225,10 @@ const handleGetAreaType = async () => {
const res = await getAreaType();
console.log("获取行业领域列表", res);
if (res.code === 200 && res.data) {
areaList.value = res.data
areaList.value = res.data.map((row) => ({
...row,
id: normalizeAreaId(row.id)
}));
}
} catch (error) {
console.error("获取行业领域列表error", error);
......@@ -270,11 +246,22 @@ const handlePageChange = p => {
// 资助体系v2.0:资助项目列表分页
const handleGetProjectListNew = async () => {
try {
const strippedArea = toAreaIdListForRequest(
stripAllAreaForRequest(selectedAreaListModel.value)
);
const allAreaIds = toAreaIdListForRequest(areaList.value.map((obj) => obj.id));
const arealist =
strippedArea.length === 0 || isSelectionCoveringAllOptions(strippedArea, allAreaIds)
? allAreaIds
: strippedArea;
const yearlist = buildYearlistForRequest(selectedPubTimeListModel.value);
let params = {
arealist: selectedAreaList.value,
arealist,
currentPage: currentPage.value,
pageSize: 10,
yearlist: selectedPubTimeList.value.map(item => item.toString().trim()).filter(item => item !== ""),
yearlist,
funSort: sort.value ? 'desc' : 'asc',
orgId: activeItem.value
}
......@@ -305,11 +292,10 @@ onMounted(async () => {
.reslib-page {
width: 1600px;
height: 1565px;
position: relative;
.select-box {
position: relative;
.reslib-sort-box {
width: 128px;
position: absolute;
top: 7px;
......@@ -370,15 +356,15 @@ onMounted(async () => {
}
.nav {
width: calc(100% - 100px);
height: 42px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 34px;
.nav-item {
width: 100%;
text-align: center;
cursor: pointer;
padding: 8px 20px;
......@@ -397,133 +383,85 @@ onMounted(async () => {
}
}
.select {
width: 128px;
position: absolute;
top: 7px;
right: 0px;
}
.main {
width: 1600px;
height: 1489px;
display: flex;
margin-bottom: 100px;
.left {
width: 300px;
width: 360px;
margin-right: 16px;
height: 100%;
padding-bottom: 24px;
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
position: relative;
.left-ti1 {
width: 8px;
height: 16px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 17px;
left: 0px;
}
.left-ti2 {
width: 8px;
height: 16px;
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
position: absolute;
top: 207px;
left: 0px;
}
.select-box {
margin-top: 16px;
.left-title {
margin-left: 25px;
color: rgb(5, 95, 194);
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
margin-top: 13px;
}
.left-content {
width: 253px;
// height: 132px;
margin-left: 25px;
margin-top: 13px;
display: flex;
flex-wrap: wrap;
/* 允许内容换行 */
justify-content: space-between;
/* 两端对齐 */
.left-item {
white-space: nowrap;
/* 保持在一行内 */
overflow: hidden;
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 超出部分显示省略号 */
width: calc(50% - 8px);
/* 每个选项占一半宽度,减去间距 */
height: 30px;
margin-bottom: 4px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
input[type="checkbox"] {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
margin-right: 8px;
border: 1px solid rgb(200, 204, 210);
border-radius: 4px;
background-color: #fff;
vertical-align: middle;
.header {
display: flex;
gap: 17px;
.icon {
margin-top: 4px;
width: 8px;
height: 16px;
background: var(--color-main-active);
border-radius: 0 4px 4px 0;
}
input[type="checkbox"]:checked {
background-color: rgb(5, 95, 194);
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
margin-right: 17px;
.title {
height: 24px;
color: var(--color-main-active);
font-family: "Source Han Sans CN";
font-size: 16px;
font-weight: 700;
line-height: 24px;
letter-spacing: 1px;
text-align: left;
}
}
input[type="checkbox"]:checked::after {
content: "";
display: block;
width: 4px;
height: 8px;
margin: 1px auto 0;
border: 2px solid #fff;
border-top: none;
border-left: none;
transform: rotate(45deg);
.select-main {
margin-left: 24px;
margin-top: 12px;
.checkbox-group {
display: grid;
grid-template-columns: repeat(2, 160px);
gap: 8px 4px;
:deep(.all-checkbox) {
width: 160px;
height: 24px;
margin: 0;
}
:deep(.filter-checkbox) {
width: 160px;
height: 24px;
margin-right: 0 !important;
}
}
}
}
.cl1 {
margin-top: 21px;
}
}
.right {
width: 1284px;
height: 1489px;
width: 1224px;
border-radius: 10px;
background-color: #fff;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
.right-title {
width: 1284px;
width: 1224px;
height: 48px;
border-bottom: 1px solid rgb(235, 238, 242);
position: relative;
......@@ -550,13 +488,13 @@ onMounted(async () => {
}
.right-main {
width: 1284px;
height: 1441px;
width: 1224px;
padding: 19px 34px 20px 29px;
position: relative;
.right-item {
width: 1221px;
width: 1161px;
height: 124px;
border-bottom: 1px solid rgb(234, 236, 238);
margin-bottom: 8px;
......@@ -588,7 +526,7 @@ onMounted(async () => {
/* 隐藏超出部分 */
text-overflow: ellipsis;
/* 超出部分显示省略号 */
width: 90%;
width: 1112px;
/* 设置一个固定的宽度或百分比 */
position: absolute;
top: 44px;
......@@ -605,6 +543,7 @@ onMounted(async () => {
top: 76px;
left: 56px;
display: flex;
gap: 8px;
.right-item-pie-item {
padding: 2px 8px;
......@@ -677,15 +616,16 @@ onMounted(async () => {
}
.page {
width: 1221px;
width: 1161px;
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
position: absolute;
bottom: 20px;
left: 20px;
padding-left: 11px;
margin-top: 29px;
.count {
font-size: 16px;
......@@ -749,7 +689,10 @@ onMounted(async () => {
background-color: #fff;
}
}
}
}
}
}
......
......@@ -14,14 +14,8 @@
<div class="main-content" ref="containerRef">
<div class="home-top-bg"></div>
<!-- 搜索栏部分 -->
<SearchContainer
style="margin-bottom: 48px; height: fit-content"
v-if="containerRef"
:countInfo="countInfo"
placeholder="搜索科研资助实体、资助记录"
:containerRef="containerRef"
areaName=""
/>
<SearchContainer style="margin-bottom: 48px; height: fit-content" v-if="containerRef" :countInfo="countInfo"
placeholder="搜索科研资助实体、资助记录" :containerRef="containerRef" areaName="" />
<!-- <div class="search"> -->
<!-- <div class="search-main">
......@@ -78,7 +72,8 @@
<!-- </div> -->
<!-- 6个数据 -->
<div class="data">
<div v-for="(item, index) in dataList" :key="item.id" class="data-item">
<div v-for="(item, index) in dataList" :key="item.orgId || item.id" class="data-item"
@click="handleClickOrg(item)">
<img v-if="item.logoUrl && /\\.(jpe?g|png)$/i.test(item.logoUrl)" :src="item.logoUrl" alt="" />
<img v-else src="./assets/images/nullcorpimg.png" alt="" />
<div class="data-text-item">
......@@ -91,28 +86,28 @@
</div>
<!-- 最新动态 -->
<div class="newdata" id="position1">
<com-title title="最新动态" />
<com-title title="最新动态" style="width: 1600px;" />
<div class="newdata-main">
<newData />
</div>
</div>
<!-- 资讯要问 -->
<div class="ask" id="position2">
<com-title title="资讯要闻" />
<com-title title="资讯要闻" style="width: 1600px;" />
<div class="ask-main">
<askPage />
</div>
</div>
<!-- 数据总览 -->
<div class="datasub" id="position3">
<com-title title="数据总览" />
<com-title title="数据总览" style="width: 1600px;" />
<div class="datasub-main">
<dataSub />
</div>
</div>
<!-- 资源库 -->
<div class="reslib" id="position4">
<com-title title="资源库" />
<com-title title="资源库" style="width: 1600px;" />
<div class="reslib-main">
<resLib />
</div>
......@@ -170,6 +165,21 @@ const handleBackHome = () => {
path: "/overview"
});
};
// 点击机构卡片跳转机构详情
const handleClickOrg = (item) => {
const orgId = item?.orgId;
if (!orgId) return;
// Institution 路由开启了 dynamicTitle,这里提前写入标题,避免沿用上一次的“白宫”等旧值
const title = item?.orgName || "";
window.sessionStorage.setItem("institutionTabName", title);
window.sessionStorage.setItem("curTabName", title);
const route = router.resolve({
path: "/institution",
query: { id: orgId, name: title }
});
window.open(route.href, "_blank");
};
// 固定数据
const dataList = ref([
{
......@@ -576,12 +586,12 @@ onMounted(async () => {
.reslib {
width: 1600px;
height: 1633px;
margin: 0 auto 0px auto;
.reslib-main {
width: 1600px;
height: 1565px;
margin-top: 26px;
}
}
......
......@@ -399,7 +399,8 @@ const reportAuthors = computed(() => {
// 点击报告作者头像,跳转到人物主页
// 与核心研究人员逻辑一致:核心依赖 personId,本页面依赖作者的 id(作为 personId 传入)
const handleClickReportAuthor = async (author) => {
const personId = author?.id;
const personId = author?.personId;
if (!personId) return;
......
......@@ -96,6 +96,7 @@
</div>
<div class="divider" v-if="index !== hearingData.length - 1"></div>
</div>
</div>
</div>
<div class="right-footer">
......@@ -107,6 +108,7 @@
@current-change="handleCurrentChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
......@@ -287,6 +289,12 @@ const handleToReportDetail = item => {
.main-content {
display: flex;
gap: 16px;
height: 100%;
margin-bottom: 100px;
.left {
width: 360px;
......@@ -299,6 +307,7 @@ const handleToReportDetail = item => {
background: rgba(255, 255, 255, 1);
position: relative;
.select-research-box {
width: 360px;
height: 100%;
......@@ -480,14 +489,20 @@ const handleToReportDetail = item => {
.right {
width: 1224px;
height: 1377px;
.card-box {
width: 100%;
height: 1248px;
height: 100%;
display: flex;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
border: 1px solid rgba(234, 236, 238, 1);
......@@ -495,12 +510,15 @@ const handleToReportDetail = item => {
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
background: rgba(255, 255, 255, 1);
.card-content {
width: 1211px;
height: 1067px;
margin-top: 33px;
margin-top: 33px;
margin-left: 37px;
padding-bottom: 27px;
.card-item {
width: 100%;
......@@ -585,21 +603,25 @@ const handleToReportDetail = item => {
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
.right-footer {
margin-top: 35px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
}
......
......@@ -58,6 +58,7 @@
<div class="right">
<div class="card-box">
<div class="card-content">
<div v-for="(item, index) in hearingData" :key="item.id ?? index">
<div class="card-item">
<img class="card-item-img" :src="item.coverImgUrl" alt="report image" />
......@@ -80,9 +81,10 @@
</div>
<div class="divider" v-if="index !== hearingData.length - 1"></div>
</div>
</div>
</div>
</div>
<div class="right-footer">
<div class="info">
{{ hearingData.length }} 篇智库报告
......@@ -92,6 +94,7 @@
@current-change="handlePageChange" :current-page="currentPage" />
</div>
</div>
</div>
</div>
</template>
......@@ -186,6 +189,7 @@ const handlePageChange = page => {
.home-main-footer-main {
margin: 0 auto;
margin-top: 36px;
width: 1600px;
display: flex;
gap: 16px;
......@@ -270,11 +274,11 @@ const handlePageChange = page => {
.right {
width: 1224px;
height: 1377px;
.card-box {
width: 100%;
height: 1134px;
display: flex;
background: rgba(255, 255, 255, 1);
box-sizing: border-box;
......@@ -282,30 +286,35 @@ const handlePageChange = page => {
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(94, 95, 95, 0.1);
padding-right: 36px;
height: 100%;
.card-content {
width: 1211px;
height: 1067px;
margin-top: 33px;
margin-left: 37px;
padding-bottom: 27px;
}
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
.right-footer {
margin-top: 43px;
display: flex;
justify-content: space-between;
.info {
height: 19px;
color: rgba(132, 136, 142, 1);
font-family: Microsoft YaHei;
font-size: 14px;
font-weight: 400;
line-height: 18px;
letter-spacing: 0px;
text-align: left;
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论