提交 3b7cd8b8 authored 作者: 刘宇琪's avatar 刘宇琪

刘宇琪科技暂存当前分支的修改:科技人物功能开发中

上级 a1df7e9b
...@@ -32,12 +32,28 @@ ...@@ -32,12 +32,28 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.27",
"postcss": "^8.5.8",
"sass": "^1.93.3", "sass": "^1.93.3",
"tailwindcss": "^3.4.17",
"unplugin-auto-import": "^0.17.0", "unplugin-auto-import": "^0.17.0",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.0.0" "vite": "^5.0.0"
} }
}, },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@antfu/utils": { "node_modules/@antfu/utils": {
"version": "0.7.10", "version": "0.7.10",
"resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz", "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
...@@ -543,12 +559,44 @@ ...@@ -543,12 +559,44 @@
"integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": { "node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5", "version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@kangc/v-md-editor": { "node_modules/@kangc/v-md-editor": {
"version": "2.3.18", "version": "2.3.18",
"resolved": "https://registry.npmmirror.com/@kangc/v-md-editor/-/v-md-editor-2.3.18.tgz", "resolved": "https://registry.npmmirror.com/@kangc/v-md-editor/-/v-md-editor-2.3.18.tgz",
...@@ -2062,6 +2110,13 @@ ...@@ -2062,6 +2110,13 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT"
},
"node_modules/anymatch": { "node_modules/anymatch": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz", "resolved": "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz",
...@@ -2089,6 +2144,13 @@ ...@@ -2089,6 +2144,13 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz",
...@@ -2185,6 +2247,43 @@ ...@@ -2185,6 +2247,43 @@
"node": ">= 4.5.0" "node": ">= 4.5.0"
} }
}, },
"node_modules/autoprefixer": {
"version": "10.4.27",
"resolved": "https://registry.npmmirror.com/autoprefixer/-/autoprefixer-10.4.27.tgz",
"integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"browserslist": "^4.28.1",
"caniuse-lite": "^1.0.30001774",
"fraction.js": "^5.3.4",
"picocolors": "^1.1.1",
"postcss-value-parser": "^4.2.0"
},
"bin": {
"autoprefixer": "bin/autoprefixer"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"peerDependencies": {
"postcss": "^8.1.0"
}
},
"node_modules/axios": { "node_modules/axios": {
"version": "1.12.2", "version": "1.12.2",
"resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz", "resolved": "https://registry.npmmirror.com/axios/-/axios-1.12.2.tgz",
...@@ -2232,6 +2331,19 @@ ...@@ -2232,6 +2331,19 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/baseline-browser-mapping": {
"version": "2.10.0",
"resolved": "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
"integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/binary-extensions/-/binary-extensions-2.3.0.tgz",
...@@ -2277,6 +2389,40 @@ ...@@ -2277,6 +2389,40 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/cache-base": { "node_modules/cache-base": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/cache-base/-/cache-base-1.0.1.tgz",
...@@ -2316,6 +2462,37 @@ ...@@ -2316,6 +2462,37 @@
"integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001776",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz",
"integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": { "node_modules/chalk": {
"version": "2.4.2", "version": "2.4.2",
"resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz",
...@@ -2533,6 +2710,19 @@ ...@@ -2533,6 +2710,19 @@
"layout-base": "^1.0.0" "layout-base": "^1.0.0"
} }
}, },
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cssfilter": { "node_modules/cssfilter": {
"version": "0.0.10", "version": "0.0.10",
"resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz", "resolved": "https://registry.npmmirror.com/cssfilter/-/cssfilter-0.0.10.tgz",
...@@ -3155,6 +3345,13 @@ ...@@ -3155,6 +3345,13 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/diff": { "node_modules/diff": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmmirror.com/diff/-/diff-5.2.0.tgz", "resolved": "https://registry.npmmirror.com/diff/-/diff-5.2.0.tgz",
...@@ -3176,6 +3373,13 @@ ...@@ -3176,6 +3373,13 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz", "resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.3.0.tgz",
...@@ -3227,6 +3431,13 @@ ...@@ -3227,6 +3431,13 @@
"echarts": "^5.0.1" "echarts": "^5.0.1"
} }
}, },
"node_modules/electron-to-chromium": {
"version": "1.5.307",
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
"integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
"dev": true,
"license": "ISC"
},
"node_modules/element-plus": { "node_modules/element-plus": {
"version": "2.11.2", "version": "2.11.2",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.2.tgz", "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.11.2.tgz",
...@@ -3355,6 +3566,16 @@ ...@@ -3355,6 +3566,16 @@
"@esbuild/win32-x64": "0.21.5" "@esbuild/win32-x64": "0.21.5"
} }
}, },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmmirror.com/escape-html/-/escape-html-1.0.3.tgz",
...@@ -3528,6 +3749,24 @@ ...@@ -3528,6 +3749,24 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
},
"peerDependencies": {
"picomatch": "^3 || ^4"
},
"peerDependenciesMeta": {
"picomatch": {
"optional": true
}
}
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz",
...@@ -3586,6 +3825,20 @@ ...@@ -3586,6 +3825,20 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/fraction.js": {
"version": "5.3.4",
"resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
"integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/rawify"
}
},
"node_modules/fragment-cache": { "node_modules/fragment-cache": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz", "resolved": "https://registry.npmmirror.com/fragment-cache/-/fragment-cache-0.2.1.tgz",
...@@ -4330,6 +4583,16 @@ ...@@ -4330,6 +4583,16 @@
"integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==", "integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/jiti": {
"version": "1.21.7",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz",
...@@ -4425,6 +4688,26 @@ ...@@ -4425,6 +4688,26 @@
"integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antonk52"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/linkify-it": { "node_modules/linkify-it": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz", "resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.0.tgz",
...@@ -5238,6 +5521,18 @@ ...@@ -5238,6 +5521,18 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
...@@ -5311,6 +5606,13 @@ ...@@ -5311,6 +5606,13 @@
"license": "MIT", "license": "MIT",
"optional": true "optional": true
}, },
"node_modules/node-releases": {
"version": "2.0.27",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
"integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
"dev": true,
"license": "MIT"
},
"node_modules/non-layered-tidy-tree-layout": { "node_modules/non-layered-tidy-tree-layout": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz",
...@@ -5333,6 +5635,16 @@ ...@@ -5333,6 +5635,16 @@
"integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-copy": { "node_modules/object-copy": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz", "resolved": "https://registry.npmmirror.com/object-copy/-/object-copy-0.1.0.tgz",
...@@ -5384,6 +5696,16 @@ ...@@ -5384,6 +5696,16 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/object-hash": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/object-visit": { "node_modules/object-visit": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/object-visit/-/object-visit-1.0.1.tgz",
...@@ -5551,6 +5873,16 @@ ...@@ -5551,6 +5873,16 @@
"@vue/devtools-kit": "^7.7.9" "@vue/devtools-kit": "^7.7.9"
} }
}, },
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/pkg-types": { "node_modules/pkg-types": {
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
...@@ -5573,9 +5905,9 @@ ...@@ -5573,9 +5905,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.8",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
...@@ -5600,6 +5932,133 @@ ...@@ -5600,6 +5932,133 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/postcss-import": {
"version": "15.1.0",
"resolved": "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-js": {
"version": "4.1.0",
"resolved": "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.1.0.tgz",
"integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"lilconfig": "^3.0.0",
"yaml": "^2.3.4"
},
"engines": {
"node": ">= 14"
},
"peerDependencies": {
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"postcss": {
"optional": true
},
"ts-node": {
"optional": true
}
}
},
"node_modules/postcss-nested": {
"version": "6.2.0",
"resolved": "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"postcss-selector-parser": "^6.1.1"
},
"engines": {
"node": ">=12.0"
},
"peerDependencies": {
"postcss": "^8.2.14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.1.2",
"resolved": "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/prismjs": { "node_modules/prismjs": {
"version": "1.30.0", "version": "1.30.0",
"resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz", "resolved": "https://registry.npmmirror.com/prismjs/-/prismjs-1.30.0.tgz",
...@@ -5662,6 +6121,26 @@ ...@@ -5662,6 +6121,26 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
}
},
"node_modules/read-cache/node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz", "resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-3.6.0.tgz",
...@@ -6278,6 +6757,39 @@ ...@@ -6278,6 +6757,39 @@
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/sucrase": {
"version": "3.35.1",
"resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz",
"integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1",
"tinyglobby": "^0.2.11",
"ts-interface-checker": "^0.1.9"
},
"bin": {
"sucrase": "bin/sucrase",
"sucrase-node": "bin/sucrase-node"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/sucrase/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/superjson": { "node_modules/superjson": {
"version": "2.2.6", "version": "2.2.6",
"resolved": "https://mirrors.huaweicloud.com/repository/npm/superjson/-/superjson-2.2.6.tgz", "resolved": "https://mirrors.huaweicloud.com/repository/npm/superjson/-/superjson-2.2.6.tgz",
...@@ -6315,6 +6827,97 @@ ...@@ -6315,6 +6827,97 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/tailwindcss": {
"version": "3.4.17",
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.6.0",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.3.2",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.21.6",
"lilconfig": "^3.1.3",
"micromatch": "^4.0.8",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.1.1",
"postcss": "^8.4.47",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.2",
"postcss-nested": "^6.2.0",
"postcss-selector-parser": "^6.1.2",
"resolve": "^1.22.8",
"sucrase": "^3.35.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/tailwindcss/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
}
},
"node_modules/thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz",
"integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
"picomatch": "^4.0.3"
},
"engines": {
"node": ">=12.0.0"
},
"funding": {
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
"node_modules/to-object-path": { "node_modules/to-object-path": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz", "resolved": "https://registry.npmmirror.com/to-object-path/-/to-object-path-0.3.0.tgz",
...@@ -6413,6 +7016,13 @@ ...@@ -6413,6 +7016,13 @@
"node": ">=6.10" "node": ">=6.10"
} }
}, },
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz",
...@@ -6703,6 +7313,37 @@ ...@@ -6703,6 +7313,37 @@
"yarn": "*" "yarn": "*"
} }
}, },
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/urix": { "node_modules/urix": {
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz", "resolved": "https://registry.npmmirror.com/urix/-/urix-0.1.0.tgz",
...@@ -6719,6 +7360,13 @@ ...@@ -6719,6 +7360,13 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/uuid": { "node_modules/uuid": {
"version": "9.0.1", "version": "9.0.1",
"resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz", "resolved": "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz",
...@@ -6912,6 +7560,22 @@ ...@@ -6912,6 +7560,22 @@
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/yaml": {
"version": "2.8.2",
"resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.8.2.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/zrender": { "node_modules/zrender": {
"version": "5.6.1", "version": "5.6.1",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz", "resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
......
...@@ -41,7 +41,10 @@ ...@@ -41,7 +41,10 @@
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.0.0", "@vitejs/plugin-vue": "^5.0.0",
"autoprefixer": "^10.4.27",
"postcss": "^8.5.8",
"sass": "^1.93.3", "sass": "^1.93.3",
"tailwindcss": "^3.4.17",
"unplugin-auto-import": "^0.17.0", "unplugin-auto-import": "^0.17.0",
"unplugin-vue-components": "^0.26.0", "unplugin-vue-components": "^0.26.0",
"vite": "^5.0.0" "vite": "^5.0.0"
......
...@@ -77,4 +77,7 @@ ...@@ -77,4 +77,7 @@
line-height: 30px; line-height: 30px;
letter-spacing: 0px; letter-spacing: 0px;
text-align: left; text-align: left;
} }
@tailwind base;
@tailwind components;
@tailwind utilities;
/**
* Mock API - 法案数据模拟接口
* 所有数据集中管理,方便后续替换为真实 API
*/
// ===== 筛选项配置数据 =====
const DOMAIN_OPTIONS = [
{ id: 'all', label: '全部领域' },
{ id: 'ai', label: '人工智能' },
{ id: 'ic', label: '集成电路' },
{ id: 'quantum', label: '量子科技' },
{ id: 'network', label: '新一代通信网络' },
{ id: 'biotech', label: '生物技术' },
{ id: 'ocean', label: '海洋' },
{ id: 'deep-sea', label: '深海' },
{ id: 'polar', label: '极地' },
{ id: 'nuclear', label: '核' },
{ id: 'other', label: '其他' },
]
const TIME_OPTIONS = [
{ id: 'all', label: '全部时间' },
{ id: '2025', label: '2025年' },
{ id: '2024', label: '2024年' },
{ id: '2023', label: '2023年' },
{ id: '2022', label: '2022年' },
{ id: '2021', label: '2021年' },
{ id: 'earlier', label: '更早' },
]
// ===== 进度阶段配置 =====
const PROGRESS_STAGES = ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过']
// ===== 法案模拟数据 =====
const MOCK_BILLS = [
{
id: 1,
billNumber: 'H.R.1',
billSerial: '第1234',
title: '国家人工智能安全法案',
titleEn: 'One Big Beautiful Bill Act',
importance: '特别重大',
proposer: { name: '乔迪', avatar: null },
committee: '众议院科技委员会',
relatedDomains: ['人工智能', '集成电路'],
latestMotion: '2025.07.04',
latestMotionResult: '成',
currentStage: 5,
congressSession: '119th',
},
{
id: 2,
billNumber: 'H.R.2648',
billSerial: '第2648',
title: '半导体供应链安全法',
titleEn: 'CHIPS and Science Act Extension',
importance: '重大',
proposer: { name: '佩洛西', avatar: null },
committee: '参议院商务委员会',
relatedDomains: ['集成电路'],
latestMotion: '2025.06.18',
latestMotionResult: '通过',
currentStage: 3,
congressSession: '119th',
},
{
id: 3,
billNumber: 'S.1052',
billSerial: '第1052',
title: '量子计算出口管制法',
titleEn: 'Quantum Computing Export Control Act',
importance: '关注',
proposer: { name: '沃纳', avatar: null },
committee: '参议院情报委员会',
relatedDomains: ['量子科技', '人工智能'],
latestMotion: '2025.05.22',
latestMotionResult: '通过',
currentStage: 1,
congressSession: '119th',
},
{
id: 4,
billNumber: 'H.R.4217',
billSerial: '第4217',
title: '清洁能源核技术推进法案',
titleEn: 'Clean Nuclear Energy Advancement Act',
importance: '一般',
proposer: { name: '格兰姆', avatar: null },
committee: '众议院能源委员会',
relatedDomains: ['核', '生物技术'],
latestMotion: '2025.04.11',
latestMotionResult: '提出',
currentStage: 0,
congressSession: '119th',
},
{
id: 5,
billNumber: 'S.3301',
billSerial: '第3301',
title: '深海战略资源保护法',
titleEn: 'Deep Sea Strategic Resources Protection Act',
importance: '特别重大',
proposer: { name: '卢比奥', avatar: null },
committee: '参议院外交委员会',
relatedDomains: ['深海', '海洋'],
latestMotion: '2025.07.01',
latestMotionResult: '通过',
currentStage: 4,
congressSession: '119th',
},
]
// ===== 模拟网络延迟 =====
function delay(ms = 300) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
// ===== API 接口函数 =====
/**
* 获取筛选项配置
*/
export async function fetchFilterOptions() {
await delay(100)
return {
code: 200,
data: {
domains: DOMAIN_OPTIONS,
timeRanges: TIME_OPTIONS,
},
}
}
/**
* 获取进度阶段配置
*/
export async function fetchProgressStages() {
await delay(50)
return {
code: 200,
data: PROGRESS_STAGES,
}
}
/**
* 获取法案列表
* @param {Object} params - 查询参数
* @param {string} params.keyword - 搜索关键词
* @param {string[]} params.domains - 选中的领域
* @param {string[]} params.timeRanges - 选中的时间范围
* @param {boolean} params.chinaRelatedOnly - 只看涉华法案
* @param {string} params.sortBy - 排序方式
*/
export async function fetchBills(params = {}) {
await delay(400)
let result = [...MOCK_BILLS]
// 模拟关键词过滤
if (params.keyword) {
const kw = params.keyword.toLowerCase()
result = result.filter(
(b) =>
b.title.toLowerCase().includes(kw) ||
b.titleEn.toLowerCase().includes(kw) ||
b.billNumber.toLowerCase().includes(kw)
)
}
// 模拟领域过滤
if (params.domains && params.domains.length > 0 && !params.domains.includes('all')) {
result = result.filter((b) =>
b.relatedDomains.some((d) => params.domains.includes(d))
)
}
return {
code: 200,
data: {
list: result,
total: result.length,
},
}
}
/**
* 获取排序选项
*/
export async function fetchSortOptions() {
await delay(50)
return {
code: 200,
data: [
{ value: 'publishTime', label: '按发布时间' },
{ value: 'importance', label: '按重要程度' },
{ value: 'progress', label: '按进展阶段' },
],
}
}
<template>
<div class="bill-card">
<div class="bill-card-inner">
<!-- Left: Document preview -->
<div class="bill-card-preview">
<DocumentPreview :bill-number="bill.billNumber" :bill-serial="bill.billSerial" />
</div>
<!-- Right: Details -->
<div class="bill-card-detail">
<!-- Upper: title + tag + description -->
<div class="bill-card-upper">
<!-- Title row -->
<div class="bill-card-title-row">
<h3 class="bill-card-title">{{ bill.title }}</h3>
<ImportanceBadge :label="bill.importance" />
</div>
<!-- Description -->
<p class="bill-card-desc">{{ bill.titleEn }}</p>
</div>
<!-- Divider -->
<div class="bill-card-divider"></div>
<!-- Meta grid area -->
<div class="bill-card-meta">
<div class="meta-row">
<span class="meta-label">提案人:</span>
<span class="meta-value">
<UserAvatar :name="bill.proposer.name" :avatar="bill.proposer.avatar" />
</span>
</div>
<div class="meta-row">
<span class="meta-label">委员会:</span>
<span class="meta-value">{{ bill.committee }}</span>
</div>
<div class="meta-row">
<span class="meta-label">相关领域:</span>
<div class="meta-tags">
<TagBadge v-for="domain in bill.relatedDomains" :key="domain" variant="blue">{{ domain }}</TagBadge>
<TagBadge v-if="bill.chinaRelated" variant="red">涉华</TagBadge>
</div>
</div>
<div class="meta-row">
<span class="meta-label">最新动议:</span>
<span class="meta-value">{{ bill.latestMotion }}</span>
</div>
<div class="meta-row meta-row-progress">
<ProgressBar :stages="progressStages" :current="bill.currentStage" />
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import DocumentPreview from './DocumentPreview.vue'
import ImportanceBadge from './ImportanceBadge.vue'
import UserAvatar from './UserAvatar.vue'
import TagBadge from './TagBadge.vue'
import ProgressBar from './ProgressBar.vue'
defineProps({
bill: { type: Object, required: true },
progressStages: { type: Array, default: () => ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过'] },
})
</script>
<style scoped>
.bill-card {
width: 1224px;
max-width: 100%;
height: 320px;
background: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-sizing: border-box;
padding: 12px 16px;
cursor: pointer;
overflow: hidden;
transition: box-shadow 0.2s;
}
.bill-card:hover {
box-shadow: 0px 0px 24px 0px rgba(25, 69, 130, 0.16);
}
.bill-card-inner {
display: flex;
flex-direction: row;
gap: 13px;
align-items: center;
height: 100%;
}
.bill-card-preview {
flex-shrink: 0;
width: 240px;
height: 296px;
}
.bill-card-detail {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
height: 100%;
}
.bill-card-upper {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
}
.bill-card-title-row {
display: flex;
align-items: flex-start;
gap: 20px;
width: 100%;
}
.bill-card-title {
font-size: 20px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
margin: 0;
line-height: 26px;
flex: 1;
min-width: 0;
}
.bill-card-desc {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
margin: 0;
line-height: 24px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.bill-card-divider {
width: 100%;
height: 1px;
background: rgba(234, 236, 238, 1);
flex-shrink: 0;
}
.bill-card-meta {
width: 100%;
flex: 1;
position: relative;
min-height: 0;
}
.meta-row {
display: flex;
align-items: center;
position: absolute;
left: 0;
width: 100%;
}
.meta-row:nth-child(1) { top: 0; }
.meta-row:nth-child(2) { top: 36px; }
.meta-row:nth-child(3) { top: 72px; }
.meta-row:nth-child(4) { top: 108px; }
.meta-row:nth-child(5) { top: 144px; }
.meta-label {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
letter-spacing: 1px;
line-height: 24px;
white-space: nowrap;
flex-shrink: 0;
width: 100px;
}
.meta-value {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 24px;
}
.meta-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: center;
}
.meta-row-progress {
left: 0;
right: 0;
width: 100%;
}
.meta-row-progress :deep(.progress-bar) {
width: 100%;
}
.meta-progress {
flex: 1;
min-width: 0;
}
</style>
<template>
<div class="bill-list">
<!-- Loading -->
<div v-if="loading" class="bill-list-loading">
<div v-for="i in 3" :key="i" class="bill-list-skeleton">
<div class="skeleton-left"></div>
<div class="skeleton-right">
<div class="skeleton-line w30"></div>
<div class="skeleton-line w50"></div>
<div class="skeleton-divider"></div>
<div class="skeleton-line w60"></div>
<div class="skeleton-line w40"></div>
</div>
</div>
</div>
<!-- Empty -->
<div v-else-if="bills.length === 0" class="bill-list-empty">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#bfbfbf" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
<p>暂无相关法案</p>
</div>
<!-- Cards -->
<template v-else>
<BillCard
v-for="bill in bills"
:key="bill.id"
:bill="bill"
:progress-stages="progressStages"
/>
</template>
</div>
</template>
<script setup>
import BillCard from './BillCard.vue'
defineProps({
bills: { type: Array, required: true },
loading: { type: Boolean, default: false },
progressStages: { type: Array, default: () => ['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过'] },
})
</script>
<style scoped>
.bill-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.bill-list-loading {
display: flex;
flex-direction: column;
gap: 16px;
}
.bill-list-skeleton {
display: flex;
gap: 24px;
background: #fff;
border: 1px solid #ebedf0;
border-radius: 8px;
padding: 24px;
}
.skeleton-left {
width: 120px;
height: 190px;
background: #f0f0f0;
border-radius: 4px;
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-right {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
}
.skeleton-line {
height: 16px;
background: #f0f0f0;
border-radius: 4px;
animation: pulse 1.5s ease-in-out infinite;
}
.skeleton-line.w30 { width: 30%; }
.skeleton-line.w40 { width: 40%; }
.skeleton-line.w50 { width: 50%; }
.skeleton-line.w60 { width: 60%; }
.skeleton-divider {
height: 1px;
background: #ebedf0;
}
.bill-list-empty {
background: #fff;
border: 1px solid #ebedf0;
border-radius: 8px;
padding: 48px;
text-align: center;
color: #bfbfbf;
font-size: 13px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
<template>
<div class="bill-tracker-root">
<!-- Top bar -->
<div class="bt-topbar">
<TopBar
:keyword="filters.keyword"
:china-related-only="filters.chinaRelatedOnly"
:sort-by="filters.sortBy"
:sort-options="sortOptions"
@update:keyword="updateKeyword"
@toggle-china-related="toggleChinaRelated"
@update:sort="updateSort"
/>
</div>
<!-- Main content area -->
<div class="bt-main">
<!-- Sidebar filter panel -->
<div class="bt-sidebar">
<SidebarFilter
:domains="filterOptions.domains"
:time-ranges="filterOptions.timeRanges"
:selected-domains="filters.selectedDomains"
:selected-time-ranges="filters.selectedTimeRanges"
@toggle-domain="toggleDomain"
@toggle-time="toggleTimeRange"
/>
</div>
<!-- Bill list -->
<div class="bt-list-area">
<!-- Total count + pagination header -->
<div class="bt-list-header">
<span class="bt-total">{{ bills.length }}项法案</span>
</div>
<BillList
:bills="bills"
:loading="loading"
:progress-stages="progressStages"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import TopBar from './TopBar.vue'
import SidebarFilter from './SidebarFilter.vue'
import BillList from './BillList.vue'
import { useBills } from '../composables/useBills.js'
import { fetchProgressStages } from '../api/bills.js'
const {
bills,
loading,
filters,
filterOptions,
sortOptions,
init,
updateKeyword,
toggleDomain,
toggleTimeRange,
toggleChinaRelated,
updateSort,
} = useBills()
const progressStages = ref(['提出', '众议院通过', '参议院通过', '分歧协调', '提交总统', '法案通过'])
onMounted(async () => {
const stagesRes = await fetchProgressStages()
if (stagesRes.code === 200) {
progressStages.value = stagesRes.data
}
await init()
})
</script>
<style scoped>
.bill-tracker-root {
width: 100%;
font-family: 'Source Han Sans CN', 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif;
-webkit-font-smoothing: antialiased;
color: rgba(59, 65, 75, 1);
background: rgba(247, 248, 249, 1);
min-height: 100vh;
}
.bt-topbar {
padding: 12px 24px;
background: #fff;
border-bottom: 1px solid rgba(234, 236, 238, 1);
}
.bt-main {
display: flex;
padding: 16px 24px;
gap: 16px;
}
.bt-sidebar {
flex-shrink: 0;
}
.bt-list-area {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 16px;
}
.bt-list-header {
display: flex;
align-items: center;
}
.bt-total {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
line-height: 24px;
}
</style>
<template>
<div class="doc-preview">
<!-- Document image area -->
<div class="doc-image">
<div class="doc-frame">
<!-- Seal icon -->
<div class="doc-seal">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="7" stroke="#999" stroke-width="0.8" fill="none"/>
<circle cx="8" cy="8" r="4" stroke="#999" stroke-width="0.5" fill="none"/>
<line x1="8" y1="1" x2="8" y2="3" stroke="#999" stroke-width="0.5"/>
<line x1="8" y1="13" x2="8" y2="15" stroke="#999" stroke-width="0.5"/>
<line x1="1" y1="8" x2="3" y2="8" stroke="#999" stroke-width="0.5"/>
<line x1="13" y1="8" x2="15" y2="8" stroke="#999" stroke-width="0.5"/>
</svg>
</div>
<!-- Simulated text -->
<div class="doc-text">
<div class="doc-line-italic">One Hundred Nineteenth Congress</div>
<div class="doc-line-small">of the</div>
<div class="doc-line-italic">United States of America</div>
<div class="doc-body-lines">
<div class="doc-body-line" style="width: 100%"></div>
<div class="doc-body-line" style="width: 83%"></div>
<div class="doc-body-line" style="width: 92%"></div>
<div class="doc-body-line" style="width: 75%"></div>
<div class="doc-body-line" style="width: 100%"></div>
<div class="doc-body-line" style="width: 67%"></div>
</div>
</div>
</div>
</div>
<!-- Overlay info -->
<div class="doc-info">
<div class="doc-number">{{ billNumber }}</div>
<div class="doc-serial">{{ billSerial }}</div>
</div>
</div>
</template>
<script setup>
defineProps({
billNumber: { type: String, required: true },
billSerial: { type: String, required: true },
})
</script>
<style scoped>
.doc-preview {
width: 240px;
height: 296px;
position: relative;
}
.doc-image {
width: 222px;
height: 296px;
position: absolute;
left: 13px;
top: 0;
}
.doc-frame {
width: 100%;
height: 100%;
background: #fff;
border: 1px solid #ebedf0;
border-radius: 4px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 12px 12px;
overflow: hidden;
box-sizing: border-box;
}
.doc-seal {
position: absolute;
top: 8px;
left: 8px;
}
.doc-text {
margin-top: 20px;
width: 100%;
display: flex;
flex-direction: column;
gap: 3px;
padding: 0 4px;
}
.doc-line-italic {
font-size: 7px;
color: rgba(95, 101, 108, 1);
text-align: center;
font-style: italic;
line-height: 1.4;
}
.doc-line-small {
font-size: 6px;
color: rgba(95, 101, 108, 1);
text-align: center;
line-height: 1.4;
}
.doc-body-lines {
margin-top: 10px;
border-top: 1px solid #ebedf0;
padding-top: 8px;
display: flex;
flex-direction: column;
gap: 5px;
}
.doc-body-line {
height: 2.5px;
background: #e0e0e0;
border-radius: 1px;
margin: 0 auto;
}
.doc-info {
position: absolute;
left: 0;
bottom: 0;
width: 240px;
height: 78px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-end;
padding-bottom: 4px;
background: linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.95) 40%);
border-radius: 0 0 4px 4px;
}
.doc-number {
font-size: 20px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
line-height: 36px;
text-align: center;
}
.doc-serial {
font-size: 16px;
font-weight: 400;
color: rgba(95, 101, 108, 1);
line-height: 24px;
text-align: center;
}
</style>
<template>
<span class="importance-badge" :class="'importance-' + level">
<span class="importance-dot"></span>
{{ label }}
</span>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
label: { type: String, required: true },
})
const level = computed(() => {
if (props.label.includes('特别重大') || props.label.includes('高')) return 'high'
if (props.label.includes('重大') || props.label.includes('中') || props.label.includes('橙')) return 'medium'
if (props.label.includes('关注') || props.label.includes('黄')) return 'warning'
return 'low'
})
</script>
<style scoped>
.importance-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 20px;
font-size: 16px;
font-weight: 400;
white-space: nowrap;
flex-shrink: 0;
line-height: 24px;
}
.importance-dot {
display: inline-block;
width: 4px;
height: 4px;
border-radius: 50%;
flex-shrink: 0;
}
/* High importance - Red */
.importance-high {
background: rgba(206, 79, 81, 0.1);
color: rgba(206, 79, 81, 1);
}
.importance-high .importance-dot {
background: rgba(206, 79, 81, 1);
}
/* Medium importance - Orange */
.importance-medium {
background: rgba(255, 149, 77, 0.1);
color: rgba(255, 149, 77, 1);
}
.importance-medium .importance-dot {
background: rgba(255, 149, 77, 1);
}
/* Warning importance - Yellow */
.importance-warning {
background: rgba(232, 189, 11, 0.1);
color: rgba(232, 189, 11, 1);
}
.importance-warning .importance-dot {
background: rgba(232, 189, 11, 1);
}
/* Low importance - Green */
.importance-low {
background: rgba(33, 129, 57, 0.1);
color: rgba(33, 129, 57, 1);
}
.importance-low .importance-dot {
background: rgba(33, 129, 57, 1);
}
</style>
<template>
<div class="progress-bar">
<span class="progress-title">法案进展:</span>
<div class="progress-steps">
<div
v-for="(stage, index) in stages"
:key="stage"
class="progress-step"
:class="{
active: index <= current,
last: index === stages.length - 1,
'last-active': index === stages.length - 1 && index <= current,
}"
>
<svg
v-if="index < stages.length - 1"
class="step-shape"
:viewBox="'0 0 120 26'"
preserveAspectRatio="none"
>
<path
:d="index === 0 ? 'M4,0 L108,0 L120,13 L108,26 L4,26 C1.8,26 0,24.2 0,22 L0,4 C0,1.8 1.8,0 4,0 Z' : 'M0,0 L108,0 L120,13 L108,26 L0,26 L12,13 Z'"
:fill="index <= current ? '#E6E7E8' : '#F7F8F9'"
:stroke="index <= current ? '#D0D1D2' : '#E6E7E8'"
stroke-width="0.5"
/>
</svg>
<div
v-else
class="step-pill"
:class="{ filled: index <= current }"
></div>
<span class="step-label" :class="{ 'label-white': index === stages.length - 1 && index <= current }">
{{ stage }}
</span>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
stages: { type: Array, required: true },
current: { type: Number, default: 0 },
})
</script>
<style scoped>
.progress-bar {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
height: 26px;
}
.progress-title {
font-size: 16px;
font-weight: 700;
color: rgba(59, 65, 75, 1);
letter-spacing: 1px;
line-height: 24px;
white-space: nowrap;
flex-shrink: 0;
}
.progress-steps {
display: flex;
align-items: center;
height: 26px;
flex: 1;
min-width: 0;
}
.progress-step {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 26px;
flex: 1;
min-width: 0;
}
.progress-step.last {
flex: 0 0 auto;
min-width: 90px;
}
.step-shape {
display: block;
width: 100%;
height: 26px;
}
.step-label {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
font-size: 13px;
font-weight: 400;
color: rgba(132, 136, 142, 1);
white-space: nowrap;
pointer-events: none;
line-height: 1;
}
.progress-step.active .step-label {
color: rgba(59, 65, 75, 1);
}
.step-label.label-white {
color: #fff;
}
.step-pill {
width: 100%;
min-width: 90px;
height: 26px;
border-radius: 13px;
background: #E6E7E8;
}
.step-pill.filled {
background: rgba(59, 65, 75, 1);
}
.progress-step.last .step-label {
color: rgba(132, 136, 142, 1);
}
.progress-step.last-active .step-label {
color: #fff;
}
</style>
<template>
<aside class="sidebar-filter">
<!-- Domain filter -->
<div class="sf-section">
<div class="sf-title-row">
<span class="sf-title-bar"></span>
<span class="sf-title-text">科技领域</span>
</div>
<div class="sf-options">
<label v-for="opt in domains" :key="opt.id" class="sf-label">
<span
class="sf-checkbox-icon"
:class="{ checked: selectedDomains.includes(opt.id) }"
@click.prevent="$emit('toggle-domain', opt.id)"
>
<svg v-if="selectedDomains.includes(opt.id)" width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect width="14" height="14" rx="4" fill="#055FC2"/>
<path d="M3.5 7L6 9.5L10.5 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span v-else class="sf-checkbox-empty"></span>
</span>
<span class="sf-text" @click="$emit('toggle-domain', opt.id)">{{ opt.label }}</span>
</label>
</div>
</div>
<!-- Time range filter -->
<div class="sf-section">
<div class="sf-title-row">
<span class="sf-title-bar"></span>
<span class="sf-title-text">发布时间</span>
</div>
<div class="sf-options">
<label v-for="opt in timeRanges" :key="opt.id" class="sf-label">
<span
class="sf-checkbox-icon"
:class="{ checked: selectedTimeRanges.includes(opt.id) }"
@click.prevent="$emit('toggle-time', opt.id)"
>
<svg v-if="selectedTimeRanges.includes(opt.id)" width="14" height="14" viewBox="0 0 14 14" fill="none">
<rect width="14" height="14" rx="4" fill="#055FC2"/>
<path d="M3.5 7L6 9.5L10.5 5" stroke="#fff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span v-else class="sf-checkbox-empty"></span>
</span>
<span class="sf-text" @click="$emit('toggle-time', opt.id)">{{ opt.label }}</span>
</label>
</div>
</div>
</aside>
</template>
<script setup>
defineProps({
domains: { type: Array, default: () => [] },
timeRanges: { type: Array, default: () => [] },
selectedDomains: { type: Array, default: () => ['all'] },
selectedTimeRanges: { type: Array, default: () => ['all'] },
})
defineEmits(['toggle-domain', 'toggle-time'])
</script>
<style scoped>
.sidebar-filter {
width: 360px;
background: #fff;
border-radius: 10px;
border: 1px solid rgba(234, 236, 238, 1);
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
box-sizing: border-box;
padding: 16px 0 24px 0;
display: flex;
flex-direction: column;
gap: 16px;
align-items: flex-start;
overflow: hidden;
}
.sf-section {
width: 100%;
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
}
.sf-title-row {
display: flex;
align-items: center;
gap: 17px;
width: 100%;
padding: 0 0 0 0;
box-sizing: border-box;
}
.sf-title-bar {
display: inline-block;
width: 8px;
height: 16px;
background: rgba(5, 95, 194, 1);
border-radius: 0 2px 2px 0;
flex-shrink: 0;
}
.sf-title-text {
font-size: 16px;
font-weight: 700;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
color: rgba(5, 95, 194, 1);
letter-spacing: 1px;
line-height: 24px;
}
.sf-options {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px 4px;
align-content: flex-start;
align-items: flex-start;
padding: 0 0 0 24px;
width: 100%;
box-sizing: border-box;
}
.sf-label {
width: 160px;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
flex-shrink: 0;
}
.sf-checkbox-icon {
display: flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
flex-shrink: 0;
cursor: pointer;
}
.sf-checkbox-empty {
display: block;
width: 14px;
height: 14px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
box-sizing: border-box;
background: #fff;
}
.sf-text {
font-size: 16px;
font-weight: 400;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
color: rgba(95, 101, 108, 1);
line-height: 24px;
cursor: pointer;
white-space: nowrap;
}
.sf-label:hover .sf-text {
color: rgba(59, 65, 75, 1);
}
</style>
<template>
<span class="tag-badge" :class="'tag-' + variant">
<slot />
</span>
</template>
<script setup>
defineProps({
variant: {
type: String,
default: 'blue',
validator: (v) => ['blue', 'red'].includes(v),
},
})
</script>
<style scoped>
.tag-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 2px 8px;
border-radius: 4px;
font-size: 14px;
font-weight: 400;
line-height: 14px;
white-space: nowrap;
}
/* Blue variant - matching design token 业务系统主色10 */
.tag-blue {
background: rgba(231, 243, 255, 1);
color: rgba(5, 95, 194, 1);
}
/* Red variant - matching design token 业务系统红色10 */
.tag-red {
background: rgba(206, 79, 81, 0.1);
color: rgba(206, 79, 81, 1);
}
</style>
<template>
<header class="topbar">
<!-- Search input -->
<div class="topbar-search">
<input
type="text"
:value="keyword"
@input="$emit('update:keyword', $event.target.value)"
placeholder="搜索法案"
class="topbar-search-input"
/>
<svg class="topbar-search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</div>
<!-- Spacer -->
<div class="topbar-spacer"></div>
<!-- Sort dropdown -->
<div class="topbar-sort" ref="sortDropdownRef">
<button class="topbar-sort-btn" @click="showSortDropdown = !showSortDropdown">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 4h13M3 8h9m-9 4h6m4 0l4-4m0 0l4 4m-4-4v12" />
</svg>
{{ currentSortLabel }}
</button>
<div v-if="showSortDropdown" class="topbar-sort-dropdown">
<button
v-for="opt in sortOptions"
:key="opt.value"
@click="selectSort(opt.value)"
class="topbar-sort-option"
:class="{ active: sortBy === opt.value }"
>
{{ opt.label }}
</button>
</div>
</div>
<!-- China-related checkbox -->
<label class="topbar-checkbox-label">
<input
type="checkbox"
:checked="chinaRelatedOnly"
@change="$emit('toggle-china-related')"
class="topbar-checkbox"
/>
<span>只看涉华法案</span>
</label>
</header>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const props = defineProps({
keyword: { type: String, default: '' },
chinaRelatedOnly: { type: Boolean, default: true },
sortBy: { type: String, default: 'publishTime' },
sortOptions: { type: Array, default: () => [] },
})
const emit = defineEmits(['update:keyword', 'toggle-china-related', 'update:sort'])
const showSortDropdown = ref(false)
const sortDropdownRef = ref(null)
const currentSortLabel = computed(() => {
const opt = props.sortOptions.find((o) => o.value === props.sortBy)
return opt ? opt.label : '发布时间'
})
function selectSort(value) {
emit('update:sort', value)
showSortDropdown.value = false
}
function handleClickOutside(e) {
if (sortDropdownRef.value && !sortDropdownRef.value.contains(e.target)) {
showSortDropdown.value = false
}
}
onMounted(() => document.addEventListener('click', handleClickOutside))
onUnmounted(() => document.removeEventListener('click', handleClickOutside))
</script>
<style scoped>
.topbar {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.topbar-search {
position: relative;
flex: 1;
min-width: 180px;
max-width: 400px;
}
.topbar-search-input {
width: 100%;
height: 32px;
padding: 0 32px 0 12px;
font-size: 14px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
outline: none;
color: rgba(59, 65, 75, 1);
background: #fff;
box-sizing: border-box;
line-height: 22px;
}
.topbar-search-input::placeholder {
color: rgba(132, 136, 142, 1);
}
.topbar-search-input:focus {
border-color: rgba(5, 95, 194, 1);
box-shadow: 0 0 0 2px rgba(5, 95, 194, 0.1);
}
.topbar-search-icon {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
color: rgba(132, 136, 142, 1);
pointer-events: none;
}
.topbar-spacer {
flex: 1;
}
.topbar-sort {
position: relative;
flex-shrink: 0;
}
.topbar-sort-btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
font-size: 14px;
border: 1px solid rgba(230, 231, 232, 1);
border-radius: 4px;
background: #fff;
color: rgba(95, 101, 108, 1);
cursor: pointer;
white-space: nowrap;
}
.topbar-sort-btn:hover {
border-color: rgba(5, 95, 194, 1);
}
.topbar-sort-dropdown {
position: absolute;
right: 0;
top: 100%;
margin-top: 4px;
background: #fff;
border: 1px solid rgba(234, 236, 238, 1);
border-radius: 8px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 4px 0;
z-index: 20;
min-width: 140px;
}
.topbar-sort-option {
display: block;
width: 100%;
text-align: left;
padding: 8px 12px;
font-size: 14px;
background: none;
border: none;
cursor: pointer;
color: rgba(95, 101, 108, 1);
}
.topbar-sort-option:hover {
background: rgba(247, 248, 249, 1);
}
.topbar-sort-option.active {
color: rgba(5, 95, 194, 1);
font-weight: 500;
}
.topbar-checkbox-label {
display: inline-flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 400;
color: rgba(132, 136, 142, 1);
white-space: nowrap;
line-height: 24px;
}
.topbar-checkbox {
width: 16px;
height: 16px;
accent-color: rgba(5, 95, 194, 1);
cursor: pointer;
}
</style>
<template>
<div class="user-avatar">
<div class="user-avatar-circle" :style="{ backgroundColor: avatarColor }">
{{ initial }}
</div>
<span class="user-avatar-name">{{ name }}</span>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
name: { type: String, required: true },
avatar: { type: String, default: null },
})
const COLORS = ['#CE4F51', '#055FC2', '#E8BD0B', '#218139', '#FF954D', '#3B414B']
const avatarColor = computed(() => {
let hash = 0
for (let i = 0; i < props.name.length; i++) {
hash = props.name.charCodeAt(i) + ((hash << 5) - hash)
}
return COLORS[Math.abs(hash) % COLORS.length]
})
const initial = computed(() => props.name.charAt(0))
</script>
<style scoped>
.user-avatar {
display: inline-flex;
align-items: center;
gap: 8px;
}
.user-avatar-circle {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 10px;
font-weight: 500;
flex-shrink: 0;
}
.user-avatar-name {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
line-height: 24px;
}
</style>
import { ref, reactive, watch } from 'vue'
import { fetchBills, fetchFilterOptions, fetchSortOptions } from '../api/bills.js'
/**
* 法案数据管理 composable
* 封装所有与法案数据相关的状态与请求逻辑
*/
export function useBills() {
// ===== 状态 =====
const bills = ref([])
const total = ref(0)
const loading = ref(false)
const filterOptions = reactive({
domains: [],
timeRanges: [],
})
const sortOptions = ref([])
const filters = reactive({
keyword: '',
selectedDomains: ['all'],
selectedTimeRanges: ['all'],
chinaRelatedOnly: true,
sortBy: 'publishTime',
})
// ===== 方法 =====
async function loadFilterOptions() {
const res = await fetchFilterOptions()
if (res.code === 200) {
filterOptions.domains = res.data.domains
filterOptions.timeRanges = res.data.timeRanges
}
}
async function loadSortOptions() {
const res = await fetchSortOptions()
if (res.code === 200) {
sortOptions.value = res.data
}
}
async function loadBills() {
loading.value = true
try {
const res = await fetchBills({
keyword: filters.keyword,
domains: filters.selectedDomains,
timeRanges: filters.selectedTimeRanges,
chinaRelatedOnly: filters.chinaRelatedOnly,
sortBy: filters.sortBy,
})
if (res.code === 200) {
bills.value = res.data.list
total.value = res.data.total
}
} finally {
loading.value = false
}
}
function updateKeyword(keyword) {
filters.keyword = keyword
}
function toggleDomain(domainId) {
if (domainId === 'all') {
filters.selectedDomains = ['all']
} else {
const idx = filters.selectedDomains.indexOf(domainId)
// Remove 'all' if selecting specific
filters.selectedDomains = filters.selectedDomains.filter((d) => d !== 'all')
if (idx > -1) {
filters.selectedDomains.splice(idx, 1)
} else {
filters.selectedDomains.push(domainId)
}
if (filters.selectedDomains.length === 0) {
filters.selectedDomains = ['all']
}
}
}
function toggleTimeRange(timeId) {
if (timeId === 'all') {
filters.selectedTimeRanges = ['all']
} else {
const idx = filters.selectedTimeRanges.indexOf(timeId)
filters.selectedTimeRanges = filters.selectedTimeRanges.filter((d) => d !== 'all')
if (idx > -1) {
filters.selectedTimeRanges.splice(idx, 1)
} else {
filters.selectedTimeRanges.push(timeId)
}
if (filters.selectedTimeRanges.length === 0) {
filters.selectedTimeRanges = ['all']
}
}
}
function toggleChinaRelated() {
filters.chinaRelatedOnly = !filters.chinaRelatedOnly
}
function updateSort(sortBy) {
filters.sortBy = sortBy
}
// 监听筛选变化自动重新获取
watch(
() => ({
domains: [...filters.selectedDomains],
timeRanges: [...filters.selectedTimeRanges],
chinaRelatedOnly: filters.chinaRelatedOnly,
sortBy: filters.sortBy,
keyword: filters.keyword,
}),
() => {
loadBills()
},
{ deep: true }
)
// 初始化
async function init() {
await Promise.all([loadFilterOptions(), loadSortOptions()])
await loadBills()
}
return {
bills,
total,
loading,
filters,
filterOptions,
sortOptions,
init,
loadBills,
updateKeyword,
toggleDomain,
toggleTimeRange,
toggleChinaRelated,
updateSort,
}
}
<template>
<div class="historical-proposal">
<div class="nav">
<el-input placeholder="搜索关键词" v-model="searchText" :suffix-icon="Search" class="search-input"></el-input>
<div class="select-box">
<el-select v-model="value1" placeholder="请选择" class="select">
<el-option label="全部法案" value="全部法案"></el-option>
</el-select>
<el-select v-model="value2" placeholder="请选择" class="select">
<el-option label="全部领域" value="全部领域"></el-option>
</el-select>
</div>
</div>
<div class="main">
<div v-for="item in CharacterProposal" :key="item.id" class="item">
<div class="img-box">
<img :src="item.img" alt="" class="img" />
<div class="info">
<div class="title">{{ item.title }}</div>
<div>
<span
v-for="tag in item.tie"
:key="tag"
class="tag"
:class="{ 'tag-1': tag.status == 1, 'tag-2': tag.status == 8, 'tag-3': tag.status == 4 }"
>{{ tag.industryName }}</span
>
</div>
</div>
</div>
<div class="info-box">
<div class="box">
<div class="label">法案描述:</div>
<div class="content">{{ item.disc }}</div>
</div>
<div class="box">
<div class="label">法案状态:</div>
<div class="content">{{ item.state }}</div>
</div>
<div class="box">
<div class="label">提案日期:</div>
<div class="content">{{ item.time }}</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
@current-change="handleCurrentChange"
:page-size="pageSize"
:current-page="currentPage"
background
layout="prev, pager, next"
:total="total"
class="custom-pagination"
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { Search } from "@element-plus/icons-vue";
import img from "./assets/img.png";
import { getCharacterProposal } from "@/api/characterPage/characterPage.js";
import { useRoute } from 'vue-router';
const route = useRoute();
const personId = ref(route.query.personId || "Y000064");
const currentPage = ref(1);
// 处理页码改变事件
const handleCurrentChange = page => {
currentPage.value = page;
getCharacterProposalFn();
};
// 获取历史提案
const CharacterProposal = ref({});
const total = ref(0);
const pageSize = ref(7);
const loading = ref(false);
const abortController = ref(null);
const getCharacterProposalFn = async () => {
// 取消上一次未完成的请求
if (abortController.value) {
abortController.value.abort();
}
// 创建新的 AbortController
abortController.value = new AbortController();
loading.value = true;
const params = {
personId: personId.value,
industryId: 1,
currentPage: currentPage.value - 1,
pageSize: pageSize.value
};
try{
const res = await getCharacterProposal(params, abortController.value.signal);
console.log("历史提案", res);
if (res.code === 200) {
if (res.data&& res.data.content) {
CharacterProposal.value = res.data.content.map(item => ({
id: item.billId,
title: item.name,
tie: item.industryList,
disc: item.description,
state: item.status,
time: item.time,
img: item.imageUrl || img
}));
total.value = res.data.totalElements;
}else {
CharacterProposal.value = [];
total.value = 0;
}
}else {
CharacterProposal.value = [];
total.value = 0;
}
loading.value = false;
}catch (error) {
if (error.name !== "AbortError") {
console.error(error);
loading.value = false;
}
}
};
onMounted(() => {
getCharacterProposalFn();
});
const searchText = ref("");
const value1 = ref("全部法案");
const value2 = ref("全部领域");
const list = ref([
{
id: 1,
title: "FY2025 National Defense Authorization Act (NDAA, S.4638)",
tie: ["法案", "国防与军事"],
disc: "提供2.82亿美元用于Ellsworth空军基地B-21轰炸机任务和军事建设,包括现代化指令。图恩强调这是其领导的条款,确保南达科他州国防投资。",
state: "2024年12月参议院通过,待总统签署。",
time: "2024年6月",
img: img
},
{
id: 2,
title: "Bipartisan Appropriations Bills",
tie: ["法案", "政府预算"],
disc: "推动农业、商务、司法和军事等四项两党拨款法案,恢复“常规程序”以允许修正案辩论,避免年底“全包”法案或继续决议(CR)。图恩公开敦促民主党合作。",
state: "委员会报告,部分进入辩论阶段。",
time: "2025年7月",
img: img
},
{
id: 3,
title: "Tax Relief, Unemployment Insurance Reauthorization, and Job Creation Act",
tie: ["法案", "税收与经济"],
disc: "续推失业保险和就业创造措施,图恩在2024报告中排名共和党第8位影响力,推动小企业税收减免。",
state: "纳入2024报告卡,影响2025税收辩论",
time: "2024年续推",
img: img
},
{
id: 4,
title: "Farm Bill Renewal Provisions",
tie: ["修正案", "农业与农村"],
disc: "加强农村基础设施和作物保险,支持南达科他州农民。图恩强调两党合作,避免拖延。",
state: "纳入2024 Farm Bill讨论,部分通过",
time: "2024年",
img: img
},
{
id: 5,
title: "Regular Order Restoration",
tie: ["修正案", "能源"],
disc: "承诺恢复委员会起草、修正案辩论和投票规范,避免领导层封闭谈判。图恩表示将允许100多项修正案投票。",
state: "实施中,2025年夏季拨款辩论",
time: "2025年1月",
img: img
},
{
id: 6,
title: "S.Amdt.3946 和 S.Amdt.3947",
tie: ["法案", "政府预算"],
disc: "改进政府资金法案,允许参议员就电话记录扣押提起诉讼,保护国会隐私(源于2020选举调查争议)。图恩提议将法院赔偿退回财政部以调整条款。",
state: "2025年11月10日失败",
time: "2025年11月10日",
img: img
}
]);
</script>
<style scoped lang="scss">
* {
margin: 0;
padding: 0;
}
.historical-proposal {
width: 1600px;
height: 644px;
.nav {
width: 100%;
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.search-input {
width: 300px;
height: 32px;
color: rgb(132, 136, 142);
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
}
.select-box {
width: 332px;
height: 32px;
display: flex;
justify-content: space-between;
align-items: center;
.select {
width: 160px;
height: 32px;
color: rgb(132, 136, 142);
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 22px;
}
}
}
.main {
width: 1600px;
height: 540px;
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-content: space-between;
.item {
width: 523px;
height: 262px;
background-color: #fff;
border-radius: 10px;
box-shadow: 0px 0px 20px 0px rgba(25, 69, 130, 0.1);
padding: 16px 19px 17px 16px;
.img-box {
width: 488px;
height: 77px;
display: flex;
margin-bottom: 12px;
img {
width: 57px;
height: 77px;
margin-right: 15px;
}
.info {
width: 420px;
height: 77px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
.title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(5, 95, 194);
}
.tag {
display: inline-block;
padding: 1px 8px;
font-size: 14px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 20px;
border-radius: 4px;
border: 1px solid;
margin-right: 8px;
}
.tag-1 {
background-color: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1);
border-color: rgba(217, 247, 190, 1);
}
.tag-2 {
background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1);
border-color: rgba(255, 241, 184, 1);
}
.tag-3 {
background-color: rgba(255, 241, 240, 1);
color: rgba(245, 34, 45, 1);
border-color: rgba(255, 163, 158, 1);
}
}
}
.info-box {
width: 488px;
height: 140px;
.box {
width: 100%;
margin-bottom: 10px;
display: flex;
.label {
width: 84px;
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
}
.content {
width:400px;
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(59, 65, 75);
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
}
.pagination-container {
width: 100%;
display: flex;
justify-content: center;
margin-top: 20px;
:deep(.el-pagination.is-background .el-pager li) {
background-color: #fff;
border: 1px solid rgba(229, 230, 235, 1);
border-radius: 4px;
color: rgba(29, 33, 41, 1);
font-weight: 400;
margin: 0 4px;
}
:deep(.el-pagination.is-background .el-pager li.is-active) {
background-color: #fff;
border-color: #165DFF;
color: #165DFF;
}
:deep(.el-pagination.is-background .btn-prev),
:deep(.el-pagination.is-background .btn-next) {
background-color: #fff;
border: 1px solid rgba(229, 230, 235, 1);
border-radius: 4px;
margin: 0 4px;
}
}
}
</style>
...@@ -14,19 +14,14 @@ ...@@ -14,19 +14,14 @@
<p>{{ characterInfo.description }}</p> <p>{{ characterInfo.description }}</p>
</div> </div>
<div class="domain"> <div class="domain">
<p <p v-for="item in characterInfo.industryList" :key="item" class="cl1" :class="{
v-for="item in characterInfo.industryList" cl1: item.status === '1',
:key="item" cl2: item.status === '2',
class="cl1" cl3: item.status === '3',
:class="{ cl4: item.status === '4',
cl1: item.status === '1', cl5: item.status === '5',
cl2: item.status === '2', cl6: item.status === '6'
cl3: item.status === '3', }">
cl4: item.status === '4',
cl5: item.status === '5',
cl6: item.status === '6'
}"
>
{{ item.industryName }} {{ item.industryName }}
</p> </p>
</div> </div>
...@@ -52,7 +47,8 @@ ...@@ -52,7 +47,8 @@
</div> </div>
<!-- 主要内容 --> <!-- 主要内容 -->
<div class="num-list"> <div class="num-list">
<div v-for="item in num" :key="item" :class="{ active: item === numActive }" @click="handleChangeYear(item)"> <div v-for="item in num" :key="item" :class="{ active: item === numActive }"
@click="handleChangeYear(item)">
{{ item }} {{ item }}
</div> </div>
</div> </div>
...@@ -80,8 +76,8 @@ ...@@ -80,8 +76,8 @@
<div class="input"> <div class="input">
<el-select v-model="selectedOption" placeholder="请选择" class="select"> <el-select v-model="selectedOption" placeholder="请选择" class="select">
<el-option @click="handleChangeYearList()" v-for="item in yearList" :key="item.value" <el-option @click="handleChangeYearList()" v-for="item in yearList" :key="item.value"
:label="item.label" :value="item.value" /> :label="item.label" :value="item.value" />
</el-select> </el-select>
</div> </div>
<div class="btn"> <div class="btn">
...@@ -91,24 +87,16 @@ ...@@ -91,24 +87,16 @@
</div> </div>
<!-- 主要内容 --> <!-- 主要内容 -->
<div class="main"> <div class="main">
<el-table <el-table :data="CharacterFundSource" style="width: 100%" :header-cell-style="{
:data="CharacterFundSource" background: 'transparent',
style="width: 100%" color: 'rgba(59, 65, 75, 1)',
:header-cell-style="{ fontWeight: 700,
background: 'transparent', fontSize: '16px'
color: 'rgba(59, 65, 75, 1)', }" :cell-style="{
fontWeight: 700, fontSize: '16px',
fontSize: '16px' fontWeight: 400,
}" color: 'rgba(59, 65, 75, 1)'
:cell-style="{ }" :row-class-name="tableRowClassName" :row-style="{ height: '60px' }" size="large">
fontSize: '16px',
fontWeight: 400,
color: 'rgba(59, 65, 75, 1)'
}"
:row-class-name="tableRowClassName"
:row-style="{ height: '60px' }"
size="large"
>
<el-table-column prop="rank" label="排名" width="100" align="center" /> <el-table-column prop="rank" label="排名" width="100" align="center" />
<el-table-column prop="contributor" label="贡献者" min-width="300" /> <el-table-column prop="contributor" label="贡献者" min-width="300" />
<el-table-column prop="totalAmount" label="总捐款" width="150" /> <el-table-column prop="totalAmount" label="总捐款" width="150" />
...@@ -118,7 +106,8 @@ ...@@ -118,7 +106,8 @@
</div> </div>
<div class="bottom"> <div class="bottom">
<img src="./assets/ai.png" alt="" class="icon"> <img src="./assets/ai.png" alt="" class="icon">
<div class="text1">约翰·图恩的竞选资金前五大贡献者以美国以色列公共事务委员会(AIPAC)为主导,总捐款超61.8万美元,主要来自个人捐款,凸显其在外交、国防和行业游说方面的强大支持网络。</div> <div class="text1">
约翰·图恩的竞选资金前五大贡献者以美国以色列公共事务委员会(AIPAC)为主导,总捐款超61.8万美元,主要来自个人捐款,凸显其在外交、国防和行业游说方面的强大支持网络。</div>
<img src="./assets/right.png" alt="" class="icon1"> <img src="./assets/right.png" alt="" class="icon1">
</div> </div>
</div> </div>
...@@ -126,7 +115,8 @@ ...@@ -126,7 +115,8 @@
<div class="title"> <div class="title">
<div class="box"></div> <div class="box"></div>
<div class="text">最新动态</div> <div class="text">最新动态</div>
<div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange"/>只看涉华动态</div> <div class="input"><input type="checkbox" v-model="isChecked" @change="handleChange" />只看涉华动态
</div>
<div class="btn"> <div class="btn">
<img src="./assets/下载按钮.png" alt="" /> <img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" /> <img src="./assets/收藏按钮.png" alt="" />
...@@ -137,22 +127,21 @@ ...@@ -137,22 +127,21 @@
<div v-for="item in CharacterLatestDynamic" :key="item" class="main-item"> <div v-for="item in CharacterLatestDynamic" :key="item" class="main-item">
<div class="time"> <div class="time">
<div class="year">{{ item.time.split("-")[0] }}</div> <div class="year">{{ item.time.split("-")[0] }}</div>
<div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日"}}</div> <div class="date">{{ item.time.split("-")[1] + "月" + item.time.split("-")[2] + "日" }}
</div>
</div> </div>
<div class="image"> <div class="image">
<img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img <img src="./assets/type1.png" alt="" v-if="item.remarks === true" /><img
src="./assets/type2.png" src="./assets/type2.png" alt="" v-else />
alt=""
v-else
/>
</div> </div>
<div class="content"> <div class="content">
<div :class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }"> <div
:class="{ 'content-type1': item.remarks === true, 'content-type2': item.remarks === false }">
<p v-if="item.remarks === true" class="content-title1">
<p v-if="item.remarks === true" class="content-title1">
{{ item.content }} {{ item.content }}
</p> </p>
<p v-else class="content-title2"> <p v-else class="content-title2">
{{ item.title }} {{ item.title }}
</p> </p>
<p v-if="item.remarks === true" class="content-title-en">{{ item.econtent }}</p> <p v-if="item.remarks === true" class="content-title-en">{{ item.econtent }}</p>
...@@ -160,19 +149,12 @@ ...@@ -160,19 +149,12 @@
<p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p> <p v-if="item.remarks === false" class="content-contentcontent">{{ item.content }}</p>
<div class="content-tag"> <div class="content-tag">
<div> <div>
<span <span v-for="tag in item.industryList" :key="tag" class="tag" :class="{
v-for="tag in item.industryList" dl1: tag === '人工智能',
:key="tag" dl2: tag === '量子科技',
class="tag" dl3: tag === '新能源',
:class="{ dl4: tag === '集成电路'
dl1: tag === '人工智能', }" @click="handleClickTag(tag)">{{ tag }}</span>
dl2: tag === '量子科技',
dl3: tag === '新能源',
dl4: tag === '集成电路'
}"
@click="handleClickTag(tag)"
>{{ tag }}</span
>
</div> </div>
<div class="origin">来源:{{ item.orgName }}</div> <div class="origin">来源:{{ item.orgName }}</div>
</div> </div>
...@@ -181,16 +163,10 @@ ...@@ -181,16 +163,10 @@
<div class="line-test"></div> <div class="line-test"></div>
</div> </div>
<div class="pagination"> <div class="pagination">
<div class="total">{{`共 ${total} 项`}}</div> <div class="total">{{ `共 ${total} 项` }}</div>
<el-pagination <el-pagination @current-change="handleCurrentChange" :page-size="pageSize"
@current-change="handleCurrentChange" :current-page="currentPage" background layout="prev, pager, next" :total="total"
:page-size="pageSize" class="custom-pagination" />
:current-page="currentPage"
background
layout="prev, pager, next"
:total="total"
class="custom-pagination"
/>
</div> </div>
</div> </div>
</div> </div>
...@@ -208,40 +184,42 @@ ...@@ -208,40 +184,42 @@
<div class="baseInfo"> <div class="baseInfo">
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">出生日期:</div> <div class="baseInfo-item-title">出生日期:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthday}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.birthday }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">现任职位:</div> <div class="baseInfo-item-title">现任职位:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.positionTitle}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.positionTitle }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">党派归属:</div> <div class="baseInfo-item-title">党派归属:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.party}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.party }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">教育背景:</div> <div class="baseInfo-item-title">教育背景:</div>
<div class="baseInfo-item-content" v-for="item in characterBasicInfo.educationList">{{item.school+item.major}}</div> <div class="baseInfo-item-content" v-for="item in characterBasicInfo.educationList">
{{ item.school + item.major }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title address">代表州/选区:</div> <div class="baseInfo-item-title address">代表州/选区:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.state}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.state }}</div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">政治立场:</div> <div class="baseInfo-item-title">政治立场:</div>
<div class="baseInfo-item-content long"> <div class="baseInfo-item-content long">
{{characterBasicInfo.political}} {{ characterBasicInfo.political }}
</div> </div>
</div> </div>
<div class="baseInfo-item"> <div class="baseInfo-item">
<div class="baseInfo-item-title">出生地:</div> <div class="baseInfo-item-title">出生地:</div>
<div class="baseInfo-item-content">{{characterBasicInfo.birthPlace}}</div> <div class="baseInfo-item-content">{{ characterBasicInfo.birthPlace }}</div>
</div> </div>
</div> </div>
<div class="company"> <div class="company">
<div class="company-title">社交媒体</div> <div class="company-title">社交媒体</div>
<div class="company-content"> <div class="company-content">
<div v-for="item in characterBasicInfo.organizationList" :key="item" class="company-item"> <div v-for="item in characterBasicInfo.organizationList" :key="item"
<img :src="item.imageUrl?item.imageUrl:DefaultIcon2" alt="" /> class="company-item">
<img :src="item.imageUrl ? item.imageUrl : DefaultIcon2" alt="" />
<div> <div>
<div class="company-cn">{{ item.name }}</div> <div class="company-cn">{{ item.name }}</div>
<div class="company-name">{{ item.ename }}</div> <div class="company-name">{{ item.ename }}</div>
...@@ -254,7 +232,7 @@ ...@@ -254,7 +232,7 @@
<div class="right-bottom"> <div class="right-bottom">
<div class="title"> <div class="title">
<div class="box"></div> <div class="box"></div>
<div class="text">政治履历</div> <div class="text"> 履历</div>
<div class="btn"> <div class="btn">
<img src="./assets/下载按钮.png" alt="" /> <img src="./assets/下载按钮.png" alt="" />
<img src="./assets/收藏按钮.png" alt="" /> <img src="./assets/收藏按钮.png" alt="" />
...@@ -263,8 +241,8 @@ ...@@ -263,8 +241,8 @@
<div class="content-main"> <div class="content-main">
<div v-for="item in CharacterResume" class="content-item"> <div v-for="item in CharacterResume" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" /> <img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime +'-' + item.endTime}}</div> <div class="content-item-time">{{ item.startTime + '-' + item.endTime }}</div>
<div class="content-item-title">{{ item.orgName +'|' + item.jobName}}</div> <div class="content-item-title">{{ item.orgName + '|' + item.jobName }}</div>
<div class="content-item-content">{{ item.content }}</div> <div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door"> <div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" /> <img src="./assets/icon02.png" alt="" />
...@@ -276,20 +254,39 @@ ...@@ -276,20 +254,39 @@
</div> </div>
</div> </div>
<!-- 历史提案 --> <!-- 历史提案 -->
<HistoricalProposal v-if="infoActive === '历史提案'" /> <!-- member-of-congress 同级的左侧添加标签栏 -->
<!-- 历史提案 tab 对应的内容区 -->
<div v-if="infoActive === '历史提案'" class="proposal-wrapper">
<!-- 左侧切换标签 —— 绝对定位到容器外侧 -->
<div class="proposal-tab-switcher">
<button :class="['proposal-tab', { active: newsTab === 'history' }]" @click="newsTab = 'history'">
<span>历史新闻</span>
<svg v-if="newsTab === 'history'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
</svg>
</button>
<button :class="['proposal-tab', { active: newsTab === 'potential' }]" @click="newsTab = 'potential'">
<span>潜在新闻</span>
<svg v-if="newsTab === 'potential'" class="proposal-tab-arrow" width="12" height="12"
viewBox="0 0 12 12" fill="currentColor">
<path d="M4 2l5 4-5 4V2z" />
</svg>
</button>
</div>
<!-- 右侧主内容区(1600px 内) -->
<HistoricalProposal v-if="newsTab === 'history'" />
<PotentialNews v-else />
</div>
<!-- <HistoricalProposal v-if="infoActive === '历史提案'" /> -->
<!-- 人物关系 --> <!-- 人物关系 -->
<CharacterRelationships v-if="infoActive === '人物关系'" /> <CharacterRelationships v-if="infoActive === '人物关系'" />
<!-- 相关情况 --> <!-- 相关情况 -->
<RelevantSituation v-if="infoActive === '相关情况'" /> <RelevantSituation v-if="infoActive === '相关情况'" />
<!-- 弹框 --> <!-- 弹框 -->
<el-dialog <el-dialog v-model="dialogVisible" width="761px" class="viewpoint-dialog" :modal="false" :draggable="true"
v-model="dialogVisible" :lock-scroll="false">
width="761px"
class="viewpoint-dialog"
:modal="false"
:draggable="true"
:lock-scroll="false"
>
<template #header> <template #header>
<div class="viewpoint-header"> <div class="viewpoint-header">
<div class="viewpoint-title">领域观点</div> <div class="viewpoint-title">领域观点</div>
...@@ -299,7 +296,8 @@ ...@@ -299,7 +296,8 @@
<div class="viewpoint-body-title">#人工智能</div> <div class="viewpoint-body-title">#人工智能</div>
<div v-for="item in CharacterFieldView" class="viewpoint-item"> <div v-for="item in CharacterFieldView" class="viewpoint-item">
<!-- <img :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" alt="" /> --> <!-- <img :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" alt="" /> -->
<el-avatar :size="42" shape="circle" :src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" class="viewpoint-item-img" /> <el-avatar :size="42" shape="circle"
:src="item.imageUrl ? getProxyUrl(item.imageUrl) : DefaultIcon1" class="viewpoint-item-img" />
<div class="viewpoint-item-content"> <div class="viewpoint-item-content">
<div class="viewpoint-item-name">{{ item.name }}</div> <div class="viewpoint-item-name">{{ item.name }}</div>
<div class="viewpoint-item-desc">{{ item.remarks }}</div> <div class="viewpoint-item-desc">{{ item.remarks }}</div>
...@@ -315,17 +313,19 @@ ...@@ -315,17 +313,19 @@
import { ref, onMounted } from "vue"; import { ref, onMounted } from "vue";
import CharacterRelationships from "./components/characterRelationships/index.vue"; import CharacterRelationships from "./components/characterRelationships/index.vue";
import RelevantSituation from "./components/relevantSituation/index.vue"; import RelevantSituation from "./components/relevantSituation/index.vue";
import HistoricalProposal from "./components/historicalProposal/index.vue"; import HistoricalProposal from "./components/historicalProposal/components/BillTracker.vue";
import getWordCloudChart from "../../utils/worldCloudChart"; import getWordCloudChart from "../../utils/worldCloudChart";
import setChart from "@/utils/setChart"; import setChart from "@/utils/setChart";
import { getCharacterGlobalInfo, import {
getCharacterGlobalInfo,
getCharacterBasicInfo, getCharacterBasicInfo,
getCharacterView, getCharacterView,
getCharacterLatestDynamic, getCharacterLatestDynamic,
getCharacterResume, getCharacterResume,
getCharacterFieldView, getCharacterFieldView,
getCharacterFundSource } from "@/api/characterPage/characterPage.js"; getCharacterFundSource
} from "@/api/characterPage/characterPage.js";
import Musk from "./assets/Musk.png"; import Musk from "./assets/Musk.png";
import cp1 from "./assets/cp1.png"; import cp1 from "./assets/cp1.png";
...@@ -345,7 +345,7 @@ import DefaultIcon2 from '@/assets/icons/default-icon2.png' ...@@ -345,7 +345,7 @@ import DefaultIcon2 from '@/assets/icons/default-icon2.png'
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
const route = useRoute(); const route = useRoute();
const personId = ref(route.query.personId || "Y000064"); const personId = ref(route.query.personId || "Y000064");
const newsTab = ref('history')
// 处理图片代理 // 处理图片代理
const getProxyUrl = (url) => { const getProxyUrl = (url) => {
if (!url) return ""; if (!url) return "";
...@@ -369,7 +369,7 @@ const getCharacterGlobalInfoFn = async () => { ...@@ -369,7 +369,7 @@ const getCharacterGlobalInfoFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterGlobalInfo(params); const res = await getCharacterGlobalInfo(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物全局信息", res); console.log("人物全局信息", res);
...@@ -377,10 +377,10 @@ const getCharacterGlobalInfoFn = async () => { ...@@ -377,10 +377,10 @@ const getCharacterGlobalInfoFn = async () => {
characterInfo.value = res.data; characterInfo.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
}; };
// 获取人物基本信息 // 获取人物基本信息
...@@ -389,7 +389,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -389,7 +389,7 @@ const getCharacterBasicInfoFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterBasicInfo(params); const res = await getCharacterBasicInfo(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物基本信息", res); console.log("人物基本信息", res);
...@@ -397,7 +397,7 @@ const getCharacterBasicInfoFn = async () => { ...@@ -397,7 +397,7 @@ const getCharacterBasicInfoFn = async () => {
characterBasicInfo.value = res.data; characterBasicInfo.value = res.data;
} }
} }
}catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
}; };
...@@ -409,23 +409,23 @@ const getCharacterViewFn = async () => { ...@@ -409,23 +409,23 @@ const getCharacterViewFn = async () => {
personId: personId.value, personId: personId.value,
year: numActive.value year: numActive.value
}; };
try{ try {
const res = await getCharacterView(params); const res = await getCharacterView(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物观点", res); console.log("人物观点", res);
if (res.data) { if (res.data) {
characterView.value = res.data.map(item=>{ characterView.value = res.data.map(item => {
return{ return {
name:item.option, name: item.option,
value:item.count value: item.count
}; };
}); });
} }
} }
}catch(error){ } catch (error) {
} }
}; };
const handleCharacterView = async () => { const handleCharacterView = async () => {
...@@ -464,31 +464,31 @@ const getCharacterFundSourceFn = async () => { ...@@ -464,31 +464,31 @@ const getCharacterFundSourceFn = async () => {
personId: personId.value, personId: personId.value,
year: selectedOption.value || "2025" year: selectedOption.value || "2025"
}; };
try{ try {
const res = await getCharacterFundSource(params); const res = await getCharacterFundSource(params);
if (res.code === 200) { if (res.code === 200) {
console.log("资金来源", res); console.log("资金来源", res);
if (res.data) { if (res.data) {
CharacterFundSource.value = res.data.map((item,index)=>{ CharacterFundSource.value = res.data.map((item, index) => {
return{ return {
rank: index + 1, rank: index + 1,
contributor: item.orgName, contributor: item.orgName,
totalAmount: item.totalDonation, totalAmount: item.totalDonation,
individualAmount: item.personalDonation, individualAmount: item.personalDonation,
pacsAmount: item.pacsDonation pacsAmount: item.pacsDonation
} }
}); });
} }
} }
}catch(error){ } catch (error) {
} }
}; };
const handleChangeYearList = async () => { const handleChangeYearList = async () => {
getCharacterFundSourceFn(); getCharacterFundSourceFn();
}; };
...@@ -498,7 +498,7 @@ const getCharacterFieldViewFn = async () => { ...@@ -498,7 +498,7 @@ const getCharacterFieldViewFn = async () => {
const params = { const params = {
areaId: window.sessionStorage.getItem("areaId") || "20", areaId: window.sessionStorage.getItem("areaId") || "20",
}; };
try{ try {
const res = await getCharacterFieldView(params); const res = await getCharacterFieldView(params);
if (res.code === 200) { if (res.code === 200) {
console.log("领域观点", res); console.log("领域观点", res);
...@@ -506,10 +506,10 @@ const getCharacterFieldViewFn = async () => { ...@@ -506,10 +506,10 @@ const getCharacterFieldViewFn = async () => {
CharacterFieldView.value = res.data; CharacterFieldView.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
}; };
...@@ -519,9 +519,9 @@ const isChecked = ref(false); ...@@ -519,9 +519,9 @@ const isChecked = ref(false);
const related = ref('N'); const related = ref('N');
const handleChange = event => { const handleChange = event => {
if(isChecked.value){ if (isChecked.value) {
related.value = 'Y'; related.value = 'Y';
}else{ } else {
related.value = 'N'; related.value = 'N';
} }
getCharacterLatestDynamicFn(); getCharacterLatestDynamicFn();
...@@ -553,16 +553,16 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -553,16 +553,16 @@ const getCharacterLatestDynamicFn = async () => {
const params = { const params = {
personId: personId.value, personId: personId.value,
cRelated: related.value, cRelated: related.value,
currentPage: currentPage.value - 1, currentPage: currentPage.value - 1,
pageSize: pageSize.value pageSize: pageSize.value
}; };
try{ try {
const res = await getCharacterLatestDynamic(params, abortController.value.signal); const res = await getCharacterLatestDynamic(params, abortController.value.signal);
console.log("最新动态", res); console.log("最新动态", res);
if (res.code === 200) { if (res.code === 200) {
if (res.data&& res.data.content) { if (res.data && res.data.content) {
CharacterLatestDynamic.value = res.data.content.map(item => ({ CharacterLatestDynamic.value = res.data.content.map(item => ({
title: item.title, title: item.title,
content: item.content, content: item.content,
...@@ -573,22 +573,22 @@ const getCharacterLatestDynamicFn = async () => { ...@@ -573,22 +573,22 @@ const getCharacterLatestDynamicFn = async () => {
remarks: item.remarks remarks: item.remarks
})); }));
total.value = res.data.totalElements; total.value = res.data.totalElements;
}else { } else {
CharacterLatestDynamic.value = []; CharacterLatestDynamic.value = [];
total.value = 0; total.value = 0;
} }
}else { } else {
CharacterLatestDynamic.value = []; CharacterLatestDynamic.value = [];
total.value = 0; total.value = 0;
} }
loading.value = false; loading.value = false;
}catch (error) { } catch (error) {
if (error.name !== "AbortError") { if (error.name !== "AbortError") {
console.error(error); console.error(error);
loading.value = false; loading.value = false;
} }
} }
}; };
//获取职业履历 //获取职业履历
...@@ -597,7 +597,7 @@ const getCharacterResumeFn = async () => { ...@@ -597,7 +597,7 @@ const getCharacterResumeFn = async () => {
const params = { const params = {
personId: personId.value personId: personId.value
}; };
try{ try {
const res = await getCharacterResume(params); const res = await getCharacterResume(params);
if (res.code === 200) { if (res.code === 200) {
console.log("人物职业履历", res); console.log("人物职业履历", res);
...@@ -605,10 +605,10 @@ const getCharacterResumeFn = async () => { ...@@ -605,10 +605,10 @@ const getCharacterResumeFn = async () => {
CharacterResume.value = res.data; CharacterResume.value = res.data;
} }
} }
}catch(error){ } catch (error) {
} }
}; };
onMounted(() => { onMounted(() => {
...@@ -907,12 +907,32 @@ const dialogData = ref([ ...@@ -907,12 +907,32 @@ const dialogData = ref([
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.news-tab-btn {
writing-mode: horizontal-tb;
font-size: 13px;
color: #8c8c8c;
background: none;
border: none;
cursor: pointer;
padding: 6px 12px;
white-space: nowrap;
}
.news-tab-btn.active {
background: #e8513f;
color: #fff;
border-radius: 20px;
font-weight: 500;
}
.member-of-congress { .member-of-congress {
width: 1600px; width: 1600px;
margin: 0 auto; margin: 0 auto;
padding-bottom: 50px; padding-bottom: 50px;
.header { .header {
width: 1600px; width: 100%;
height: 200px; height: 200px;
margin: 16px auto; margin: 16px auto;
background-color: rgba(255, 255, 255, 0.8); background-color: rgba(255, 255, 255, 0.8);
...@@ -921,19 +941,23 @@ const dialogData = ref([ ...@@ -921,19 +941,23 @@ const dialogData = ref([
padding: 20px; padding: 20px;
display: flex; display: flex;
align-items: center; align-items: center;
.avatar { .avatar {
width: 160px; width: 160px;
height: 160px; height: 160px;
margin-right: 24px; margin-right: 24px;
overflow: hidden; overflow: hidden;
img { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
} }
.info { .info {
flex: 1; flex: 1;
.name-cn { .name-cn {
font-size: 32px; font-size: 32px;
font-weight: 700; font-weight: 700;
...@@ -942,6 +966,7 @@ const dialogData = ref([ ...@@ -942,6 +966,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 8px; margin-bottom: 8px;
} }
.name-en { .name-en {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -950,6 +975,7 @@ const dialogData = ref([ ...@@ -950,6 +975,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 6px; margin-bottom: 6px;
} }
.introduction { .introduction {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -958,8 +984,10 @@ const dialogData = ref([ ...@@ -958,8 +984,10 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 6px; margin-bottom: 6px;
} }
.domain { .domain {
font-size: 14px; font-size: 14px;
p { p {
display: inline-block; display: inline-block;
padding: 1px 8px; padding: 1px 8px;
...@@ -967,31 +995,37 @@ const dialogData = ref([ ...@@ -967,31 +995,37 @@ const dialogData = ref([
margin-right: 8px; margin-right: 8px;
border: 1px solid; border: 1px solid;
} }
.cl1 { .cl1 {
border-color: rgba(186, 224, 255, 1); border-color: rgba(186, 224, 255, 1);
background-color: rgba(230, 244, 255, 1); background-color: rgba(230, 244, 255, 1);
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
} }
.cl2 { .cl2 {
border-color: rgba(217, 247, 190, 1); border-color: rgba(217, 247, 190, 1);
background-color: rgba(246, 255, 237, 1); background-color: rgba(246, 255, 237, 1);
color: rgba(82, 196, 26, 1); color: rgba(82, 196, 26, 1);
} }
.cl3 { .cl3 {
border-color: rgba(255, 241, 184, 1); border-color: rgba(255, 241, 184, 1);
background-color: rgba(255, 251, 230, 1); background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1); color: rgba(250, 173, 20, 1);
} }
.cl4 { .cl4 {
border-color: rgba(255, 204, 199, 1); border-color: rgba(255, 204, 199, 1);
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
color: rgba(255, 77, 79, 1); color: rgba(255, 77, 79, 1);
} }
.cl5 { .cl5 {
border-color: rgba(255, 241, 184, 1); border-color: rgba(255, 241, 184, 1);
background-color: rgba(255, 251, 230, 1); background-color: rgba(255, 251, 230, 1);
color: rgba(250, 173, 20, 1); color: rgba(250, 173, 20, 1);
} }
.cl6 { .cl6 {
border-color: rgba(255, 204, 199, 1); border-color: rgba(255, 204, 199, 1);
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
...@@ -1000,8 +1034,9 @@ const dialogData = ref([ ...@@ -1000,8 +1034,9 @@ const dialogData = ref([
} }
} }
} }
.info-divide { .info-divide {
width: 1600px; width: 100%;
height: 64px; height: 64px;
margin: 16px auto; margin: 16px auto;
background-color: rgba(255, 255, 255, 0.65); background-color: rgba(255, 255, 255, 0.65);
...@@ -1011,6 +1046,7 @@ const dialogData = ref([ ...@@ -1011,6 +1046,7 @@ const dialogData = ref([
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
div { div {
width: 530px; width: 530px;
height: 54px; height: 54px;
...@@ -1022,10 +1058,12 @@ const dialogData = ref([ ...@@ -1022,10 +1058,12 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
border-radius: 10px; border-radius: 10px;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background: rgba(231, 243, 255, 1); background: rgba(231, 243, 255, 1);
} }
} }
.active { .active {
font-size: 24px; font-size: 24px;
font-weight: 700; font-weight: 700;
...@@ -1036,16 +1074,19 @@ const dialogData = ref([ ...@@ -1036,16 +1074,19 @@ const dialogData = ref([
border: 2px solid rgba(174, 214, 255, 1); border: 2px solid rgba(174, 214, 255, 1);
} }
} }
.info-content { .info-content {
width: 1600px; width: 1600px;
height: 2174px; height: 2174px;
margin-bottom: 50px; margin-bottom: 50px;
margin: 16px auto; margin: 16px auto;
display: flex; display: flex;
.left { .left {
width: 1064px; width: 1064px;
height: 100%; height: 100%;
margin-right: 16px; margin-right: 16px;
.left-top { .left-top {
width: 100%; width: 100%;
height: 300px; height: 300px;
...@@ -1053,12 +1094,14 @@ const dialogData = ref([ ...@@ -1053,12 +1094,14 @@ const dialogData = ref([
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.title { .title {
width: 100%; width: 100%;
height: 56px; height: 56px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 16px 0; padding: 14px 12px 16px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1067,6 +1110,7 @@ const dialogData = ref([ ...@@ -1067,6 +1110,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1074,25 +1118,30 @@ const dialogData = ref([ ...@@ -1074,25 +1118,30 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.num-list { .num-list {
display: flex; display: flex;
align-items: center; align-items: center;
padding-left: 24px; padding-left: 24px;
margin-bottom: 16px; margin-bottom: 16px;
div { div {
padding: 1px 8px; padding: 1px 8px;
line-height: 30px; line-height: 30px;
...@@ -1105,12 +1154,14 @@ const dialogData = ref([ ...@@ -1105,12 +1154,14 @@ const dialogData = ref([
border-radius: 4px; border-radius: 4px;
margin-right: 8px; margin-right: 8px;
} }
.active { .active {
background-color: rgba(231, 243, 255, 1); background-color: rgba(231, 243, 255, 1);
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
border-color: rgb(5, 95, 194); border-color: rgb(5, 95, 194);
} }
} }
.echarts { .echarts {
width: 1009px; width: 1009px;
height: 170px; height: 170px;
...@@ -1141,6 +1192,7 @@ const dialogData = ref([ ...@@ -1141,6 +1192,7 @@ const dialogData = ref([
} }
} }
} }
.left-center { .left-center {
width: 100%; width: 100%;
height: 512px; height: 512px;
...@@ -1148,6 +1200,7 @@ const dialogData = ref([ ...@@ -1148,6 +1200,7 @@ const dialogData = ref([
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
margin-bottom: 16px; margin-bottom: 16px;
.title { .title {
width: 100%; width: 100%;
height: 56px; height: 56px;
...@@ -1155,6 +1208,7 @@ const dialogData = ref([ ...@@ -1155,6 +1208,7 @@ const dialogData = ref([
align-items: center; align-items: center;
padding: 14px 12px 16px 0; padding: 14px 12px 16px 0;
position: relative; position: relative;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1163,6 +1217,7 @@ const dialogData = ref([ ...@@ -1163,6 +1217,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1170,34 +1225,41 @@ const dialogData = ref([ ...@@ -1170,34 +1225,41 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
.input { .input {
position: absolute; position: absolute;
top: 15px; top: 15px;
right: 114px; right: 114px;
.select { .select {
width: 120px; width: 120px;
} }
} }
} }
.main { .main {
width: 1016px; width: 1016px;
height: 360px; height: 360px;
margin-left: 24px; margin-left: 24px;
margin-bottom: 16px; margin-bottom: 16px;
} }
.bottom { .bottom {
width: 1016px; width: 1016px;
height: 64px; height: 64px;
...@@ -1208,11 +1270,13 @@ const dialogData = ref([ ...@@ -1208,11 +1270,13 @@ const dialogData = ref([
border-radius: 4px; border-radius: 4px;
border: 1px solid rgba(231, 243, 255, 1); border: 1px solid rgba(231, 243, 255, 1);
padding: 6px 12px; padding: 6px 12px;
.icon { .icon {
width: 19px; width: 19px;
height: 20px; height: 20px;
margin-right: 13px; margin-right: 13px;
} }
.text1 { .text1 {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1221,18 +1285,21 @@ const dialogData = ref([ ...@@ -1221,18 +1285,21 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
margin-right: 13px; margin-right: 13px;
} }
.icon1 { .icon1 {
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
} }
} }
.left-bottom { .left-bottom {
width: 100%; width: 100%;
height: 1330px; height: 1330px;
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.title { .title {
width: 100%; width: 100%;
height: 85px; height: 85px;
...@@ -1240,6 +1307,7 @@ const dialogData = ref([ ...@@ -1240,6 +1307,7 @@ const dialogData = ref([
align-items: center; align-items: center;
padding: 14px 12px 45px 0; padding: 14px 12px 45px 0;
position: relative; position: relative;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1248,6 +1316,7 @@ const dialogData = ref([ ...@@ -1248,6 +1316,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1255,19 +1324,23 @@ const dialogData = ref([ ...@@ -1255,19 +1324,23 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
.input { .input {
position: absolute; position: absolute;
top: 15px; top: 15px;
...@@ -1277,11 +1350,13 @@ const dialogData = ref([ ...@@ -1277,11 +1350,13 @@ const dialogData = ref([
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
line-height: 24px; line-height: 24px;
color: rgb(132, 136, 142); color: rgb(132, 136, 142);
input { input {
margin-right: 8px; margin-right: 8px;
} }
} }
} }
.main { .main {
width: 1064px; width: 1064px;
height: 1133px; height: 1133px;
...@@ -1289,10 +1364,12 @@ const dialogData = ref([ ...@@ -1289,10 +1364,12 @@ const dialogData = ref([
padding-right: 50px; padding-right: 50px;
position: relative; position: relative;
z-index: 110; z-index: 110;
.main-item { .main-item {
width: 1014px; width: 1014px;
margin-bottom: 40px; margin-bottom: 40px;
display: flex; display: flex;
.time { .time {
width: 77px; width: 77px;
box-sizing: border-box; box-sizing: border-box;
...@@ -1300,6 +1377,7 @@ const dialogData = ref([ ...@@ -1300,6 +1377,7 @@ const dialogData = ref([
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-end; align-items: flex-end;
.year { .year {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1307,6 +1385,7 @@ const dialogData = ref([ ...@@ -1307,6 +1385,7 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
line-height: 24px; line-height: 24px;
} }
.date { .date {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1315,20 +1394,25 @@ const dialogData = ref([ ...@@ -1315,20 +1394,25 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
} }
} }
.image { .image {
margin-right: 20px; margin-right: 20px;
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
} }
.content { .content {
width: 873px; width: 873px;
.content-type1 { .content-type1 {
background-color: rgba(246, 250, 255, 1); background-color: rgba(246, 250, 255, 1);
border-radius: 10px; border-radius: 10px;
border: 1px solid rgb(234, 236, 238); border: 1px solid rgb(234, 236, 238);
padding: 12px 14px 12px 15px; padding: 12px 14px 12px 15px;
.content-title1 { .content-title1 {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1339,6 +1423,7 @@ const dialogData = ref([ ...@@ -1339,6 +1423,7 @@ const dialogData = ref([
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
cursor: pointer; cursor: pointer;
} }
.content-title-en { .content-title-en {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1349,6 +1434,7 @@ const dialogData = ref([ ...@@ -1349,6 +1434,7 @@ const dialogData = ref([
cursor: pointer; cursor: pointer;
} }
} }
.content-type2 { .content-type2 {
.content-title2 { .content-title2 {
font-size: 20px; font-size: 20px;
...@@ -1360,6 +1446,7 @@ const dialogData = ref([ ...@@ -1360,6 +1446,7 @@ const dialogData = ref([
cursor: pointer; cursor: pointer;
} }
} }
.content-contentcontent { .content-contentcontent {
display: -webkit-box; display: -webkit-box;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
...@@ -1374,10 +1461,12 @@ const dialogData = ref([ ...@@ -1374,10 +1461,12 @@ const dialogData = ref([
margin-bottom: 8px; margin-bottom: 8px;
cursor: pointer; cursor: pointer;
} }
.content-tag { .content-tag {
width: 873px; width: 873px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.tag { .tag {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
...@@ -1389,26 +1478,31 @@ const dialogData = ref([ ...@@ -1389,26 +1478,31 @@ const dialogData = ref([
border: 1px solid; border: 1px solid;
cursor: pointer; cursor: pointer;
} }
.dl1 { .dl1 {
background-color: rgba(230, 255, 251, 1); background-color: rgba(230, 255, 251, 1);
color: rgba(19, 168, 168, 1); color: rgba(19, 168, 168, 1);
border-color: rgba(135, 232, 222, 1); border-color: rgba(135, 232, 222, 1);
} }
.dl2 { .dl2 {
background-color: rgba(255, 241, 240, 1); background-color: rgba(255, 241, 240, 1);
color: rgb(206, 79, 81); color: rgb(206, 79, 81);
border-color: rgba(255, 163, 158, 1); border-color: rgba(255, 163, 158, 1);
} }
.dl3 { .dl3 {
background-color: rgba(255, 247, 230, 1); background-color: rgba(255, 247, 230, 1);
color: rgba(250, 140, 22, 1); color: rgba(250, 140, 22, 1);
border-color: rgba(255, 213, 145, 1); border-color: rgba(255, 213, 145, 1);
} }
.dl4 { .dl4 {
background-color: rgba(249, 240, 255, 1); background-color: rgba(249, 240, 255, 1);
color: rgba(114, 46, 209, 1); color: rgba(114, 46, 209, 1);
border-color: rgba(211, 173, 247, 1); border-color: rgba(211, 173, 247, 1);
} }
.origin { .origin {
font-size: 14px; font-size: 14px;
font-weight: 400; font-weight: 400;
...@@ -1420,6 +1514,7 @@ const dialogData = ref([ ...@@ -1420,6 +1514,7 @@ const dialogData = ref([
} }
} }
} }
.line-test { .line-test {
position: absolute; position: absolute;
top: 10px; top: 10px;
...@@ -1429,6 +1524,7 @@ const dialogData = ref([ ...@@ -1429,6 +1524,7 @@ const dialogData = ref([
z-index: -1; z-index: -1;
} }
} }
.line { .line {
width: 100%; width: 100%;
height: 1px; height: 1px;
...@@ -1436,6 +1532,7 @@ const dialogData = ref([ ...@@ -1436,6 +1532,7 @@ const dialogData = ref([
margin-top: 30px; margin-top: 30px;
border: none; border: none;
} }
.pagination { .pagination {
width: 100%; width: 100%;
height: 76px; height: 76px;
...@@ -1445,16 +1542,19 @@ const dialogData = ref([ ...@@ -1445,16 +1542,19 @@ const dialogData = ref([
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
border-top: 1px solid rgb(234, 236, 238); border-top: 1px solid rgb(234, 236, 238);
.total { .total {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
:deep(.custom-pagination) { :deep(.custom-pagination) {
display: flex; display: flex;
align-items: center; align-items: center;
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li) { :deep(.custom-pagination.el-pagination.is-background .el-pager li) {
min-width: 32px; min-width: 32px;
height: 32px; height: 32px;
...@@ -1468,11 +1568,13 @@ const dialogData = ref([ ...@@ -1468,11 +1568,13 @@ const dialogData = ref([
font-weight: 400; font-weight: 400;
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li.is-active) { :deep(.custom-pagination.el-pagination.is-background .el-pager li.is-active) {
background-color: #fff; background-color: #fff;
color: rgba(22, 119, 255, 1); color: rgba(22, 119, 255, 1);
border-color: rgba(22, 119, 255, 1); border-color: rgba(22, 119, 255, 1);
} }
:deep(.custom-pagination.el-pagination.is-background .el-pager li.is-ellipsis) { :deep(.custom-pagination.el-pagination.is-background .el-pager li.is-ellipsis) {
border: none; border: none;
background-color: transparent; background-color: transparent;
...@@ -1480,6 +1582,7 @@ const dialogData = ref([ ...@@ -1480,6 +1582,7 @@ const dialogData = ref([
min-width: 16px; min-width: 16px;
margin: 0 6px; margin: 0 6px;
} }
:deep(.custom-pagination.el-pagination.is-background .btn-prev), :deep(.custom-pagination.el-pagination.is-background .btn-prev),
:deep(.custom-pagination.el-pagination.is-background .btn-next) { :deep(.custom-pagination.el-pagination.is-background .btn-next) {
min-width: 32px; min-width: 32px;
...@@ -1492,6 +1595,7 @@ const dialogData = ref([ ...@@ -1492,6 +1595,7 @@ const dialogData = ref([
font-family: "Microsoft YaHei"; font-family: "Microsoft YaHei";
margin: 0 6px; margin: 0 6px;
} }
:deep(.custom-pagination.el-pagination.is-background .btn-prev.is-disabled), :deep(.custom-pagination.el-pagination.is-background .btn-prev.is-disabled),
:deep(.custom-pagination.el-pagination.is-background .btn-next.is-disabled) { :deep(.custom-pagination.el-pagination.is-background .btn-next.is-disabled) {
color: rgba(95, 101, 108, 0.45); color: rgba(95, 101, 108, 0.45);
...@@ -1501,9 +1605,11 @@ const dialogData = ref([ ...@@ -1501,9 +1605,11 @@ const dialogData = ref([
} }
} }
} }
.right { .right {
width: 520px; width: 520px;
height: 100%; height: 100%;
.right-top { .right-top {
width: 520px; width: 520px;
height: 602px; height: 602px;
...@@ -1511,12 +1617,14 @@ const dialogData = ref([ ...@@ -1511,12 +1617,14 @@ const dialogData = ref([
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
margin-bottom: 16px; margin-bottom: 16px;
.title { .title {
width: 100%; width: 100%;
height: 60px; height: 60px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 20px 0; padding: 14px 12px 20px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1525,6 +1633,7 @@ const dialogData = ref([ ...@@ -1525,6 +1633,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1532,32 +1641,39 @@ const dialogData = ref([ ...@@ -1532,32 +1641,39 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.main-content { .main-content {
width: 520px; width: 520px;
height: 517px; height: 517px;
padding: 0 48px 50px 34px; padding: 0 48px 50px 34px;
.baseInfo { .baseInfo {
width: 438px; width: 438px;
height: 314px; height: 314px;
padding-bottom: 50px; padding-bottom: 50px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
.baseInfo-item { .baseInfo-item {
display: flex; display: flex;
margin-bottom: 12px; margin-bottom: 12px;
.baseInfo-item-title { .baseInfo-item-title {
width: 88px; width: 88px;
font-size: 16px; font-size: 16px;
...@@ -1566,9 +1682,11 @@ const dialogData = ref([ ...@@ -1566,9 +1682,11 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
.address { .address {
width: 110px; width: 110px;
} }
.baseInfo-item-content { .baseInfo-item-content {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1576,15 +1694,18 @@ const dialogData = ref([ ...@@ -1576,15 +1694,18 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
.long { .long {
width: 349px; width: 349px;
} }
} }
} }
.company { .company {
width: 438px; width: 438px;
height: 176px; height: 176px;
padding-top: 19px; padding-top: 19px;
.company-title { .company-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1593,12 +1714,14 @@ const dialogData = ref([ ...@@ -1593,12 +1714,14 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 19px; margin-bottom: 19px;
} }
.company-content { .company-content {
width: 409px; width: 409px;
height: 114px; height: 114px;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow-y: scroll; overflow-y: scroll;
.company-item { .company-item {
width: 180px; width: 180px;
height: 49px; height: 49px;
...@@ -1606,6 +1729,7 @@ const dialogData = ref([ ...@@ -1606,6 +1729,7 @@ const dialogData = ref([
display: flex; display: flex;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
img { img {
width: 48px; width: 48px;
height: 48px; height: 48px;
...@@ -1620,9 +1744,10 @@ const dialogData = ref([ ...@@ -1620,9 +1744,10 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.company-name { .company-name {
width: 130px; width: 130px;
font-size: 16px; font-size: 16px;
...@@ -1631,13 +1756,15 @@ const dialogData = ref([ ...@@ -1631,13 +1756,15 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
} }
.company-item:nth-child(2n-1) { .company-item:nth-child(2n-1) {
margin-right: 39px; margin-right: 39px;
} }
.company-item:last-child { .company-item:last-child {
.company-name { .company-name {
width: 150px; width: 150px;
...@@ -1647,18 +1774,21 @@ const dialogData = ref([ ...@@ -1647,18 +1774,21 @@ const dialogData = ref([
} }
} }
} }
.right-bottom { .right-bottom {
width: 520px; width: 520px;
height: 1556px; height: 1556px;
background-color: rgba(255, 255, 255, 1); background-color: rgba(255, 255, 255, 1);
border-radius: 10px; border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1); box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.title { .title {
width: 100%; width: 100%;
height: 80px; height: 80px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 14px 12px 40px 0; padding: 14px 12px 40px 0;
.box { .box {
width: 8px; width: 8px;
height: 20px; height: 20px;
...@@ -1667,6 +1797,7 @@ const dialogData = ref([ ...@@ -1667,6 +1797,7 @@ const dialogData = ref([
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-right: 14px; margin-right: 14px;
} }
.text { .text {
font-size: 20px; font-size: 20px;
font-weight: 700; font-weight: 700;
...@@ -1674,29 +1805,35 @@ const dialogData = ref([ ...@@ -1674,29 +1805,35 @@ const dialogData = ref([
line-height: 26px; line-height: 26px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.btn { .btn {
width: 60px; width: 60px;
height: 28px; height: 28px;
margin-left: auto; margin-left: auto;
img { img {
width: 28px; width: 28px;
height: 28px; height: 28px;
cursor: pointer; cursor: pointer;
} }
img:first-child { img:first-child {
margin-right: 4px; margin-right: 4px;
} }
} }
} }
.content-main { .content-main {
width: 480px; width: 480px;
height: 1020px; height: 1020px;
margin-left: 16px; margin-left: 16px;
.content-item { .content-item {
width: 454px; width: 454px;
margin-bottom: 60px; margin-bottom: 60px;
margin-left: 26px; margin-left: 26px;
position: relative; position: relative;
.image01 { .image01 {
width: 14px; width: 14px;
height: 12.13px; height: 12.13px;
...@@ -1704,6 +1841,7 @@ const dialogData = ref([ ...@@ -1704,6 +1841,7 @@ const dialogData = ref([
top: 8px; top: 8px;
left: -26px; left: -26px;
} }
.content-item-time { .content-item-time {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1712,6 +1850,7 @@ const dialogData = ref([ ...@@ -1712,6 +1850,7 @@ const dialogData = ref([
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-title { .content-item-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1720,6 +1859,7 @@ const dialogData = ref([ ...@@ -1720,6 +1859,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-content { .content-item-content {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1728,6 +1868,7 @@ const dialogData = ref([ ...@@ -1728,6 +1868,7 @@ const dialogData = ref([
color: rgb(95, 101, 108); color: rgb(95, 101, 108);
margin-bottom: 8px; margin-bottom: 8px;
} }
.content-item-door { .content-item-door {
width: 300px; width: 300px;
height: 32px; height: 32px;
...@@ -1737,11 +1878,13 @@ const dialogData = ref([ ...@@ -1737,11 +1878,13 @@ const dialogData = ref([
border-radius: 4px; border-radius: 4px;
background-color: rgba(255, 246, 240, 1); background-color: rgba(255, 246, 240, 1);
border: 1px solid rgba(250, 140, 22, 0.4); border: 1px solid rgba(250, 140, 22, 0.4);
img { img {
width: 20px; width: 20px;
height: 24px; height: 24px;
margin-right: 10px; margin-right: 10px;
} }
span { span {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1766,20 +1909,24 @@ const dialogData = ref([ ...@@ -1766,20 +1909,24 @@ const dialogData = ref([
height: 669px; height: 669px;
padding: 0; padding: 0;
border-radius: 4px; border-radius: 4px;
.el-dialog__body { .el-dialog__body {
padding: 0; padding: 0;
} }
.el-dialog__header { .el-dialog__header {
padding: 0; padding: 0;
margin: 0; margin: 0;
position: relative; position: relative;
height: 48px; height: 48px;
} }
.el-dialog__headerbtn { .el-dialog__headerbtn {
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
right: 12px; right: 12px;
} }
.viewpoint-header { .viewpoint-header {
width: 761px; width: 761px;
height: 48px; height: 48px;
...@@ -1789,6 +1936,7 @@ const dialogData = ref([ ...@@ -1789,6 +1936,7 @@ const dialogData = ref([
padding: 0 24px; padding: 0 24px;
border-bottom: 1px solid rgb(234, 236, 238); border-bottom: 1px solid rgb(234, 236, 238);
} }
.viewpoint-title { .viewpoint-title {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1796,11 +1944,13 @@ const dialogData = ref([ ...@@ -1796,11 +1944,13 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(5, 95, 194); color: rgb(5, 95, 194);
} }
.viewpoint-body { .viewpoint-body {
padding: 24px 24px 35px 24px; padding: 24px 24px 35px 24px;
height: calc(669px - 48px); height: calc(669px - 48px);
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
.viewpoint-body-title { .viewpoint-body-title {
font-size: 28px; font-size: 28px;
font-weight: 700; font-weight: 700;
...@@ -1809,6 +1959,7 @@ const dialogData = ref([ ...@@ -1809,6 +1959,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 32px; margin-bottom: 32px;
} }
.viewpoint-item { .viewpoint-item {
width: 713px; width: 713px;
min-height: 81px; min-height: 81px;
...@@ -1816,6 +1967,7 @@ const dialogData = ref([ ...@@ -1816,6 +1967,7 @@ const dialogData = ref([
margin-bottom: 12px; margin-bottom: 12px;
position: relative; position: relative;
padding-left: 48px; padding-left: 48px;
.viewpoint-item-img { .viewpoint-item-img {
position: absolute; position: absolute;
top: 0; top: 0;
...@@ -1823,6 +1975,7 @@ const dialogData = ref([ ...@@ -1823,6 +1975,7 @@ const dialogData = ref([
width: 42px; width: 42px;
height: 42px; height: 42px;
} }
.viewpoint-item-content { .viewpoint-item-content {
width: 665px; width: 665px;
min-height: 81px; min-height: 81px;
...@@ -1835,6 +1988,7 @@ const dialogData = ref([ ...@@ -1835,6 +1988,7 @@ const dialogData = ref([
padding-top: 12px; padding-top: 12px;
padding-bottom: 13px; padding-bottom: 13px;
box-sizing: border-box; box-sizing: border-box;
.viewpoint-item-name { .viewpoint-item-name {
font-size: 16px; font-size: 16px;
font-weight: 700; font-weight: 700;
...@@ -1843,6 +1997,7 @@ const dialogData = ref([ ...@@ -1843,6 +1997,7 @@ const dialogData = ref([
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
margin-bottom: 5px; margin-bottom: 5px;
} }
.viewpoint-item-desc { .viewpoint-item-desc {
font-size: 16px; font-size: 16px;
font-weight: 400; font-weight: 400;
...@@ -1850,6 +2005,7 @@ const dialogData = ref([ ...@@ -1850,6 +2005,7 @@ const dialogData = ref([
line-height: 24px; line-height: 24px;
color: rgb(59, 65, 75); color: rgb(59, 65, 75);
} }
.viewpoint-item-job { .viewpoint-item-job {
position: absolute; position: absolute;
top: 8px; top: 8px;
...@@ -1864,4 +2020,53 @@ const dialogData = ref([ ...@@ -1864,4 +2020,53 @@ const dialogData = ref([
} }
} }
} }
.proposal-wrapper {
position: relative;
/* 作为定位参考 */
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
}
.proposal-tab-switcher {
position: absolute;
right: calc(100% + 24px);
top: 0;
display: flex;
flex-direction: column;
gap: 12px;
z-index: 1;
}
.proposal-tab {
width: 120px;
height: 32px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 12px;
font-family: 'Source Han Sans CN', 'Noto Sans SC', sans-serif;
font-size: 16px;
font-weight: 400;
color: #8c8c8c;
background: none;
border: none;
border-radius: 20px;
cursor: pointer;
white-space: nowrap;
box-sizing: border-box;
}
.proposal-tab.active {
background: #055FC2;
color: #fff;
}
.proposal-tab-arrow {
flex-shrink: 0;
}
</style> </style>
<template>
<div class="resume-card">
<div class="resume-card__header">
<div class="resume-card__indicator"></div>
<div class="resume-card__title">{{ title }}</div>
<div class="resume-card__tabs" v-if="tabs.length > 0">
<div
v-for="(tab, i) in tabs"
:key="tab.key"
class="resume-card__tab"
:class="{ 'resume-card__tab--active': activeTab === tab.key }"
@click="switchTab(tab.key)"
>{{ tab.label }}</div>
</div>
<div class="resume-card__actions">
<img
:src="downloadIcon"
alt="下载"
class="resume-card__action-btn"
@click="$emit('download')"
/>
<img
:src="collectIcon"
alt="收藏"
class="resume-card__action-btn"
@click="$emit('collect')"
/>
</div>
</div>
<div class="resume-card__body">
<div v-for="(item, index) in currentList" :key="index" class="resume-item">
<img :src="timelineIcon" alt="" class="resume-item__icon" />
<div class="resume-item__time">{{ item.startTime + '-' + item.endTime }}</div>
<div class="resume-item__title">{{ item.orgName + '|' + item.jobName }}</div>
<div class="resume-item__content">{{ item.content }}</div>
<div class="resume-item__door" v-if="item.door">
<img :src="doorIcon" alt="" />
<span>{{ item.door }}</span>
</div>
</div>
</div>
</div>
</template>
<script>
import defaultDownloadIcon from "../assets/下载按钮.png";
import defaultCollectIcon from "../assets/收藏按钮.png";
import defaultTimelineIcon from "../assets/icon01.png";
import defaultDoorIcon from "../assets/icon02.png";
export default {
name: "ResumeCard",
props: {
/** 卡片标题 */
title: {
type: String,
default: "生涯履历",
},
/**
* Tab 配置数组,示例:
* [{ key: "career", label: "职业履历" }, { key: "edu", label: "教育履历" }]
* 不传则无 tab,直接显示 list 数据
*/
tabs: {
type: Array,
default: () => [],
},
/**
* 无 tab 时的数据数组;有 tab 时忽略此 prop,使用 dataMap
*/
list: {
type: Array,
default: () => [],
},
/**
* 有 tab 时的数据映射,key 对应 tabs[].key,示例:
* { career: [...], edu: [...] }
*/
dataMap: {
type: Object,
default: () => ({}),
},
/** 自定义图标路径(可选) */
downloadIcon: {
type: String,
default: defaultDownloadIcon,
},
collectIcon: {
type: String,
default: defaultCollectIcon,
},
timelineIcon: {
type: String,
default: defaultTimelineIcon,
},
doorIcon: {
type: String,
default: defaultDoorIcon,
},
},
emits: ["download", "collect", "tab-change"],
data() {
return {
activeTab: this.tabs.length > 0 ? this.tabs[0].key : "",
};
},
computed: {
/** 当前展示的列表数据 */
currentList() {
if (this.tabs.length > 0 && this.activeTab) {
return this.dataMap[this.activeTab] || [];
}
return this.list;
},
},
methods: {
switchTab(key) {
this.activeTab = key;
this.$emit("tab-change", key);
},
},
};
</script>
<style scoped lang="scss">
.resume-card {
width: 520px;
min-height: 200px;
background-color: rgba(255, 255, 255, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
}
.resume-card__header {
width: 100%;
height: 80px;
display: flex;
align-items: center;
padding: 14px 12px 40px 0;
}
.resume-card__indicator {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.resume-card__title {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.resume-card__tabs {
display: flex;
align-items: center;
gap: 12px;
margin-left: 20px;
}
.resume-card__tab {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
padding: 2px 12px;
border-radius: 4px;
border: 1px solid transparent;
cursor: pointer;
transition: all 0.2s;
&:hover {
color: rgb(5, 95, 194);
}
&--active {
color: rgb(5, 95, 194);
font-weight: 700;
border-color: rgb(5, 95, 194);
background-color: rgba(5, 95, 194, 0.05);
}
}
.resume-card__actions {
margin-left: auto;
display: flex;
align-items: center;
gap: 4px;
}
.resume-card__action-btn {
width: 28px;
height: 28px;
cursor: pointer;
}
.resume-card__body {
width: 480px;
margin-left: 16px;
padding-bottom: 20px;
}
.resume-item {
width: 454px;
// margin-bottom: 60px;
margin-left: 26px;
position: relative;
}
.resume-item__icon {
width: 14px;
height: 12.13px;
position: absolute;
top: 8px;
left: -26px;
}
.resume-item__time {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 30px;
color: rgb(5, 95, 194);
margin-bottom: 8px;
}
.resume-item__title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 30px;
color: rgb(59, 65, 75);
margin-bottom: 8px;
}
.resume-item__content {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
margin-bottom: 8px;
}
.resume-item__door {
width: 300px;
height: 32px;
display: flex;
align-items: center;
padding: 4px 0 4px 11px;
border-radius: 4px;
background-color: rgba(255, 246, 240, 1);
border: 1px solid rgba(250, 140, 22, 0.4);
img {
width: 20px;
height: 24px;
margin-right: 10px;
}
span {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgba(255, 149, 77, 1);
}
}
</style>
...@@ -205,28 +205,12 @@ ...@@ -205,28 +205,12 @@
</div> </div>
</div> </div>
</div> </div>
<div class="right-bottom"> <ResumeCard
<div class="title"> title="政治履历"
<div class="box"></div> :list="CharacterResume"
<div class="text">政治履历</div> @download="handleDownload"
<div class="btn"> @collect="handleCollect"
<img src="./assets/下载按钮.png" alt="" /> />
<img src="./assets/收藏按钮.png" alt="" />
</div>
</div>
<div class="content-main">
<div v-for="item in CharacterResume" class="content-item">
<img src="./assets/icon01.png" alt="" class="image01" />
<div class="content-item-time">{{ item.startTime +'-' + item.endTime}}</div>
<div class="content-item-title">{{ item.orgName +'|' + item.jobName}}</div>
<div class="content-item-content">{{ item.content }}</div>
<div class="content-item-door" v-if="item.door">
<img src="./assets/icon02.png" alt="" />
<span>{{ item.door }}</span>
</div>
</div>
</div>
</div>
</div> </div>
</div> </div>
<!-- 成果报告 --> <!-- 成果报告 -->
...@@ -281,7 +265,7 @@ import { getCharacterGlobalInfo, ...@@ -281,7 +265,7 @@ import { getCharacterGlobalInfo,
import Musk from "./assets/Musk.png"; import Musk from "./assets/Musk.png";
import cp1 from "./assets/cp1.png"; import cp1 from "./assets/cp1.png";
import cp2 from "./assets/cp2.png"; import cp2 from "./assets/cp2.png";
import ResumeCard from "./components/Resume.vue";
import img1 from "./assets/img1.png"; import img1 from "./assets/img1.png";
import img2 from "./assets/img2.png"; import img2 from "./assets/img2.png";
...@@ -1433,112 +1417,7 @@ const dialogData = ref([ ...@@ -1433,112 +1417,7 @@ const dialogData = ref([
} }
} }
} }
.right-bottom {
width: 520px;
height: 1292px;
background-color: rgba(255, 255, 255, 1);
border-radius: 10px;
box-shadow: 0px 0px 20px rgba(25, 69, 130, 0.1);
.title {
width: 100%;
height: 80px;
display: flex;
align-items: center;
padding: 14px 12px 40px 0;
.box {
width: 8px;
height: 20px;
background-color: rgb(5, 95, 194);
border-bottom-right-radius: 4px;
border-top-right-radius: 4px;
margin-right: 14px;
}
.text {
font-size: 20px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 26px;
color: rgb(5, 95, 194);
}
.btn {
width: 60px;
height: 28px;
margin-left: auto;
img {
width: 28px;
height: 28px;
cursor: pointer;
}
img:first-child {
margin-right: 4px;
}
}
}
.content-main {
width: 480px;
height: 1020px;
margin-left: 16px;
.content-item {
width: 454px;
margin-bottom: 60px;
margin-left: 26px;
position: relative;
.image01 {
width: 14px;
height: 12.13px;
position: absolute;
top: 8px;
left: -26px;
}
.content-item-time {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 30px;
color: rgb(5, 95, 194);
margin-bottom: 8px;
}
.content-item-title {
font-size: 16px;
font-weight: 700;
font-family: "Microsoft YaHei";
line-height: 30px;
color: rgb(59, 65, 75);
margin-bottom: 8px;
}
.content-item-content {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgb(95, 101, 108);
margin-bottom: 8px;
}
.content-item-door {
width: 300px;
height: 32px;
display: flex;
align-items: center;
padding: 4px 0 4px 11px;
border-radius: 4px;
background-color: rgba(255, 246, 240, 1);
border: 1px solid rgba(250, 140, 22, 0.4);
img {
width: 20px;
height: 24px;
margin-right: 10px;
}
span {
font-size: 16px;
font-weight: 400;
font-family: "Microsoft YaHei";
line-height: 24px;
color: rgba(255, 149, 77, 1);
}
}
}
}
}
} }
} }
} }
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="person-info"> <div class="person-info" @click="handleClickToCharacter(item.personId)">
<div class="person-name">{{ item.name }}</div> <div class="person-name">{{ item.name }}</div>
<div class="person-position">{{ item.position }}</div> <div class="person-position">{{ item.position }}</div>
</div> </div>
...@@ -49,7 +49,8 @@ ...@@ -49,7 +49,8 @@
import { ref,onMounted,defineProps,watch } from "vue"; import { ref,onMounted,defineProps,watch } from "vue";
import personData from "../json/personData.json"; // 引入JSON数据 import personData from "../json/personData.json"; // 引入JSON数据
import {getMainCharactersView } from "@/api/technologyFigures/technologyFigures"; import {getMainCharactersView } from "@/api/technologyFigures/technologyFigures";
import { useCharacterNav } from "../utils/useCharacterNav";
const { handleClickToCharacter } = useCharacterNav();
const props = defineProps({ const props = defineProps({
persontypeid: { persontypeid: {
type: String, type: String,
...@@ -86,7 +87,8 @@ const handlegetMainCharactersViewFn = async () => { ...@@ -86,7 +87,8 @@ const handlegetMainCharactersViewFn = async () => {
position: item.positionTitle, position: item.positionTitle,
tags: ["1", "2"], tags: ["1", "2"],
chinaRelatedCount: item.remarksCount, chinaRelatedCount: item.remarksCount,
mediaQuoteCount: item.remarksCount mediaQuoteCount: item.remarksCount,
personId:item.personId
} }
}); });
......
<template> <template>
<div ref="map" style="width: 1600px; height: 551px"></div> <div class="map-wrapper">
<div <div ref="map" class="map-container"></div>
style="width: 1231px; height: 72px; margin-top: -55px; background: linear-gradient(to top, #DAEBFD, #ffffff); margin-left: 182px"> <!-- 弹窗浮层:绝对定位在地图上方 -->
<div style="width: 1231px; height: 40px; display: flex; justify-content: space-between;"> <div class="map-overlay" ref="mapOverlay">
<div style="display: flex; align-self: center;"> <div
<div class="time-btn"> v-for="(card, idx) in mapCards"
:key="idx"
class="map-card"
:style="{ left: card.x + 'px', top: card.y + 'px' }"
>
<img class="map-card__bg" src="/icon/map-text-bg.png" alt="" />
<img class="map-card__avatar" :src="card.avatar" alt="" />
<div class="map-card__info">
<div class="map-card__time">{{ card.time }}</div>
<div class="map-card__text">{{ card.text }}</div>
</div> </div>
<div class="time-btn"> </div>
</div>
<div class="timeline-panel">
<!-- 控制栏 -->
<div class="control-bar">
<!-- 左侧:播放控制按钮 -->
<div class="control-left">
<button class="ctrl-btn" @click="handlePause" :title="isPlaying ? '暂停' : '播放'">
<!-- 暂停图标:两竖线 -->
<svg v-if="isPlaying" width="10" height="14" viewBox="0 0 10 14" fill="none">
<rect x="0" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
<rect x="7" y="0" width="3" height="14" rx="0.5" fill="#04295A"/>
</svg>
<!-- 播放图标:三角 -->
<svg v-else width="10" height="14" viewBox="0 0 10 14" fill="none">
<polygon points="0,0 10,7 0,14" fill="#04295A"/>
</svg>
</button>
<button class="ctrl-btn" @click="handleStop" title="停止">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
<rect x="0" y="0" width="12" height="12" rx="1" fill="#04295A"/>
</svg>
</button>
<button class="ctrl-btn ctrl-btn--text" @click="handleReset">重置</button>
</div> </div>
<div class="time-btn"> <!-- 右侧:时间 / 刻度 / 速率 -->
重置 <div class="control-right">
<span class="control-label">当前时间</span>
<el-date-picker
v-model="currentDate"
type="date"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
placeholder="选择日期"
class="timeline-date-picker"
@change="handleDateChange"
/>
<span class="control-label control-label--gap">刻度</span>
<el-select v-model="scaleMode" class="timeline-select" @change="handleScaleChange">
<el-option label="周制" value="week" />
<el-option label="月制" value="month" />
<el-option label="日制" value="day" />
</el-select>
<span class="control-label control-label--gap">速率</span>
<el-select v-model="speedRate" class="timeline-select" @change="handleSpeedChange">
<el-option label="1X" value="1" />
<el-option label="2X" value="2" />
<el-option label="4X" value="4" />
</el-select>
</div> </div>
</div> </div>
<div style="display: flex; align-self: center;"> <!-- 时间轴(可滚动) -->
<div> <div class="timeline-track" ref="timelineTrack"
当前时间: @mousedown="onTimelineMouseDown"
>
<div class="timeline-inner" ref="timelineInner">
<template v-for="(tick, index) in visibleTicks" :key="tick.date">
<div class="tick-cell" :style="{ width: tickWidth + 'px' }" @click="onTickClick(tick.globalIndex)">
<div class="tick-line" :class="{ 'tick-line--major': tick.showLabel }"></div>
<span class="tick-label" v-if="tick.showLabel">{{ tick.label }}</span>
</div>
</template>
<!-- 当前指示竖线 -->
<div class="timeline-cursor" v-if="cursorLeft >= 0" :style="{ left: cursorLeft + 'px' }"></div>
</div> </div>
<input></input>
<div style="margin: 0 10px 0 40px; color: #04295A;">刻度</div>
<select name="firstSelect" id="firstSelect">
<option value="option1">周制</option>
</select>
<div style="margin: 0 10px 0 40px; color: #04295A;">速率</div>
<select name="secondSelect" id="secondSelect" style="margin-right: 20px;">
<option value="optionA">1X</option>
<option value="optionB">2X</option>
</select>
</div> </div>
</div> </div>
<div ref="timeLineChart" style="width: 1231px; height: 30px; background-color: #6091D6;"></div>
</div> </div>
</template> </template>
<script> <script>
import * as echarts from "echarts"; import * as echarts from "echarts";
import worldJson from "@/assets/json/world.json"; import worldJson from "@/assets/json/world.json";
import {getCharacterTrends} from "@/api/technologyFigures/technologyFigures" import { getCharacterTrends } from "@/api/technologyFigures/technologyFigures";
import { ref,onMounted } from "vue"; import { ref } from "vue";
// const props = defineProps({
// peoDate: {
// type: String,
// default:"jinri"
// }
// })
// 获取人物动向 // 获取人物动向
const CharacterTrends = ref([]); const CharacterTrends = ref([]);
const time = ref(""); const time = ref("");
const date = ref(""); const date = ref("");
const handlegetCharacterTrendsFn = async () => { const handlegetCharacterTrendsFn = async () => {
const params = { const params = {
startTime: "2025-01-01" || date.value startTime: "2025-01-01" || date.value,
}; };
try { try {
const res = await getCharacterTrends(params); const res = await getCharacterTrends(params);
console.log("人物动向", res); if (res.code === 200) {
CharacterTrends.value = res.data.map((item) => {
if (res.code === 200) { return {
CharacterTrends.value = res.data.map(item=>{
return{
time: item.newsDate, time: item.newsDate,
text: item.newsContent.substring(0, 17), text: item.newsContent.substring(0, 17),
lon: item.lon || (Math.random() * 360 - 180).toFixed(6), //没数据 lon: item.lon || (Math.random() * 360 - 180).toFixed(6),
lat: item.lat || (Math.random() * 180 - 90).toFixed(6), //没数据 lat: item.lat || (Math.random() * 180 - 90).toFixed(6),
avatar: item.imageUrl avatar: item.imageUrl,
}; };
}); });
}
} } catch (error) {}
} catch (error) {}
}; };
function getDateDaysAgo(days) { function getDateDaysAgo(days) {
// 获取当前日期 const currentDate = new Date();
const currentDate = new Date(); const pastDate = new Date(currentDate);
// 计算指定月数之前的日期 pastDate.setDate(currentDate.getDate() - days);
const pastDate = new Date(currentDate); const year = pastDate.getFullYear();
pastDate.setDate(currentDate.getDate() - days); // 减去指定的天数 const month = String(pastDate.getMonth() + 1).padStart(2, "0");
// 格式化日期为 "YYYY-MM-DD" 的形式 const day = String(pastDate.getDate()).padStart(2, "0");
const year = pastDate.getFullYear(); return `${year}-${month}-${day}`;
const month = String(pastDate.getMonth() + 1).padStart(2, "0"); // 月份从0开始,需要加1
const day = String(pastDate.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
} }
// onMounted(async () => { // 生成时间轴刻度
// handlegetCharacterTrendsFn(); function generateTicks(startDate, endDate, mode) {
// }) const ticks = [];
const start = new Date(startDate);
const end = new Date(endDate);
const current = new Date(start);
while (current <= end) {
const m = String(current.getMonth() + 1).padStart(2, "0");
const d = String(current.getDate()).padStart(2, "0");
ticks.push({
date: `${current.getFullYear()}-${m}-${d}`,
label: `${m}.${d}`,
showLabel: ticks.length % 7 === 0, // 每 7 天显示一个标签
});
if (mode === "week") {
current.setDate(current.getDate() + 7);
} else if (mode === "month") {
current.setMonth(current.getMonth() + 1);
} else {
current.setDate(current.getDate() + 1);
}
}
return ticks;
}
export default { export default {
name: "MapAnimation", name: "MapAnimation",
props: { props: {
peoDate: { peoDate: {
type: String, type: String,
default:"本周" default: "本周",
} },
},
data() {
const today = new Date();
const todayStr = today.getFullYear() + "-" +
String(today.getMonth() + 1).padStart(2, "0") + "-" +
String(today.getDate()).padStart(2, "0");
return {
isPlaying: false,
playTimer: null,
currentDate: todayStr,
scaleMode: "day",
speedRate: "2",
currentTickIndex: 0,
timelineTicks: [],
startDate: "",
endDate: "",
chartInstance: null,
mapCards: [], // DOM 浮层卡片数据
tickWidth: 10, // 每天格子宽度 px
viewStartIndex: 0, // 可视区左侧对应的 tick index
viewDays: 120, // 可视区天数(约 4 个月)
};
},
computed: {
visibleTicks() {
const start = Math.max(0, this.viewStartIndex);
const end = Math.min(this.timelineTicks.length, start + this.viewDays);
return this.timelineTicks.slice(start, end).map((tick, i) => ({
...tick,
globalIndex: start + i,
}));
},
cursorLeft() {
const offset = this.currentTickIndex - this.viewStartIndex;
if (offset < 0 || offset >= this.viewDays) return -9999; // 不在可视区
return offset * this.tickWidth + this.tickWidth / 2;
},
}, },
watch: { watch: {
async peoDate(Val) { async peoDate(Val) {
time.value = Val; time.value = Val;
const days = ref(); const days = ref();
if(time.value === "本周"){ if (time.value === "本周") days.value = 7;
days.value = 7; else if (time.value === "今天") days.value = 0;
}else if(time.value === "今天"){ else if (time.value === "昨天") days.value = 1;
days.value = 0; else if (time.value === "近三天") days.value = 3;
}else if(time.value === "昨天"){ else if (time.value === "本月") days.value = new Date().getDate() - 1;
days.value = 1;
}else if(time.value === "近三天"){
days.value = 3;
}else if(time.value === "本月"){
days.value = new Date().getDate()-1;
}
date.value = getDateDaysAgo(days.value); date.value = getDateDaysAgo(days.value);
await handlegetCharacterTrendsFn(); await handlegetCharacterTrendsFn();
this.initTimelineFromData();
this.initMap(); this.initMap();
} },
},
setup(props) {
onMounted(async () => {
time.value = props.peoDate;
const days = ref();
if(time.value === "本周"){
days.value = 7;
}else if(time.value === "今天"){
days.value = 0;
}else if(time.value === "昨天"){
days.value = 1;
}else if(time.value === "近三天"){
days.value = 3;
}else if(time.value === "本月"){
days.value = new Date().getDate()-1;
}
date.value = getDateDaysAgo(days.value);
});
}, },
async mounted() { async mounted() {
// 原 setup onMounted 中的初始化逻辑
time.value = this.peoDate;
let days = 7;
if (time.value === "本周") days = 7;
else if (time.value === "今天") days = 0;
else if (time.value === "昨天") days = 1;
else if (time.value === "近三天") days = 3;
else if (time.value === "本月") days = new Date().getDate() - 1;
date.value = getDateDaysAgo(days);
await handlegetCharacterTrendsFn(); await handlegetCharacterTrendsFn();
this.initTimelineFromData();
this.initMap(); this.initMap();
// 在捕获阶段拦截滚轮事件,阻止页面Y轴滚动
this._mapWheelHandler = (e) => { e.preventDefault(); };
const mapEl = this.$refs.map;
if (mapEl) {
mapEl.addEventListener("wheel", this._mapWheelHandler, { passive: false, capture: true });
}
// 时间轴滚轮:手动注册 non-passive 监听器,阻止页面滚动并横向滚动时间轴
this._tlWheelHandler = (e) => {
e.preventDefault();
this.onTimelineWheel(e);
};
const tlEl = this.$refs.timelineTrack;
if (tlEl) {
tlEl.addEventListener("wheel", this._tlWheelHandler, { passive: false });
}
},
beforeUnmount() {
this.clearPlayTimer();
const mapEl = this.$refs.map;
if (mapEl && this._mapWheelHandler) {
mapEl.removeEventListener("wheel", this._mapWheelHandler, { capture: true });
}
const tlEl = this.$refs.timelineTrack;
if (tlEl && this._tlWheelHandler) {
tlEl.removeEventListener("wheel", this._tlWheelHandler);
}
if (this.chartInstance) {
this.chartInstance.dispose();
this.chartInstance = null;
}
}, },
methods: { methods: {
// ---- 时间轴(可滚动)----
/**
* 鼠标滚轮左右滚动时间轴
*/
onTimelineWheel(e) {
const delta = e.deltaY || e.deltaX;
const step = delta > 0 ? 7 : -7; // 每次滚 7 天
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
this.viewStartIndex + step
));
},
/**
* 鼠标拖拽滚动时间轴
*/
onTimelineMouseDown(e) {
const startX = e.clientX;
const startViewIndex = this.viewStartIndex;
const onMove = (ev) => {
const dx = ev.clientX - startX;
const daysMoved = Math.round(dx / this.tickWidth);
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
startViewIndex - daysMoved
));
};
const onUp = () => {
document.removeEventListener("mousemove", onMove);
document.removeEventListener("mouseup", onUp);
};
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
},
/**
* 点击某个刻度跳转
*/
onTickClick(globalIndex) {
if (this.isPlaying) this.pausePlay();
this.currentTickIndex = globalIndex;
this.syncDateFromTick();
this.updateMapByDate(this.currentDate);
this.$emit("timeline-change", this.currentDate);
},
/**
* 将可视窗口居中到指定 tick
*/
scrollTimelineTo(tickIdx) {
const half = Math.floor(this.viewDays / 2);
this.viewStartIndex = Math.max(0, Math.min(
this.timelineTicks.length - this.viewDays,
tickIdx - half
));
},
rebuildTimeline() {
if (!this.startDate || !this.endDate) return;
this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
this.initTimelinePosition();
this.scrollTimelineTo(this.currentTickIndex);
},
handleDateChange(val) {
this.currentDate = val;
// 检查是否超出当前时间轴范围,如果是则重建
if (val < this.startDate || val > this.endDate) {
const y = parseInt(val.substring(0, 4));
if (val < this.startDate) {
this.startDate = y + "-01-01";
}
if (val > this.endDate) {
this.endDate = y + "-12-31";
}
this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
}
const idx = this.timelineTicks.findIndex((t) => t.date >= val);
this.currentTickIndex = idx >= 0 ? idx : this.timelineTicks.length - 1;
this.currentDate = this.timelineTicks[this.currentTickIndex].date;
this.updateMapByDate(this.currentDate);
this.scrollTimelineTo(this.currentTickIndex);
this.$emit("timeline-change", this.currentDate);
},
handleScaleChange(val) {
this.$emit("scale-change", val);
},
handleSpeedChange(val) {
this.$emit("speed-change", val);
// 速率变了,如果正在播放则重新调度(setTimeout 方式天然支持,下一帧会用新速率)
if (this.isPlaying) {
this.clearPlayTimer();
this.scheduleNextTick();
}
},
// ---- 播放控制 ----
handlePause() {
if (this.isPlaying) {
this.pausePlay();
} else {
// 只有播放到末尾时才从当前位置重新开始(不跳到开头)
if (this.currentTickIndex >= this.timelineTicks.length - 1) {
// 已在末尾,不自动跳回开头,用户需要手动选择起始位置或点重置
return;
}
this.startPlay();
}
this.$emit("play-toggle", this.isPlaying);
},
handleStop() {
this.clearPlayTimer();
this.isPlaying = false;
// 停止时停留在当前位置,不重置
this.$emit("stop", this.currentDate);
},
handleReset() {
this.clearPlayTimer();
this.isPlaying = false;
// 回到系统当前时间
const now = new Date();
this.currentDate = now.getFullYear() + "-" +
String(now.getMonth() + 1).padStart(2, "0") + "-" +
String(now.getDate()).padStart(2, "0");
this.initTimelinePosition();
this.scrollTimelineTo(this.currentTickIndex);
this.updateMapByDate(this.currentDate);
this.$emit("reset");
this.$emit("timeline-change", this.currentDate);
},
startPlay() {
this.clearPlayTimer();
this.isPlaying = true;
this.scheduleNextTick();
},
pausePlay() {
this.clearPlayTimer();
this.isPlaying = false;
},
scheduleNextTick() {
// 基础间隔 1000ms,按速率缩短
const interval = 1000 / Number(this.speedRate);
this.playTimer = setTimeout(() => {
if (this.currentTickIndex < this.timelineTicks.length - 1) {
this.currentTickIndex++;
this.syncDateFromTick();
this.updateMapByDate(this.currentDate);
this.scrollTimelineTo(this.currentTickIndex);
this.$emit("timeline-change", this.currentDate);
// 递归调度下一帧
this.scheduleNextTick();
} else {
// 播放结束
this.isPlaying = false;
this.playTimer = null;
this.$emit("play-end");
}
}, interval);
},
clearPlayTimer() {
if (this.playTimer) {
clearTimeout(this.playTimer);
this.playTimer = null;
}
},
// 将当前 tick 索引同步到 currentDate
syncDateFromTick() {
const tick = this.timelineTicks[this.currentTickIndex];
if (tick) {
this.currentDate = tick.date;
}
},
// ---- 地图 ----
initMap() { initMap() {
// 注册自定义地图数据
echarts.registerMap("China", worldJson); echarts.registerMap("China", worldJson);
// const eventsData = [ // 如果已有实例则销毁重建
// { if (this.chartInstance) {
// time: "9月23日", this.chartInstance.dispose();
// text: "随美国总统特朗普进行国事访问", }
// lon: 116.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "出席中国发展高层论坛2025年年会",
// lon: 116.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "与民主党领导人查克·舒默及哈基姆...",
// lon: 1.46,
// lat: 39.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "与阿拉伯国家领导人会晤,商讨加...",
// lon: 116.46,
// lat: -44.92,
// avatar: "/testData/united_states 1 copy.png"
// },
// {
// time: "9月23日",
// text: "对印度进行为期四天的访问,与总理...",
// lon: 78.1,
// lat: 20.7,
// avatar: "/testData/united_states 1 copy.png"
// }
// ];
const eventsData = CharacterTrends;
const chart = echarts.init(this.$refs.map); const chart = echarts.init(this.$refs.map);
this.chartInstance = chart;
const option = { const option = {
grid: { grid: {
...@@ -206,211 +444,454 @@ export default { ...@@ -206,211 +444,454 @@ export default {
right: "10%", right: "10%",
bottom: "10%", bottom: "10%",
top: "10%", top: "10%",
containLabel: true containLabel: true,
}, },
geo: { geo: {
map: "China", map: "China",
roam: true, roam: true,
label: { label: {
emphasis: { emphasis: { show: false, color: "#fff" },
show: false,
color: "#fff"
}
}, },
silent: true, silent: true,
itemStyle: { itemStyle: {
areaColor: "#F6FAFF", areaColor: "#F6FAFF",
borderColor: "#B9DCFF" borderColor: "#B9DCFF",
} },
}, },
series: [ series: [
{ {
name: "行程", name: "行程",
type: "effectScatter", type: "effectScatter",
coordinateSystem: "geo", coordinateSystem: "geo",
data: eventsData.value.map(item => ({ data: [],
value: [item.lon, item.lat],
time: item.time,
text: item.text,
avatar: item.avatar,
itemStyle: { color: "#ffcc00" }
})),
symbolSize: 10, symbolSize: 10,
showEffectOn: "render", showEffectOn: "render",
rippleEffect: { rippleEffect: { brushType: "stroke" },
brushType: "stroke"
},
// label: {
// normal: {
// show: true,
// formatter: params => {
// const { time, text } = eventsData[params.dataIndex];
// return `{time|${time}} \n {text|${text}}`;
// },
// rich: {
// time: { fontSize: 16, color: 'rgba(5, 95, 194, 1)' },
// text: { fontSize: 14, color: 'rgb(95, 101, 108)' }
// }
// }
// },
itemStyle: { itemStyle: {
color: "#ddb926", color: "#ddb926",
shadowBlur: 10, shadowBlur: 10,
shadowColor: "#333" shadowColor: "#333",
} },
} },
] ],
}; };
chart.setOption(option); chart.setOption(option);
// 地图拖拽/缩放时重新计算 DOM 卡片位置(加防抖避免频繁���发)
// 添加 graphic 元素并设置其位置 let georoamTimer = null;
this.updateGraphics(chart, eventsData); chart.on("georoam", () => {
if (georoamTimer) clearTimeout(georoamTimer);
// 监听地图缩放和移动事件,以更新 graphic 的位置 georoamTimer = setTimeout(() => {
chart.on('georoam', () => { const events = this._currentDisplayData || [];
this.updateGraphics(chart, eventsData); this.updateMapCards(events);
}, 100);
}); });
const chart2 = echarts.init(this.$refs.timeLineChart); // 初始化:默认定位到当前日期
this.initTimelinePosition();
const option2 = { this.scrollTimelineTo(this.currentTickIndex);
xAxis: { this.updateMapByDate(this.currentDate);
type: "time", },
position: "top",
axisLine: { /**
show: true, * 根据数据范围 + 当前日期,构建覆盖完整年份的每日时间轴
lineStyle: { * 取数据最小年份1月1日 ~ 当前年份12月31日
color: "#000" */
} initTimelineFromData() {
}, const allData = CharacterTrends.value || [];
axisTick: { const now = new Date();
show: false const currentYear = now.getFullYear();
}, let minYear = currentYear;
axisLabel: { let maxYear = currentYear;
formatter: "{value}日", // 从数据中找最早和最晚的年份
color: "#000" allData.forEach((item) => {
}, if (item.time) {
splitLine: { const y = parseInt(item.time.substring(0, 4));
show: true if (y < minYear) minYear = y;
}, if (y > maxYear) maxYear = y;
data: [ }
"2024-03-25", });
"2024-04-08", this.startDate = minYear + "-01-01";
"2024-04-15", this.endDate = maxYear + "-12-31";
"2024-04-22", this.timelineTicks = generateTicks(this.startDate, this.endDate, "day");
"2024-04-29", this.initTimelinePosition();
"2024-05-06", },
"2024-05-13",
"2024-05-20", /**
"2024-05-27", * 将时间轴指针定位到当前日期(today),如果 today 超出范围则取最近端
"2024-06-03", */
"2024-06-10", initTimelinePosition() {
"2024-06-17", if (!this.timelineTicks.length) return;
"2024-06-24", const idx = this.timelineTicks.findIndex((t) => t.date >= this.currentDate);
"2024-07-01", if (idx >= 0) {
"2024-07-08", this.currentTickIndex = idx;
"2024-07-15", this.currentDate = this.timelineTicks[idx].date;
"2024-07-22", } else {
"2024-07-29" // currentDate 超出时间轴末尾,定位到最后
] this.currentTickIndex = this.timelineTicks.length - 1;
}, this.currentDate = this.timelineTicks[this.currentTickIndex].date;
yAxis: { }
type: "value", },
show: false
}, /**
* 按日期筛选数据并更新地图
* 窗口模式:仅显示 [currentDate - 7天, currentDate + 7天] 内的数据
*/
updateMapByDate(dateStr) {
const chart = this.chartInstance;
if (!chart) return;
const allData = CharacterTrends.value || [];
// 计算前后一周范围
const cur = new Date(dateStr);
const weekBefore = new Date(cur);
weekBefore.setDate(cur.getDate() - 7);
const weekAfter = new Date(cur);
weekAfter.setDate(cur.getDate() + 7);
const fmt = (d) => d.getFullYear() + "-" +
String(d.getMonth() + 1).padStart(2, "0") + "-" +
String(d.getDate()).padStart(2, "0");
const rangeStart = fmt(weekBefore);
const rangeEnd = fmt(weekAfter);
const filteredData = allData.filter((item) => item.time >= rangeStart && item.time <= rangeEnd);
// 保存当前显示的数据,供 georoam 时刷新位置用
this._currentDisplayData = filteredData;
// 更新散点
chart.setOption({
series: [ series: [
{ {
data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], data: filteredData.map((item) => ({
type: "line", value: [item.lon, item.lat],
areaStyle: {}, time: item.time,
emphasis: { text: item.text,
focus: "series" avatar: item.avatar,
} itemStyle: { color: "#ffcc00" },
} })),
},
], ],
visualMap: { });
show: false,
dimension: 1,
pieces: [{ gt: 0, lte: 1, color: "#ffcc00" }]
}
};
chart2.setOption(option2); // 更新 DOM 浮窗卡片位置
this.updateMapCards(filteredData);
}, },
updateGraphics(chart, eventsData) {
const graphics = eventsData.value.map((event, index) => { /**
const position = chart.convertToPixel({ geoIndex: 0 }, [event.lon, event.lat]); * 根据事件数据更新 DOM 浮窗卡片(绝对定位在地图上方)
*/
updateMapCards(events) {
const chart = this.chartInstance;
if (!chart) {
this.mapCards = [];
return;
}
this.mapCards = events.map((event) => {
const pos = chart.convertToPixel({ geoIndex: 0 }, [event.lon, event.lat]);
if (!pos) return null;
return { return {
type: 'group', x: pos[0] - 170,
position: position, y: pos[1] - 72,
children: [ avatar: event.avatar,
time: event.time,
{ text: event.text,
type: 'image',
x: -170, y: -72,
style: {
image: "/icon/map-text-bg.png",
borderWidth: 2,
borderColor: '#1890ff', width: 339, height: 72
}
},
{
type: 'image',
x: -160, y: -63,
style: {
image: event.avatar,
borderWidth: 2,
borderColor: '#1890ff', width: 42, height: 42
}
}, {
type: 'text',
x: -106, y: -63,
style: {
text: event.time,
fontSize: 16,
fontWeight: '800',
fill: 'rgba(5, 95, 194, 1)', // 文字颜色(ECharts 中用 fill,不是 color)
textAlign: 'left'
}
},
{
type: 'text',
x: -106, y: -40,
style: {
text: event.text,
fontSize: 16,
fontWeight: '400',
fill: 'rgba(59, 65, 75, 1)', // 文字颜色(ECharts 中用 fill,不是 color)
textAlign: 'left',
}
}
]
}; };
}); }).filter(Boolean);
},
chart.setOption({ /**
graphic: graphics * 地图拖拽/缩放后,重新计算 DOM 卡片位置
}); */
chart.resize(); // 强制刷新图表 refreshGraphicPositions() {
} const events = this._currentDisplayData || [];
} this.updateMapCards(events);
},
},
}; };
</script> </script>
<style scoped> <style lang="scss" scoped>
#map { .map-wrapper {
width: 1600px; width: 1600px;
height: 581px; position: relative;
font-family: Microsoft YaHei; font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
.map-container {
width: 100%;
height: 551px;
position: relative;
z-index: 1;
}
/* ========== 地图浮窗卡片 ========== */
.map-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 551px;
pointer-events: none;
z-index: 2;
overflow: hidden;
}
.map-card {
position: absolute;
width: 339px;
height: 72px;
pointer-events: auto;
}
.map-card__bg {
position: absolute;
top: 0;
left: 0;
width: 339px;
height: 72px;
object-fit: fill;
}
.map-card__avatar {
position: absolute;
top: 9px;
left: 10px;
width: 42px;
height: 42px;
border: 2px solid #1890ff;
border-radius: 2px;
object-fit: cover;
z-index: 1;
}
.map-card__info {
position: absolute;
top: 9px;
left: 64px;
z-index: 1;
}
.map-card__time {
font-size: 16px;
font-weight: 800;
color: rgba(5, 95, 194, 1);
line-height: 1.2;
}
.map-card__text {
font-size: 16px;
font-weight: 400;
color: rgba(59, 65, 75, 1);
line-height: 1.4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 260px;
}
/* ========== 时间线面板 ========== */
.timeline-panel {
width: 1231px;
margin: -50px auto 0 182px;
background: #fff;
border: 1px solid #c8d8e8;
border-radius: 2px;
overflow: hidden;
position: relative;
z-index: 10;
}
/* ========== 控制栏 ========== */
.control-bar {
display: flex;
align-items: center;
justify-content: space-between;
height: 42px;
padding: 0 12px;
}
/* -- 左侧按钮组 -- */
.control-left {
display: flex;
align-items: center;
gap: 8px;
}
.ctrl-btn {
display: flex;
align-items: center;
justify-content: center;
min-width: 34px;
height: 28px;
padding: 0 8px;
border: 1px solid #04295a;
border-radius: 2px;
background-color: #fff;
color: #04295a;
cursor: pointer;
transition: all 0.15s;
position: relative;
z-index: 11;
pointer-events: auto;
&:hover {
background-color: #eaf2fd;
}
&:active {
background-color: #d4e3f5;
}
}
.ctrl-btn--text {
font-size: 14px;
font-weight: 400;
padding: 0 14px;
color: #04295a;
letter-spacing: 2px;
}
/* -- 右侧控制区 -- */
.control-right {
display: flex;
align-items: center;
gap: 8px;
}
.control-label {
font-size: 14px;
color: #04295a;
white-space: nowrap;
user-select: none;
}
.control-label--gap {
margin-left: 24px;
}
/* Element Plus 日期选择器 */
.timeline-date-picker {
width: 160px !important;
:deep(.el-input__wrapper) {
height: 28px;
border-radius: 2px;
box-shadow: 0 0 0 1px #b8cde5 inset !important;
background: #fff;
padding: 0 8px;
}
:deep(.el-input__inner) {
font-size: 14px;
color: #04295a;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
:deep(.el-input__prefix) {
display: none;
}
}
/* Element Plus 下拉选择器 */
.timeline-select {
width: 80px !important;
:deep(.el-input__wrapper) {
height: 28px;
border-radius: 2px;
box-shadow: 0 0 0 1px #b8cde5 inset !important;
background: #fff;
padding: 0 8px;
}
:deep(.el-input__inner) {
font-size: 14px;
color: #04295a;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
}
}
/* ========== 可滚动时间轴 ========== */
.timeline-track {
width: 100%;
height: 32px;
background-color: #6593d7;
position: relative;
overflow: hidden;
user-select: none;
cursor: grab;
&:active {
cursor: grabbing;
}
/* 顶部 1px 白色横线 */
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: rgba(255, 255, 255, 0.85);
z-index: 5;
pointer-events: none;
}
}
.timeline-inner {
height: 100%;
position: relative;
display: flex;
align-items: flex-start;
}
.tick-cell {
flex-shrink: 0;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
position: relative;
&:hover {
background-color: rgba(255, 255, 255, 0.08);
}
}
.tick-line {
width: 1px;
height: 5px;
background: rgba(255, 255, 255, 0.35);
margin-top: 2px;
}
.tick-line--major {
height: 8px;
background: rgba(255, 255, 255, 0.6);
}
.tick-label {
position: absolute;
top: 12px;
left: 50%;
transform: translateX(-50%);
font-size: 11px;
color: #fff;
white-space: nowrap;
pointer-events: none;
font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
line-height: 1;
} }
.time-btn { .timeline-cursor {
color: #04295A; position: absolute;
background-color: #F9FDFE; top: 0;
border: 1px solid #04295A; bottom: 0;
padding: 0 10px; width: 2px;
margin: 0 10px; background: #fff;
box-shadow: 0 0 4px rgba(255, 255, 255, 0.8);
z-index: 6;
transform: translateX(-50%);
pointer-events: none;
} }
</style> </style>
\ No newline at end of file
...@@ -3,16 +3,16 @@ ...@@ -3,16 +3,16 @@
<div class="speech-stance-container"> <div class="speech-stance-container">
<div class="speech-stance-grid"> <div class="speech-stance-grid">
<div v-for="(item, index) in PersonRelation" :key="index" class="speech-stance-card"> <div v-for="(item, index) in PersonRelation" :key="index" class="speech-stance-card">
<div class="speech-stance-avatar-wrapper"> <div class="speech-stance-avatar-wrapper" @click="handleClcikToCharacter(item.personId)">
<img :src="item.personImage" alt="" class="speech-stance-avatar" /> <img :src="item.personImage" alt="" class="speech-stance-avatar" />
</div> </div>
<div class="speech-stance-text-content"> <div class="speech-stance-text-content" @click="handleClcikToCharacter(item.personId)">
<div style="display: flex; width: 683px;"> <div style="display: flex; width: 683px;">
<h3 class="speech-stance-name">{{ item.personName }}</h3> <h3 class="speech-stance-name">{{ item.personName }}</h3>
<p class="speech-stance-title">{{ item.positionTitle }}</p> <p class="speech-stance-title">{{ item.positionTitle }}</p>
</div> </div>
<p class="speech-stance-content">{{ item.remarks }}</p> <p class="speech-stance-content" :title="item.remarks">{{ item.remarks }}</p>
</div> </div>
</div> </div>
...@@ -26,6 +26,9 @@ import { onMounted, ref,defineProps,watch } from "vue"; ...@@ -26,6 +26,9 @@ import { onMounted, ref,defineProps,watch } from "vue";
import speechStance from '../json/speechStance.json'; import speechStance from '../json/speechStance.json';
import {getPersonRelation} from "@/api/technologyFigures/technologyFigures" import {getPersonRelation} from "@/api/technologyFigures/technologyFigures"
import { getPersonSummaryInfo } from "@/api/technologyFigures/technologyFigures";
import { useRouter } from "vue-router";
const router = useRouter();
const props = defineProps({ const props = defineProps({
fieldSelected: { fieldSelected: {
type: String, type: String,
...@@ -36,7 +39,59 @@ const props = defineProps({ ...@@ -36,7 +39,59 @@ const props = defineProps({
default: 0 default: 0
} }
}); });
// 跳转人物主页
const handleClcikToCharacter = async id => {
const personTypeList = JSON.parse(window.sessionStorage.getItem("personTypeList"));
let type = 0;
let personTypeName = "";
const params = {
personId: id
};
try {
const res = await getPersonSummaryInfo(params);
console.log("人物全局信息", res);
if (res.code === 200 && res.data) {
const arr = personTypeList.filter(item => {
return item.typeId === res.data.personType;
});
console.log("arr", arr);
if (arr && arr.length > 0) {
personTypeName = arr[0].typeName;
console.log("personTypeName", personTypeName);
if (personTypeName === "科技企业领袖") {
type = 1;
} else if (personTypeName === "国会议员") {
type = 2;
} else if (personTypeName === "智库研究人员") {
type = 3;
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const route = router.resolve({
path: "/characterPage",
query: {
type: type, // type=1为科技企业领袖,2为国会议员,3为智库研究人员
personId: id
}
});
window.open(route.href, "_blank");
} else {
personTypeName = "";
ElMessage.warning("找不到当前人员的类型值!");
return;
}
} else {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
} catch (error) {}
};
const aId = ref(); const aId = ref();
const params = ref({}); const params = ref({});
......
...@@ -92,7 +92,7 @@ ...@@ -92,7 +92,7 @@
<div class="box1-header"> <div class="box1-header">
<div class="box1-header-left"> <div class="box1-header-left">
<div class="icon"> <div class="icon">
<img src="./assets/images/TechnologyFigures-icon4.png" alt="" /> <img src="./assets/images/TechnologyFigures-icon5.png" alt="" />
</div> </div>
<div class="title">{{ "人物新闻动态" }}</div> <div class="title">{{ "人物新闻动态" }}</div>
</div> </div>
...@@ -1080,7 +1080,9 @@ onMounted(async () => { ...@@ -1080,7 +1080,9 @@ onMounted(async () => {
.home-box { .home-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: url("./assets/images/background.png"); background: url("./assets/images/background.png") center/cover no-repeat, /* 背景图片配置 */
linear-gradient(to bottom, #f6fbff, transparent); /* 渐变背景色(从上到下透明) */
background-size: 100% 100%; background-size: 100% 100%;
overflow-y: auto; overflow-y: auto;
// .home-header { // .home-header {
......
/**
* useCharacterNav - 人物跳转复用方法(Vue 3 Composable)
*
* 使用方式:
* import { useCharacterNav } from "@/hooks/useCharacterNav";
* const { handleClickToCharacter } = useCharacterNav();
* handleClickToCharacter(personId);
*/
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { getPersonSummaryInfo } from "@/api/technologyFigures/technologyFigures";
// 人物类型名称 -> type 映射
const PERSON_TYPE_MAP = {
"科技企业领袖": 1,
"国会议员": 2,
"智库研究人员": 3,
};
export function useCharacterNav() {
const router = useRouter();
/**
* 根据人物 ID 查询类型并跳转到人物详情页
* @param {string|number} id - 人物 ID
*/
const handleClickToCharacter = async (id) => {
const personTypeList = JSON.parse(
window.sessionStorage.getItem("personTypeList") || "[]"
);
const params = { personId: id };
console.log('dsdsdwdwf',id)
try {
const res = await getPersonSummaryInfo(params);
if (res.code !== 200 || !res.data) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const matched = personTypeList.find(
(item) => item.typeId === res.data.personType
);
if (!matched) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const personTypeName = matched.typeName;
const type = PERSON_TYPE_MAP[personTypeName];
if (!type) {
ElMessage.warning("找不到当前人员的类型值!");
return;
}
const route = router.resolve({
path: "/characterPage",
query: { type, personId: id },
});
window.open(route.href, "_blank");
} catch (error) {
console.error("查询人物信息失败:", error);
}
};
return { handleClickToCharacter };
}
...@@ -56,6 +56,11 @@ export default defineConfig({ ...@@ -56,6 +56,11 @@ export default defineConfig({
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
}, },
// '/api': {
// target: 'http://10.119.133.162:28080/',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\/api/, '')
// },
'/sseChat': { '/sseChat': {
// target: 'http://192.168.26.115:8000', // target: 'http://192.168.26.115:8000',
// target: 'http://8.140.26.4:10018/', // target: 'http://8.140.26.4:10018/',
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论