Merge pull request 'feature/0902_antd组件替换' (#41) from feature/0902_antd组件替换 into main

Reviewed-on: ai-team/lingji-work-fe#41
This commit is contained in:
2025-09-10 08:07:56 +00:00
236 changed files with 5667 additions and 6194 deletions

View File

@ -1,6 +1,6 @@
## template-admin-ts ## template-admin-ts
基于 Arco Design Pro 的中后台管理模板 基于 Ant Design Vue 的中后台管理模板
- vite 4.x - vite 4.x
- vue 3.x - vue 3.x
@ -10,7 +10,7 @@
- axios - axios
- dayjs - dayjs
- lodash - lodash
- arco-design - ant-design-vue
- less - less
- eslint + prettier - eslint + prettier
@ -183,7 +183,7 @@ export { default as Comp } from './comp/index.vue';
</template> </template>
``` ```
### `ui` 框架 [`ArcoVue`](https://arco.design/vue/docs/start) ### `ui` 框架 [`Ant Design Vue`](https://antdv.com/)
- 自动导入组件,无需再次导入 - 自动导入组件,无需再次导入
- 图标库已配置,同组件使用 - 图标库已配置,同组件使用

View File

@ -39,6 +39,7 @@
"vue-draggable-next": "^2.2.1", "vue-draggable-next": "^2.2.1",
"vue-draggable-plus": "^0.6.0", "vue-draggable-plus": "^0.6.0",
"vue-echarts": "^7.0.3", "vue-echarts": "^7.0.3",
"vue-lazyload": "^3.0.0",
"vue-router": "^4.4.0", "vue-router": "^4.4.0",
"vuedraggable": "^4.1.0" "vuedraggable": "^4.1.0"
}, },

108
pnpm-lock.yaml generated
View File

@ -92,6 +92,9 @@ importers:
vue-echarts: vue-echarts:
specifier: ^7.0.3 specifier: ^7.0.3
version: 7.0.3(@vue/runtime-core@3.5.18)(echarts@5.6.0)(vue@3.5.18(typescript@4.9.5)) version: 7.0.3(@vue/runtime-core@3.5.18)(echarts@5.6.0)(vue@3.5.18(typescript@4.9.5))
vue-lazyload:
specifier: ^3.0.0
version: 3.0.0
vue-router: vue-router:
specifier: ^4.4.0 specifier: ^4.4.0
version: 4.5.1(vue@3.5.18(typescript@4.9.5)) version: 4.5.1(vue@3.5.18(typescript@4.9.5))
@ -447,133 +450,133 @@ packages:
resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==}
'@esbuild/android-arm64@0.16.17': '@esbuild/android-arm64@0.16.17':
resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@esbuild/android-arm@0.16.17': '@esbuild/android-arm@0.16.17':
resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@esbuild/android-x64@0.16.17': '@esbuild/android-x64@0.16.17':
resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [android] os: [android]
'@esbuild/darwin-arm64@0.16.17': '@esbuild/darwin-arm64@0.16.17':
resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@esbuild/darwin-x64@0.16.17': '@esbuild/darwin-x64@0.16.17':
resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@esbuild/freebsd-arm64@0.16.17': '@esbuild/freebsd-arm64@0.16.17':
resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@esbuild/freebsd-x64@0.16.17': '@esbuild/freebsd-x64@0.16.17':
resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@esbuild/linux-arm64@0.16.17': '@esbuild/linux-arm64@0.16.17':
resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
'@esbuild/linux-arm@0.16.17': '@esbuild/linux-arm@0.16.17':
resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
'@esbuild/linux-ia32@0.16.17': '@esbuild/linux-ia32@0.16.17':
resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
'@esbuild/linux-loong64@0.16.17': '@esbuild/linux-loong64@0.16.17':
resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
'@esbuild/linux-mips64el@0.16.17': '@esbuild/linux-mips64el@0.16.17':
resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
'@esbuild/linux-ppc64@0.16.17': '@esbuild/linux-ppc64@0.16.17':
resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
'@esbuild/linux-riscv64@0.16.17': '@esbuild/linux-riscv64@0.16.17':
resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
'@esbuild/linux-s390x@0.16.17': '@esbuild/linux-s390x@0.16.17':
resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
'@esbuild/linux-x64@0.16.17': '@esbuild/linux-x64@0.16.17':
resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
'@esbuild/netbsd-x64@0.16.17': '@esbuild/netbsd-x64@0.16.17':
resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [netbsd] os: [netbsd]
'@esbuild/openbsd-x64@0.16.17': '@esbuild/openbsd-x64@0.16.17':
resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@esbuild/sunos-x64@0.16.17': '@esbuild/sunos-x64@0.16.17':
resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
'@esbuild/win32-arm64@0.16.17': '@esbuild/win32-arm64@0.16.17':
resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@esbuild/win32-ia32@0.16.17': '@esbuild/win32-ia32@0.16.17':
resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@esbuild/win32-x64@0.16.17': '@esbuild/win32-x64@0.16.17':
resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz}
engines: {node: '>=12'} engines: {node: '>=12'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -654,85 +657,91 @@ packages:
engines: {node: ^14.16.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} engines: {node: ^14.16.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0}
'@parcel/watcher-android-arm64@2.5.1': '@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==, tarball: https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@parcel/watcher-darwin-arm64@2.5.1': '@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==, tarball: https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@parcel/watcher-darwin-x64@2.5.1': '@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==, tarball: https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.1': '@parcel/watcher-freebsd-x64@2.5.1':
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==, tarball: https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.1': '@parcel/watcher-linux-arm-glibc@2.5.1':
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm-musl@2.5.1': '@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-arm64-glibc@2.5.1': '@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-arm64-musl@2.5.1': '@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-linux-x64-glibc@2.5.1': '@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc]
'@parcel/watcher-linux-x64-musl@2.5.1': '@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl]
'@parcel/watcher-win32-arm64@2.5.1': '@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@parcel/watcher-win32-ia32@2.5.1': '@parcel/watcher-win32-ia32@2.5.1':
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@parcel/watcher-win32-x64@2.5.1': '@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@parcel/watcher@2.5.1': '@parcel/watcher@2.5.1':
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==, tarball: https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.1.tgz}
engines: {node: '>= 10.0.0'} engines: {node: '>= 10.0.0'}
'@polka/url@1.0.0-next.21': '@polka/url@1.0.0-next.21':
@ -791,13 +800,13 @@ packages:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
'@types/sortablejs@1.15.8': '@types/sortablejs@1.15.8':
resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==} resolution: {integrity: sha512-b79830lW+RZfwaztgs1aVPgbasJ8e7AXtZYHTELNXZPsERt4ymJdjV4OccDbHQAvHrCcFpbF78jkm0R6h/pZVg==, tarball: https://registry.npmmirror.com/@types/sortablejs/-/sortablejs-1.15.8.tgz}
'@types/svgo@2.6.4': '@types/svgo@2.6.4':
resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==} resolution: {integrity: sha512-l4cmyPEckf8moNYHdJ+4wkHvFxjyW6ulm9l4YGaOxeyBWPhBOT0gvni1InpFPdzx1dKf/2s62qGITwxNWnPQng==}
'@types/trusted-types@2.0.7': '@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==, tarball: https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz}
'@types/web-bluetooth@0.0.16': '@types/web-bluetooth@0.0.16':
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
@ -1678,7 +1687,7 @@ packages:
resolution: {integrity: sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==} resolution: {integrity: sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==}
canvg@3.0.11: canvg@3.0.11:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==, tarball: https://registry.npmmirror.com/canvg/-/canvg-3.0.11.tgz}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
chalk@1.1.3: chalk@1.1.3:
@ -1868,7 +1877,7 @@ packages:
resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==} resolution: {integrity: sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==}
core-js@3.45.0: core-js@3.45.0:
resolution: {integrity: sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==} resolution: {integrity: sha512-c2KZL9lP4DjkN3hk/an4pWn5b5ZefhRJnAc42n6LJ19kSnbeRbdQZE5dSeE2LBol1OwJD3X1BQvFTAsa8ReeDA==, tarball: https://registry.npmmirror.com/core-js/-/core-js-3.45.0.tgz}
core-util-is@1.0.3: core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -2188,7 +2197,7 @@ packages:
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
errno@0.1.8: errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==, tarball: https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz}
hasBin: true hasBin: true
error-ex@1.3.2: error-ex@1.3.2:
@ -2605,7 +2614,7 @@ packages:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fsevents@2.3.2: fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin] os: [darwin]
@ -2769,7 +2778,7 @@ packages:
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
graceful-fs@4.2.10: graceful-fs@4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.10.tgz}
grapheme-splitter@1.0.4: grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@ -3707,7 +3716,7 @@ packages:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
make-dir@2.1.0: make-dir@2.1.0:
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, tarball: https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz}
engines: {node: '>=6'} engines: {node: '>=6'}
make-iterator@1.0.1: make-iterator@1.0.1:
@ -3828,7 +3837,7 @@ packages:
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
mime@1.6.0: mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, tarball: https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz}
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
@ -3957,7 +3966,7 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
needle@3.2.0: needle@3.2.0:
resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==} resolution: {integrity: sha512-oUvzXnyLiVyVGoianLijF9O/RecZUf7TkBfimjGrLM4eQhXyeJwM6GeAWccwfQ9aa4gMCZKqhAOuLaMIcQxajQ==, tarball: https://registry.npmmirror.com/needle/-/needle-3.2.0.tgz}
engines: {node: '>= 4.4.x'} engines: {node: '>= 4.4.x'}
hasBin: true hasBin: true
@ -4863,7 +4872,7 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.6.1: source-map@0.6.1:
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
source-map@0.7.4: source-map@0.7.4:
@ -5547,6 +5556,9 @@ packages:
peerDependencies: peerDependencies:
eslint: '>=6.0.0' eslint: '>=6.0.0'
vue-lazyload@3.0.0:
resolution: {integrity: sha512-h2keL/Rj550dLgesgOtXJS9qOiSMmuJNeVlfNAYV1/IYwOQYaWk5mFJlwRxmZDK9YC5gECcFLYYj7z1lKSf9ug==, tarball: https://registry.npmmirror.com/vue-lazyload/-/vue-lazyload-3.0.0.tgz}
vue-router@4.5.1: vue-router@4.5.1:
resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==} resolution: {integrity: sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==}
peerDependencies: peerDependencies:
@ -12409,6 +12421,8 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
vue-lazyload@3.0.0: {}
vue-router@4.5.1(vue@3.5.18(typescript@4.9.5)): vue-router@4.5.1(vue@3.5.18(typescript@4.9.5)):
dependencies: dependencies:
'@vue/devtools-api': 6.6.4 '@vue/devtools-api': 6.6.4

View File

@ -1,23 +1,25 @@
<template> <template>
<a-config-provider :locale="zhCN" size="small" :theme="redTheme"> <ConfigProvider :locale="zhCN" :theme="redTheme">
<router-view v-if="$route.path === '/login' || ['ExploreList', 'ExploreDetail'].includes($route.name)" /> <router-view v-if="$route.path === '/login' || ['ExploreList', 'ExploreDetail'].includes($route.name)" />
<LayoutBasic v-else /> <LayoutBasic v-else />
</a-config-provider> </ConfigProvider>
</template> </template>
<script setup> <script setup>
import { useUserStore } from '@/stores'; import { useUserStore } from '@/stores';
import { useChatStore } from '@/stores/modules/chat'; // import { useChatStore } from '@/stores/modules/chat';
import { initApp } from '@/utils/user'; import { initApp } from '@/utils/user';
import { useSidebarStore } from '@/stores/modules/side-bar'; import { useSidebarStore } from '@/stores/modules/side-bar';
import zhCN from '@arco-design/web-vue/es/locale/lang/zh-cn'; import { ConfigProvider } from 'ant-design-vue';
import zhCN from 'ant-design-vue/es/locale/zh_CN';
import 'dayjs/locale/zh-cn';
const userStore = useUserStore(); const userStore = useUserStore();
const route = useRoute(); // const route = useRoute();
const sidebarStore = useSidebarStore(); const sidebarStore = useSidebarStore();
const chatStore = useChatStore(); // const chatStore = useChatStore();
const redTheme = { const redTheme = {
token: { token: {

View File

@ -8,7 +8,7 @@
import router from '@/router'; import router from '@/router';
import { clearToken } from '@/utils/auth'; import { clearToken } from '@/utils/auth';
import { Message } from '@arco-design/web-vue'; import { message } from 'ant-design-vue';
/** /**
* 处理业务逻辑定义的错误code * 处理业务逻辑定义的错误code
@ -28,5 +28,5 @@ export const handleCodeError = (error: any) => {
default: default:
errMessage = error.msg || `未知错误-${error.code}`; errMessage = error.msg || `未知错误-${error.code}`;
} }
Message.error(errMessage); message.error(errMessage);
}; };

View File

@ -7,6 +7,7 @@
*/ */
import axios from 'axios'; import axios from 'axios';
import { message } from 'ant-design-vue';
import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import { handleUserLogout, goUserLogin } from '@/utils/user'; import { handleUserLogout, goUserLogin } from '@/utils/user';
import { useEnterpriseStore } from '@/stores/modules/enterprise'; import { useEnterpriseStore } from '@/stores/modules/enterprise';
@ -84,7 +85,7 @@ export class Request {
break; break;
} }
AMessage.error(errMessage); message.error(errMessage);
return Promise.reject(err.response); return Promise.reject(err.response);
}, },
); );

View File

@ -1,2 +0,0 @@
export { default as TabBar } from './tab-bar/index.vue';
export { default as ModalSimple } from './modal/index.vue';

View File

@ -1,32 +0,0 @@
<script setup lang="ts">
import type { Component, DefineComponent } from 'vue';
import IconHover from '@arco-design/web-vue/es/_components/icon-hover';
defineProps<{
title?: string;
content?: string | (() => DefineComponent | Component);
}>();
defineEmits(['close']);
</script>
<template>
<slot name="header">
<div class="flex justify-end mb7">
<slot name="close">
<icon-hover @click="$emit('close')">
<icon-close />
</icon-hover>
</slot>
</div>
</slot>
<slot>
<div class="flex flex-col text-center">
<div v-if="title" class="mb4 text-lg font-600">{{ title }}</div>
<template v-else />
<component :is="content" v-if="typeof content === 'function'" />
<div v-else>{{ content }}</div>
</div>
</slot>
</template>

View File

@ -1,83 +0,0 @@
<script lang="ts" setup>
import type { RouteLocationNormalized } from 'vue-router';
import { listenerRouteChange, removeRouteListener } from '@/utils/route-listener';
import { useAppStore, useTabBarStore } from '@/stores';
import TabItem from './tab-item.vue';
const appStore = useAppStore();
const tabBarStore = useTabBarStore();
const affixRef = ref();
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const offsetTop = computed(() => {
return appStore.navbar ? 60 : 0;
});
watch(
() => appStore.navbar,
() => {
affixRef.value.updatePosition();
},
);
listenerRouteChange((route: RouteLocationNormalized) => {
if (!route.meta.noAffix && !tagList.value.some((tag) => tag.fullPath === route.fullPath)) {
tabBarStore.updateTabList(route);
}
}, true);
onUnmounted(() => {
removeRouteListener();
});
</script>
<template>
<div class="tab-bar-container">
<a-affix ref="affixRef" :offset-top="offsetTop">
<div class="tab-bar-box">
<div class="tab-bar-scroll">
<div class="tags-wrap">
<tab-item v-for="(tag, index) in tagList" :key="tag.fullPath" :index="index" :item-data="tag" />
</div>
</div>
<div class="tag-bar-operation"></div>
</div>
</a-affix>
</div>
</template>
<style scoped lang="scss">
.tab-bar-container {
position: relative;
background-color: var(--color-bg-2);
.tab-bar-box {
display: flex;
padding: 0 0 0 20px;
background-color: var(--color-bg-2);
border-bottom: 1px solid var(--color-border);
.tab-bar-scroll {
height: 32px;
flex: 1;
overflow: hidden;
.tags-wrap {
padding: 4px 0;
height: 48px;
white-space: nowrap;
overflow-x: auto;
:deep(.arco-tag) {
display: inline-flex;
align-items: center;
margin-right: 6px;
cursor: pointer;
&:first-child {
.arco-tag-close-btn {
display: none;
}
}
}
}
}
}
.tag-bar-operation {
width: 100px;
height: 32px;
}
}
</style>

View File

@ -1,177 +0,0 @@
<script lang="ts" setup>
import type { PropType } from 'vue';
import type { TagProps } from '@/stores/modules/tab-bar/types';
import { useTabBarStore } from '@/stores';
import { DEFAULT_ROUTE_NAME, REDIRECT_ROUTE_NAME } from '@/router/constants';
const props = defineProps({
itemData: {
type: Object as PropType<TagProps>,
default() {
return [];
},
},
index: {
type: Number,
default: 0,
},
});
// eslint-disable-next-line no-shadow
enum Eaction {
reload = 'reload',
current = 'current',
left = 'left',
right = 'right',
others = 'others',
all = 'all',
}
const router = useRouter();
const route = useRoute();
const tabBarStore = useTabBarStore();
const goto = (tag: TagProps) => {
router.push({ ...tag });
};
const tagList = computed(() => {
return tabBarStore.getTabList;
});
const disabledReload = computed(() => {
return props.itemData.fullPath !== route.fullPath;
});
const disabledCurrent = computed(() => {
return props.index === 0;
});
const disabledLeft = computed(() => {
return [0, 1].includes(props.index);
});
const disabledRight = computed(() => {
return props.index === tagList.value.length - 1;
});
const tagClose = (tag: TagProps, idx: number) => {
tabBarStore.deleteTag(idx, tag);
if (props.itemData.fullPath === route.fullPath) {
const latest = tagList.value[idx - 1]; // 获取队列的前一个tab
router.push({ name: latest.name });
}
};
const findCurrentRouteIndex = () => {
return tagList.value.findIndex((el) => el.fullPath === route.fullPath);
};
const actionSelect = async (value: any) => {
const { itemData, index } = props;
const copyTagList = [...tagList.value];
if (value === Eaction.current) {
tagClose(itemData, index);
} else if (value === Eaction.left) {
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(1, props.index - 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx < index) {
router.push({ name: itemData.name });
}
} else if (value === Eaction.right) {
const currentRouteIdx = findCurrentRouteIndex();
copyTagList.splice(props.index + 1);
tabBarStore.freshTabList(copyTagList);
if (currentRouteIdx > index) {
router.push({ name: itemData.name });
}
} else if (value === Eaction.others) {
const filterList = tagList.value.filter((el, idx) => {
return idx === 0 || idx === props.index;
});
tabBarStore.freshTabList(filterList);
router.push({ name: itemData.name });
} else if (value === Eaction.reload) {
tabBarStore.deleteCache(itemData);
await router.push({
name: REDIRECT_ROUTE_NAME,
params: {
path: route.fullPath,
},
});
tabBarStore.addCache(itemData.name);
} else {
tabBarStore.resetTabList();
router.push({ name: DEFAULT_ROUTE_NAME });
}
};
</script>
<template>
<a-dropdown trigger="contextMenu" :popup-max-height="false" @select="actionSelect">
<span
:class="[
'arco-tag arco-tag-size-medium arco-tag-checked',
{ 'link-activated': itemData.fullPath === $route.fullPath },
]"
@click="goto(itemData)"
>
<span class="tag-link">{{ itemData.title }}</span>
<span
class="arco-icon-hover arco-tag-icon-hover arco-icon-hover-size-medium arco-tag-close-btn"
@click.stop="tagClose(itemData, index)"
>
<icon-close />
</span>
</span>
<template #content>
<a-doption :disabled="disabledReload" :value="Eaction.reload">
<icon-refresh />
<span>重新加载</span>
</a-doption>
<a-doption class="sperate-line" :disabled="disabledCurrent" :value="Eaction.current">
<icon-close />
<span>关闭当前标签页</span>
</a-doption>
<a-doption :disabled="disabledLeft" :value="Eaction.left">
<icon-to-left />
<span>关闭左侧标签页</span>
</a-doption>
<a-doption class="sperate-line" :disabled="disabledRight" :value="Eaction.right">
<icon-to-right />
<span>关闭右侧标签页</span>
</a-doption>
<a-doption :value="Eaction.others">
<icon-swap />
<span>关闭其它标签页</span>
</a-doption>
<a-doption :value="Eaction.all">
<icon-folder-delete />
<span>关闭全部标签页</span>
</a-doption>
</template>
</a-dropdown>
</template>
<style scoped lang="scss">
.tag-link {
color: var(--color-text-2);
text-decoration: none;
}
.link-activated {
color: rgb(var(--link-6));
.tag-link {
color: rgb(var(--link-6));
}
& + .arco-tag-close-btn {
color: rgb(var(--link-6));
}
}
:deep(.arco-dropdown-option-content) {
span {
margin-left: 10px;
}
}
.arco-dropdown-open {
.tag-link {
color: rgb(var(--danger-6));
}
.arco-tag-close-btn {
color: rgb(var(--danger-6));
}
}
.sperate-line {
border-bottom: 1px solid var(--color-neutral-3);
}
</style>

View File

@ -3,23 +3,26 @@
* @Date: 2025-06-25 14:02:40 * @Date: 2025-06-25 14:02:40
--> -->
<template> <template>
<a-select <Select
v-model="selectedValues" v-model:value="selectedValues"
:multiple="multiple" :mode="multiple ? 'multiple' : undefined"
size="medium" size="middle"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="allClear" :allowClear="allClear"
:allow-search="allowSearch" :showSearch="allowSearch"
:max-tag-count="maxTagCount" showArrow
:maxTagCount="maxTagCount"
@change="handleChange" @change="handleChange"
> >
<a-option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name"> <Option v-for="(item, index) in options" :key="index" :value="item.id" :label="item.name">
{{ item.name }} {{ item.name }}
</a-option> </Option>
</a-select> </Select>
</template> </template>
<script setup> <script setup>
import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
const props = defineProps({ const props = defineProps({

View File

@ -3,14 +3,15 @@
* @Date: 2025-06-30 10:54:49 * @Date: 2025-06-30 10:54:49
--> -->
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
title="自定义列" title="自定义列"
width="960px" width="960px"
unmountOnClose unmountOnClose
titleAlign="start" titleAlign="start"
class="custom-table-column-modal" centered
@close="close" wrapClassName="custom-table-column-modal"
@cancel="close"
> >
<div class="modal-body"> <div class="modal-body">
<!-- 左侧分组 --> <!-- 左侧分组 -->
@ -20,16 +21,16 @@
<span class="text">{{ group.label }}</span> <span class="text">{{ group.label }}</span>
</div> </div>
<div class="fields"> <div class="fields">
<a-checkbox <Checkbox
v-for="option in group.columns" v-for="option in group.columns"
:key="option.value" :key="option.value"
:model-value="isCheck(option)" :checked="isCheck(option)"
:value="option.value" :value="option.value"
:disabled="option.is_require === ENUM_STATUS.NO" :disabled="option.is_require === ENUM_STATUS.NO"
@change="(checked) => onCheckChange(checked, option)" @change="(e) => onCheckChange(e.target.checked, option)"
> >
{{ option.label }} {{ option.label }}
</a-checkbox> </Checkbox>
</div> </div>
</div> </div>
</div> </div>
@ -64,15 +65,16 @@
</div> </div>
<template #footer> <template #footer>
<div style="text-align: right"> <div class="flex">
<a-button class="mr-8px" size="medium" @click="close">取消</a-button> <Button @click="close">取消</Button>
<a-button type="primary" size="medium" @click="onSubmit">确定</a-button> <Button type="primary" @click="onSubmit">确定</Button>
</div> </div>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { Checkbox, Modal, Button } from 'ant-design-vue';
import { ref, defineExpose } from 'vue'; import { ref, defineExpose } from 'vue';
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';

View File

@ -1,5 +1,5 @@
.custom-table-column-modal { .custom-table-column-modal {
.arco-modal-body { .ant-modal-body {
.modal-body { .modal-body {
height: 504px; height: 504px;
border-radius: 8px; border-radius: 8px;

View File

@ -1,13 +1,31 @@
<template> <template>
<a-modal modal-class="delete-modal" body-class="body" cancel-text="返回" ok-text="确定删除" v-bind="$attrs"> <Modal wrapClassName="delete-modal" body-class="body" v-bind="$attrs" centered title="删除账号">
<h2 class="delete-modal-title flex item-center"> <h2 class="delete-modal-title flex item-center">
<img src="@/assets/warning.svg" alt="" /> <img src="@/assets/warning.svg" alt="" />
{{ $attrs.title }} {{ $attrs.content }}
</h2> </h2>
<slot></slot> <p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
</a-modal> <template #footer>
<div style="text-align: right">
<Button @click="close">返回</Button>
<Button type="primary" danger @click="onSubmit">确定删除</Button>
</div>
</template> </template>
<script setup lang="ts"></script> </Modal>
</template>
<script setup lang="ts">
import { Modal, Button } from 'ant-design-vue';
const emit = defineEmits(['close', 'ok']);
const close = () => {
emit('close');
};
const onSubmit = () => {
emit('ok');
};
</script>
<style lang="scss"> <style lang="scss">
:deep(.arco-btn-status-danger) { :deep(.arco-btn-status-danger) {
background-color: red !important; background-color: red !important;
@ -18,7 +36,7 @@
display: none; display: none;
} }
.delete-modal-title { .delete-modal-title {
margin-top: 24px; // margin-top: 24px;
font-family: $font-family-medium; font-family: $font-family-medium;
font-weight: 400; font-weight: 400;
font-size: 14px; font-size: 14px;
@ -29,6 +47,14 @@
margin-right: 12px; margin-right: 12px;
} }
} }
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.arco-modal-footer { .arco-modal-footer {
border-top: none; border-top: none;
:first-child { :first-child {
@ -53,5 +79,6 @@
} }
.body { .body {
padding: 0 24px; padding: 0 24px;
} }
</style> </style>

View File

@ -3,13 +3,13 @@
* @Date: 2025-08-11 22:15:35 * @Date: 2025-08-11 22:15:35
--> -->
<template> <template>
<a-popover <Popover
:trigger="'hover'" trigger="hover"
class="hover-big-image-preview-popover" :placement="props.position"
:position="props.position" :mouseEnterDelay="props.enterDelay / 1000"
:mouse-enter-delay="props.enterDelay" :mouseLeaveDelay="props.leaveDelay / 1000"
:mouse-leave-delay="props.leaveDelay" :open="props.src ? undefined : false"
:disabled="!props.src" overlayClassName="hover-big-image-preview-popover"
> >
<template #content> <template #content>
<div class="preview-container"> <div class="preview-container">
@ -18,17 +18,30 @@
</template> </template>
<slot /> <slot />
</a-popover> </Popover>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Popover } from 'ant-design-vue';
// import { computed, onMounted, ref, watch } from 'vue'; // import { computed, onMounted, ref, watch } from 'vue';
// import type { ImageOrientation } from '@/utils/tools'; // import type { ImageOrientation } from '@/utils/tools';
// import { getImageOrientationByUrl } from '@/utils/tools'; // import { getImageOrientationByUrl } from '@/utils/tools';
interface Props { interface Props {
src: string; src: string;
position?: 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb'; position?:
| 'top'
| 'topLeft'
| 'topRight'
| 'bottom'
| 'bottomLeft'
| 'bottomRight'
| 'left'
| 'leftTop'
| 'leftBottom'
| 'right'
| 'rightTop'
| 'rightBottom';
enterDelay?: number; enterDelay?: number;
leaveDelay?: number; leaveDelay?: number;
} }
@ -37,6 +50,7 @@ const props = withDefaults(defineProps<Props>(), {
position: 'right', position: 'right',
enterDelay: 100, enterDelay: 100,
leaveDelay: 200, leaveDelay: 200,
src: '',
}); });
// const orientation = ref<ImageOrientation>('landscape'); // const orientation = ref<ImageOrientation>('landscape');
@ -67,7 +81,11 @@ const props = withDefaults(defineProps<Props>(), {
<style lang="scss"> <style lang="scss">
.hover-big-image-preview-popover { .hover-big-image-preview-popover {
.arco-popover-popup-content { .ant-popover-content {
padding: 0 !important;
}
.ant-popover-inner {
padding: 16px !important; padding: 16px !important;
border-radius: 8px; border-radius: 8px;
background: #fff; background: #fff;

View File

@ -0,0 +1,90 @@
<template>
<div class="img-lazy" v-lazy:background-image="imgSrc" :key="src" :class="imgClass" :style="style" />
</template>
<script setup>
import { ref, onMounted, watch, computed } from 'vue';
const emit = defineEmits(['click']);
const props = defineProps({
width: {
type: [String, Number],
},
height: {
type: [String, Number],
},
loadingSize: {
type: [String],
default: '5',
},
errorSize: {
type: [String],
default: '5',
},
fit: {
type: [String],
default: 'cover',
},
src: {
type: String,
},
customImg: {
type: String,
},
});
const style = computed(() => {
return {
'background-size': props.fit,
width: props.width ? parseInt(props.width) + 'px' : undefined,
height: props.height ? parseInt(props.height) + 'px' : undefined,
};
});
const imgClass = computed(() => {
return {
['loading-size-' + props.loadingSize]: true,
};
});
const imgSrc = computed(() => {
return props.innerSrc || props.src;
});
const innerSrc = ref('');
watch(
() => props.customImg,
() => {
innerSrc.value = '';
if (props.customImg) {
const img = new Image();
img.src = props.src;
img.onerror = () => {
innerSrc.value = props.customImg;
};
img.onload = () => {
innerSrc.value = props.src;
};
}
},
{ immediate: true },
);
onMounted(() => {});
</script>
<style scoped lang="scss">
.img-lazy.block {
display: block;
}
.img-lazy {
display: inline-block;
overflow: hidden;
background-size: 100% 100%;
background-position: center center;
background-repeat: no-repeat;
@for $i from 0 to 10 {
&.loading-size-#{$i}[lazy='loading'] {
background-size: #{$i * 10 + '%'} !important;
}
}
}
</style>

View File

@ -9,6 +9,7 @@
<script setup lang="ts"> <script setup lang="ts">
import Modal from '@components/modal.vue'; import Modal from '@components/modal.vue';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { message } from 'ant-design-vue';
import { getQueryParam } from '@/utils/helper'; import { getQueryParam } from '@/utils/helper';
import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all'; import { getEnterpriseByInviteCode, joinEnterpriseByInviteCode } from '@/api/all';
@ -26,7 +27,7 @@ async function getEnterprise() {
async function handleJoin() { async function handleJoin() {
await joinEnterpriseByInviteCode(inviteCode.value); await joinEnterpriseByInviteCode(inviteCode.value);
AMessage.success('加入成功'); message.success('加入成功');
} }
// onMounted(() => { // onMounted(() => {
// getEnterprise(); // getEnterprise();

View File

@ -1,9 +1,11 @@
<template> <template>
<a-modal title-align="start" modal-class="modal" body-class="body" v-bind="$attrs"> <Modal title-align="start" wrapClassName="modal" cancelText="取消" okText="确定" body-class="body" v-bind="$attrs" centered>
<slot></slot> <slot></slot>
</a-modal> </Modal>
</template> </template>
<script setup lang="ts"></script> <script setup lang="ts">
import { Modal } from 'ant-design-vue';
</script>
<style lang="scss"> <style lang="scss">
.modal { .modal {
.arco-modal-header { .arco-modal-header {

View File

@ -1,18 +1,16 @@
<template> <template>
<a-upload <Upload
:custom-request="customRequest" :customRequest="customRequest"
action="/" action="/"
:limit="limit" :maxCount="limit"
:fileList="fileList" :fileList="fileList"
@change="onChange" @change="onChange"
@success="handleSuccess"
@error="handleError"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { Message } from '@arco-design/web-vue'; import { Upload, message } from 'ant-design-vue';
import { fetchImageUploadFile, fetchUploadFile } from '@/api/all'; import { fetchImageUploadFile, fetchUploadFile } from '@/api/all';
import axios from 'axios'; import axios from 'axios';
@ -70,8 +68,10 @@ watch(
); );
let previousFileListLength = 0; let previousFileListLength = 0;
//删除图片 const onChange = (info) => {
const onChange = (fileList) => { const { fileList } = info;
// 如果删除了文件
if (fileList.length < previousFileListLength) { if (fileList.length < previousFileListLength) {
if (props.limit === 1) { if (props.limit === 1) {
if (fileList.length === 0) { if (fileList.length === 0) {
@ -87,27 +87,34 @@ const onChange = (fileList) => {
} }
} }
// 处理上传成功的文件
if (info.file.status === 'done' && info.file.response) {
handleSuccess(info.file);
} else if (info.file.status === 'error') {
handleError(info.file.error);
}
previousFileListLength = fileList.length; previousFileListLength = fileList.length;
}; };
const beforeUpload = (file, files) => { const beforeUpload = (file, files) => {
if (props.limit > 0 && files.length >= props.limit) { if (props.limit > 0 && files.length >= props.limit) {
Message.warning(`最多只能上传 ${props.limit} 张图片`); message.warning(`最多只能上传 ${props.limit} 张图片`);
return false; // 阻止上传 return false; // 阻止上传
} }
return true; return true;
}; };
const handleError = (error) => { const handleError = (error) => {
Message.error('上传失败'); message.error('上传失败');
console.error(error); console.error(error);
}; };
const customRequest = async (option) => { const customRequest = async (option) => {
const { onProgress, onError, onSuccess, fileItem, name } = option; const { onProgress, onError, onSuccess, file, name } = option;
try { try {
// 1. 获取预签名上传URL // 1. 获取预签名上传URL
const response = await fetchUploadFile({ suffix: getFileExtension(fileItem.file.name) }); const response = await fetchUploadFile({ suffix: getFileExtension(file.name) });
const preSignedUrl = response?.data?.upload_url; const preSignedUrl = response?.data?.upload_url;
if (!preSignedUrl) { if (!preSignedUrl) {
@ -115,9 +122,9 @@ const customRequest = async (option) => {
} }
console.log('preSignedUrl', preSignedUrl); console.log('preSignedUrl', preSignedUrl);
// 2. 使用预签名URL上传文件 // 2. 使用预签名URL上传文件
const blob = new Blob([fileItem.file], { type: fileItem.file.type }); const blob = new Blob([file], { type: file.type });
await axios.put(preSignedUrl, blob, { await axios.put(preSignedUrl, blob, {
headers: { 'Content-Type': fileItem.file.type }, headers: { 'Content-Type': file.type },
}); });
onSuccess(JSON.stringify(response)); onSuccess(JSON.stringify(response));

View File

@ -1,20 +1,19 @@
<template> <template>
<a-upload <Upload
:custom-request="customRequest" :customRequest="customRequest"
list-type="picture-card" listType="picture-card"
action="/" action="/"
:limit="limit" :maxCount="limit"
:fileList="fileList" :fileList="fileList"
image-preview :showUploadList="{ showPreviewIcon: true, showRemoveIcon: true }"
@change="onChange" @change="onChange"
@success="handleSuccess" @preview="handlePreview"
@error="handleError"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import { Message } from '@arco-design/web-vue'; import { Upload, message } from 'ant-design-vue';
import { fetchImageUploadFile } from '@/api/all'; import { fetchImageUploadFile } from '@/api/all';
import axios from 'axios'; import axios from 'axios';
@ -72,8 +71,14 @@ watch(
); );
let previousFileListLength = 0; let previousFileListLength = 0;
//删除图片 const handlePreview = (file) => {
const onChange = (fileList) => { console.log('Preview file:', file);
};
const onChange = (info) => {
const { fileList } = info;
// 如果删除了文件
if (fileList.length < previousFileListLength) { if (fileList.length < previousFileListLength) {
if (props.limit === 1) { if (props.limit === 1) {
if (fileList.length === 0) { if (fileList.length === 0) {
@ -89,27 +94,34 @@ const onChange = (fileList) => {
} }
} }
// 处理上传成功的文件
if (info.file.status === 'done' && info.file.response) {
handleSuccess(info.file);
} else if (info.file.status === 'error') {
handleError(info.file.error);
}
previousFileListLength = fileList.length; previousFileListLength = fileList.length;
}; };
const beforeUpload = (file, files) => { const beforeUpload = (file, files) => {
if (props.limit > 0 && files.length >= props.limit) { if (props.limit > 0 && files.length >= props.limit) {
Message.warning(`最多只能上传 ${props.limit} 张图片`); message.warning(`最多只能上传 ${props.limit} 张图片`);
return false; // 阻止上传 return false; // 阻止上传
} }
return true; return true;
}; };
const handleError = (error) => { const handleError = (error) => {
Message.error('上传失败'); message.error('上传失败');
console.error(error); console.error(error);
}; };
const customRequest = async (option) => { const customRequest = async (option) => {
const { onProgress, onError, onSuccess, fileItem, name } = option; const { onProgress, onError, onSuccess, file, name } = option;
try { try {
// 1. 获取预签名上传URL // 1. 获取预签名上传URL
const response = await fetchImageUploadFile({ suffix: getFileExtension(fileItem.file.name) }); const response = await fetchImageUploadFile({ suffix: getFileExtension(file.name) });
const preSignedUrl = response?.data?.upload_url; const preSignedUrl = response?.data?.upload_url;
if (!preSignedUrl) { if (!preSignedUrl) {
@ -117,9 +129,9 @@ const customRequest = async (option) => {
} }
console.log('preSignedUrl', preSignedUrl); console.log('preSignedUrl', preSignedUrl);
// 2. 使用预签名URL上传文件 // 2. 使用预签名URL上传文件
const blob = new Blob([fileItem.file], { type: fileItem.file.type }); const blob = new Blob([file], { type: file.type });
await axios.put(preSignedUrl, blob, { await axios.put(preSignedUrl, blob, {
headers: { 'Content-Type': fileItem.file.type }, headers: { 'Content-Type': file.type },
}); });
onSuccess(JSON.stringify(response)); onSuccess(JSON.stringify(response));

View File

@ -1,5 +1,5 @@
<script lang="tsx"> <script lang="tsx">
import { Button } from '@arco-design/web-vue'; import { Button } from 'ant-design-vue';
import { Bubble } from '@/components/xt-chat/xt-bubble'; import { Bubble } from '@/components/xt-chat/xt-bubble';
import Http from '@/api'; import Http from '@/api';
@ -79,8 +79,8 @@ export default {
<header class="header flex justify-end items-center mb-16px px-32px"> <header class="header flex justify-end items-center mb-16px px-32px">
{hasMediaCenter.value && ( {hasMediaCenter.value && (
<Button <Button
type="outline" type="primary"
size="medium" ghost
class="mr-16px" class="mr-16px"
v-slots={{ icon: () => <icon-plus size="14" /> }} v-slots={{ icon: () => <icon-plus size="14" /> }}
onClick={onAddMediaCenter} onClick={onAddMediaCenter}
@ -90,8 +90,8 @@ export default {
)} )}
<Button <Button
type="outline" type="primary"
size="medium" ghost
class="mr-16px" class="mr-16px"
v-slots={{ icon: () => <icon-plus size="14" /> }} v-slots={{ icon: () => <icon-plus size="14" /> }}
onClick={onAddTaskManage} onClick={onAddTaskManage}

View File

@ -1,2 +1 @@
export * from './responsive'; export * from './responsive';
export * from './modal';

View File

@ -1,55 +0,0 @@
/*
* @Author: 田鑫
* @Date: 2023-02-21 15:11:01
* @LastEditors: 田鑫
* @LastEditTime: 2023-02-21 15:11:02
* @Description:
*/
import type { AppContext, Component, DefineComponent } from 'vue';
import type { ModalConfig } from '@arco-design/web-vue';
import { ModalSimple } from '@/components/_base';
type CompType = DefineComponent | Component;
interface SlotsType {
default?: CompType;
header?: CompType;
close?: CompType;
}
export const useModal = () => {
const instance = getCurrentInstance();
const Modal = AModal;
Modal._context = instance?.appContext as AppContext;
Modal.simple = (config: ModalConfig, slots: SlotsType) => {
const { title, content, ..._config } = config || {};
const modal = Modal.open({
..._config,
simple: false,
footer: false,
closable: false,
content: () =>
h(
ModalSimple,
{
title,
content,
onClose: modal.close,
},
{
default: slots?.default,
header: slots?.header,
close: slots?.close,
},
),
});
return modal;
};
return { Modal };
};

View File

@ -1,172 +0,0 @@
/*
* @Author: 田鑫
* @Date: 2023-02-16 15:02:51
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-09 11:20:59
* @Description: table-hooks
*/
import type { PaginationProps, TableBorder, TableColumnData, TableData, TableRowSelection } from '@arco-design/web-vue';
type Size = 'mini' | 'small' | 'medium' | 'large';
interface IDefaultProps {
/** 是否显示边框 */
bordered?: TableBorder;
/** 是否显示选中效果 */
hoverable?: boolean;
/** 表格的大小 */
size?: Size;
/** 是否允许调整列宽 */
'column-resizable'?: boolean;
/** 是否为加载中状态 */
loading?: boolean;
/** 分页参数 */
pagination?: PaginationProps;
/** table数据类型 */
data?: any[];
/** 表头参数 */
columns?: TableColumnData[];
/** 表格行 key 的取值字段 */
'row-key'?: string;
/** 表格的行选择器配置 */
'row-selection'?: TableRowSelection;
'selected-keys'?: (string | number)[];
[x: string]: any;
}
interface IPagination {
/** 当前页数 */
current?: number;
/** 总页数默认是0条 */
total?: number;
}
interface ITableResponse<T> {
current?: number;
records?: T[];
size?: number;
total?: number;
[x: string]: any;
}
type GetListFunc<T> = (v: object) => Promise<ITableResponse<T>>;
export default function useTableProps<T>(loadListFunc: GetListFunc<T>) {
const defaultProps: IDefaultProps = {
bordered: { cell: true },
size: 'large',
'column-resizable': true,
loading: true,
data: [] as any[],
pagination: {
current: 1,
pageSize: 20,
total: 0,
showPageSize: true,
showTotal: true,
},
hoverable: false,
columns: [],
};
//* 属性组
const propsRes = reactive<IDefaultProps>(defaultProps);
//* 设置请求参数,如果出了分页参数还有搜索参数,在模板页面调用此方法,可以加入参数
const loadListParams = reactive<object>({
page: 1,
size: 20,
});
/**
* 单独设置默认属性
* @param params
*/
const setProps = (params: IDefaultProps) => {
if (Object.keys(params).length > 0) {
Object.assign(defaultProps, params);
}
};
/**
* 设置表头数据
* @param columns
*/
const setColumns = (columns: TableColumnData[]) => {
propsRes.columns = columns;
};
/**
* 设置loading
* @param status
*/
const setLoading = (status: boolean) => {
propsRes.loading = status;
};
/**
* 设置分页
* @param param0
*/
const setPagination = ({ current, total }: IPagination) => {
propsRes.pagination!.current = current;
total && (propsRes.pagination!.total = total);
Object.assign(loadListParams, { page: current });
};
/**
* 设置列表请求参数
* @param params
*/
const setLoadListParams = <R>(params?: R) => {
Object.assign(loadListParams, params);
};
/**
* 加载列表
* @returns
*/
const loadTableData = async (resetPageIndex = false) => {
if (resetPageIndex) {
setPagination({ current: 1 });
}
setLoading(true);
try {
const resData = await loadListFunc({
...loadListParams,
});
console.log(resData);
const response = resData as ITableResponse<T>;
propsRes.data = response.records;
setPagination({
current: response.current,
total: response.total,
});
setLoading(false);
return resData;
} catch (error) {
setLoading(false);
return [];
}
};
// 事件触发组
const propsEvent = reactive({
// 排序触发
sorterChange: (dataIndex: string, direction: string) => {
console.log(dataIndex, direction);
},
// 分页触发
pageChange: (current: number) => {
setPagination({ current });
loadTableData();
},
// 修改每页显示条数
pageSizeChange: (size: number) => {
propsRes.pagination!.pageSize = size;
Object.assign(loadListParams, { size });
loadTableData();
},
selectionChange: (rowKeys: (string | number)[]) => {
propsRes['selected-keys'] = rowKeys;
},
});
return {
propsRes,
propsEvent,
loadListParams,
setProps,
setColumns,
setLoading,
setPagination,
loadTableData,
setLoadListParams,
};
}

View File

@ -22,30 +22,28 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
const rowKey = options.rowKey || 'id'; const rowKey = options.rowKey || 'id';
const selectedRowKeys = ref<Array<string | number>>([]); const selectedRowKeys = ref<Array<string | number>>([]);
const selectedRows = ref<any[]>([]); const selectedRows = ref<Array<any>>([]);
const pageInfo = ref(merge({}, DEFAULT_PAGE_INFO, options.pageInfo)); const pageInfo = ref(merge({}, DEFAULT_PAGE_INFO, options.pageInfo));
const dataSource = ref<any[]>([]); const dataSource = ref<any[]>([]);
// 单行选择 // 单行选择
const handleSelect = (selectedKeys: (string | number)[], rowKeyValue: string | number, record: any) => { const handleSelect = (record: any, select: boolean) => {
const select = selectedKeys.includes(rowKeyValue); const _targetKey = record[rowKey];
selectedRowKeys.value = selectedKeys;
if (select) { if (select) {
if (!selectedRows.value.some((v) => v[rowKey] === record[rowKey])) {
selectedRows.value.push(record); selectedRows.value.push(record);
} selectedRowKeys.value.push(_targetKey);
} else { } else {
selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== record[rowKey]); selectedRows.value = selectedRows.value.filter((v) => v[rowKey] !== _targetKey);
selectedRowKeys.value = selectedRowKeys.value.filter((key) => key !== _targetKey);
} }
options.onSelectChange?.(); options.onSelectChange?.();
}; };
// 全选/取消全选 // 全选/取消全选
const handleSelectAll = (checked: boolean) => { const handleSelectAll = (checked: boolean) => {
console.log('handleSelectAll', checked)
const currentPageRows = dataSource.value; const currentPageRows = dataSource.value;
const currentPageKeys = currentPageRows.map((v) => v[rowKey]); const currentPageKeys = currentPageRows.map((v) => v[rowKey]);
@ -62,18 +60,21 @@ export function useTableSelectionWithPagination(options: UseTableSelectionWithPa
options.onSelectChange?.(); options.onSelectChange?.();
}; };
const onPageChange = (page: number) => { const onPageChange = (page: number, pageSize: number) => {
// console.log('onPageChange', page, pageSize);
pageInfo.value.page = page; pageInfo.value.page = page;
pageInfo.value.page_size = pageSize;
options.onPageChange?.(page); options.onPageChange?.(page);
}; };
const onPageSizeChange = (size: number) => { const onPageSizeChange = (current: number, size: number) => {
pageInfo.value.page_size = size; // console.log('onPageSizeChange', current, size);
pageInfo.value.page = 1; // pageInfo.value.page_size = size;
options.onPageSizeChange?.(size); // pageInfo.value.page = 1;
// options.onPageSizeChange?.(size);
}; };
const resetPageInfo = () => { const resetPageInfo = () => {
pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO) pageInfo.value = cloneDeep(DEFAULT_PAGE_INFO);
} };
const rowSelection = computed(() => ({ const rowSelection = computed(() => ({
type: 'checkbox', type: 'checkbox',

View File

@ -55,7 +55,7 @@ const checkHasInviteCode = () => {
<template> <template>
<Layout :class="['layout-wrap', { mobile: appStore.hideMenu }]" class="h-full flex flex-col w-full"> <Layout :class="['layout-wrap', { mobile: appStore.hideMenu }]" class="h-full flex flex-col w-full">
<JoinModal v-model:visible="joinEnterpriseVisible" ref="joinModalRef" /> <JoinModal v-model:open="joinEnterpriseVisible" centered ref="joinModalRef" />
<Layout.Header class="layout-header-wrap"> <Layout.Header class="layout-header-wrap">
<Navbar /> <Navbar />
</Layout.Header> </Layout.Header>

View File

@ -1,4 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Button, Result } from 'ant-design-vue';
import { useRouter } from 'vue-router';
const router = useRouter(); const router = useRouter();
const back = () => { const back = () => {
router.replace('/'); router.replace('/');
@ -7,9 +10,9 @@ const back = () => {
<template> <template>
<div class="content"> <div class="content">
<a-result class="result" status="404" subtitle="页面跑路了" /> <Result class="result" status="404" sub-title="页面跑路了" />
<div class="operation-row"> <div class="operation-row flex justify-center">
<a-button key="back" type="primary" @click="back">返回</a-button> <Button key="back" type="primary" @click="back">返回</Button>
</div> </div>
</div> </div>
</template> </template>
@ -19,8 +22,10 @@ const back = () => {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
margin-left: -95px; transform: translate(-50%, -50%);
margin-top: -121px; padding: 32px 32px 24px;
text-align: center; // margin-left: -95px;
// margin-top: -121px;
// text-align: center;
} }
</style> </style>

View File

@ -3,13 +3,14 @@
* @Date: 2025-06-26 17:23:52 * @Date: 2025-06-26 17:23:52
--> -->
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
width="400px" width="400px"
modal-class="exit-account-modal" wrapClassName="exit-account-modal"
show-close="false" show-close="false"
:footer="false" :footer="null"
@close="onClose" @cancel="onClose"
centered
> >
<div class="flex items-center mb-16px"> <div class="flex items-center mb-16px">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
@ -17,15 +18,14 @@
</div> </div>
<p class="m-0 p-0 mb-24px s2 ml-32px">退出登录后你将无法收到该账号的通知</p> <p class="m-0 p-0 mb-24px s2 ml-32px">退出登录后你将无法收到该账号的通知</p>
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<a-button class="!rounded-4px" size="medium" @click="onClose">返回</a-button> <Button @click="onClose">返回</Button>
<a-button type="primary" class="ml-16px danger-btn" status="danger" size="medium" @click="onLogout" <Button danger type="primary" @click="onLogout" class="ml-8px">退出登录</Button>
>退出登录</a-button
>
</div> </div>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { Modal, Button, message } from 'ant-design-vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { fetchLogOut } from '@/api/all/login'; import { fetchLogOut } from '@/api/all/login';
import { handleUserLogout } from '@/utils/user'; import { handleUserLogout } from '@/utils/user';
@ -46,7 +46,7 @@ async function onLogout() {
const { code } = await fetchLogOut(); const { code } = await fetchLogOut();
if (code === 200) { if (code === 200) {
handleUserLogout(); handleUserLogout();
AMessage.success('退出登录成功'); message.success('退出登录成功');
onClose(); onClose();
} }
} }
@ -56,14 +56,14 @@ defineExpose({ open });
<style lang="scss"> <style lang="scss">
.exit-account-modal { .exit-account-modal {
border-radius: 8px; // border-radius: 8px;
border: 1px solid var(--BG-300, #e6e6e8) !important; // border: 1px solid var(--BG-300, #e6e6e8) !important;
background-color: var(--BG-white, #fff) !important; // background-color: var(--BG-white, #fff) !important;
box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1); // box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1);
.arco-modal-header { .ant-modal-header {
display: none; display: none;
} }
.arco-modal-body { .ant-modal-body {
padding: 24px; padding: 24px;
.s1 { .s1 {
color: var(--Text-1, #211f24); color: var(--Text-1, #211f24);
@ -81,15 +81,6 @@ defineExpose({ open });
font-weight: 400; font-weight: 400;
line-height: 20px; /* 166.667% */ line-height: 20px; /* 166.667% */
} }
.cancel-btn {
border-radius: 4px;
}
.danger-btn {
background: var(--Functional-Danger-6, #f64b31) !important;
&:hover {
background: var(--Functional-Danger-6, #f64b31) !important;
}
}
} }
} }
</style> </style>

View File

@ -8,42 +8,42 @@
width: 560px; width: 560px;
height: 36px; height: 36px;
padding: 0 2px 0 16px; padding: 0 2px 0 16px;
border-radius: 50px; border-radius: 50px !important;
background: rgba(255, 255, 255, 0.6); background-color: rgba(255, 255, 255, 0.6) !important;
backdrop-filter: blur(8px); backdrop-filter: blur(8px);
border-color: #fff; border-color: #fff !important;
box-shadow: none; box-shadow: none;
transition: all 0.3s; transition: all 0.3s;
display: flex; display: flex;
align-items: center; align-items: center;
&.ant-input-affix-wrapper-focused { &.ant-input-affix-wrapper-focused {
border-color: #6d4cfe; border-color: #6d4cfe !important;
caret-color: #6d4cfe; caret-color: #6d4cfe !important;
} }
&:hover { &:hover {
border-color: #6d4cfe; border-color: #6d4cfe !important;
} }
.ant-input-suffix { .ant-input-suffix {
margin-inline-start: 0; margin-inline-start: 0 !important;
} }
.ant-input { .ant-input {
padding-right: 16px; padding-right: 16px;
border: none !important; border: none !important;
background-color: transparent; background-color: transparent !important;
box-shadow: none; box-shadow: none;
font-family: $font-family-regular; font-family: $font-family-regular;
color: #211f24; color: #211f24;
font-size: 12px; font-size: 12px !important;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
line-height: 20px; line-height: 20px;
&::placeholder { &::placeholder {
color: #939499; color: #939499 !important;
} }
&:focus-within { &:focus-within {
&::after { &::after {
border-width: 1px; border-width: 1px !important;
} }
} }
} }

View File

@ -10,33 +10,31 @@
<div class="agent-entry mx-16px" :class="isAgentRoute ? 'agent' : ''" @click="handleAgentClick"></div> <div class="agent-entry mx-16px" :class="isAgentRoute ? 'agent' : ''" @click="handleAgentClick"></div>
<!-- 头像设置 --> <!-- 头像设置 -->
<a-dropdown trigger="click" class="layout-avatar-dropdown"> <Dropdown trigger="click" overlayClassName="layout-avatar-dropdown">
<a-avatar class="cursor-pointer" :size="32"> <img alt="avatar" src="@/assets/avatar.svg" class="cursor-pointer w-32px h-32px rounded-50%" />
<img alt="avatar" src="@/assets/avatar.svg" /> <template #overlay>
</a-avatar> <Menu>
<template #content> <MenuItem>
<div> <div class="h-full flex justify-between items-center w-100%" @click="setServerMenu">
<a-doption>
<a-space class="flex justify-between w-100%" @click="setServerMenu">
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" class="w-16px h-16px mr-8px" /> <img :src="icon1" class="w-16px h-16px mr-8px" />
<span>管理中心</span> <span>管理中心</span>
</div> </div>
<icon-right size="12" /> <icon-right size="12" />
</a-space> </div>
</a-doption> </MenuItem>
<a-dsubmenu value="option-1" position="lt" trigger="hover" class="enterprises-dsubmenu"> <MenuItem>
<a-doption class="enterprises-doption"> <SubMenu value="option-1" position="lt" trigger="hover" popupClassName="enterprises-dsubmenu">
<a-space class="flex justify-between w-100%"> <template #title>
<div class="flex justify-between w-100% h-full items-center">
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon3" class="w-16px h-16px mr-8px" /> <img :src="icon3" class="w-16px h-16px mr-8px" />
<span>切换企业账号</span> <span>切换企业账号</span>
</div> </div>
<icon-right size="12" /> <icon-right size="12" />
</a-space> </div>
</a-doption> </template>
<template #content> <MenuItem
<a-doption
v-for="(item, index) in enterprises" v-for="(item, index) in enterprises"
:key="index" :key="index"
class="rounded-8px hover:bg-#F2F3F5" class="rounded-8px hover:bg-#F2F3F5"
@ -49,21 +47,21 @@
<span>{{ item.name }}</span> <span>{{ item.name }}</span>
<icon-check v-if="enterpriseInfo?.id === item.id" size="16" /> <icon-check v-if="enterpriseInfo?.id === item.id" size="16" />
</div> </div>
</a-doption> </MenuItem>
</template> </SubMenu>
</a-dsubmenu> </MenuItem>
<a-doption> <MenuItem>
<a-space class="flex justify-between w-100%" @click="clickExit"> <div class="flex justify-between w-100% h-full items-center" @click="clickExit">
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon2" class="w-16px h-16px mr-8px" /> <img :src="icon2" class="w-16px h-16px mr-8px" />
<span>退出登录</span> <span>退出登录</span>
</div> </div>
<icon-right size="12" /> <icon-right size="12" />
</a-space>
</a-doption>
</div> </div>
</MenuItem>
</Menu>
</template> </template>
</a-dropdown> </Dropdown>
<ExitAccountModal ref="exitAccountModalRef" /> <ExitAccountModal ref="exitAccountModalRef" />
<DownloadCenterModal ref="downloadCenterModalRef" /> <DownloadCenterModal ref="downloadCenterModalRef" />
@ -71,6 +69,7 @@
</template> </template>
<script setup> <script setup>
import { Dropdown, Menu, MenuItem, SubMenu } from 'ant-design-vue';
import router from '@/router'; import router from '@/router';
import { useEnterpriseStore } from '@/stores/modules/enterprise'; import { useEnterpriseStore } from '@/stores/modules/enterprise';
import { useSidebarStore } from '@/stores/modules/side-bar'; import { useSidebarStore } from '@/stores/modules/side-bar';
@ -137,20 +136,37 @@ const handleAgentClick = () => {
<style lang="scss"> <style lang="scss">
.layout-avatar-dropdown, .layout-avatar-dropdown,
.enterprises-dsubmenu { .enterprises-dsubmenu {
.arco-dropdown { .ant-dropdown-menu {
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--BG-300, #e6e6e8); border: 1px solid var(--BG-300, #e6e6e8);
background: var(--BG-white, #fff); background: var(--BG-white, #fff);
padding: 12px 0px; padding: 12px 0px;
.arco-dropdown-option { .ant-dropdown-menu-item {
padding: 0 12px; padding: 0 12px;
margin-bottom: 4px; margin-bottom: 4px;
&-content {
.ant-dropdown-menu-title-content {
display: flex; display: flex;
height: 40px; height: 40px;
width: 100%; width: 100%;
padding: 10px 24px; padding: 10px 24px;
align-items: center; align-items: center;
.ant-dropdown-menu-submenu {
width: 100%;
.ant-dropdown-menu-submenu-title {
padding: 0;
&:hover {
background: none;
}
.ant-dropdown-menu-title-content {
padding: 0 !important;
}
}
.ant-dropdown-menu-submenu-arrow {
display: none;
}
}
}
.menu-item-text { .menu-item-text {
color: var(--Text-2, #3c4043); color: var(--Text-2, #3c4043);
font-family: $font-family-regular; font-family: $font-family-regular;
@ -159,13 +175,12 @@ const handleAgentClick = () => {
font-weight: 400; font-weight: 400;
line-height: 22px; line-height: 22px;
} }
} .ant-dropdown-menu-title-content {
.arco-dropdown-option-content {
border-radius: 8px !important; border-radius: 8px !important;
} }
&:not(.arco-dropdown-option-disabled):hover { &:not(.ant-dropdown-menu-item):hover {
background-color: transparent; background-color: transparent;
.arco-dropdown-option-content { .ant-dropdown-menu-title-content {
background: var(--BG-200, #f2f3f5); background: var(--BG-200, #f2f3f5);
} }
} }
@ -175,29 +190,29 @@ const handleAgentClick = () => {
.layout-avatar-dropdown, .layout-avatar-dropdown,
.enterprises-dsubmenu { .enterprises-dsubmenu {
width: 200px; width: 200px;
.arco-dropdown { .ant-dropdown-menu {
padding: 12px 4px; padding: 12px 4px;
.arco-dropdown-option { .ant-dropdown-menu-item {
padding: 0 !important; padding: 0 !important;
&-content { .ant-dropdown-menu-title-content {
padding: 0 12px !important; padding: 0 12px !important;
} }
} }
} }
.arco-dropdown-option-suffix { .ant-dropdown-option-suffix {
display: none; display: none;
} }
.enterprises-doption { // .enterprises-doption {
.arco-dropdown-option-content { // .ant-dropdown-menu-title-content {
padding: 0 !important; // padding: 0 !important;
border-radius: 8px; // border-radius: 8px;
} // }
&:not(.arco-dropdown-option-disabled):hover { // &:not(.ant-dropdown-option-disabled):hover {
background-color: transparent; // background-color: transparent;
.arco-dropdown-option-content { // .ant-dropdown-menu-title-content {
background: var(--BG-200, #f2f3f5); // background: var(--BG-200, #f2f3f5);
} // }
} // }
} // }
} }
</style> </style>

View File

@ -1,24 +1,24 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
:title="isBatch ? '批量删除下载记录' : '删除下载记录'" :title="isBatch ? '批量删除下载记录' : '删除下载记录'"
width="400px" width="400px"
@close="onClose" @cancel="onClose"
centered
> >
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ accountName }} 这条记录吗</span> <span>确认删除 {{ accountName }} 这条记录吗</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="onClose">取消</a-button> <Button @click="onClose">取消</Button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete" <Button type="primary" danger @click="onDelete">确定</Button>
>确定</a-button
>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { Modal, Button, message } from 'ant-design-vue';
import { ref } from 'vue'; import { ref } from 'vue';
import { deleteTask, deleteBatchTasks } from '@/api/all/common'; import { deleteTask, deleteBatchTasks } from '@/api/all/common';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -52,7 +52,7 @@ async function onDelete() {
const { code } = await _fn(_params); const { code } = await _fn(_params);
if (code === 200) { if (code === 200) {
AMessage.success('删除成功'); message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update'); isBatch.value ? emits('batchUpdate') : emits('update');
onClose(); onClose();
} }

View File

@ -1,7 +1,8 @@
<script lang="jsx"> <script lang="jsx">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-vue'; import { Button, Checkbox, Input, Tooltip, Table, Pagination, message, notification } from 'ant-design-vue';
import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon'; import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
import NoData from '@/components/no-data'; import NoData from '@/components/no-data';
import { getTask, postRedoTask, postBatchDownload, batchQueryTaskStatus } from '@/api/all/common'; import { getTask, postRedoTask, postBatchDownload, batchQueryTaskStatus } from '@/api/all/common';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants'; import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
@ -21,8 +22,6 @@ export default {
dataSource, dataSource,
pageInfo, pageInfo,
onPageChange, onPageChange,
onPageSizeChange,
rowSelection,
handleSelect, handleSelect,
handleSelectAll, handleSelectAll,
DEFAULT_PAGE_INFO, DEFAULT_PAGE_INFO,
@ -30,9 +29,6 @@ export default {
onPageChange: () => { onPageChange: () => {
getData(); getData();
}, },
onPageSizeChange: () => {
getData();
},
}); });
let queryTaskTimer = null; let queryTaskTimer = null;
@ -75,10 +71,12 @@ export default {
getData(); getData();
}; };
const handleSorterChange = (column, order) => { const handleSorterChange = (pagination, filters, sorter) => {
query.value.sort_column = column; if (sorter && !Array.isArray(sorter) && sorter.columnKey) {
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc'; query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload(); reload();
}
}; };
const handleSearch = () => { const handleSearch = () => {
@ -137,10 +135,10 @@ export default {
completeTaskNum++; completeTaskNum++;
const notificationId = downloadTaskInfos.value.find((v) => v.id === id)?.randomId; const notificationId = downloadTaskInfos.value.find((v) => v.id === id)?.randomId;
notificationId && Notification.remove(notificationId); notificationId && notification.close(notificationId);
if (status === 1) { if (status === 1) {
AMessage.success('批量下载已完成,正在下载文件...'); message.success('批量下载已完成,正在下载文件...');
downloadByUrl(file); downloadByUrl(file);
} else if (status === 2) { } else if (status === 2) {
const onReDownload = () => { const onReDownload = () => {
@ -208,11 +206,11 @@ export default {
<div class="filter-row-item flex items-center"> <div class="filter-row-item flex items-center">
<span class="label">操作人员</span> <span class="label">操作人员</span>
<Input <Input
v-model={query.value.operator_name} v-model:value={query.value.operator_name}
class="w-240px" class="w-240px"
placeholder="请输入操作人员" placeholder="请输入操作人员"
size="medium" size="middle"
allow-clear allowClear
onChange={handleSearch} onChange={handleSearch}
v-slots={{ v-slots={{
prefix: () => <IconSearch />, prefix: () => <IconSearch />,
@ -222,11 +220,11 @@ export default {
<div class="filter-row-item flex items-center"> <div class="filter-row-item flex items-center">
<span class="label">所属模块</span> <span class="label">所属模块</span>
<Input <Input
v-model={query.value.module} v-model:value={query.value.module}
class="w-240px" class="w-240px"
placeholder="请输入所属模块" placeholder="请输入所属模块"
size="medium" size="middle"
allow-clear allowClear
onChange={handleSearch} onChange={handleSearch}
v-slots={{ v-slots={{
prefix: () => <IconSearch />, prefix: () => <IconSearch />,
@ -239,17 +237,17 @@ export default {
{dataSource.value.length > 0 && selectedRows.value.length > 0 && ( {dataSource.value.length > 0 && selectedRows.value.length > 0 && (
<div <div
class={[ class={[
'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-42px', 'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-48px items-center',
selectedRows.value.length > 0 ? ' selected' : '', selectedRows.value.length > 0 ? ' selected' : '',
].join('')} ].join('')}
> >
<div class="flex items-center"> <div class="flex items-center">
<div class="flex items-center"> <div class="flex items-center">
<Checkbox <Checkbox
modelValue={checkedAll.value} checked={checkedAll.value}
indeterminate={indeterminate.value} indeterminate={indeterminate.value}
class="mr-8px" class="mr-8px"
onChange={handleSelectAll} onChange={(e) => handleSelectAll(e.target.checked)}
/> />
<span class="label mr-24px"> <span class="label mr-24px">
已选 已选
@ -271,45 +269,44 @@ export default {
{/* 表格 */} {/* 表格 */}
<Table <Table
ref="tableRef" ref="tableRef"
data={dataSource.value} dataSource={dataSource.value}
column-resizable rowKey="id"
row-key="id" rowSelection={{
row-selection={rowSelection.value} selectedRowKeys: selectedRowKeys.value,
selected-keys={selectedRowKeys.value} onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false} pagination={false}
scroll={{ x: '100%', y: '100%' }} scroll={{ x: '100%', y: '100%' }}
class="w-100% flex-1 overflow-hidden" class="w-100% flex-1 overflow-hidden"
bordered bordered
onSorterChange={handleSorterChange} showSorterTooltip={false}
onSelect={handleSelect} onChange={handleSorterChange}
onSelectAll={handleSelectAll}
v-slots={{ v-slots={{
empty: () => <NoData />, emptyText: () => <NoData />,
columns: () => ( }}
<> >
{TABLE_COLUMNS.map((column) => ( {TABLE_COLUMNS.map((column) => (
<TableColumn <Table.Column
key={column.dataIndex} key={column.dataIndex}
data-index={column.dataIndex} dataIndex={column.dataIndex}
fixed={column.fixed} fixed={column.fixed}
width={column.width} width={column.width}
min-width={column.minWidth} minWidth={column.minWidth}
sortable={column.sortable} sorter={column.sortable}
align={column.align} align={column.align}
ellipsis ellipsis
tooltip title={() => (
v-slots={{ <>
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span> <span class="cts mr-4px">{column.title}</span>
{column.tooltip && ( {column.tooltip && (
<Tooltip content={column.tooltip} position="top"> <Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} /> <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip> </Tooltip>
)} )}
</div> </>
), )}
cell: ({ record }) => { customRender={({ record }) => {
if (column.dataIndex === 'status') { if (column.dataIndex === 'status') {
return ( return (
<div class={['status-box', `status-box-${record.status}`]}> <div class={['status-box', `status-box-${record.status}`]}>
@ -323,17 +320,15 @@ export default {
} else { } else {
return formatTableField(column, record, true); return formatTableField(column, record, true);
} }
},
}} }}
/> />
))} ))}
<TableColumn <Table.Column
data-index="operation" dataIndex="operation"
width={dataSource.value.some((record) => record.status !== enumTaskStatus.Exporting) ? 120 : 60} width={dataSource.value.some((record) => record.status !== enumTaskStatus.Exporting) ? 120 : 60}
fixed="right" fixed="right"
title="操作" title="操作"
v-slots={{ customRender={({ record }) => (
cell: ({ record }) => (
<div class="flex items-center"> <div class="flex items-center">
<img <img
src={icon1} src={icon1}
@ -343,32 +338,27 @@ export default {
onClick={() => handleDelete(record)} onClick={() => handleDelete(record)}
/> />
{record.status !== enumTaskStatus.Exporting && ( {record.status !== enumTaskStatus.Exporting && (
<Button type="outline" size="mini" class="search-btn" onClick={() => handleDownload(record)}> <Button type="primary" ghost size="small" class="search-btn" onClick={() => handleDownload(record)}>
{record.status === enumTaskStatus.Failed ? '重新导出' : '下载'} {record.status === enumTaskStatus.Failed ? '重新导出' : '下载'}
</Button> </Button>
)} )}
</div> </div>
), )}
}}
/>
</>
),
}}
/> />
</Table>
{/* 分页 */} {/* 分页 */}
{pageInfo.value.total > 0 && ( {pageInfo.value.total > 0 && (
<div class="flex justify-end my-16px"> <div class="flex justify-end my-16px">
<Pagination <Pagination
total={pageInfo.value.total} total={pageInfo.value.total}
size="mini" size="small"
show-total showTotal={(total, range) => `${total} 条记录`}
show-jumper showQuickJumper
show-page-size showSizeChanger
current={pageInfo.value.page} current={pageInfo.value.page}
page-size={pageInfo.value.page_size} pageSize={pageInfo.value.page_size}
onChange={onPageChange} onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/> />
</div> </div>
)} )}

View File

@ -1,25 +1,25 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
:title="isBatch ? '批量删除导入记录' : '删除导入记录'" :title="isBatch ? '批量删除导入记录' : '删除导入记录'"
width="400px" width="400px"
@close="onClose" centered
@cancel="onClose"
> >
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ accountName }} 这条记录吗</span> <span>确认删除 {{ accountName }} 这条记录吗</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="onClose">取消</a-button> <Button @click="onClose">取消</Button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete" <Button type="primary" danger @click="onDelete">确定</Button>
>确定</a-button
>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Modal, Button, message } from 'ant-design-vue';
import { deleteTask, deleteBatchTasks } from '@/api/all/common'; import { deleteTask, deleteBatchTasks } from '@/api/all/common';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -51,7 +51,7 @@ async function onDelete() {
const _params = isBatch.value ? { ids: taskId.value } : taskId.value; const _params = isBatch.value ? { ids: taskId.value } : taskId.value;
const { code } = await _fn(_params); const { code } = await _fn(_params);
if (code === 200) { if (code === 200) {
AMessage.success('删除成功'); message.success('删除成功');
isBatch.value ? emits('batchUpdate') : emits('update'); isBatch.value ? emits('batchUpdate') : emits('update');
onClose(); onClose();
} }

View File

@ -1,6 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { Input, Table, TableColumn, Checkbox, Pagination, Button, Tooltip, Notification } from '@arco-design/web-vue'; import { Button, Tooltip, Table, Pagination } from 'ant-design-vue';
import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon'; import { IconSearch, IconClose, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
import NoData from '@/components/no-data'; import NoData from '@/components/no-data';
import { getTask } from '@/api/all/common'; import { getTask } from '@/api/all/common';
@ -21,8 +21,6 @@ export default {
dataSource, dataSource,
pageInfo, pageInfo,
onPageChange, onPageChange,
onPageSizeChange,
rowSelection,
handleSelect, handleSelect,
handleSelectAll, handleSelectAll,
DEFAULT_PAGE_INFO, DEFAULT_PAGE_INFO,
@ -30,9 +28,6 @@ export default {
onPageChange: () => { onPageChange: () => {
getData(); getData();
}, },
onPageSizeChange: () => {
getData();
},
}); });
const query = ref({ ...INITIAL_FORM }); const query = ref({ ...INITIAL_FORM });
@ -72,10 +67,12 @@ export default {
getData(); getData();
}; };
const handleSorterChange = (column, order) => { const handleSorterChange = (pagination, filters, sorter) => {
query.value.sort_column = column; if (sorter && sorter.columnKey) {
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc'; query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload(); reload();
}
}; };
const handleSearch = () => { const handleSearch = () => {
@ -118,112 +115,46 @@ export default {
return () => ( return () => (
<div class="import-task-wrap"> <div class="import-task-wrap">
{/* 筛选行 */}
{/* <div class="filter-row flex mb-16px">
<div class="filter-row-item flex items-center">
<span class="label">操作人员</span>
<Input
v-model={query.value.operator_name}
class="w-240px"
placeholder="请输入操作人员"
size="medium"
allow-clear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
}}
/>
</div>
<div class="filter-row-item flex items-center">
<span class="label">所属模块</span>
<Input
v-model={query.value.module}
class="w-240px"
placeholder="请输入所属模块"
size="medium"
allow-clear
onChange={handleSearch}
v-slots={{
prefix: () => <IconSearch />,
}}
/>
</div>
</div> */}
{/* 已选提示行 */}
{/* {dataSource.value.length > 0 && selectedRows.value.length > 0 && (
<div
class={[
'tip-row flex justify-between px-16px py-10px w-100% mb-16px h-42px',
selectedRows.value.length > 0 ? ' selected' : '',
].join('')}
>
<div class="flex items-center">
<div class="flex items-center">
<Checkbox
modelValue={checkedAll.value}
indeterminate={indeterminate.value}
class="mr-8px"
onChange={handleSelectAll}
/>
<span class="label mr-24px">
已选
<span class="color-#6D4CFE">{selectedRows.value.length}</span>
个文件
</span>
<span class="operation-btn" onClick={handleBatchDownload}>
批量下载
</span>
<span class="operation-btn red" onClick={handleBatchDelete}>
批量删除
</span>
</div>
</div>
<IconClose size={16} class="cursor-pointer color-#737478" onClick={handleCloseTip} />
</div>
)} */}
{/* 表格 */}
<Table <Table
ref="tableRef" ref="tableRef"
data={dataSource.value} dataSource={dataSource.value}
column-resizable rowKey="id"
row-key="id" rowSelection={{
selected-keys={selectedRowKeys.value} selectedRowKeys: selectedRowKeys.value,
onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false} pagination={false}
scroll={{ x: '100%', y: '100%' }} scroll={{ x: '100%', y: '100%' }}
class="w-100% flex-1 overflow-hidden" class="w-100% flex-1 overflow-hidden"
bordered bordered
onSorterChange={handleSorterChange} showSorterTooltip={false}
onSelect={handleSelect} onChange={handleSorterChange}
onSelectAll={handleSelectAll}
v-slots={{ v-slots={{
empty: () => <NoData />, empty: () => <NoData />,
columns: () => ( }}
<> >
{TABLE_COLUMNS.map((column) => ( {TABLE_COLUMNS.map((column) => (
<TableColumn <Table.Column
key={column.dataIndex} key={column.dataIndex}
data-index={column.dataIndex} dataIndex={column.dataIndex}
fixed={column.fixed} fixed={column.fixed}
width={column.width} width={column.width}
min-width={column.minWidth} minWidth={column.minWidth}
sortable={column.sortable} sorter={column.sortable}
align={column.align} align={column.align}
ellipsis ellipsis
tooltip title={() => (
v-slots={{ <>
title: () => (
<div class="flex items-center">
<span class="cts mr-4px">{column.title}</span> <span class="cts mr-4px">{column.title}</span>
{column.tooltip && ( {column.tooltip && (
<Tooltip content={column.tooltip} position="top"> <Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} /> <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip> </Tooltip>
)} )}
</div> </>
), )}
cell: ({ record }) => { customRender={({ record }) => {
if (column.dataIndex === 'status') { if (column.dataIndex === 'status') {
return ( return (
<div class={['status-box', `status-box-${record.status}`]}> <div class={['status-box', `status-box-${record.status}`]}>
@ -237,17 +168,15 @@ export default {
} else { } else {
return formatTableField(column, record, true); return formatTableField(column, record, true);
} }
},
}} }}
/> />
))} ))}
<TableColumn <Table.Column
data-index="operation" dataIndex="operation"
width={dataSource.value.some((record) => record.status === enumTaskStatus.Failed) ? 180 : 60} width={dataSource.value.some((record) => record.status === enumTaskStatus.Failed) ? 180 : 60}
fixed="right" fixed="right"
title="操作" title="操作"
v-slots={{ customRender={({ record }) => (
cell: ({ record }) => (
<div class="flex items-center"> <div class="flex items-center">
<img <img
src={icon1} src={icon1}
@ -257,32 +186,27 @@ export default {
onClick={() => handleDelete(record)} onClick={() => handleDelete(record)}
/> />
{record.status === enumTaskStatus.Failed && ( {record.status === enumTaskStatus.Failed && (
<Button type="outline" size="mini" class="search-btn" onClick={() => handleDownload(record)}> <Button type="primary" ghost size="small" class="search-btn" onClick={() => handleDownload(record)}>
下载问题表格 下载问题表格
</Button> </Button>
)} )}
</div> </div>
), )}
}}
/>
</>
),
}}
/> />
</Table>
{/* 分页 */} {/* 分页 */}
{pageInfo.value.total > 0 && ( {pageInfo.value.total > 0 && (
<div class="flex justify-end my-16px"> <div class="flex justify-end my-16px">
<Pagination <Pagination
total={pageInfo.value.total} total={pageInfo.value.total}
size="mini" size="small"
show-total showTotal={(total, range) => `${total} 条记录`}
show-jumper showQuickJumper
show-page-size showSizeChanger
current={pageInfo.value.page} current={pageInfo.value.page}
page-size={pageInfo.value.page_size} pageSize={pageInfo.value.page_size}
onChange={onPageChange} onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/> />
</div> </div>
)} )}

View File

@ -1,37 +1,39 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
title="任务中心" title="任务中心"
modal-class="task-center-modal" wrapClassName="task-center-modal"
width="860px" width="860px"
:mask-closable="false" :mask-closable="false"
:footer="false" :footer="null"
@close="onClose" @cancel="onClose"
centered
> >
<a-tabs :active-key="activeTab" @tab-click="handleTabClick"> <Tabs v-model:activeKey="activeTab" @change="handleTabClick">
<a-tab-pane key="0" title="导入"> </a-tab-pane> <TabPane key="1" tab="导入"> </TabPane>
<a-tab-pane key="1" title="导出"> </a-tab-pane> <TabPane key="2" tab="导出"> </TabPane>
</a-tabs> </Tabs>
<div class="content"> <div class="content">
<component :is="activeTab === '0' ? ImportTask : ExportTask" ref="componentRef" /> <component :is="activeTab === '1' ? ImportTask : ExportTask" ref="componentRef" />
</div> </div>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { Notification } from '@arco-design/web-vue'; import { Checkbox, Modal, Button, Tabs, notification } from 'ant-design-vue';
const { TabPane } = Tabs;
import ExportTask from './components/export-task'; import ExportTask from './components/export-task';
import ImportTask from './components/import-task'; import ImportTask from './components/import-task';
const visible = ref(false); const visible = ref(false);
const componentRef = ref(null); const componentRef = ref(null);
const activeTab = ref('0'); const activeTab = ref('1');
let timer = null; let timer = null;
const handleTabClick = (key) => { const handleTabClick = (key) => {
activeTab.value = key; // activeTab.value = key;
nextTick(() => { nextTick(() => {
getData(); getData();
}); });
@ -42,20 +44,21 @@ const getData = () => {
}; };
const open = () => { const open = () => {
visible.value = true;
nextTick(() => {
getData(); getData();
});
timer = setInterval(() => { timer = setInterval(() => {
getData(); getData();
}, 10000); }, 10000);
visible.value = true;
}; };
const onClose = () => { const onClose = () => {
activeTab.value = '0'; activeTab.value = '0';
clearTimer(); clearTimer();
componentRef.value?.unloadComp?.(); componentRef.value?.unloadComp?.();
Notification.clear(); notification.destroy();
visible.value = false; visible.value = false;
}; };
const clearTimer = () => { const clearTimer = () => {

View File

@ -1,7 +1,7 @@
.task-center-modal { .task-center-modal {
.arco-modal-header { .ant-modal-header {
border-bottom: none !important; border-bottom: none !important;
.arco-modal-title { .ant-modal-title {
color: var(--Text-1, #211f24); color: var(--Text-1, #211f24);
font-size: 16px; font-size: 16px;
font-style: normal; font-style: normal;
@ -10,7 +10,7 @@
font-family: $font-family-medium; font-family: $font-family-medium;
} }
} }
.arco-modal-body { .ant-modal-body {
padding: 0 !important; padding: 0 !important;
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -7,17 +7,21 @@ import router from './router';
import store from './stores'; import store from './stores';
import * as directives from '@/directives'; import * as directives from '@/directives';
import VueLazyLoad from "vue-lazyload";
import NoData from '@/components/no-data/index.vue'; import NoData from '@/components/no-data/index.vue';
import SvgIcon from '@/components/svg-icon/index.vue'; import SvgIcon from '@/components/svg-icon/index.vue';
import '@/api/index'; import '@/api/index';
import '@arco-design/web-vue/dist/arco.css'; // Arco 默认样式
import './core'; import './core';
import '@arco-design/web-vue/dist/arco.css'; // 已移除 Arco 样式
import 'normalize.css'; import 'normalize.css';
import 'uno.css'; import 'uno.css';
import 'virtual:svg-icons-register'; import 'virtual:svg-icons-register';
import errorImage from '@/assets/img/error-img.png';
import loadingImage from '@/assets/img/error-img.png';
// import '@/styles/vars.css'; // 优先加载 // import '@/styles/vars.css'; // 优先加载
const app = createApp(App); const app = createApp(App);
@ -27,6 +31,10 @@ app.component('SvgIcon', SvgIcon);
app.use(store); app.use(store);
app.use(router); app.use(router);
app.use(VueLazyLoad, {
error: errorImage,
loading: loadingImage,
});
Object.values(directives).forEach((directive) => { Object.values(directives).forEach((directive) => {
app.use(directive); app.use(directive);

View File

@ -3,6 +3,7 @@
* @Date: 2025-06-22 22:59:16 * @Date: 2025-06-22 22:59:16
*/ */
import type { Router } from 'vue-router'; import type { Router } from 'vue-router';
import { message } from 'ant-design-vue';
import NProgress from 'nprogress'; import NProgress from 'nprogress';
import { goUserLogin } from '@/utils/user'; import { goUserLogin } from '@/utils/user';
// import router from '@/router'; // import router from '@/router';
@ -29,7 +30,7 @@ export default function setupUserLoginInfoGuard(router: Router) {
// if (requiresAuth) { // if (requiresAuth) {
// const hasPermission = checkRoutePermission(routeName); // const hasPermission = checkRoutePermission(routeName);
// if (!hasPermission) { // if (!hasPermission) {
// AMessage.error('您没有权限访问该页面'); // message.error('您没有权限访问该页面');
// next('/'); // next('/');
// return; // return;
// } // }

View File

@ -1,89 +1,89 @@
// import { IconBookmark } from '@arco-design/web-vue/es/icon'; import { IconBookmark } from '@arco-design/web-vue/es/icon';
// import type { AppRouteRecordRaw } from '../types'; import type { AppRouteRecordRaw } from '../types';
// import { MENU_GROUP_IDS } from '@/router/constants'; import { MENU_GROUP_IDS } from '@/router/constants';
// const COMPONENTS: AppRouteRecordRaw[] = [ const COMPONENTS: AppRouteRecordRaw[] = [
// { {
// path: '/dataEngine', path: '/dataEngine',
// name: 'DataEngine', name: 'DataEngine',
// redirect: 'dataEngine/hotTranslation', redirect: 'dataEngine/hotTranslation',
// meta: { meta: {
// locale: '全域数据引擎', locale: '全域数据引擎',
// icon: IconBookmark, icon: IconBookmark,
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// id: MENU_GROUP_IDS.DATA_ENGINE_ID, id: MENU_GROUP_IDS.DATA_ENGINE_ID,
// }, },
// children: [ children: [
// { {
// path: 'hotTranslation', path: 'hotTranslation',
// name: 'DataEngineHotTranslation', name: 'DataEngineHotTranslation',
// meta: { meta: {
// locale: '行业热门话题洞察', locale: '行业热门话题洞察',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/hotTranslation.vue'), component: () => import('@/views/components/dataEngine/hotTranslation.vue'),
// }, },
// { {
// path: 'hotCloud', path: 'hotCloud',
// name: 'DataEngineHotCloud', name: 'DataEngineHotCloud',
// meta: { meta: {
// locale: '行业词云', locale: '行业词云',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/hotCloud.vue'), component: () => import('@/views/components/dataEngine/hotCloud.vue'),
// }, },
// { {
// path: 'keyWord', path: 'keyWord',
// name: 'DataEngineKeyWord', name: 'DataEngineKeyWord',
// meta: { meta: {
// locale: '行业关键词动向', locale: '行业关键词动向',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/keyWord.vue'), component: () => import('@/views/components/dataEngine/keyWord.vue'),
// }, },
// { {
// path: 'userPainPoints', path: 'userPainPoints',
// name: 'DataEngineUserPainPoints', name: 'DataEngineUserPainPoints',
// meta: { meta: {
// locale: '用户痛点观察', locale: '用户痛点观察',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/userPainPoints.vue'), component: () => import('@/views/components/dataEngine/userPainPoints.vue'),
// }, },
// { {
// path: 'keyBrandMovement', path: 'keyBrandMovement',
// name: 'DataEngineKeyBrandMovement', name: 'DataEngineKeyBrandMovement',
// meta: { meta: {
// locale: '重点品牌动向', locale: '重点品牌动向',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/keyBrandMovement.vue'), component: () => import('@/views/components/dataEngine/keyBrandMovement.vue'),
// }, },
// { {
// path: 'userPersona', path: 'userPersona',
// name: 'DataEngineUserPersona', name: 'DataEngineUserPersona',
// meta: { meta: {
// locale: '用户画像', locale: '用户画像',
// requiresAuth: true, requiresAuth: true,
// requireLogin: true, requireLogin: true,
// roles: ['*'], roles: ['*'],
// }, },
// component: () => import('@/views/components/dataEngine/userPersona.vue'), component: () => import('@/views/components/dataEngine/userPersona.vue'),
// }, },
// ], ],
// }, },
// ]; ];
// export default COMPONENTS; export default COMPONENTS;

View File

@ -7,9 +7,9 @@ import { MENU_GROUP_IDS } from '@/router/constants';
import IconRepository from '@/assets/svg/svg-repository.svg'; import IconRepository from '@/assets/svg/svg-repository.svg';
import IconMediaAccount from '@/assets/svg/svg-mediaAccount.svg'; import IconMediaAccount from '@/assets/svg/svg-mediaAccount.svg';
import IconPutAccount from '@/assets/svg/svg-putAccount.svg'; // import IconPutAccount from '@/assets/svg/svg-putAccount.svg';
import IconIntelligentSolution from '@/assets/svg/svg-intelligentSolution.svg'; // import IconIntelligentSolution from '@/assets/svg/svg-intelligentSolution.svg';
import IconProjectManagement from '@/assets/svg/svg-projectManagement.svg'; // import IconProjectManagement from '@/assets/svg/svg-projectManagement.svg';
const COMPONENTS: AppRouteRecordRaw[] = [ const COMPONENTS: AppRouteRecordRaw[] = [
{ {
@ -196,32 +196,32 @@ const COMPONENTS: AppRouteRecordRaw[] = [
// }, // },
// ], // ],
// }, // },
{ // {
path: '/project-manage', // path: '/project-manage',
name: 'ProjectManagement', // name: 'ProjectManagement',
redirect: 'project-manage/project-list', // redirect: 'project-manage/project-list',
meta: { // meta: {
locale: '项目管理', // locale: '项目管理',
icon: IconProjectManagement, // icon: IconProjectManagement,
requiresAuth: true, // requiresAuth: true,
requireLogin: true, // requireLogin: true,
roles: ['*'], // roles: ['*'],
id: MENU_GROUP_IDS.PROPERTY_ID, // id: MENU_GROUP_IDS.PROPERTY_ID,
}, // },
children: [ // children: [
{ // {
path: 'project-list', // path: 'project-list',
name: 'ProjectList', // name: 'ProjectList',
meta: { // meta: {
locale: '项目列表', // locale: '项目列表',
requiresAuth: true, // requiresAuth: true,
requireLogin: true, // requireLogin: true,
roles: ['*'], // roles: ['*'],
}, // },
component: () => import('@/views/property-marketing/project-manage/project-list/index.vue'), // component: () => import('@/views/property-marketing/project-manage/project-list/index.vue'),
}, // },
], // ],
}, // },
]; ];
export default COMPONENTS; export default COMPONENTS;

View File

@ -3,7 +3,7 @@
* @Date: 2025-06-23 03:56:22 * @Date: 2025-06-23 03:56:22
*/ */
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import type { AppState, RouteRecordNormalized, NotificationReturn } from './types'; import type { AppState, RouteRecordNormalized } from './types';
import defaultSettings from '@/config/settings.json'; import defaultSettings from '@/config/settings.json';
import type { AppRouteRecordRaw } from '@/router/routes/types'; import type { AppRouteRecordRaw } from '@/router/routes/types';

View File

@ -1,8 +1,7 @@
import type { RouteRecordNormalized } from 'vue-router'; import type { RouteRecordNormalized } from 'vue-router';
import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
import type { AppRouteRecordRaw } from '@/router/routes/types'; import type { AppRouteRecordRaw } from '@/router/routes/types';
export { RouteRecordNormalized, NotificationReturn }; export { RouteRecordNormalized };
export interface AppState { export interface AppState {
theme: string; theme: string;

View File

@ -1,9 +1,9 @@
.arco-modal { .ant-modal {
.arco-modal-header { .ant-modal-header {
border-bottom: 1px solid var(--Border-1, #d7d7d9); border-bottom: 1px solid var(--Border-1, #d7d7d9);
height: 56px; height: 56px;
padding: 0 20px; padding: 0 20px;
.arco-modal-title { .ant-modal-title {
font-family: $font-family-medium; font-family: $font-family-medium;
color: #211f24; color: #211f24;
font-size: 16px; font-size: 16px;
@ -13,11 +13,11 @@
} }
} }
.arco-modal-body { .ant-modal-body {
padding: 24px 20px; padding: 24px 20px;
} }
.arco-modal-footer { .ant-modal-footer {
display: flex; display: flex;
height: 64px; height: 64px;
padding: 0px 20px; padding: 0px 20px;

View File

@ -1,4 +1,4 @@
import { Notification } from '@arco-design/web-vue'; import { notification } from 'ant-design-vue';
import { downloadByUrl } from '@/utils/tools'; import { downloadByUrl } from '@/utils/tools';
import { IconLoading } from '@arco-design/web-vue/es/icon'; import { IconLoading } from '@arco-design/web-vue/es/icon';
@ -16,30 +16,30 @@ interface RenderNotificationData {
// 下载通知框 // 下载通知框
export function showExportNotification(label: string, others: { id?: string, duration?: number }) { export function showExportNotification(label: string, others: { id?: string, duration?: number }) {
const { id = '', duration = 3000 } = others ?? {} const { id = '', duration = 3 } = others ?? {}
Notification.warning({ notification.warning({
id, key: id,
showIcon: false, icon: () => null,
closable: true, message: () => null,
content: () => ( description: (
<div class="flex items-center pr-16px"> <div class="flex items-center pr-16px">
<IconLoading size={16} class="color-#6D4CFE mr-8px" /> <IconLoading size={16} class="color-#6D4CFE mr-8px" />
<p class="text-14px lh-22px font-400 color-#211F24">{label}</p> <p class="text-14px lh-22px font-400 color-#211F24">{label}</p>
</div> </div>
), ),
duration, duration,
class: 'px-16px py-9px w-450px rounded-2px bg-#F0EDFF', class: 'w-450px rounded-2px bg-#F0EDFF',
}); });
} }
// 下载失败框 // 下载失败框
export function showFailExportNotification(label: string, others: { id?: string, duration?: number, onReDownload?: Function }) { export function showFailExportNotification(label: string, others: { id?: string, duration?: number, onReDownload?: Function }) {
const { id = '', duration = 0, onReDownload } = others ?? {} const { id = '', duration = 0, onReDownload } = others ?? {}
Notification.warning({ notification.warning({
id, key: id,
showIcon: false, icon: () => null,
closable: true, message: () => null,
content: () => ( description: (
<div class="flex items-center justify-between pr-16px"> <div class="flex items-center justify-between pr-16px">
<div class="flex items-center mr-10px"> <div class="flex items-center mr-10px">
<img src={icon3} width={16} height={16} class=" mr-8px" /> <img src={icon3} width={16} height={16} class=" mr-8px" />
@ -50,7 +50,7 @@ export function showFailExportNotification(label: string, others: { id?: string,
</div> </div>
), ),
duration, duration,
class: 'px-16px py-9px w-500px rounded-2px bg-#FFE9E7', class: 'w-500px rounded-2px bg-#FFE9E7',
}); });
} }
@ -61,10 +61,10 @@ export const showImportResultNotification = (data: RenderNotificationData) => {
file && downloadByUrl(file); file && downloadByUrl(file);
}; };
Notification.warning({ notification.warning({
showIcon: false, icon: () => null,
closable: true, message: () => null,
content: () => ( description: (
<div> <div>
<div class="flex items-center mb-4px"> <div class="flex items-center mb-4px">
<img src={hasError ? icon1 : icon2} width="16" height="16" class="mr-8px" /> <img src={hasError ? icon1 : icon2} width="16" height="16" class="mr-8px" />
@ -89,6 +89,6 @@ export const showImportResultNotification = (data: RenderNotificationData) => {
</div> </div>
), ),
duration: 3000, duration: 3000,
class: `px-16px py-16px w-400px rounded-2px ${hasError ? 'bg-#FFF7E5' : 'bg-#EBF7F2'}`, class: `w-400px rounded-2px ${hasError ? 'bg-#FFF7E5' : 'bg-#EBF7F2'}`,
}); });
}; };

View File

@ -7,9 +7,9 @@
</div> </div>
<div class="info-section"> <div class="info-section">
<div class="title-group"> <div class="title-group">
<a-tooltip :content="cozeInfo.name"> <Tooltip :title="cozeInfo.name">
<div class="title">{{ cozeInfo.name }}</div> <div class="title">{{ cozeInfo.name }}</div>
</a-tooltip> </Tooltip>
<div class="tag"> <div class="tag">
<div> <div>
<img class="status-icon" :src="chatbotIcon" /> <img class="status-icon" :src="chatbotIcon" />
@ -18,12 +18,8 @@
</div> </div>
</div> </div>
<div class="usage-info"> <div class="usage-info">
<a-space>
<span class="count">{{ cozeInfo.views }}</span> <span class="count">{{ cozeInfo.views }}</span>
</a-space>
<a-space>
<span class="label"> 次使用 </span> <span class="label"> 次使用 </span>
</a-space>
</div> </div>
</div> </div>
<div class="description-section"> <div class="description-section">
@ -36,6 +32,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { defineProps } from 'vue'; import { defineProps } from 'vue';
import { Tooltip } from 'ant-design-vue';
import chatbotIcon from '@/assets/svg/chatbot.svg'; import chatbotIcon from '@/assets/svg/chatbot.svg';
const props = defineProps({ const props = defineProps({

View File

@ -36,9 +36,9 @@
<div class="body"> <div class="body">
<div class=""> <div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse"> <div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'"> <Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" /> <img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip> </Tooltip>
</div> </div>
</div> </div>
</div> </div>
@ -55,6 +55,7 @@ import { getChatAgent } from '@/api/all/agent';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import menuFold from '@/assets/svg/menu-fold.svg'; import menuFold from '@/assets/svg/menu-fold.svg';
import menuUnfold from '@/assets/svg/menu-unfold.svg'; import menuUnfold from '@/assets/svg/menu-unfold.svg';
import { Tooltip } from 'ant-design-vue';
import { formatNumberShow } from '@/utils/tools'; import { formatNumberShow } from '@/utils/tools';
const router = useRouter(); const router = useRouter();

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="agent-wrap relative h-full pl-16px"> <div class="agent-wrap relative h-full pl-16px">
<a-input <Input
v-model="query.name" v-model:value="query.name"
@press-enter="getData()" @press-enter="getData()"
placeholder="搜索智能体" placeholder="搜索智能体"
size="large" size="large"
@ -11,31 +11,32 @@
<template #prefix> <template #prefix>
<icon-search @click="getData()" /> <icon-search @click="getData()" />
</template> </template>
</a-input> </Input>
<div v-for="(item, index) in list" :key="index"> <div v-for="(item, index) in list" :key="index">
<p class="span-title w-fit mb-16px">{{ item.name }}</p> <p class="span-title w-fit mb-16px">{{ item.name }}</p>
<a-row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0"> <Row class="grid-demo" :gutter="[20, 16]" v-if="item.agent_products.length > 0">
<a-col :xs="24" <Col :xs="24" :sm="12" :md="8" :lg="5" :xl="6" :xxl="4" v-for="(product, k) in item.agent_products" :key="k">
:sm="12"
:md="8"
:lg="5"
:xl="6"
:xxl="4"
v-for="(product, k) in item.agent_products" :key="k">
<div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)"> <div class="card-container cursor-pointer !h-252px" @click="goDetail(product?.type, product?.id)">
<div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url"> <div class="card-image h-120px w-100% bg-cover bg-center mb-8px" v-image-main-color="product.image_url">
<img class="object-contain h-full w-100%" :src="product?.image_url" /> <img class="object-contain h-full w-100%" :src="product?.image_url" />
</div> </div>
<div class="card-content w-full"> <div class="card-content w-full">
<TextoverTips :context="product.name" class="card-title mb-4px !text-16px"/> <TextOverTips :context="product.name" class="card-title mb-4px !text-16px" />
<TextoverTips :context="product.description" class="card-description mb-8px color-#737478 text-14px lh-22px font-400" :line="2" /> <TextOverTips
:context="product.description"
class="card-description mb-8px color-#737478 text-14px lh-22px font-400"
:line="2"
/>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<div <div
:class="['status-tag', product.type === 1 ? 'blue-tag' : 'red-tag']" :class="['status-tag', product.type === 1 ? 'blue-tag' : 'red-tag']"
:style="{ background: product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)' }" :style="{
background:
product.type === 1 ? 'var(--Functional-Blue-1, #F0EDFF)' : 'var(--Functional-Red-1, #FFE9E7)',
}"
data-size="mini-20px" data-size="mini-20px"
> >
<SvgIcon <SvgIcon
@ -48,13 +49,15 @@
<div class="status-text">{{ product.type === 1 ? '对话式' : '工作流' }}</div> <div class="status-text">{{ product.type === 1 ? '对话式' : '工作流' }}</div>
</div> </div>
<div class="usage-info"> <div class="usage-info">
<div class="usage-count mr-2px">{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}</div> <div class="usage-count mr-2px">
{{ formatNumberShow({ value: product?.views, showExactValue: true }) }}
</div>
<div class="usage-label">次使用</div> <div class="usage-label">次使用</div>
</div> </div>
</div> </div>
</div> </div>
</a-col> </Col>
</a-row> </Row>
<NoData v-else /> <NoData v-else />
</div> </div>
@ -62,10 +65,11 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Input, Row, Col } from 'ant-design-vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { getAgentList } from '@/api/all/agent'; import { getAgentList } from '@/api/all/agent';
import { formatNumberShow } from "@/utils/tools"; import { formatNumberShow } from '@/utils/tools';
import TextoverTips from "@/components/text-over-tips"; import TextOverTips from '@/components/text-over-tips';
const router = useRouter(); const router = useRouter();

View File

@ -1,29 +1,26 @@
<template> <template>
<div class="form-container"> <div class="form-container">
<a-form :model="formData" ref="formRef" layout="vertical"> <Form :model="formData" ref="formRef" layout="vertical">
<a-form-item <FormItem
v-for="(field, index) in formFields" v-for="(field, index) in formFields"
:key="index" :key="index"
:label="field.props.label" :label="field.props.label"
:field="field.props.name" :name="field.props.name"
:rules="field.props.rules" :rules="field.props.rules"
:tooltip="field.props.tip" :tooltip="field.props.tip"
> >
<a-input <Input
allowClear allowClear
v-if="field.type === 'input'" v-if="field.type === 'input'"
v-model="formData[field.props.name]" v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder" :placeholder="field?.props?.placeholder"
/> />
<a-textarea <TextArea
v-if="field.type === 'textarea'" v-if="field.type === 'textarea'"
style="width: 500px; height: 200px" style="width: 500px; height: 200px"
v-model="formData[field.props.name]" v-model:value="formData[field.props.name]"
:placeholder="field?.props?.placeholder" :placeholder="field?.props?.placeholder"
/> />
<!-- <a-color-picker v-if="field.type === 'color_picker'"
style="width: 500px; height: 200px"
v-model="formData[field.props.name]" /> -->
<ImageUpload <ImageUpload
v-if="field.type == 'upload_image'" v-if="field.type == 'upload_image'"
v-model="formData[field.props.name]" v-model="formData[field.props.name]"
@ -34,24 +31,28 @@
v-model="formData[field.props.name]" v-model="formData[field.props.name]"
:limit="field.props.limit" :limit="field.props.limit"
></FileUpload> ></FileUpload>
<a-select <Select
v-else-if="field.type === 'select'" v-else-if="field.type === 'select'"
v-model="formData[field.props.name]" v-model:value="formData[field.props.name]"
:placeholder="field.placeholder" :placeholder="field.placeholder"
> >
<a-option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value"> <Option v-for="(option, optIndex) in field.props.options" :key="optIndex" :value="option.value">
{{ option.label }} {{ option.label }}
</a-option> </Option>
</a-select> </Select>
</a-form-item> </FormItem>
</a-form> </Form>
<a-button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</a-button> <Button class="submit-btn" type="primary" :disabled="loading" @click="handleSubmit">提交执行</Button>
</div> </div>
</template> </template>
<script setup> <script setup>
import { defineProps, defineEmits } from 'vue'; import { defineProps, defineEmits } from 'vue';
import ImageUpload from '@/components/upload/ImageUpload.vue'; import ImageUpload from '@/components/upload/ImageUpload.vue';
import FileUpload from '@/components/upload/FileUpload.vue'; import FileUpload from '@/components/upload/FileUpload.vue';
import { Button, Input, Select, Row, Col, Form } from 'ant-design-vue';
const { TextArea } = Input;
const { Option } = Select;
const { Item: FormItem } = Form;
const props = defineProps({ const props = defineProps({
formFields: { formFields: {
@ -79,24 +80,25 @@ const handleSubmit = async () => {
<style scoped lang="scss"> <style scoped lang="scss">
.form-container { .form-container {
:deep(.arco-input-wrapper), :deep(.ant-input),
:deep(.arco-textarea-wrapper) { :deep(.ant-input:focus),
:deep(.ant-input-focused) {
border-radius: 4px; border-radius: 4px;
border-color: #d7d7d9; border-color: #d7d7d9;
background-color: #fff; background-color: #fff;
height: 35px; height: 35px;
width: 300px; width: 300px;
&:focus-within, &:focus,
&.arco-input-focus { &:focus-within {
background-color: var(--color-bg-2); background-color: var(--color-bg-2);
border-color: rgb(var(--primary-6)); border-color: rgb(var(--primary-6));
box-shadow: 0 0 0 0 var(--color-primary-light-2); box-shadow: 0 0 0 0 var(--color-primary-light-2);
} }
&.arco-textarea-wrapper {
height: 60px;
} }
&.ant-textarea-wrapper {
height: 60px;
} }
.submit-btn { .submit-btn {

View File

@ -42,15 +42,14 @@
{{ item.title }} {{ item.title }}
</div> </div>
<div class="trigger-container"> <div class="trigger-container">
<a-trigger <Dropdown
mouse-leave-delay="200" :mouseLeaveDelay="200"
position="top" placement="top"
trigger="hover" trigger="hover"
:auto-fit-position="false" :overlayStyle="{ width: 'auto' }"
:unmount-on-close="true"
> >
<SvgIcon size="12" name="svg-more" class="icon-more" /> <SvgIcon size="12" name="svg-more" class="icon-more" />
<template #content> <template #overlay>
<div class=""> <div class="">
<div class="history-item-dropdown"> <div class="history-item-dropdown">
<div class="dropdown-item"> <div class="dropdown-item">
@ -61,18 +60,19 @@
</div> </div>
<div class="dropdown-item"> <div class="dropdown-item">
<SvgIcon size="12" name="svg-delete" class="icon color-#6D4CFE" /> <SvgIcon size="12" name="svg-delete" class="icon color-#6D4CFE" />
<a-popconfirm <Popconfirm
content="你确认删除该历史对话吗" content="你确认删除该历史对话吗"
@ok="deleteHistory(item.id, index)" @confirm="deleteHistory(item.id, index)"
type="error" ok-text="确定"
cancel-text="取消"
> >
<div class="text delete">删除</div> <div class="text delete">删除</div>
</a-popconfirm> </Popconfirm>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
</a-trigger> </Dropdown>
</div> </div>
</div> </div>
</div> </div>
@ -83,9 +83,9 @@
<div class="body"> <div class="body">
<div class=""> <div class="">
<div class="toggle-btn cursor-pointer" @click="toggleCollapse"> <div class="toggle-btn cursor-pointer" @click="toggleCollapse">
<a-tooltip :content="isCollapsed ? '展开' : '折叠'"> <Tooltip :title="isCollapsed ? '展开' : '折叠'">
<img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" /> <img class="status-icon" :src="isCollapsed ? menuUnfold : menuFold" />
</a-tooltip> </Tooltip>
</div> </div>
</div> </div>
</div> </div>
@ -95,7 +95,7 @@
<DynamicForm :formFields="formFields.form" :formData="formData" :loading="loading" @submit="handleSubmit" /> <DynamicForm :formFields="formFields.form" :formData="formData" :loading="loading" @submit="handleSubmit" />
</div> </div>
<div class="res h-full"> <div class="res h-full">
<a-spin v-if="loading" class="spin-center" tip="生成中。。。" /> <Spin v-if="loading" wrapperClassName="spin-center" tip="生成中。。。" />
<div <div
class="markdown-container" class="markdown-container"
v-if="workFlowRes.output != '' && loading === false" v-if="workFlowRes.output != '' && loading === false"
@ -107,15 +107,16 @@
</div> </div>
</div> </div>
<a-modal style="width: 500px" v-model:visible="editHistoryVisible"> <Modal style="width: 500px" v-model:open="editHistoryVisible" centered>
<template #title> Title</template> <template #title> Title</template>
<div></div> <div></div>
</a-modal> </Modal>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, reactive } from 'vue'; import { ref, reactive } from 'vue';
import { Modal, Tooltip, message, Popconfirm, Spin, Dropdown } from 'ant-design-vue';
import DynamicForm from './components/DynamicForm.vue'; import DynamicForm from './components/DynamicForm.vue';
import { import {
executeWorkFlow, executeWorkFlow,
@ -214,17 +215,17 @@ const handleTop = async (id, sort, event) => {
//置顶 //置顶
const topHistory = async (id, sort) => { const topHistory = async (id, sort) => {
const { code, message } = await topWorkflowHistoryApi(id); const { code, message: msg } = await topWorkflowHistoryApi(id);
if (code === 200) { if (code === 200) {
AMessage.success(message); message.success(msg);
getWorkflowHistoryList(); getWorkflowHistoryList();
} }
}; };
//取消置顶 //取消置顶
const canceltopHistory = async (id, sort) => { const canceltopHistory = async (id, sort) => {
const { code, message } = await cancelTopWorkflowHistoryApi(id); const { code, message: msg } = await cancelTopWorkflowHistoryApi(id);
if (code === 200) { if (code === 200) {
AMessage.success(message); message.success(msg);
getWorkflowHistoryList(); getWorkflowHistoryList();
} }
}; };

View File

@ -1,13 +1,15 @@
<template> <template>
<a-select allow-search v-model="selectedValue" placeholder="请选择账号" allow-clear filterable @change="handleChange"> <Select showSearch v-model:value="selectedValue" placeholder="请选择账号" allowClear filterable @change="handleChange">
<a-option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name"> <Option v-for="account in filteredAccounts" :key="account.id" :value="account.id" :label="account.name">
{{ account.name }} {{ account.name }}
</a-option> </Option>
</a-select> </Select>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, onMounted } from 'vue';
import { getPlacementAccountsList } from '@/api/all/propertyMarketing'; import { getPlacementAccountsList } from '@/api/all/propertyMarketing';
// 定义账号对象类型 // 定义账号对象类型

View File

@ -1,13 +1,15 @@
<template> <template>
<a-select allow-search v-model="selectedValue" placeholder="请选择计划" allow-clear filterable @change="handleChange"> <Select showSearch v-model:value="selectedValue" placeholder="请选择计划" allowClear filterable @change="handleChange">
<a-option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name"> <Option v-for="item in listData" :key="item.id" :value="item.id" :label="item.name">
{{ item.name }} {{ item.name }}
</a-option> </Option>
</a-select> </Select>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted } from 'vue'; import { Select } from 'ant-design-vue';
const { Option } = Select;
import { ref, computed, onMounted, type PropType } from 'vue';
import { getplacementAccountProjectsLlist } from '@/api/all/propertyMarketing'; import { getplacementAccountProjectsLlist } from '@/api/all/propertyMarketing';
interface Account { interface Account {
@ -17,7 +19,7 @@ interface Account {
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: Array, type: Array as PropType<number[]>,
default: () => [], default: () => [],
}, },
}); });
@ -25,7 +27,7 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue', 'change']); const emit = defineEmits(['update:modelValue', 'change']);
// 响应式数据 // 响应式数据
const selectedValue = ref(props.modelValue); const selectedValue = ref<number | undefined>(props.modelValue?.[0]);
const allAccounts = ref<Account[]>([]); const allAccounts = ref<Account[]>([]);
const listData = ref<Account[]>([]); const listData = ref<Account[]>([]);
const loading = ref(false); const loading = ref(false);

View File

@ -1,13 +1,13 @@
<template> <template>
<view> <view>
<topHeader ref="topHeaderRef" @search="search"></topHeader> <topHeader ref="topHeaderRef" @search="search"></topHeader>
<a-space direction="vertical" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"> <div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">行业词云</span> <span class="title mr-4px">行业词云</span>
<a-tooltip> <Tooltip>
<template #content>基于行业内内容提取的高频词汇</template> <template #title>基于行业内内容提取的高频词汇</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<div class="multi-row-tag-cloud h-472px"> <div class="multi-row-tag-cloud h-472px">
@ -20,7 +20,7 @@
class="tag-row" class="tag-row"
:style="{ justifyContent: row.align || 'center' }" :style="{ justifyContent: row.align || 'center' }"
> >
<a-tag <Tag
v-for="(tag, tagIndex) in row.tags" v-for="(tag, tagIndex) in row.tags"
:key="tagIndex" :key="tagIndex"
class="cursor-pointer" class="cursor-pointer"
@ -39,16 +39,14 @@
@mouseenter="hoverTag = tag" @mouseenter="hoverTag = tag"
@mouseleave="hoverTag = null" @mouseleave="hoverTag = null"
> >
<a-space> <Tooltip :title="`性价比:${Number(tag.rate * 100)}%`" placement="topLeft">
<a-tooltip :content="`性价比:${Number(tag.rate * 100)}%`" position="tl"> <span>{{ tag.term }}</span>
<a-space>{{ tag.term }}</a-space> </Tooltip>
</a-tooltip> </Tag>
</a-space>
</a-tag>
</div> </div>
</template> </template>
</div> </div>
</a-space> </div>
</view> </view>
</template> </template>
@ -56,6 +54,7 @@
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { fetchindustryTerms } from '@/api/all/index'; import { fetchindustryTerms } from '@/api/all/index';
import { Tooltip, Tag } from 'ant-design-vue';
const topHeaderRef = ref(); const topHeaderRef = ref();
// 从topHeader获取统一的状态 // 从topHeader获取统一的状态
@ -170,7 +169,7 @@ const processTagData = (apiData) => {
color: #6d4cfe !important; color: #6d4cfe !important;
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
:deep(.arco-modal-body) { :deep(.ant-modal-body) {
padding: 0px; padding: 0px;
} }
@ -199,7 +198,7 @@ const processTagData = (apiData) => {
} }
/* 悬停放大效果 */ /* 悬停放大效果 */
a-tag:hover { .ant-tag:hover {
transform: scale(1.1); transform: scale(1.1);
z-index: 1; z-index: 1;
} }

View File

@ -2,63 +2,44 @@
<view> <view>
<topHeader ref="topHeaderRef" @search="search"></topHeader> <topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- tabel --> <!-- tabel -->
<a-space <div class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px">
direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
>
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">行业热门话题洞察</span> <span class="title mr-4px">行业热门话题洞察</span>
<a-tooltip> <Tooltip>
<template #content>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template> <template #title>基于社交内容平台的行业数据分析用户关注的热门话题与趋势</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table <Table
:columns="columns" :dataSource="dataList"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <Table.Column
<NoData /> v-for="column in columns"
</template> :key="column.dataIndex"
<template #hotTitle> :title="column.title"
<a-space> :dataIndex="column.dataIndex"
<span>热度指数</span> :width="column.width"
<a-tooltip> :minWidth="column.minWidth"
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template> :sortable="column.sortable"
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #sentimentTitle>
<a-space>
<span>情感倾向</span>
<a-tooltip>
<template #content
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
> >
<icon-question-circle size="14" class="!color-#737478" /> <template v-if="column.slotName === 'rank'" #customRender="{ record }">
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" /> <img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span> <span v-else>{{ record.rank }}</span>
</template> </template>
<template #keywords="{ record }"> <template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<a-tag <Tag
v-for="item in record.keywords" v-for="item in record.keywords"
:key="item" :key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px" class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag >{{ item }}</Tag
> >
</template> </template>
<template #hot="{ record }"> <template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img <img
v-for="i in record.hot" v-for="i in record.hot"
:key="i" :key="i"
@ -67,23 +48,53 @@
class="mr-2px" class="mr-2px"
/> />
</template> </template>
<template #sentiment="{ record }"> <template v-else-if="column.slotName === 'sentiment'" #customRender="{ record }">
<img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" /> <img v-if="record.felling == '2'" src="@/assets/img/hottranslation/good.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" /> <img v-else-if="record.felling == '1'" src="@/assets/img/hottranslation/normal.png" class="w-24px h-24px" />
<img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" /> <img v-else-if="record.felling == '0'" src="@/assets/img/hottranslation/poor.png" class="w-24px h-24px" />
</template> </template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<template #optional="{ record }"> <Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template> </template>
</a-table> <template v-else-if="column.titleSlotName === 'hotTitle'" #title>
</a-space> <div class="flex items-center">
<a-modal :visible="visible" unmountOnClose modal-class="hot-translation-modal" width="640px" @cancel="handleCancel"> <span class="mr-8px">热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
<template v-else-if="column.titleSlotName === 'sentimentTitle'" #title>
<div class="flex items-center">
<span class="mr-8px">情感倾向</span>
<Tooltip>
<template #title
>统计该行业下全部内容的情绪分布选取占比最高的情绪类型作为该话题的整体情感倾向</template
>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</div>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
</Table>
</div>
<Modal
v-model:open="visible"
unmountOnClose
centered
wrapClassName="hot-translation-modal"
width="640px"
@cancel="handleCancel"
>
<template #title> <template #title>
<span style="text-align: left; width: 100%">行业热门话题洞察</span> <span style="text-align: left; width: 100%">行业热门话题洞察</span>
</template> </template>
<div> <div>
<a-space direction="vertical"> <Space direction="vertical">
<div class="mb-4px flex items-center"> <div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">话题名称</p> <p class="cts !mr-16px flex-shrink-0 w-48px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span> <span class="cts">{{ topicInfo.name }}</span>
@ -94,11 +105,11 @@
</div> </div>
<div class="mb-4px flex items-center"> <div class="mb-4px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-48px">关键词</p> <p class="cts !mr-16px flex-shrink-0 w-48px">关键词</p>
<a-tag <Tag
v-for="item in topicInfo.keywords" v-for="item in topicInfo.keywords"
:key="item" :key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px" class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag >{{ item }}</Tag
> >
</div> </div>
<div class="mb-4px flex items-center"> <div class="mb-4px flex items-center">
@ -123,25 +134,27 @@
<p class="!mr-16px w-48px cts relative top-2px">原始来源</p> <p class="!mr-16px w-48px cts relative top-2px">原始来源</p>
<div class="flex flex-col"> <div class="flex flex-col">
<div v-for="item in topicInfo.industry_topic_sources" :key="item" class="mb-18px flex items-center"> <div v-for="item in topicInfo.industry_topic_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{ <Link :href="item.link" target="_blank" class="!text-12px">{{
item.title item.title
}}</a-link> }}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" /> <img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div> </div>
</div> </div>
</div> </div>
</a-space> </Space>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="handleCancel">取消</a-button> <Button size="large" @click="handleCancel">取消</Button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button> <Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template> </template>
</a-modal> </Modal>
</view> </view>
</template> </template>
<script setup> <script setup>
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { fetchIndustriesTree, fetchIndustryTopics, fetchIndustryTopicDetail } from '@/api/all/index'; import { fetchIndustriesTree, fetchIndustryTopics, fetchIndustryTopicDetail } from '@/api/all/index';
import star1 from '@/assets/img/hottranslation/star-fill1.png'; import star1 from '@/assets/img/hottranslation/star-fill1.png';
@ -251,7 +264,7 @@ onMounted(() => {
const getIndustriesTree = async () => { const getIndustriesTree = async () => {
const res = await fetchIndustriesTree(); const res = await fetchIndustriesTree();
industriesTree.value = res; industriesTree.value = res;
selectedIndustry.value = res[0].id; selectedIndustry.value = res[0]?.id;
getIndustryTopics(); getIndustryTopics();
}; };
@ -320,7 +333,7 @@ const handleOk = () => {
color: #6d4cfe !important; color: #6d4cfe !important;
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
:deep(.arco-modal-body) { :deep(.ant-modal-body) {
padding: 0px; padding: 0px;
} }
@ -374,7 +387,7 @@ const handleOk = () => {
} }
} }
.arco-modal-body { .ant-modal-body {
padding: 12px 20px 0; padding: 12px 20px 0;
.cts { .cts {
color: var(--Text-2, #3c4043); color: var(--Text-2, #3c4043);

View File

@ -3,102 +3,122 @@
<view> <view>
<topHeader ref="topHeaderRef" @click="search"></topHeader> <topHeader ref="topHeaderRef" @click="search"></topHeader>
<!-- 重点品牌列表 --> <!-- 重点品牌列表 -->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">重点品牌列表</span> <span class="title mr-4px">重点品牌列表</span>
<a-tooltip> <Tooltip>
<template #content <template #title
>基于该行业中近期提及频次高用户互动活跃的品牌内容筛选出关注度较高的代表性品牌</template >基于该行业中近期提及频次高用户互动活跃的品牌内容筛选出关注度较高的代表性品牌</template
> >
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table <Table
:columns="columns" :dataSource="dataList"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <Table.Column
<NoData /> v-for="column in columns"
</template> :key="column.dataIndex"
<template #hotTitle> :title="column.title"
<a-space> :dataIndex="column.dataIndex"
<span>热度指数</span> :width="column.width"
<a-tooltip> :minWidth="column.minWidth"
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template> :sortable="column.sortable"
<icon-question-circle size="14" class="!color-#737478" /> >
</a-tooltip> <template v-if="column.slotName === 'rank'" #customRender="{ record }">
</a-space>
</template>
<template #trendTitle>
<a-space>
<span>变化幅度</span>
<a-tooltip>
<template #content>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #volume_rateTitle>
<a-space>
<span>占总声量比例</span>
<a-tooltip>
<template #content>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</a-tooltip>
</a-space>
</template>
<template #rank="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" /> <img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span> <span v-else>{{ record.rank }}</span>
</template> </template>
<template #hot="{ record }"> <template v-else-if="column.slotName === 'hot'" #customRender="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" /> <img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template> </template>
<template #trend="{ record }"> <template v-else-if="column.slotName === 'trend'" #customRender="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'"> <div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
<icon-arrow-up v-if="record.trend > 0" size="16" /> <icon-arrow-up v-if="record.trend > 0" size="16" />
<icon-arrow-down v-else size="16" /> <icon-arrow-down v-else size="16" />
{{ `${(record.trend * 100).toFixed(2)}%` }} {{ `${(record.trend * 100).toFixed(2)}%` }}
</div> </div>
</template> </template>
<template #volumeRate="{ record }"> <a-statistic :value="record.volume_rate * 100" />% </template> <template v-else-if="column.slotName === 'volumeRate'" #customRender="{ record }">
</a-table> <Statistic :value="record.volume_rate * 100" />%
</a-space> </template>
<template v-else-if="column.titleSlotName === 'hotTitle'" #title>
<Space>
<span>热度指数</span>
<Tooltip>
<template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'trendTitle'" #title>
<Space>
<span>变化幅度</span>
<Tooltip>
<template #title>仅基于品牌出现频次</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
<template v-else-if="column.titleSlotName === 'volume_rateTitle'" #title>
<Space>
<span>占总声量比例</span>
<Tooltip>
<template #title>该品牌在当前周期内被提及的内容量占整个行业内容总量的比例</template>
<icon-question-circle size="14" class="!color-#737478" />
</Tooltip>
</Space>
</template>
</Table.Column>
<template #emptyText>
<NoData />
</template>
</Table>
</Space>
<!-- 舆情 & 敏感动态--> <!-- 舆情 & 敏感动态-->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">舆情 & 敏感动态</span> <span class="title mr-4px">舆情 & 敏感动态</span>
<a-tooltip> <Tooltip>
<template #content <template #title
>基于情绪分析与敏感词识别对行业内容中的负面或争议性话题进行监测辅助判断舆情风险动态</template >基于情绪分析与敏感词识别对行业内容中的负面或争议性话题进行监测辅助判断舆情风险动态</template
> >
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table :data="otherList" :columns="columns2" :pagination="false" :scroll="true" style="font-size: 12px"> <Table :dataSource="otherList" :pagination="false" :showSorterTooltip="false" style="font-size: 12px">
<template #empty> <Table.Column
v-for="column in columns2"
:key="column.dataIndex"
:title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
/>
<template #emptyText>
<NoData /> <NoData />
</template> </template>
</a-table> </Table>
</a-space> </Space>
</view> </view>
</template> </template>
<script setup> <script setup>
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { Tooltip, Space, Table, Statistic } from 'ant-design-vue';
import { fetchFocusBrandsList, fetchEventDynamicsList } from '@/api/all/index'; import { fetchFocusBrandsList, fetchEventDynamicsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import star1 from '@/assets/img/hottranslation/star-fill1.png'; import star1 from '@/assets/img/hottranslation/star-fill1.png';

View File

@ -3,46 +3,46 @@
<view> <view>
<topHeader ref="topHeaderRef" @search="search"></topHeader> <topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 关键词热度榜 --> <!-- 关键词热度榜 -->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-20px" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-20px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">关键词热度榜</span> <span class="title mr-4px">关键词热度榜</span>
<a-tooltip> <Tooltip>
<template #content>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template> <template #title>基于该行业用户内容中提及频率较高的关键词按热度进行排序反映近期关注焦点</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table <Table
:columns="columns" :columns="columns"
:data="dataList" :dataSource="dataList"
:filter-icon-align-left="alignLeft" :scroll="{ x: true }"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <template #emptyText>
<NoData /> <NoData />
</template> </template>
<template #heatLevel> <template #heatLevel>
<a-space> <Space>
<span>热度指数</span> <span>热度指数</span>
<a-tooltip> <Tooltip>
<template #content>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template> <template #title>综合话题出现频次互动数据如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="14" class="!color-#737478" /> <icon-question-circle size="14" class="!color-#737478" />
</a-tooltip> </Tooltip>
</a-space> </Space>
</template> </template>
<template #trendTitle> <template #trendTitle>
<a-space> <Space>
<span>变化幅度</span> <span>变化幅度</span>
<a-tooltip> <Tooltip>
<template #content>仅基于关键词出现频次</template> <template #title>仅基于关键词出现频次</template>
<icon-question-circle size="14" class="!color-#737478" /> <icon-question-circle size="14" class="!color-#737478" />
</a-tooltip> </Tooltip>
</a-space> </Space>
</template> </template>
<template #rank="{ record }"> <template #rank="{ record }">
@ -52,7 +52,7 @@
<span v-else>{{ record.rank }}</span> <span v-else>{{ record.rank }}</span>
</template> </template>
<template #keywords="{ record }"> <template #keywords="{ record }">
<a-tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</a-tag> <Tag v-for="item in record.keywords" :key="item" style="margin-right: 5px">{{ item }}</Tag>
</template> </template>
<template #hot="{ record }"> <template #hot="{ record }">
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" /> <img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
@ -69,21 +69,21 @@
{{ `${(record.trend * 100).toFixed(2)}%` }} {{ `${(record.trend * 100).toFixed(2)}%` }}
</div> </div>
</template> </template>
</a-table> </Table>
</a-space> </Space>
<!-- 行业情绪 --> <!-- 行业情绪 -->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px" class="bg-#fff rounded-8px w-100% py-0 px-20px mb-24px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">行业情绪</span> <span class="title mr-4px">行业情绪</span>
<a-tooltip> <Tooltip>
<template #content <template #title
>对该行业下用户内容进行情绪分析按情绪类别统计占比提取占比最高者作为行业情绪代表</template >对该行业下用户内容进行情绪分析按情绪类别统计占比提取占比最高者作为行业情绪代表</template
> >
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<div class="flex items-center w-100%"> <div class="flex items-center w-100%">
@ -103,17 +103,16 @@
</div> </div>
</div> </div>
</div> </div>
<a-table <Table
class="flex-1" class="flex-1"
:columns="columns2" :columns="columns2"
:data="sortedRowData" :dataSource="sortedRowData"
:span-method="spanMethod" :scroll="{ x: true }"
:filter-icon-align-left="alignLeft"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <template #emptyText>
<NoData /> <NoData />
</template> </template>
<template #felling="{ record }"> <template #felling="{ record }">
@ -122,33 +121,33 @@
<span>{{ fellingStatus[record.felling].label }}</span> <span>{{ fellingStatus[record.felling].label }}</span>
</div> </div>
</template> </template>
</a-table> </Table>
</div> </div>
</a-space> </Space>
<!-- 新兴关键词 --> <!-- 新兴关键词 -->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px w-100% py-0 px-20px" class="bg-#fff rounded-8px w-100% py-0 px-20px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">新兴关键词</span> <span class="title mr-4px">新兴关键词</span>
<a-tooltip> <Tooltip>
<template #content <template #title
>指当前周期中首次出现或相较上一周期词频显著增长的关键词反映近期出现的新关注点</template >指当前周期中首次出现或相较上一周期词频显著增长的关键词反映近期出现的新关注点</template
> >
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table <Table
:columns="columns3" :columns="columns3"
:data="keywordList" :dataSource="keywordList"
:filter-icon-align-left="alignLeft" :scroll="{ x: true }"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <template #emptyText>
<NoData /> <NoData />
</template> </template>
<template #rank="{ record }"> <template #rank="{ record }">
@ -181,23 +180,23 @@
<img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" /> <img v-for="i in record.hot" :key="i" :src="starImages[i - 1]" style="width: 16px; height: 16px" />
</template> </template>
<template #hotTitle="{ record }"> <template #hotTitle>
<a-space> <Space>
<span>当前热度指数</span> <span>当前热度指数</span>
<a-tooltip> <Tooltip>
<template #content>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template> <template #title>综合关键词出现频次互动表现如点赞收藏评论加权计算的热度得分</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</a-space> </Space>
</template> </template>
<template #trendTitle="{ record }"> <template #trendTitle>
<a-space> <Space>
<span>变化幅度</span> <span>变化幅度</span>
<a-tooltip> <Tooltip>
<template #content>仅基于关键词出现频次</template> <template #title>仅基于关键词出现频次</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</a-space> </Space>
</template> </template>
<template #tred="{ record }"> <template #tred="{ record }">
<div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'"> <div class="flex items-center" :class="record.trend > 0 ? 'color-#F64B31' : 'color-#25C883'">
@ -207,15 +206,16 @@
</div> </div>
</template> </template>
<template #optional="{ record }"> <template #optional="{ record }">
<a-button type="outline" @click="gotoDetail(record)">详情</a-button> <Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
</template> </template>
</a-table> </Table>
</a-space> </Space>
<!-- modal --> <!-- modal -->
<a-modal <Modal
:visible="visible" v-model:open="visible"
modal-class="keyword-modal" wrapClassName="keyword-modal"
unmountOnClose unmountOnClose
centered
width="640px" width="640px"
@ok="handleOk" @ok="handleOk"
@cancel="handleCancel" @cancel="handleCancel"
@ -224,7 +224,7 @@
<span style="text-align: left; width: 100%">新兴关键词</span> <span style="text-align: left; width: 100%">新兴关键词</span>
</template> </template>
<div> <div>
<a-space direction="vertical"> <Space direction="vertical">
<div class="mb-12px flex items-center"> <div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-83px">话题名称</p> <p class="cts !mr-16px flex-shrink-0 w-83px">话题名称</p>
<span class="cts">{{ topicInfo.name }}</span> <span class="cts">{{ topicInfo.name }}</span>
@ -250,25 +250,27 @@
<p class="!mr-16px w-83px cts relative top-2px">原始来源</p> <p class="!mr-16px w-83px cts relative top-2px">原始来源</p>
<div class="flex flex-col"> <div class="flex flex-col">
<div v-for="item in topicInfo.industry_new_keyword_sources" :key="item" class="mb-18px flex items-center"> <div v-for="item in topicInfo.industry_new_keyword_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{ <Link :href="item.link" target="_blank" class="!text-12px">{{
item.title item.title
}}</a-link> }}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" /> <img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div> </div>
</div> </div>
</div> </div>
</a-space> </Space>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="handleCancel">取消</a-button> <Button size="large" @click="handleCancel">取消</Button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button> <Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template> </template>
</a-modal> </Modal>
</view> </view>
</template> </template>
<script setup> <script setup>
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { import {
fetchKeywordTrendsList, fetchKeywordTrendsList,
fetchIndustryEmotions, fetchIndustryEmotions,
@ -690,7 +692,7 @@ onMounted(() => {
} }
} }
.arco-modal-body { .ant-modal-body {
padding: 12px 20px 0; padding: 12px 20px 0;
.cts { .cts {
color: var(--Text-2, #3c4043); color: var(--Text-2, #3c4043);

View File

@ -1,97 +1,100 @@
<template> <template>
<view> <view>
<!-- 头部 --> <!-- 头部 -->
<a-space <Space
direction="vertical" direction="vertical"
class="bg-#fff rounded-8px mb-20px" class="bg-#fff rounded-8px mb-20px"
style="background-color: #fff; width: 100%; padding: 24px; color: #737478; font-size: 14px" style="background-color: #fff; width: 100%; padding: 24px; color: #737478; font-size: 14px"
> >
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px"> <Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">行业大类</span> <span style="flex-shrink: 0; line-height: 28px" class="mr-32px">行业大类</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start"> <div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag <Tag
v-for="item in industriesTree" v-for="item in industriesTree"
:key="item.id" :key="item.id"
size="Medium" size="Medium"
:checkable="true" :checkable="true"
:checked="selectedIndustry == item.id" :checked="selectedIndustry == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px" style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style=" :style="
selectedIndustry == item.id selectedIndustry == item.id
? 'color: #6D4CFE; background-color: #F0EDFF' ? 'color: #6D4CFE; background-color: #F0EDFF'
: 'color: #3C4043; background-color: #F7F8FA' : 'color: #3C4043; background-color: #F7F8FA'
" "
@check="handleIndustryCheck(item.id)" @click="handleIndustryCheck(item.id)"
>{{ item.name }}</a-tag >{{ item.name }}</Tag
> >
</div> </div>
</a-space> </Space>
<!-- 二级类目 --> <!-- 二级类目 -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px"> <Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">二级类目</span> <span style="flex-shrink: 0; line-height: 28px" class="mr-32px">二级类目</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start"> <div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag <Tag
v-for="item in subCategories" v-for="item in subCategories"
:key="item.id" :key="item.id"
size="Medium" size="small"
:checkable="true" :checkable="true"
:checked="selectedSubCategory == item.id" :checked="selectedSubCategory == item.id"
style="padding: 10px 16px; border-radius: 30px; height: 28px" style="padding: 0 16px; border-radius: 30px; height: 28px"
class="lh-28px cursor-pointer"
:style=" :style="
selectedSubCategory == item.id selectedSubCategory == item.id
? 'color: #6d4cfe; background-color: #f0edff' ? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA' : 'color: #3C4043; background-color: #F7F8FA'
" "
@check="handleSubCategoryCheck(item.id)" @click="handleSubCategoryCheck(item.id)"
>{{ item.name }}</a-tag >{{ item.name }}</Tag
> >
</div> </div>
</a-space> </Space>
<!-- </a-space> --> <!-- </Space> -->
<a-space align="start" style="width: 100%; align-items: flex-start" class="mb-12px"> <Space align="start" style="width: 100%; align-items: flex-start" class="mb-12px">
<span style="flex-shrink: 0; line-height: 28px" class="mr-32px">时间筛选</span> <span style="flex-shrink: 0; line-height: 28px" class="mr-32px">时间筛选</span>
<div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start"> <div style="display: flex; flex-wrap: wrap; gap: 16px; width: 100%; align-items: flex-start">
<a-tag <Tag
v-for="item in timePeriods" v-for="item in timePeriods"
:key="item.value" :key="item.value"
size="Medium" size="Medium"
:checkable="true" :checkable="true"
:checked="selectedTimePeriod == item.value" :checked="selectedTimePeriod == item.value"
style="padding: 10px 16px; border-radius: 30px; height: 28px" class="lh-28px cursor-pointer"
style="padding: 0 16px; border-radius: 30px; height: 28px"
:style=" :style="
selectedTimePeriod == item.value selectedTimePeriod == item.value
? 'color: #6d4cfe; background-color: #f0edff' ? 'color: #6d4cfe; background-color: #f0edff'
: 'color: #3C4043; background-color: #F7F8FA' : 'color: #3C4043; background-color: #F7F8FA'
" "
@check="handleTimePeriodCheck(item.value)" @click="handleTimePeriodCheck(item.value)"
>{{ item.label }} >{{ item.label }}
</a-tag> </Tag>
</div> </div>
</a-space> </Space>
<!-- 搜索区域 --> <!-- 搜索区域 -->
<a-space style="margin-left: 'auto'"> <Space style="margin-left: 'auto'">
<a-button type="primary" size="medium" @click="handleSearch"> <Button type="primary" @click="handleSearch">
<template #icon> <template #icon>
<icon-search /> <icon-search class="mr-8px"/>
</template> </template>
<!-- Use the default slot to avoid extra spaces --> <!-- Use the default slot to avoid extra spaces -->
<template #default>搜索</template> <template #default>搜索</template>
</a-button> </Button>
<a-button class="w-84px reset-btn" size="medium" @click="handleReset"> <Button class="w-84px reset-btn" @click="handleReset">
<template #icon> <template #icon>
<icon-refresh /> <icon-refresh class="mr-8px"/>
</template> </template>
<template #default>重置</template> <template #default>重置</template>
</a-button> </Button>
</a-space> </Space>
</a-space> </Space>
</view> </view>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { fetchIndustriesTree } from '@/api/all/index'; import { fetchIndustriesTree } from '@/api/all/index';
import { Button, Space, Tag } from 'ant-design-vue';
const emit = defineEmits<(e: 'search') => void>(); const emit = defineEmits<(e: 'search') => void>();
// 行业大类 // 行业大类
const industriesTree = ref([]); const industriesTree = ref([]);
@ -139,6 +142,7 @@ const handleIndustryCheck = (id) => {
}; };
const handleSubCategoryCheck = (id) => { const handleSubCategoryCheck = (id) => {
console.log('handleSubCategoryCheck');
selectedSubCategory.value = id; selectedSubCategory.value = id;
}; };
@ -185,7 +189,7 @@ const handleReset = () => {
color: #6d4cfe !important; color: #6d4cfe !important;
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
:deep(.arco-modal-body) { :deep(.ant-modal-body) {
padding: 0px; padding: 0px;
} }
.reset-btn { .reset-btn {

View File

@ -3,63 +3,71 @@
<topHeader ref="topHeaderRef" @search="search"></topHeader> <topHeader ref="topHeaderRef" @search="search"></topHeader>
<!-- 用户痛点观察 --> <!-- 用户痛点观察 -->
<a-space <Space
direction="vertical" direction="vertical"
style="background-color: #fff; width: 100%; padding: 0 20px" style="background-color: #fff; width: 100%; padding: 0 20px"
class="bg-#fff rounded-8px mb-24px" class="bg-#fff rounded-8px mb-24px"
> >
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">用户痛点观察</span> <span class="title mr-4px">用户痛点观察</span>
<a-tooltip> <Tooltip>
<template #content <template #title
>基于用户内容中的情绪分析与表达模式提取反复出现的负面倾向主题反映典型使用痛点</template >基于用户内容中的情绪分析与表达模式提取反复出现的负面倾向主题反映典型使用痛点</template
> >
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-table <Table
:columns="columns" :dataSource="dataList"
:data="dataList"
:filter-icon-align-left="alignLeft"
:scroll="true"
:pagination="false" :pagination="false"
:showSorterTooltip="false"
@change="handleChange" @change="handleChange"
> >
<template #empty> <Table.Column
<NoData /> v-for="column in columns"
</template> :key="column.dataIndex"
<template #rank="{ record }"> :title="column.title"
:dataIndex="column.dataIndex"
:width="column.width"
:minWidth="column.minWidth"
:sortable="column.sortable"
>
<template v-if="column.slotName === 'rank'" #customRender="{ record }">
<img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" /> <img v-if="record.rank == 1" :src="topImages[0]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 2" :src="topImages[1]" style="width: 25px; height: 17px" />
<img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" /> <img v-else-if="record.rank == 3" :src="topImages[2]" style="width: 25px; height: 17px" />
<span v-else>{{ record.rank }}</span> <span v-else>{{ record.rank }}</span>
</template> </template>
<template #keywords="{ record }"> <template v-else-if="column.slotName === 'keywords'" #customRender="{ record }">
<a-tag <Tag
v-for="item in record.keywords" v-for="item in record.keywords"
:key="item" :key="item"
class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px" class="!rounded-2px !px-8px !py-1px !bg-#F2F3F5 !h-22px !color-#3C4043 mb-5px mr-5px"
>{{ item }}</a-tag >{{ item }}</Tag
> >
</template> </template>
<template #frequency="{ record }"> <template v-else-if="column.slotName === 'frequency'" #customRender="{ record }">
<a-tag <Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${ :class="`!rounded-2px !px-8px !py-1px !bg-${frequencyStatus[record.frequency].bgColor} !h-22px !color-${
frequencyStatus[record.frequency].color frequencyStatus[record.frequency].color
}`" }`"
>{{ frequencyStatus[record.frequency].label }}</a-tag >{{ frequencyStatus[record.frequency].label }}</Tag
> >
</template> </template>
<template v-else-if="column.slotName === 'optional'" #customRender="{ record }">
<template #optional="{ record }"> <Button type="primary" ghost @click="gotoDetail(record)">详情</Button>
<a-button type="outline" class="!rounded-4px" @click="gotoDetail(record)">详情</a-button>
</template> </template>
</a-table> </Table.Column>
</a-space> <template #emptyText>
<NoData />
</template>
</Table>
</Space>
<a-modal <Modal
:visible="visible" v-model:open="visible"
modal-class="user-pain-points-modal" wrapClassName="user-pain-points-modal"
centered
unmountOnClose unmountOnClose
width="640px" width="640px"
@ok="handleOk" @ok="handleOk"
@ -69,27 +77,27 @@
<span style="text-align: left; width: 100%">用户痛点观察</span> <span style="text-align: left; width: 100%">用户痛点观察</span>
</template> </template>
<div> <div>
<a-space direction="vertical" style="font-size: 12px"> <Space direction="vertical" style="font-size: 12px">
<div class="mb-12px flex items-center"> <div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">痛点</p> <p class="cts !mr-16px flex-shrink-0 w-60px">痛点</p>
<span class="cts">{{ topicInfo.name }}</span> <span class="cts">{{ topicInfo.name }}</span>
</div> </div>
<div class="mb-12px flex items-center"> <div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">关键词</p> <p class="cts !mr-16px flex-shrink-0 w-60px">关键词</p>
<a-tag <Tag
v-for="item in topicInfo.keywords" v-for="item in topicInfo.keywords"
:key="item" :key="item"
class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px" class="mr-8px py-10px px-8px rounded-4px bg-#F2F3F5 cts !h-24px"
>{{ item }}</a-tag >{{ item }}</Tag
> >
</div> </div>
<div class="mb-12px flex items-center"> <div class="mb-12px flex items-center">
<p class="cts !mr-16px flex-shrink-0 w-60px">频次</p> <p class="cts !mr-16px flex-shrink-0 w-60px">频次</p>
<a-tag <Tag
:class="`!rounded-2px !px-8px !py-1px !bg-${ :class="`!rounded-2px !px-8px !py-1px !bg-${
frequencyStatus[topicInfo.frequency].bgColor frequencyStatus[topicInfo.frequency].bgColor
} !h-22px !color-${frequencyStatus[topicInfo.frequency].color}`" } !h-22px !color-${frequencyStatus[topicInfo.frequency].color}`"
>{{ frequencyStatus[topicInfo.frequency].label }}</a-tag >{{ frequencyStatus[topicInfo.frequency].label }}</Tag
> >
</div> </div>
<div class="mb-12px flex items-center"> <div class="mb-12px flex items-center">
@ -100,25 +108,27 @@
<p class="cts !mr-16px flex-shrink-0 w-60px">原始来源</p> <p class="cts !mr-16px flex-shrink-0 w-60px">原始来源</p>
<div class="flex flex-col"> <div class="flex flex-col">
<div v-for="item in topicInfo.user_pain_point_sources" :key="item" class="mb-18px flex items-center"> <div v-for="item in topicInfo.user_pain_point_sources" :key="item" class="mb-18px flex items-center">
<a-link style="background-color: initial" :href="item.link" target="_blank" class="!text-12px">{{ <Link :href="item.link" target="_blank" class="!text-12px">{{
item.title item.title
}}</a-link> }}</Link>
<img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" /> <img src="@/assets/img/hottranslation/xhs.png" width="16" height="16" />
</div> </div>
</div> </div>
</div> </div>
</a-space> </Space>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="handleCancel">取消</a-button> <Button size="large" @click="handleCancel">取消</Button>
<a-button type="primary" size="large" class="rounded-4px" @click="handleOk"> 确定 </a-button> <Button type="primary" size="large" @click="handleOk"> 确定 </Button>
</template> </template>
</a-modal> </Modal>
</view> </view>
</template> </template>
<script setup> <script setup>
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { Modal, Button, Tooltip, Space, Table, Tag, Typography } from 'ant-design-vue';
const { Link } = Typography;
import { fetchUserPainPointsDetail, fetchUserPainPointsList } from '@/api/all/index'; import { fetchUserPainPointsDetail, fetchUserPainPointsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import top1 from '@/assets/img/captcha/top1.svg'; import top1 from '@/assets/img/captcha/top1.svg';
@ -286,16 +296,16 @@ const search = () => {
</style> </style>
<style lang="scss"> <style lang="scss">
.user-pain-points-modal { .user-pain-points-modal {
.arco-modal-header { .ant-modal-header {
border-bottom: none; border-bottom: none;
height: 56px; height: 56px;
padding: 0 20px; padding: 0 20px;
.arco-modal-title { .ant-modal-title {
justify-content: flex-start; justify-content: flex-start;
} }
} }
.arco-modal-body { .ant-modal-body {
padding: 12px 20px 0; padding: 12px 20px 0;
.cts { .cts {
color: var(--Text-2, #3c4043); color: var(--Text-2, #3c4043);
@ -311,7 +321,7 @@ const search = () => {
} }
} }
.arco-modal-footer { .ant-modal-footer {
display: flex; display: flex;
height: 64px; height: 64px;
padding: 0px 20px; padding: 0px 20px;

View File

@ -6,52 +6,52 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px w-600px mr-24px"> <div class="bg-#fff rounded-8px w-100% py-0 px-20px w-600px mr-24px">
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">性别分布</span> <span class="title mr-4px">性别分布</span>
<a-tooltip> <Tooltip>
<template #content>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template> <template #title>基于社交内容平台中用户资料互动行为及语义特征进行智能识别与估算</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-space v-if="genderData.length > 0"> <Space v-if="genderData.length > 0">
<div id="container" class="w-300px h-300px"></div> <div id="container" class="w-300px h-300px"></div>
<a-space direction="vertical" style="font-size: 14px"> <Space direction="vertical" style="font-size: 14px">
<a-space> <Space>
<span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span> <span style="width: 8px; height: 8px; background-color: #f64b31; border-radius: 50%"></span>
<span>女性</span> <span>女性</span>
<span>{{ (girlData.rate * 100).toFixed(2) }}%</span> <span>{{ (girlData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span> <span>TGI</span>
<span>{{ girlData.tgi }}</span> <span>{{ girlData.tgi }}</span>
</a-space> </Space>
<a-space> <Space>
<span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span> <span style="width: 8px; height: 8px; background-color: #2a59f3; border-radius: 50%"></span>
<span>男性</span> <span>男性</span>
<span>{{ (boyData.rate * 100).toFixed(2) }}%</span> <span>{{ (boyData.rate * 100).toFixed(2) }}%</span>
<span>TGI</span> <span>TGI</span>
<span>{{ boyData.tgi }}</span> <span>{{ boyData.tgi }}</span>
</a-space> </Space>
</a-space> </Space>
</a-space> </Space>
<div v-else> <div v-else>
<NoData class="w-100% h-100%" /> <NoData class="w-100% h-100%" />
</div> </div>
</div> </div>
<!-- 2. 年龄分布 --> <!-- 2. 年龄分布 -->
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 flex flex-col"> <div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 flex flex-col">
<a-space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px"> <Space style="display: flex; justify-content: space-between; width: 100%; font-size: 12px">
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">年龄分布</span> <span class="title mr-4px">年龄分布</span>
<a-tooltip> <Tooltip>
<template #content>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template> <template #title>基于社交平台的公开信息内容偏好与行为模式通过算法进行年龄段归类和统计</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<a-space v-if="ageValueData.length > 0" align="center"> <Space v-if="ageValueData.length > 0" align="center">
<span style="width: 16px; height: 8px; background-color: #6d4cfe; border-radius: 2px"></span> <span style="width: 16px; height: 8px; background-color: #6d4cfe; border-radius: 2px"></span>
<span style="color: #6d4cfe">占比</span> <span style="color: #6d4cfe">占比</span>
<span style="width: 16px; height: 8px; background-color: #f64b31; border-radius: 2px"></span> <span style="width: 16px; height: 8px; background-color: #f64b31; border-radius: 2px"></span>
<span style="color: #f64b31">TGI比</span> <span style="color: #f64b31">TGI比</span>
</a-space> </Space>
</a-space> </Space>
<div v-if="ageValueData.length === 0" class="w-100% flex-1"> <div v-if="ageValueData.length === 0" class="w-100% flex-1">
<NoData /> <NoData />
</div> </div>
@ -61,17 +61,17 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 pb-20px"> <div class="bg-#fff rounded-8px w-100% py-0 px-20px flex-1 pb-20px">
<div class="title-row"> <div class="title-row">
<span class="title mr-4px">地域分布</span> <span class="title mr-4px">地域分布</span>
<a-tooltip> <Tooltip>
<template #content>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template> <template #title>基于社交平台的IP归属地位置标签内容发布地等数据推测用户活跃区域</template>
<icon-question-circle size="16" class="!color-#737478" /> <icon-question-circle size="16" class="!color-#737478" />
</a-tooltip> </Tooltip>
</div> </div>
<div class="flex"> <div class="flex">
<a-space direction="vertical"> <Space direction="vertical">
<div id="chinaMap" style="height: 416px; width: 640px"></div> <div id="chinaMap" style="height: 416px; width: 640px"></div>
<a-space direction="vertical" style="font-size: 14px"> <Space direction="vertical" style="font-size: 14px">
<span class="cts">搜索指数</span> <span class="cts">搜索指数</span>
<a-space> <Space>
<span class="cts"></span> <span class="cts"></span>
<span <span
v-for="item in 5" v-for="item in 5"
@ -86,44 +86,38 @@
}" }"
></span> ></span>
<span class="cts"></span> <span class="cts"></span>
</a-space> </Space>
</a-space> </Space>
</a-space> </Space>
<div class="flex flex-col h-486px"> <div class="flex flex-col h-486px">
<a-tabs default-active-key="1" class="h-100%" @change="tabChange"> <Tabs defaultActiveKey="1" class="h-100%" @change="tabChange" size="large">
<a-tab-pane key="1" title="省份"> <TabPane key="1" tab="省份">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }"> <Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
<template #empty> <template #emptyText>
<NoData /> <NoData />
</template> </template>
<template #columns> <Column title="排名" dataIndex="rank" />
<a-table-column title="排名" data-index="rank" /> <Column title="省份" dataIndex="geo" />
<a-table-column title="省份" data-index="geo" /> <Column title="分布占比" dataIndex="rate" />
<a-table-column title="分布占比" data-index="rate" /> <Column title="TGI指数" dataIndex="tgi" />
</Table>
<a-table-column title="TGI指数" data-index="tgi" /> </TabPane>
</template> <TabPane key="2" tab="城市">
</a-table> <Table :dataSource="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }" :showSorterTooltip="false">
</a-tab-pane> <template #emptyText>
<a-tab-pane key="2" title="城市">
<a-table :data="geoList" :pagination="false" class="h-100%" :scroll="{ y: '100%' }">
<template #empty>
<NoData /> <NoData />
</template> </template>
<template #columns> <Column title="排名" dataIndex="rank" />
<a-table-column title="排名" data-index="rank" /> <Column title="城市" dataIndex="geo" />
<a-table-column title="城市" data-index="geo" /> <Column title="分布占比" dataIndex="rate">
<a-table-column title="分布占比" data-index="rate"> <template #customRender="{ record }">
<template #cell="{ record }">
<span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span> <span class="cts">{{ (record.rate * 100).toFixed(2) }}%</span>
</template> </template>
</a-table-column> </Column>
<Column title="TGI指数" dataIndex="tgi" />
<a-table-column title="TGI指数" data-index="tgi" /> </Table>
</template> </TabPane>
</a-table> </Tabs>
</a-tab-pane>
</a-tabs>
</div> </div>
</div> </div>
</div> </div>
@ -133,11 +127,15 @@
<script setup> <script setup>
import topHeader from './topHeader.vue'; import topHeader from './topHeader.vue';
import { fetchAgeDistributionsList, fetchGeoDistributionsList, fetchGenderDistributionsList } from '@/api/all/index'; import { fetchAgeDistributionsList, fetchGeoDistributionsList, fetchGenderDistributionsList } from '@/api/all/index';
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed, watch, nextTick } from 'vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import chinaJson from '@/assets/maps/china.json'; import chinaJson from '@/assets/maps/china.json';
echarts.registerMap('china', chinaJson); echarts.registerMap('china', chinaJson);
import { Tabs, Tooltip, Space, Table } from 'ant-design-vue';
const { TabPane } = Tabs;
const { Column } = Table;
const scope = ref(1); // 地域范围1-省2-市 const scope = ref(1); // 地域范围1-省2-市
const chartInstance = (ref < echarts.ECharts) | (null > null); const chartInstance = (ref < echarts.ECharts) | (null > null);
const topHeaderRef = ref(); const topHeaderRef = ref();

View File

@ -9,7 +9,7 @@
<img src="@/assets/img/Frame.svg" class="w-480 h-480 mr-40" alt="" /> <img src="@/assets/img/Frame.svg" class="w-480 h-480 mr-40" alt="" />
</div> </div>
<div class="flex items-center w-400 h-100%"> <div class="flex items-center w-400 h-100%">
<a-space <Space
direction="vertical" direction="vertical"
size="large" size="large"
align="center" align="center"
@ -17,30 +17,27 @@
> >
<img src="@/assets/img/icon-logo.png" alt="" width="96" height="24" class="mb-8px" /> <img src="@/assets/img/icon-logo.png" alt="" width="96" height="24" class="mb-8px" />
<span class="text-4 color-#737478">AI营销工具</span> <span class="text-4 color-#737478">AI营销工具</span>
<a-form ref="formRef" :model="loginForm" :rules="formRules" auto-label-width class="w-320 mt-48px form-wrap"> <Form ref="formRef" :model="loginForm" :rules="formRules" auto-label-width class="w-320 mt-48px form-wrap">
<a-form-item field="mobile" hide-label> <FormItem name="mobile">
<a-input <Input
v-model="loginForm.mobile" v-model:value="loginForm.mobile"
placeholder="输入手机号" placeholder="输入手机号"
class="form-input border border-solid border-#d7d7d9 x w-100% h-48px text-14 rounded-4px color-#333 bg-#fff" class="form-input border border-solid !border-#d7d7d9 w-100% h-48px !text-14px rounded-4px color-#333 bg-#fff"
clearable clearable
allow-clear allowClear
@blur="validateField('mobile')"
@input="clearError('mobile')"
/> />
</a-form-item> </FormItem>
<a-form-item field="captcha" hide-label> <FormItem name="captcha">
<div <div
class="form-input border border-solid border-#d7d7d9 w-100% h-48px text-14 rounded-4px color-#333 bg-#fff flex justify-between items-center" class="form-input border border-solid !border-#d7d7d9 w-100% h-48px !text-14px rounded-4px color-#333 bg-#fff flex justify-between items-center"
> >
<a-input <Input
v-model="loginForm.captcha" v-model:value="loginForm.captcha"
placeholder="验证码" placeholder="验证码"
style="background-color: #fff; border: none" style="background-color: #fff; border: none !important;"
allow-clear allowClear
maxlength="6" class="form-input"
@blur="validateField('captcha')" :maxlength="6"
@input="clearError('captcha')"
/> />
<span <span
class="w-120 font-400 text-right mr-4 text-16px" class="w-120 font-400 text-right mr-4 text-16px"
@ -52,27 +49,27 @@
>{{ countdown > 0 ? `${countdown}s` : hasGetCode ? '重新发送' : '发送验证码' }}</span >{{ countdown > 0 ? `${countdown}s` : hasGetCode ? '重新发送' : '发送验证码' }}</span
> >
</div> </div>
</a-form-item> </FormItem>
<a-form-item hide-label class="mt-68px mb-16px"> <FormItem class="mt-68px mb-16px">
<a-button <Button
type="primary" type="primary"
class="w-480 h-48 !text-16px !rounded-8px" class="w-full h-48 !text-16px !rounded-8px"
:class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'" :class="disabledSubmitBtn ? 'cursor-no-drop' : 'cursor-pointer'"
:disabled="disabledSubmitBtn" :disabled="disabledSubmitBtn"
@click="handleSubmit" @click="handleSubmit"
> >
{{ isLogin ? '登录' : '注册并开通企业账号' }} {{ isLogin ? '登录' : '注册并开通企业账号' }}
</a-button> </Button>
</a-form-item> </FormItem>
</a-form> </Form>
<a-space class="text-12px color-#737478 justify-start items-center"> <Space class="text-12px color-#737478 justify-start items-center">
<a-checkbox v-model="hasCheck" class="!text-12px mr-8px"></a-checkbox> <Checkbox v-model:checked="hasCheck" class="!text-12px mr-8px"></Checkbox>
<span class="text-12px color-#737478">{{ isLogin ? '登录' : '注册' }}即代表同意</span> <span class="text-12px color-#737478">{{ isLogin ? '登录' : '注册' }}即代表同意</span>
<a-link href="link" class="form-link color-#211F24" target="_blank">用户协议</a-link> <Link href="link" class="form-link color-#211F24" target="_blank">用户协议</Link>
<span class="text-12px color-#737478"></span> <span class="text-12px color-#737478"></span>
<a-link href="link" class="form-link color-#211f24" target="_blank">隐私政策</a-link> <Link href="link" class="form-link color-#211f24" target="_blank">隐私政策</Link>
</a-space> </Space>
</a-space> </Space>
</div> </div>
</section> </section>
@ -85,18 +82,18 @@
@submit="handleVerificationSubmit" @submit="handleVerificationSubmit"
@cancel="isVerificationVisible = false" @cancel="isVerificationVisible = false"
/> />
<a-modal :visible="visible" unmountOnClose hide-cancel @ok="handleOk" @cancel="handleCancel"> <Modal v-model:open="visible" centered unmountOnClose @cancel="handleCancel">
<template #title> <template #title>
<span style="text-align: left; width: 100%">选择账号</span> <span style="text-align: left; width: 100%">选择账号</span>
</template> </template>
<div class="account-bind-container"> <div class="account-bind-container">
<a-card :bordered="false" class="bind-card"> <Card :bordered="false" class="bind-card">
<div class="bind-header"> <div class="bind-header">
<a-typography-text class="mobile-number">{{ mobileNumber }} 已在以下企业绑定了账号</a-typography-text> <Typography.Text class="mobile-number">{{ mobileNumber }} 已在以下企业绑定了账号</Typography.Text>
</div> </div>
<a-list :bordered="false" :split="false" class="account-list"> <List :bordered="false" :split="false" class="account-list">
<a-list-item <List.Item
v-for="(account, index) in accounts" v-for="(account, index) in accounts"
:key="index" :key="index"
class="account-item" class="account-item"
@ -107,22 +104,29 @@
}" }"
@click="selectAccount(account, index)" @click="selectAccount(account, index)"
> >
<a-list-item-meta> <List.Item.Meta>
<template #title> <template #title>
<div style="display: flex; align-items: center; gap: 12px"> <div style="display: flex; align-items: center; gap: 12px">
<a-checkbox :model-value="selectedAccountIndex === index" /> <Checkbox :checked="selectedAccountIndex === index" />
<a-typography-text>{{ account.name || '-' }}</a-typography-text> <Typography.Text>{{ account.name || '-' }}</Typography.Text>
</div> </div>
</template> </template>
</a-list-item-meta> </List.Item.Meta>
</a-list-item> </List.Item>
</a-list> </List>
</a-card> </Card>
</div> </div>
</a-modal> <template #footer>
<div class="flex">
<Button type="primary" @click="handleOk">确定</Button>
</div>
</template>
</Modal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Checkbox, Modal, Button, Form, FormItem, Input, Space, message, Typography, Card, List } from 'ant-design-vue';
const { Link } = Typography;
import PuzzleVerification from './components/PuzzleVerification.vue'; import PuzzleVerification from './components/PuzzleVerification.vue';
import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo } from '@/api/all/login'; import { fetchLoginCaptCha, fetchAuthorizationsCaptcha, fetchProfileInfo } from '@/api/all/login';
import { joinEnterpriseByInviteCode } from '@/api/all'; import { joinEnterpriseByInviteCode } from '@/api/all';
@ -159,15 +163,14 @@ const formRules = {
mobile: [ mobile: [
{ {
required: true, required: true,
message: '请填写手机号', validator: (_rule: any, value: string) => {
trigger: ['blur', 'change'], if (!value) {
}, return Promise.reject('请填写手机号');
{ }
validator: (value: string, callback: (error?: string) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) { if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确'); return Promise.reject('手机号格式不正确');
} else { } else {
callback(); return Promise.resolve();
} }
}, },
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
@ -176,15 +179,14 @@ const formRules = {
captcha: [ captcha: [
{ {
required: true, required: true,
message: '请填写验证码', validator: (_rule: any, value: string) => {
trigger: ['blur', 'change'], if (!value) {
}, return Promise.reject('请填写验证码');
{ }
validator: (value: string, callback: (error?: string) => void) => {
if (!/^\d{6}$/.test(value)) { if (!/^\d{6}$/.test(value)) {
callback('验证码必须是6位数字'); return Promise.reject('验证码必须是6位数字');
} else { } else {
callback(); return Promise.resolve();
} }
}, },
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
@ -216,7 +218,7 @@ const selectAccount = (account: any, index: any) => {
}; };
const validateField = (field: string) => { const validateField = (field: string) => {
formRef.value.validateField(field); formRef.value.validateFields(field);
}; };
const clearError = (field: string) => { const clearError = (field: string) => {
@ -239,11 +241,9 @@ const getCode = async () => {
// 先重置验证状态 // 先重置验证状态
formRef.value.clearValidate('mobile'); formRef.value.clearValidate('mobile');
const result = await formRef.value.validateField('mobile'); formRef.value.validateFields('mobile').then(() => {
// 只有当验证通过时才会显示滑块验证
if (result === true || result === undefined) {
isVerificationVisible.value = true; isVerificationVisible.value = true;
} });
}; };
// 验证码验证通过后 // 验证码验证通过后
@ -252,8 +252,10 @@ const handleVerificationSubmit = async () => {
startCountdown(); startCountdown();
try { try {
await fetchLoginCaptCha({ mobile: loginForm.mobile }); const { code, message: msg } = await fetchLoginCaptCha({ mobile: loginForm.mobile });
AMessage.success('验证码发送成功'); if (code === 200) {
message.success(msg);
}
} catch (error) { } catch (error) {
// 重置倒计时 // 重置倒计时
countdown.value = 0; countdown.value = 0;
@ -290,7 +292,7 @@ const handleSubmit = async () => {
await formRef.value.validate(); await formRef.value.validate();
if (!hasCheck.value) { if (!hasCheck.value) {
AMessage.error('请先勾选同意用户协议'); message.error('请先勾选同意用户协议');
return; return;
} }
@ -299,14 +301,14 @@ const handleSubmit = async () => {
if (code === 200) { if (code === 200) {
// 处理登录成功逻辑 // 处理登录成功逻辑
AMessage.success(isLogin.value ? '登录成功' : '注册成功'); message.success(isLogin.value ? '登录成功' : '注册成功');
userStore.setToken(data.access_token); userStore.setToken(data.access_token);
const { invite_code } = route.query; const { invite_code } = route.query;
if (invite_code) { if (invite_code) {
const { code } = await joinEnterpriseByInviteCode(invite_code as string); const { code } = await joinEnterpriseByInviteCode(invite_code as string);
if (code === 200) { if (code === 200) {
AMessage.success('加入企业成功'); message.success('加入企业成功');
} }
} }

View File

@ -1,17 +1,22 @@
.login-wrap { .login-wrap {
.arco-input-wrapper, // :deep(.ant-input),
.arco-select-view-single, // .arco-select-view-single,
.arco-textarea-wrapper, // .arco-textarea-wrapper,
.arco-picker, // .arco-picker,
.arco-select-view-multiple { // .arco-select-view-multiple {
border-radius: 4px; // border-color: #d7d7d9 !important;
border-color: #d7d7d9 !important; // background-color: #fff !important;
background-color: #fff !important; // &:focus-within,
&:focus-within, // &.arco-input-focus {
&.arco-input-focus { // background-color: var(--color-bg-2);
background-color: var(--color-bg-2); // // border-color: rgb(var(--primary-6));
// border-color: rgb(var(--primary-6)); // box-shadow: 0 0 0 0 var(--color-primary-light-2);
box-shadow: 0 0 0 0 var(--color-primary-light-2); // }
// }
:deep(.ant-form) {
color: red !important;
.ant-input {
height: 100%;
} }
} }
.login-bg { .login-bg {
@ -64,6 +69,7 @@
width: 100%; width: 100%;
flex-direction: column; flex-direction: column;
align-items: start; align-items: start;
box-shadow: none;
} }
.bind-header { .bind-header {
@ -106,6 +112,9 @@
background-color: rgba(109, 76, 254, 0.1); background-color: rgba(109, 76, 254, 0.1);
box-shadow: 0 2px 4px 0 rgba(109, 76, 254, 0.5); box-shadow: 0 2px 4px 0 rgba(109, 76, 254, 0.5);
} }
:deep(.ant-list-item-meta-title) {
margin: 0;
}
} }
.account-item:deep(.arco-list-item-main) { .account-item:deep(.arco-list-item-main) {

View File

@ -2,40 +2,44 @@
<div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px"> <div class="bg-#fff rounded-8px w-100% py-0 px-20px pb-24px">
<div class="title-row"> <div class="title-row">
<span class="title">账号管理</span> <span class="title">账号管理</span>
<a-button type="outline" class="add-account-button" @click="handleAddAccount">添加子账号</a-button> <Button type="primary" ghost class="add-account-button" @click="handleAddAccount">添加子账号</Button>
</div> </div>
<a-table <Table
:columns="columns" :dataSource="dataSource"
:data="dataSource"
:pagination="pagination" :pagination="pagination"
:showSorterTooltip="false"
class="mt-8px" class="mt-8px"
@page-change="handlePageChange" @change="handleTableChange"
@page-size-change="handlePageSizeChange"
> >
<template #empty> <Table.Column title="手机号" dataIndex="mobile">
<NoData /> <template #customRender="{ record }">
</template>
<template #mobile="{ record }">
<div class="flex item-center pt-13px pb-13px"> <div class="flex item-center pt-13px pb-13px">
<span class="mr-4px">{{ record.mobile }}</span> <span class="mr-4px">{{ record.mobile }}</span>
<a-tag v-if="record.type === 0" class="primary-account">主账号</a-tag> <Tag v-if="record.type === 0" class="primary-account">主账号</Tag>
<a-tag v-else class="sub-account">子账号</a-tag> <Tag v-else class="sub-account">子账号</Tag>
</div> </div>
</template> </template>
<template #action="{ record }"> </Table.Column>
<a-button <Table.Column title="操作" dataIndex="action" width="120">
<template #customRender="{ record }">
<Button
v-if="record.type !== 0" v-if="record.type !== 0"
class="delete-button" class="delete-button"
size="mini" size="small"
type="outline" type="primary"
status="danger" ghost
danger
@click="openDeleteModal(record)" @click="openDeleteModal(record)"
> >
删除 删除
</a-button> </Button>
</template> </template>
</a-table> </Table.Column>
<Modal v-model:visible="addAccountVisible" width="480px" title="添加子账号" :okText="okText" @ok="handleOk"> <template #emptyText>
<NoData />
</template>
</Table>
<Modal v-model:open="addAccountVisible" centered width="480px" title="添加子账号" :okText="okText" @ok="handleOk" >
<div v-if="canAddAccount" class="add-account-container"> <div v-if="canAddAccount" class="add-account-container">
<h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2> <h2 class="add-account-title">生成企业专属链接成员通过访问即可注册并加入企业账号</h2>
<p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p> <p class="add-account-subtitle">子账号可独立登录权限继承主账号配置</p>
@ -52,15 +56,15 @@
<p class="cannot-add-account-subtitle">如需添加更多子账号您可联系销售人员进行购买和权限扩展</p> <p class="cannot-add-account-subtitle">如需添加更多子账号您可联系销售人员进行购买和权限扩展</p>
</div> </div>
</Modal> </Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" /> <CustomerServiceModal v-model:open="customerServiceVisible" centered/>
<DeleteModal v-model:visible="deleteVisible" :title="deleteTitle" @ok="handleDelete"> <DeleteModal v-model:open="deleteVisible" centered :content="deleteTitle" @ok="handleDelete" @cancel="deleteVisible = false">
<p class="delete-modal-content">删除后该账号将无法登录您的企业</p>
</DeleteModal> </DeleteModal>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Container from '@/components/container.vue';
import { ref, onMounted, reactive, computed } from 'vue'; import { ref, onMounted, reactive, computed } from 'vue';
import { Button, Table, message, Tag } from 'ant-design-vue';
import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all'; import { fetchSubAccountPage, removeEnterpriseAccount, getEnterpriseInviteCode } from '@/api/all';
import Modal from '@/components/modal.vue'; import Modal from '@/components/modal.vue';
import DeleteModal from '@/components/delete-modal.vue'; import DeleteModal from '@/components/delete-modal.vue';
@ -82,10 +86,10 @@ const columns = [
const dataSource = ref([]); const dataSource = ref([]);
const pagination = reactive({ const pagination = reactive({
total: 0, total: 0,
showPageSize: true, showSizeChanger: true,
showTotal: true, showTotal: (total: number, range: [number, number]) => `${total} 条记录`,
defaultCurrent: 1, current: 1,
defaultPageSize: 10, pageSize: 10,
}); });
const params = reactive({ const params = reactive({
@ -120,13 +124,12 @@ const currentSelectAccount = ref();
const { copy, copied, isSupported } = useClipboard({ source: inviteUrl }); const { copy, copied, isSupported } = useClipboard({ source: inviteUrl });
function handlePageChange(current: number) { function handleTableChange(paginationInfo: any, filters: any, sorter: any) {
params.page = current; params.page = paginationInfo.current;
getSubAccount(); params.page_size = paginationInfo.pageSize;
} // 更新分页状态
pagination.current = paginationInfo.current;
function handlePageSizeChange(pageSize: number) { pagination.pageSize = paginationInfo.pageSize;
params.page_size = pageSize;
getSubAccount(); getSubAccount();
} }
@ -155,13 +158,13 @@ function handleOk() {
return; return;
} }
if (!isSupported) { if (!isSupported) {
AMessage.error('您的浏览器不支持复制,请手动复制!'); message.error('您的浏览器不支持复制,请手动复制!');
} }
copy(inviteUrl.value); copy(inviteUrl.value);
if (!copied) { if (!copied) {
AMessage.error('复制失败,请手动复制!'); message.error('复制失败,请手动复制!');
} }
AMessage.success('复制成功!'); message.success('复制成功!');
} }
function openDeleteModal(record: { id: number; mobile: string }) { function openDeleteModal(record: { id: number; mobile: string }) {
@ -172,7 +175,7 @@ function openDeleteModal(record: { id: number; mobile: string }) {
async function handleDelete() { async function handleDelete() {
await removeEnterpriseAccount(currentSelectAccount.value.id); await removeEnterpriseAccount(currentSelectAccount.value.id);
AMessage.success('移除成功!'); message.success('移除成功!');
} }
onMounted(() => { onMounted(() => {
@ -275,14 +278,7 @@ onMounted(() => {
color: var(--Text-2, rgba(60, 64, 67, 1)); color: var(--Text-2, rgba(60, 64, 67, 1));
} }
} }
.delete-modal-content {
margin-left: 34px;
margin-top: 16px;
font-family: $font-family-medium;
font-weight: 400;
font-size: 12px;
color: var(--Text-2, rgba(60, 64, 67, 1));
}
.title-row { .title-row {
display: flex; display: flex;
height: 64px; height: 64px;

View File

@ -3,39 +3,53 @@
<div class="title-row"> <div class="title-row">
<span class="title">企业信息</span> <span class="title">企业信息</span>
</div> </div>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px"> <Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<template #empty> <Table.Column title="企业信息" dataIndex="info">
<NoData /> <template #customRender="{ record }">
</template>
<template #info="{ record }">
{{ record.name }} {{ record.name }}
</template> </template>
<template #action> </Table.Column>
<a-button class="edit-button" size="mini" type="outline" @click="handleUpdate">修改</a-button> <Table.Column title="操作" dataIndex="action" width="120">
<template #customRender>
<Button class="edit-button" size="small" type="primary" ghost @click="handleUpdate">修改</Button>
</template> </template>
</a-table> </Table.Column>
<Modal v-model:visible="infoVisible" width="480px" title="修改企业名称" :okText="okText" @ok="handleOk"> <template #emptyText>
<NoData />
</template>
</Table>
<Modal
v-model:open="infoVisible"
width="480px"
centered
title="修改企业名称"
:okText="okText"
@ok="handleOk"
cancelText="取消"
>
<p class="tips"> <p class="tips">
企业名称只能修改2次请谨慎操作<span 企业名称只能修改2次请谨慎操作<span
>剩余{{ enterpriseInfo!.update_name_quota - enterpriseInfo!.used_update_name_count }} >剩余{{ enterpriseInfo!.update_name_quota - enterpriseInfo!.used_update_name_count }}
</span> </span>
</p> </p>
<a-form <Form
:model="form" :model="form"
:rules="rules"
class="form" class="form"
:label-col-props="{ span: 6, offset: 0 }" :label-col-props="{ span: 6, offset: 0 }"
:wrapper-col-props="{ span: 18, offset: 0 }" :wrapper-col-props="{ span: 18, offset: 0 }"
label-align="left" label-align="left"
> >
<a-form-item required field="name" label="新企业名称"> <FormItem required name="name" label="新企业名称">
<a-input v-model.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" /> <Input v-model:value.trim="form.name" size="small" :disabled="!canUpdate" placeholder="请输入新企业名称" />
</a-form-item> </FormItem>
</a-form> </Form>
</Modal> </Modal>
<CustomerServiceModal v-model:visible="customerServiceVisible" /> <CustomerServiceModal v-model:open="customerServiceVisible" centered />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message } from 'ant-design-vue';
import Container from '@/components/container.vue'; import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue'; import Modal from '@/components/modal.vue';
import { ref, reactive, computed } from 'vue'; import { ref, reactive, computed } from 'vue';
@ -48,6 +62,16 @@ const form = reactive({
name: '', name: '',
}); });
const rules = {
name: [
{
required: true,
message: '请输入新企业名称',
trigger: ['blur'],
},
],
};
const enterpriseInfo = computed(() => { const enterpriseInfo = computed(() => {
return store.enterpriseInfo ?? {}; return store.enterpriseInfo ?? {};
}); });
@ -98,7 +122,7 @@ async function handleOk() {
await updateEnterpriseName({ name: form.name }); await updateEnterpriseName({ name: form.name });
store.setEnterpriseName(form.name); store.setEnterpriseName(form.name);
store.incUsedUpdateNameCount(); store.incUsedUpdateNameCount();
AMessage.success('修改成功!'); message.success('修改成功!');
} }
</script> </script>

View File

@ -3,32 +3,37 @@
<div class="title-row"> <div class="title-row">
<span class="title">个人信息</span> <span class="title">个人信息</span>
</div> </div>
<a-table :columns="columns" :data="dataSource" :pagination="false" class="mt-8px"> <Table :dataSource="dataSource" :pagination="false" :showSorterTooltip="false" class="mt-8px">
<template #empty> <Table.Column title="用户信息" dataIndex="info">
<NoData /> <template #customRender="{ record }">
</template>
<template #info="{ record }">
<div class="pt-3px pb-3px"> <div class="pt-3px pb-3px">
<a-avatar :image-url="record.head_image" :size="32" /> <Avatar :src="record.head_image" :size="32" />
{{ record.name || '-' }} {{ record.name || '-' }}
<icon-edit size="13" class="ml-8px" @click="openEditInfoModal" /> <icon-edit size="13" class="ml-8px" @click="openEditInfoModal" />
</div> </div>
</template> </template>
<template #mobile="{ record }"> </Table.Column>
<Table.Column title="手机号" dataIndex="mobile">
<template #customRender="{ record }">
{{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }} {{ record.mobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') }}
<icon-edit size="13" class="ml-8px" @click="openEditMobileModal" /> <icon-edit size="13" class="ml-8px" @click="openEditMobileModal" />
</template> </template>
</a-table> </Table.Column>
<Modal v-model:visible="infoVisible" title="修改用户信息" @ok="handleSubmitUserInfo"> <template #emptyText>
<a-form <NoData />
</template>
</Table>
<Modal v-model:open="infoVisible" centered title="修改用户信息" @ok="handleSubmitUserInfo">
<Form
class="form" class="form"
:rules="rules"
:model="userInfoForm" :model="userInfoForm"
:label-col-props="{ span: 3, offset: 0 }" :label-col-props="{ span: 3, offset: 0 }"
:wrapper-col-props="{ span: 21, offset: 0 }" :wrapper-col-props="{ span: 21, offset: 0 }"
> >
<a-form-item field="head_image" label="头像"> <FormItem name="head_image" label="头像">
<div class="flex items-center"> <div class="flex items-center">
<a-avatar :image-url="userInfoForm.file_url" :size="48" /> <Avatar :src="userInfoForm.file_url" :size="48" />
<span class="upload-button" @click="triggerFileInput"> <span class="upload-button" @click="triggerFileInput">
<input <input
ref="uploadInputRef" ref="uploadInputRef"
@ -37,40 +42,40 @@
style="display: none" style="display: none"
@change="handleFileChange" @change="handleFileChange"
/> />
<a-button><icon-upload />上传新头像</a-button> <Button><icon-upload />上传新头像</Button>
</span> </span>
</div> </div>
</a-form-item> </FormItem>
<a-form-item field="name" label="昵称"> <FormItem name="name" label="昵称">
<a-input v-model.trim="userInfoForm.name" placeholder="请输入昵称" /> <Input v-model:value="userInfoForm.name" placeholder="请输入昵称" />
</a-form-item> </FormItem>
</a-form> </Form>
</Modal> </Modal>
<Modal v-model:visible="imageVisible" title="头像裁剪"> <Modal v-model:open="imageVisible" centered title="头像裁剪">
<VueCropper></VueCropper> <VueCropper></VueCropper>
</Modal> </Modal>
<Modal v-model:visible="mobileVisible" title="修改手机号" @ok="handleUpdateMobile"> <Modal v-model:open="mobileVisible" centered title="修改手机号" @ok="handleUpdateMobile">
<a-form <Form
ref="formRef" ref="formRef"
:model="form" :model="form"
class="form" class="form"
:rules="formRules" :rules="formRules"
:label-col-props="{ span: 5, offset: 0 }" labelAlign="right"
:wrapper-col-props="{ span: 19, offset: 0 }" :labelCol="{ span: 4 }"
label-align="left" :wrapperCol="{ span: 20 }"
> >
<a-form-item required field="mobile" label="新手机号"> <FormItem required name="mobile" label="新手机号">
<a-input v-model.trim="form.mobile" size="small" placeholder="请输入新的手机号" /> <Input v-model:value="form.mobile" size="small" placeholder="请输入新的手机号" />
</a-form-item> </FormItem>
<a-form-item required field="captcha" label="获取验证码"> <FormItem required name="captcha" label="获取验证码">
<a-input v-model.trim="form.captcha" size="small" placeholder="请输入验证码"> <Input v-model:value="form.captcha" size="small" placeholder="请输入验证码">
<template #suffix> <template #suffix>
<span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span> <span v-if="countdown <= 0" @click="sendCaptcha">发送验证码</span>
<span v-else>{{ countdown }}s</span> <span v-else>{{ countdown }}s</span>
</template> </template>
</a-input> </Input>
</a-form-item> </FormItem>
</a-form> </Form>
<PuzzleVerification <PuzzleVerification
:show="verificationVisible" :show="verificationVisible"
@submit="handleVerificationSubmit" @submit="handleVerificationSubmit"
@ -80,6 +85,7 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Button, Form, FormItem, Input, Table, message, Avatar } from 'ant-design-vue';
import Container from '@/components/container.vue'; import Container from '@/components/container.vue';
import Modal from '@/components/modal.vue'; import Modal from '@/components/modal.vue';
import PuzzleVerification from '@/views/components/login/components/PuzzleVerification.vue'; import PuzzleVerification from '@/views/components/login/components/PuzzleVerification.vue';
@ -124,35 +130,32 @@ const dataSource = computed(() => {
const formRules = { const formRules = {
mobile: [ mobile: [
{ {
required: true, validator: (_rule: any, value: string) => {
message: '请填写手机号', if (!value) {
trigger: ['blur', 'change'], return Promise.reject('请填写手机号');
},
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^1[3-9]\d{9}$/.test(value)) {
callback('手机号格式不正确');
} else {
callback();
} }
if (!/^1[3-9]\d{9}$/.test(value)) {
return Promise.reject('手机号格式不正确');
}
return Promise.resolve();
}, },
required: true,
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
captcha: [ captcha: [
{ {
required: true, required: true,
message: '请填写验证码', validator: (rule, value) => {
trigger: ['blur', 'change'], if (!value) {
}, return Promise.reject('请填写验证码');
{
validator: (value: string, callback: (error?: string) => void) => {
if (!/^\d{6}$/.test(value)) {
callback('验证码必须是6位数字');
} else {
callback();
} }
if (!/^\d{6}$/.test(value)) {
return Promise.reject('验证码必须是6位数字');
}
return Promise.resolve();
}, },
trigger: ['blur', 'change'], trigger: ['blur', 'change'],
}, },
], ],
@ -205,7 +208,7 @@ function openEditMobileModal() {
async function handleSubmitUserInfo() { async function handleSubmitUserInfo() {
await updateMyInfo(userInfoForm); await updateMyInfo(userInfoForm);
AMessage.success('修改成功!'); message.success('修改成功!');
} }
async function sendCaptcha() { async function sendCaptcha() {
@ -215,7 +218,7 @@ async function sendCaptcha() {
verificationVisible.value = true; verificationVisible.value = true;
isSendCaptcha.value = true; isSendCaptcha.value = true;
} }
AMessage.error('请填写正确的手机号!'); message.error('请填写正确的手机号!');
} catch (error) { } catch (error) {
console.log('手机号验证失败:', error); console.log('手机号验证失败:', error);
} }
@ -233,7 +236,7 @@ function beginCountdown() {
async function handleVerificationSubmit() { async function handleVerificationSubmit() {
await sendUpdateMobileCaptcha({ mobile: form.mobile }); await sendUpdateMobileCaptcha({ mobile: form.mobile });
AMessage.success('发送成功'); message.success('发送成功');
verificationVisible.value = false; verificationVisible.value = false;
countdown.value = 60; countdown.value = 60;
beginCountdown(); beginCountdown();
@ -241,13 +244,13 @@ async function handleVerificationSubmit() {
async function handleUpdateMobile() { async function handleUpdateMobile() {
if (!isSendCaptcha.value) { if (!isSendCaptcha.value) {
AMessage.error('请先获取验证码!'); message.error('请先获取验证码!');
return false; return false;
} }
const res = await formRef.value.validate(); const res = await formRef.value.validate();
if (res === true || res === undefined) { if (res === true || res === undefined) {
await updateMobile(form); await updateMobile(form);
AMessage.success('修改成功!'); message.success('修改成功!');
} }
} }

View File

@ -1,34 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 15:13:44
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:43:18
* @Description: 模拟登录鉴权页
-->
<template>
<a-modal title="登录鉴权页" :visible="true" :footer="false">
<div w100 align-center>
<a-button w-160 @click="login" :loading="loading" type="primary">登录</a-button>
</div>
</a-modal>
</template>
<script lang="ts" setup>
const route = useRoute();
const router = useRouter();
const loading = ref(false);
function login() {
loading.value = true;
localStorage.setItem('satoken', '123asdzxc');
AMessage.success('登录鉴权成功,准备跳转');
setTimeout(() => {
loading.value = false;
router.push({ name: route.query?.redirect ? route.query?.redirect : 'dashboard' });
}, 1500);
}
</script>
<style lang="scss" scoped>
</style>

View File

@ -1,19 +0,0 @@
<!--
* @Author: 田鑫
* @Date: 2023-03-05 14:27:21
* @LastEditors: 田鑫
* @LastEditTime: 2023-03-05 15:14:15
* @Description:
-->
<template>
<a-modal title="选择企业:" :visible="true">
<a-select v-model="enterprise" placeholder="请选择您的企业"></a-select>
</a-modal>
</template>
<script lang="ts" setup>
const enterprise = ref('');
</script>
<style lang="scss" scoped>
</style>

View File

@ -6,7 +6,7 @@
<div class="m-auto mt-24px max-w-1000px"> <div class="m-auto mt-24px max-w-1000px">
<Container title="推荐产品" class="container-body"> <Container title="推荐产品" class="container-body">
<div class="grid grid-cols-3 gap-20px"> <div class="grid grid-cols-3 gap-20px">
<Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" /> <!-- <Product v-for="product in products" :key="product.id" :product="product" @refresh="getProductList" /> -->
</div> </div>
<NoData v-if="products.length === 0" /> <NoData v-if="products.length === 0" />
</Container> </Container>
@ -20,7 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import Container from '@/components/container.vue'; import Container from '@/components/container.vue';
import Product from '@/views/components/workplace/modules/product.vue'; // import Product from '@/views/components/workplace/modules/product.vue';
import Case from '@/views/components/workplace/modules/case.vue'; import Case from '@/views/components/workplace/modules/case.vue';
import { fetchProductList, fetchSuccessCaseList } from '@/api/all/index'; import { fetchProductList, fetchSuccessCaseList } from '@/api/all/index';
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';

View File

@ -2,16 +2,15 @@
<div class="container"> <div class="container">
<div class="flex arco-row-justify-space-between"> <div class="flex arco-row-justify-space-between">
<img class="avatar" :src="props.product.image" :alt="props.product.name" /> <img class="avatar" :src="props.product.image" :alt="props.product.name" />
<a-tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</a-tag> <Tag v-if="props.product.status === Status.Enable" class="status status-enable">已开通</Tag>
<a-tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</a-tag> <Tag v-if="props.product.status === Status.Disable" class="status status-disable">未开通</Tag>
<a-tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</a-tag> <Tag v-if="props.product.status === Status.EXPIRED" class="status status-expired">已到期</Tag>
<a-tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</a-tag> <Tag v-if="props.product.status === Status.TRIAL_ENDS" class="status status-expired">试用结束</Tag>
<a-countdown <Countdown
v-if="props.product.status === Status.ON_TRIAL" v-if="props.product.status === Status.ON_TRIAL"
class="status-on-trill" class="status-on-trill"
title="试用中" title="试用中"
:value="1000 * (props.product.expired_at ?? 0)" :value="1000 * (props.product.expired_at ?? 0)"
:now="now()"
format="D天H时m分s秒" format="D天H时m分s秒"
/> />
</div> </div>
@ -22,47 +21,50 @@
</p> </p>
</div> </div>
<div class="footer flex arco-row-justify-start"> <div class="footer flex arco-row-justify-start">
<a-button <Button
v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL" v-if="props.product.status === Status.Enable || props.product.status === Status.ON_TRIAL"
type="primary" type="primary"
size="mini" size="small"
class="mr-8px" class="mr-8px"
@click="gotoModule(props.product.id)" @click="gotoModule(props.product.id)"
> >
进入模块 进入模块
</a-button> </Button>
<a-button <Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED" v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
size="mini" size="small"
type="primary" type="primary"
class="mr-8px" class="mr-8px"
@click="visible = true" @click="visible = true"
> >
立即购买 立即购买
</a-button> </Button>
<a-button <Button
v-if="props.product.status === Status.ON_TRIAL" v-if="props.product.status === Status.ON_TRIAL"
class="mr-8px" class="mr-8px"
size="mini" size="small"
type="outline" type="primary"
ghost
@click="visible = true" @click="visible = true"
> >
升级购买 升级购买
</a-button> </Button>
<a-button <Button
v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED" v-if="props.product.status === Status.TRIAL_ENDS || props.product.status === Status.EXPIRED"
class="mr-8px" class="mr-8px"
size="mini" size="small"
type="outline" type="primary"
ghost
@click="visible = true" @click="visible = true"
> >
联系客服 联系客服
</a-button> </Button>
<a-popconfirm focusLock title="试用产品" content="确定试用该产品吗?" @ok="handleTrial(props.product.id)"> <Popconfirm title="试用产品" ok-text="确定" cancel-text="取消" @confirm="handleTrial(props.product.id)">
<a-button v-if="props.product.status === Status.Disable" size="mini" type="outline"> 免费试用7天 </a-button> <template #description>确定试用该产品吗</template>
</a-popconfirm> <Button v-if="props.product.status === Status.Disable" size="small" type="default" ghost> 免费试用7天 </Button>
</Popconfirm>
</div> </div>
<CustomerServiceModal v-model:visible="visible" /> <CustomerServiceModal v-model:open="visible" centered/>
</div> </div>
</template> </template>
@ -71,6 +73,8 @@ import { now } from '@vueuse/core';
import { trialProduct } from '@/api/all'; import { trialProduct } from '@/api/all';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import CustomerServiceModal from '@/components/customer-service-modal.vue'; import CustomerServiceModal from '@/components/customer-service-modal.vue';
import { Button, message, Tag, Statistic, Popconfirm } from 'ant-design-vue';
const { Countdown } = Statistic;
import { useSidebarStore } from '@/stores/modules/side-bar'; import { useSidebarStore } from '@/stores/modules/side-bar';
import { useEnterpriseStore } from '@/stores/modules/enterprise'; import { useEnterpriseStore } from '@/stores/modules/enterprise';
@ -110,7 +114,7 @@ const handleTrial = async (id: any) => {
if (code === 200) { if (code === 200) {
getUserEnterpriseInfo(); getUserEnterpriseInfo();
AMessage.success('试用成功!'); message.success('试用成功!');
emit('refresh'); emit('refresh');
} }
}; };
@ -174,7 +178,7 @@ const gotoModule = (menuId: number) => {
border-radius: 4px; border-radius: 4px;
background: rgba(255, 245, 222, 1); background: rgba(255, 245, 222, 1);
:deep(.arco-statistic-title) { :deep(.ant-statistic-title) {
font-family: $font-family-medium; font-family: $font-family-medium;
font-weight: 400; font-weight: 400;
font-size: 12px; font-size: 12px;
@ -184,7 +188,7 @@ const gotoModule = (menuId: number) => {
padding: 0; padding: 0;
} }
:deep(.arco-statistic-value) { :deep(.ant-statistic-content) {
font-family: $font-family-medium; font-family: $font-family-medium;
font-weight: 400; font-weight: 400;
font-size: 10px; font-size: 10px;

View File

@ -1,20 +1,19 @@
<template> <template>
<a-modal v-model:visible="visible" title="删除对话" width="400px" @close="onClose"> <Modal v-model:open="visible" title="删除对话" width="400px" @cancel="onClose" centered>
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除对话吗删除后聊天记录将不可恢复</span> <span>确认删除对话吗删除后聊天记录将不可恢复</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="onClose">取消</a-button> <Button @click="onClose">取消</Button>
<a-button type="primary" class="ml-16px !bg-#f64b31 !border-none" status="danger" size="large" @click="onDelete" <Button type="primary" danger @click="onDelete">确定</Button>
>确定</a-button
>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Modal, Button, message } from 'ant-design-vue';
import { deleteHistoryItem } from '@/api/all/chat'; import { deleteHistoryItem } from '@/api/all/chat';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -41,7 +40,7 @@ const open = (record) => {
async function onDelete() { async function onDelete() {
const { code } = await deleteHistoryItem(conversationId.value); const { code } = await deleteHistoryItem(conversationId.value);
if (code === 200) { if (code === 200) {
AMessage.success('删除成功'); message.success('删除成功');
emits('delete', conversationId.value); emits('delete', conversationId.value);
onClose(); onClose();
} }

View File

@ -1,18 +1,19 @@
<template> <template>
<a-modal v-model:visible="visible" title="确定删除评论?" width="400px" @close="onClose"> <Modal v-model:open="visible" title="确定删除评论?" width="400px" @cancel="onClose" centered>
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>删除的评论将从对话中消失但仍在被引用的评论中可见</span> <span>删除的评论将从对话中消失但仍在被引用的评论中可见</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="large" @click="onClose">取消</a-button> <Button @click="onClose">取消</Button>
<a-button type="primary" class="ml-16px !border-none" size="large" @click="onDelete">删除</a-button> <Button type="primary" danger @click="onDelete">确定</Button>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Modal, Button } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const emits = defineEmits(['delete', 'close']); const emits = defineEmits(['delete', 'close']);

View File

@ -1,5 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import { Image, Spin, Button, Input, Textarea, Affix } from '@arco-design/web-vue'; import { Button, Input, Image } from 'ant-design-vue';
const { TextArea } = Input;
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import SvgIcon from '@/components/svg-icon/index.vue'; import SvgIcon from '@/components/svg-icon/index.vue';
import DeleteCommentModal from './delete-comment-modal.vue'; import DeleteCommentModal from './delete-comment-modal.vue';
@ -125,23 +126,23 @@ export default {
</div> </div>
)} )}
<Textarea <TextArea
ref={textAreaRef} ref={textAreaRef}
auto-size autoSize
class={`max-h-220px overflow-y-auto ${isReplay.value ? 'pt-38px' : ''}`} class={`max-h-220px overflow-y-auto textarea-box ${isReplay.value ? 'pt-38px' : ''}`}
size="large" size="large"
placeholder="输入评论" placeholder="输入评论"
v-model={comment.value} v-model:value={comment.value}
onPressEnter={onComment} onPressEnter={onComment}
/> />
</div> </div>
{comment.value && ( {comment.value && (
<div class="flex justify-end mt-12px"> <div class="flex justify-end mt-12px">
<Button type="outline" class="mr-12px rounded-8px" size="medium" onClick={onClearComment}> <Button type="primary" ghost class="cancel-btn mr-12px !rounded-8px" onClick={onClearComment}>
取消 取消
</Button> </Button>
<Button type="primary" class="rounded-8px" size="medium" onClick={onComment}> <Button type="primary" class="!rounded-8px" onClick={onComment}>
发送 发送
</Button> </Button>
</div> </div>

View File

@ -19,6 +19,9 @@
font-family: $font-family-medium; font-family: $font-family-medium;
} }
} }
.cancel-btn {
background-color: transparent !important;
}
.ai-text { .ai-text {
font-family: $font-family-medium; font-family: $font-family-medium;
font-size: 16px; font-size: 16px;
@ -30,7 +33,7 @@
-webkit-background-clip: text; -webkit-background-clip: text;
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
:deep(.arco-textarea-wrapper) { .textarea-box {
min-height: 38px; min-height: 38px;
display: flex; display: flex;
border-color: transparent !important; border-color: transparent !important;
@ -39,14 +42,11 @@
background-color: #fff; background-color: #fff;
color: #211f24 !important; color: #211f24 !important;
transition: all 0.3s; transition: all 0.3s;
.arco-textarea-mirror,
.arco-textarea {
padding: 8px 16px !important; padding: 8px 16px !important;
}
&:hover { &:hover {
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
&.arco-textarea-focus { &:focus-within {
border-color: #6d4cfe !important; border-color: #6d4cfe !important;
} }
} }

View File

@ -1,5 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import { Image, Spin, Button } from '@arco-design/web-vue'; import { Button } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import AiSuggest from './components/ai-suggest/'; import AiSuggest from './components/ai-suggest/';
import { getShareWorksList, getShareWorksDetail, patchShareWorksConfirm } from '@/api/all/generationWorkshop.ts'; import { getShareWorksList, getShareWorksDetail, patchShareWorksConfirm } from '@/api/all/generationWorkshop.ts';
@ -186,7 +187,7 @@ export default {
下一条 下一条
</Button> </Button>
)} )}
<Button type="outline" size="large" class="mr-12px" onClick={onBackList}> <Button type="primary" ghost size="large" class="mr-12px" onClick={onBackList}>
返回列表 返回列表
</Button> </Button>
{renderConfirmBtn()} {renderConfirmBtn()}
@ -220,7 +221,7 @@ export default {
</div> </div>
</header> </header>
{loading.value ? ( {loading.value ? (
<Spin spinning={loading.value} class="flex-1 w-full flex justify-center items-center" size={60} /> <Spin spinning={loading.value} wrapperClassName="flex-1 w-full flex justify-center items-center" size="large" />
) : ( ) : (
<section class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}> <section class={`page-wrap relative ${isExpand.value ? 'expand' : ''}`}>
<div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}> <div class="fold-box cursor-pointer" onClick={() => (isExpand.value = true)}>

View File

@ -1,6 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import { Image, Spin } from '@arco-design/web-vue'; import { Image, Spin } from 'ant-design-vue';
import { exactFormatTime } from '@/utils/tools'; import { exactFormatTime } from '@/utils/tools';
import { handleUserHome } from '@/utils/user.ts'; import { handleUserHome } from '@/utils/user.ts';
@ -52,7 +52,7 @@ export default {
</header> </header>
<section class="page-wrapper flex justify-center"> <section class="page-wrapper flex justify-center">
{loading.value ? ( {loading.value ? (
<Spin spinning={loading.value} class="w-full flex justify-center items-center" size={60} /> <Spin spinning={loading.value} wrapperClassName="w-full flex justify-center items-center" size="large" />
) : ( ) : (
<div class="explore-container"> <div class="explore-container">
<div class="explore-list-wrap pt-24px pb-28px"> <div class="explore-list-wrap pt-24px pb-28px">

View File

@ -1,5 +1,5 @@
<script lang="tsx"> <script lang="tsx">
import { Tabs, TabPane } from 'ant-design-vue'; import { Tabs, TabPane, Button } from 'ant-design-vue';
import ManuscriptList from './manuscript/list/index.vue'; import ManuscriptList from './manuscript/list/index.vue';
import ManuscriptCheckList from './manuscript/check-list/index.vue'; import ManuscriptCheckList from './manuscript/check-list/index.vue';
import ShareManuscriptModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/index.vue'; import ShareManuscriptModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/index.vue';
@ -33,17 +33,17 @@ export default defineComponent({
v-slots={{ v-slots={{
rightExtra: () => ( rightExtra: () => (
<div class="flex items-center"> <div class="flex items-center">
<a-button type="outline" size="medium" onClick={handleShareModal}> <Button type="primary" ghost size="medium" onClick={handleShareModal}>
分享内容稿件 分享内容稿件
</a-button> </Button>
{showManuscriptList.value && ( {showManuscriptList.value && (
<a-button <Button
type="primary" type="primary"
size="medium" size="medium"
class="ml-12px" class="ml-12px"
onClick={openUploadModal} onClick={openUploadModal}
v-slots={{ v-slots={{
icon: () => <icon-plus size="16" />, icon: () => <icon-plus size="16" class="mr-8px" />,
default: () => '上传内容稿件', default: () => '上传内容稿件',
}} }}
/> />

View File

@ -8,42 +8,42 @@
<div class="filter-row"> <div class="filter-row">
<div class="filter-row-item"> <div class="filter-row-item">
<span class="label">内容稿件标题</span> <span class="label">内容稿件标题</span>
<a-space size="medium"> <Space size="medium">
<a-input <Input
v-model="query.title" v-model:value="query.title"
class="!w-240px" class="!w-240px"
placeholder="请输入内容稿件标题" placeholder="请输入内容稿件标题"
size="medium" size="medium"
allow-clear allowClear
@change="handleSearch" @change="handleSearch"
> >
<template #prefix> <template #prefix>
<icon-search /> <icon-search />
</template> </template>
</a-input> </Input>
</a-space> </Space>
</div> </div>
<div class="filter-row-item"> <div class="filter-row-item">
<span class="label">序号</span> <span class="label">序号</span>
<a-space size="medium"> <Space size="medium">
<a-input <Input
v-model="query.uid" v-model:value="query.uid"
class="!w-160px" class="!w-160px"
placeholder="请输入序号" placeholder="请输入序号"
size="medium" size="medium"
allow-clear allowClear
@change="handleSearch" @change="handleSearch"
> >
<template #prefix> <template #prefix>
<icon-search /> <icon-search />
</template> </template>
</a-input> </Input>
</a-space> </Space>
</div> </div>
<div class="filter-row-item" v-if="query.audit_status === AuditStatus.Pending"> <div class="filter-row-item" v-if="query.audit_status === AuditStatus.Pending">
<span class="label">上传时间</span> <span class="label">上传时间</span>
<a-range-picker <DatePicker.RangePicker
v-model="created_at" v-model:value="created_at"
size="medium" size="medium"
allow-clear allow-clear
format="YYYY-MM-DD" format="YYYY-MM-DD"
@ -54,25 +54,25 @@
<template v-if="[AuditStatus.Auditing, AuditStatus.Passed].includes(query.audit_status)"> <template v-if="[AuditStatus.Auditing, AuditStatus.Passed].includes(query.audit_status)">
<div class="filter-row-item"> <div class="filter-row-item">
<span class="label">审核平台</span> <span class="label">审核平台</span>
<a-select <Select
v-model="query.audit_platform" v-model:value="query.audit_platform"
size="medium" size="middle"
placeholder="全部" placeholder="全部"
allow-clear allowClear
@change="handleSearch" @change="handleSearch"
class="!w-160px" class="!w-160px"
> >
<a-option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{ <Option v-for="(item, index) in PLATFORMS" :key="index" :value="item.value" :label="item.label">{{
item.label item.label
}}</a-option> }}</Option>
</a-select> </Select>
</div> </div>
<div class="filter-row-item"> <div class="filter-row-item">
<span class="label">审核时间</span> <span class="label">审核时间</span>
<a-range-picker <DatePicker.RangePicker
v-model="audit_started_at" v-model:value="audit_started_at"
size="medium" size="medium"
allow-clear allowClear
format="YYYY-MM-DD" format="YYYY-MM-DD"
class="!w-280px" class="!w-280px"
@change="(value) => onDateChange(value, 'audit_started_at')" @change="(value) => onDateChange(value, 'audit_started_at')"
@ -81,27 +81,31 @@
</template> </template>
<div class="filter-row-item"> <div class="filter-row-item">
<a-button type="outline" class="mr-12px" size="medium" @click="handleSearch"> <Button type="primary" ghost class="mr-12px" size="medium" @click="handleSearch">
<template #icon> <template #icon>
<icon-search /> <icon-search class="mr-8px"/>
</template> </template>
<template #default>搜索</template> <template #default>搜索</template>
</a-button> </Button>
<a-button size="medium" @click="handleReset"> <Button size="medium" @click="handleReset">
<template #icon> <template #icon>
<icon-refresh /> <icon-refresh class="mr-8px" />
</template> </template>
<template #default>重置</template> <template #default>重置</template>
</a-button> </Button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { Button, Input, Select, Space, DatePicker } from 'ant-design-vue';
const { Option } = Select;
import { defineEmits, defineProps } from 'vue'; import { defineEmits, defineProps } from 'vue';
import { PLATFORMS } from '@/views/material-center/components/finished-products/manuscript/check-list/constants'; import { PLATFORMS } from '@/views/material-center/components/finished-products/manuscript/check-list/constants';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants'; import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
import { ref, nextTick } from 'vue';
import dayjs from 'dayjs';
const props = defineProps({ const props = defineProps({
query: { query: {
@ -110,7 +114,7 @@ const props = defineProps({
}, },
}); });
const emits = defineEmits('search', 'reset', 'update:query'); const emits = defineEmits(['search', 'reset', 'update:query']);
const created_at = ref([]); const created_at = ref([]);
const audit_started_at = ref([]); const audit_started_at = ref([]);
@ -137,6 +141,7 @@ const onDateChange = (value, type) => {
const handleReset = () => { const handleReset = () => {
created_at.value = []; created_at.value = [];
audit_started_at.value = [];
emits('reset'); emits('reset');
}; };
</script> </script>

View File

@ -1,25 +1,27 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
title="删除内容稿件" title="删除内容稿件"
width="480px" width="480px"
@close="onClose" centered
@cancel="onClose"
> >
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>确认删除 {{ projectName }} 这个内容稿件吗</span> <span>确认删除 {{ projectName }} 这个内容稿件吗</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="medium" @click="onClose">取消</a-button> <Button size="medium" @click="onClose">取消</Button>
<a-button type="primary" class="ml-16px" status="danger" size="medium" @click="onDelete" <Button type="primary" class="ml-16px" danger size="medium" @click="onDelete"
>确认删除</a-button >确认删除</Button
> >
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Button, Modal, message } from 'ant-design-vue';
import { deleteWork } from '@/api/all/generationWorkshop'; import { deleteWork } from '@/api/all/generationWorkshop';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
@ -48,7 +50,7 @@ const open = (record) => {
async function onDelete() { async function onDelete() {
const { code } = await deleteWork(projectId.value); const { code } = await deleteWork(projectId.value);
if (code === 200) { if (code === 200) {
AMessage.success('删除成功'); message.success('删除成功');
update() update()
onClose(); onClose();
} }

View File

@ -1,45 +1,38 @@
<template> <template>
<a-table <Table
ref="tableRef" ref="tableRef"
:data="dataSource" :dataSource="dataSource"
row-key="id" rowKey="id"
column-resizable
:pagination="false" :pagination="false"
:scroll="{ x: '100%' }" :scroll="{ x: '100%' }"
class="flex-1 manuscript-table w-100%" class="flex-1 manuscript-table w-100%"
bordered bordered
:row-selection="rowSelection" :rowSelection="rowSelection"
:selected-row-keys="selectedRowKeys" :showSorterTooltip="false"
@sorter-change="handleSorterChange" @change="handleTableChange"
@select="(selectedKeys, rowKeyValue, record) => emits('select', selectedKeys, rowKeyValue, record)"
@select-all="(check) => emits('selectAll', check)"
> >
<template #empty> <template #emptyText>
<NoData text="暂无稿件" /> <NoData text="暂无稿件" />
</template> </template>
<template #columns> <Column
<a-table-column
v-for="column in tableColumns" v-for="column in tableColumns"
:key="column.dataIndex" :key="column.dataIndex"
:data-index="column.dataIndex" :dataIndex="column.dataIndex"
:fixed="column.fixed" :fixed="column.fixed"
:width="column.width" :width="column.width"
:min-width="column.minWidth" :minWidth="column.minWidth"
:sortable="column.sortable" :sorter="column.sortable"
:align="column.align" :align="column.align"
ellipsis :ellipsis="true"
tooltip
> >
<template #title> <template #title>
<div class="flex items-center">
<span class="cts mr-4px">{{ column.title }}</span> <span class="cts mr-4px">{{ column.title }}</span>
<a-tooltip v-if="column.tooltip" :content="column.tooltip" position="top"> <Tooltip v-if="column.tooltip" :title="column.tooltip" placement="top">
<icon-question-circle class="tooltip-icon color-#737478" size="16" /> <icon-question-circle class="tooltip-icon color-#737478" size="16" />
</a-tooltip> </Tooltip>
</div>
</template> </template>
<template v-if="column.dataIndex === 'customer_opinion'" #cell="{ record }"> <template v-if="column.dataIndex === 'customer_opinion'" #customRender="{ record }">
<p <p
class="h-28px px-8px flex items-center rounded-2px w-fit" class="h-28px px-8px flex items-center rounded-2px w-fit"
:style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }" :style="{ background: getCustomerOpinionInfo(record.customer_opinion)?.bg }"
@ -49,7 +42,7 @@
}}</span> }}</span>
</p> </p>
</template> </template>
<template v-else-if="column.dataIndex === 'platform'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'platform'" #customRender="{ record }">
<template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template> <template v-if="!PLATFORMS.find((item) => item.value === record.platform)"> - </template>
<img <img
v-else v-else
@ -59,75 +52,66 @@
:src="PLATFORMS.find((item) => item.value === record.platform)?.icon" :src="PLATFORMS.find((item) => item.value === record.platform)?.icon"
/> />
</template> </template>
<template v-else-if="column.dataIndex === 'compliance_level'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'compliance_level'" #customRender="{ record }">
<span class="cts num !color-#6D4CFE">{{ <span class="cts num !color-#6D4CFE">{{
record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-' record.ai_review?.compliance_level ? `${record.ai_review?.compliance_level}%` : '-'
}}</span> }}</span>
</template> </template>
<template v-else-if="column.dataIndex === 'title'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'title'" #customRender="{ record }">
<TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" /> <TextOverTips :context="record.title" :line="3" class="title" @click="onDetail(record)" />
</template> </template>
<template v-else-if="column.dataIndex === 'type'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'type'" #customRender="{ record }">
<div class="flex items-center"> <div class="flex items-center">
<img <img :src="record.type === EnumManuscriptType.Image ? icon2 : icon3" width="16" height="16" class="mr-4px" />
:src="record.type === EnumManuscriptType.Image ? icon2 : icon3"
width="16"
height="16"
class="mr-4px"
/>
<span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{ <span class="cts" :class="record.type === EnumManuscriptType.Image ? '!color-#25C883' : '!color-#6D4CFE'">{{
record.type === EnumManuscriptType.Image ? '图文' : '视频' record.type === EnumManuscriptType.Image ? '图文' : '视频'
}}</span> }}</span>
</div> </div>
</template> </template>
<template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #cell="{ record }"> <template v-else-if="['uploader', 'last_modifier'].includes(column.dataIndex)" #customRender="{ record }">
{{ record[column.dataIndex].name || record[column.dataIndex].mobile }} {{ record[column.dataIndex].name || record[column.dataIndex].mobile }}
</template> </template>
<template <template
#cell="{ record }" #customRender="{ record }"
v-else-if=" v-else-if="['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)"
['created_at', 'last_modified_at', 'audit_started_at', 'audit_passed_at'].includes(column.dataIndex)
"
> >
<span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span> <span class="cts num">{{ exactFormatTime(record[column.dataIndex]) }}</span>
</template> </template>
<template v-else-if="column.dataIndex === 'cover'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'cover'" #customRender="{ record }">
<HoverImagePreview :src="record.cover"> <HoverImagePreview :src="record.cover">
<a-image :width="64" :height="64" :src="record.cover" class="!rounded-6px" fit="cover"> <ImgLazyLoad :width="64" :height="64" :src="record.cover" class="!rounded-6px" />
<template #error>
<img :src="icon4" class="w-full h-full" />
</template>
</a-image>
</HoverImagePreview> </HoverImagePreview>
</template> </template>
<template v-else-if="column.dataIndex === 'operation'" #cell="{ record }"> <template v-else-if="column.dataIndex === 'operation'" #customRender="{ record }">
<div class="flex items-center"> <div class="flex items-center">
<img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" /> <img class="mr-8px cursor-pointer" :src="icon1" width="14" height="14" @click="onDelete(record)" />
<a-button type="outline" size="mini" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed" <Button type="primary" ghost size="small" @click="onShare(record)" v-if="audit_status === AuditStatus.Passed"
>分享</a-button >分享</Button
> >
<a-button <Button
type="outline" type="primary"
size="mini" ghost
size="small"
@click="onCheck(record)" @click="onCheck(record)"
v-else-if="audit_status === AuditStatus.Pending" v-else-if="audit_status === AuditStatus.Pending"
>审核</a-button >审核</Button
> >
<a-button type="outline" size="mini" @click="onScan(record)" v-else>查看</a-button> <Button type="primary" ghost size="small" @click="onScan(record)" v-else>查看</Button>
</div> </div>
</template> </template>
<template v-else #cell="{ record }"> <template v-else #customRender="{ record }">
{{ formatTableField(column, record, true) }} {{ formatTableField(column, record, true) }}
</template> </template>
</a-table-column> </Column>
</template> </Table>
</a-table>
<ShareModal ref="shareModalRef" /> <ShareModal ref="shareModalRef" />
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Button, Tooltip, Table, Image } from 'ant-design-vue';
const { Column } = Table;
import { formatTableField, exactFormatTime } from '@/utils/tools'; import { formatTableField, exactFormatTime } from '@/utils/tools';
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants'; import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { patchWorkAuditsAudit } from '@/api/all/generationWorkshop'; import { patchWorkAuditsAudit } from '@/api/all/generationWorkshop';
@ -139,14 +123,15 @@ import { AuditStatus } from '@/views/material-center/components/finished-product
import { slsWithCatch } from '@/utils/stroage.ts'; import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips'
import ImgLazyLoad from "@/components/img-lazy-load";
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue'; import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal.vue';
import HoverImagePreview from '@/components/hover-image-preview'; import HoverImagePreview from '@/components/hover-image-preview';
import icon1 from '@/assets/img/media-account/icon-delete.png'; import icon1 from '@/assets/img/media-account/icon-delete.png';
import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png'; import icon2 from '@/assets/img/creative-generation-workshop/icon-photo.png';
import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png'; import icon3 from '@/assets/img/creative-generation-workshop/icon-video.png';
import icon4 from '@/assets/img/error-img.png'; // import icon4 from '@/assets/img/error-img.png';
const emits = defineEmits(['sorterChange', 'delete', 'select', 'selectAll']); const emits = defineEmits(['sorterChange', 'delete', 'select', 'selectAll']);
const router = useRouter(); const router = useRouter();
@ -160,10 +145,6 @@ const props = defineProps({
type: Array, type: Array,
default: () => [], default: () => [],
}, },
rowSelection: {
type: Array,
default: () => [],
},
selectedRowKeys: { selectedRowKeys: {
type: Array, type: Array,
default: () => [], default: () => [],
@ -176,8 +157,20 @@ const props = defineProps({
const tableRef = ref(null); const tableRef = ref(null);
const shareModalRef = ref(null); const shareModalRef = ref(null);
const handleSorterChange = (column, order) => { const handleTableChange = (pagination, filters, sorter) => {
emits('sorterChange', column, order === 'ascend' ? 'asc' : 'desc'); if (sorter && sorter.field) {
emits('sorterChange', sorter.field, sorter.order === 'ascend' ? 'asc' : 'desc');
}
};
const rowSelection = {
selectedRowKeys: computed(() => props.selectedRowKeys),
onSelect: (record, selected) => {
emits('select', record, selected);
},
onSelectAll: (selected) => {
emits('selectAll', selected);
},
}; };
const onDelete = (item) => { const onDelete = (item) => {
emits('delete', item); emits('delete', item);

View File

@ -219,7 +219,7 @@ export const INITIAL_QUERY = {
title: '', title: '',
created_at: [], created_at: [],
audit_started_at: [], audit_started_at: [],
audit_platform: '', audit_platform: undefined,
sort_column: undefined, sort_column: undefined,
sort_order: undefined, sort_order: undefined,
}; };

View File

@ -13,28 +13,29 @@
class="flex justify-end mb-12px" class="flex justify-end mb-12px"
v-if="[AuditStatus.Pending, AuditStatus.Auditing].includes(query.audit_status)" v-if="[AuditStatus.Pending, AuditStatus.Auditing].includes(query.audit_status)"
> >
<a-button <Button
type="outline" type="primary"
ghost
class="w-fit" class="w-fit"
size="medium" size="medium"
@click="handleBatchCheck" @click="handleBatchCheck"
v-if="query.audit_status === AuditStatus.Pending" v-if="query.audit_status === AuditStatus.Pending"
>批量审核</a-button >批量审核</Button
> >
<a-button <Button
type="outline" type="primary"
ghost
class="w-fit" class="w-fit"
size="medium" size="medium"
@click="handleBatchView" @click="handleBatchView"
v-if="query.audit_status === AuditStatus.Auditing" v-if="query.audit_status === AuditStatus.Auditing"
>批量查看</a-button >批量查看</Button
> >
</div> </div>
<ManuscriptCheckTable <ManuscriptCheckTable
:key="query.audit_status" :key="query.audit_status"
:tableColumns="tableColumns" :tableColumns="tableColumns"
:rowSelection="rowSelection"
:selectedRowKeys="selectedRowKeys" :selectedRowKeys="selectedRowKeys"
:dataSource="dataSource" :dataSource="dataSource"
:audit_status="query.audit_status" :audit_status="query.audit_status"
@ -44,16 +45,15 @@
@selectAll="handleSelectAll" @selectAll="handleSelectAll"
/> />
<div v-if="pageInfo.total > 0" class="pagination-row"> <div v-if="pageInfo.total > 0" class="pagination-row">
<a-pagination <Pagination
:total="pageInfo.total" :total="pageInfo.total"
size="mini" size="small"
show-total :showTotal="(total, range) => `共 ${total} 条`"
show-jumper showSizeChanger
show-page-size showQuickJumper
:current="pageInfo.page" :current="pageInfo.page"
:page-size="pageInfo.page_size" :pageSize="pageInfo.page_size"
@change="onPageChange" @change="onPageChange"
@page-size-change="onPageSizeChange"
/> />
</div> </div>
</div> </div>
@ -62,7 +62,7 @@
</div> </div>
</template> </template>
<script lang="jsx" setup> <script lang="jsx" setup>
import { Button, Message as AMessage } from '@arco-design/web-vue'; import { Button, Pagination, message } from 'ant-design-vue';
import FilterBlock from './components/filter-block'; import FilterBlock from './components/filter-block';
import ManuscriptCheckTable from './components/manuscript-check-table'; import ManuscriptCheckTable from './components/manuscript-check-table';
import DeleteManuscriptModal from './components/manuscript-check-table/delete-manuscript-modal.vue'; import DeleteManuscriptModal from './components/manuscript-check-table/delete-manuscript-modal.vue';
@ -83,22 +83,16 @@ const props = defineProps({
const { const {
dataSource, dataSource,
pageInfo, pageInfo,
rowSelection,
onPageChange, onPageChange,
onPageSizeChange,
resetPageInfo, resetPageInfo,
selectedRowKeys, selectedRowKeys,
selectedRows, selectedRows,
handleSelect, handleSelect,
handleSelectAll, handleSelectAll,
DEFAULT_PAGE_INFO,
} = useTableSelectionWithPagination({ } = useTableSelectionWithPagination({
onPageChange: () => { onPageChange: () => {
getData(); getData();
}, },
onPageSizeChange: () => {
getData();
},
}); });
const router = useRouter(); const router = useRouter();
const tableColumns = ref([]); const tableColumns = ref([]);
@ -140,7 +134,7 @@ const handleSorterChange = (column, order) => {
}; };
const handleBatchCheck = () => { const handleBatchCheck = () => {
if (!selectedRows.value.length) { if (!selectedRows.value.length) {
AMessage.warning('请选择需审核的内容稿件'); message.warning('请选择需审核的内容稿件');
return; return;
} }
@ -151,7 +145,7 @@ const handleBatchCheck = () => {
}; };
const handleBatchView = () => { const handleBatchView = () => {
if (!selectedRows.value.length) { if (!selectedRows.value.length) {
AMessage.warning('请选择需查看的内容稿件'); message.warning('请选择需查看的内容稿件');
return; return;
} }
@ -175,7 +169,6 @@ const handleDelete = (item) => {
deleteManuscriptModalRef.value?.open({ id, name: `${title}` }); deleteManuscriptModalRef.value?.open({ id, name: `${title}` });
}; };
watch( watch(
() => props.audit_status, () => props.audit_status,
(newVal) => { (newVal) => {

View File

@ -1,9 +1,10 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
:title="action === 'exit' ? '退出审核' : '切换内容稿件'" :title="action === 'exit' ? '退出审核' : '切换内容稿件'"
width="480px" width="480px"
@close="onClose" centered
@cancel="onClose"
> >
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
@ -14,16 +15,17 @@
}}</span> }}</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button> <Button size="medium" @click="onClose">继续编辑</Button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm"> <Button type="primary" class="ml-8px" size="medium" @click="onConfirm">
{{ action === 'exit' ? '确认退出' : '确认切换' }} {{ action === 'exit' ? '确认退出' : '确认切换' }}
</a-button> </Button>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';

View File

@ -1,10 +1,11 @@
<template> <template>
<a-modal <Modal
v-model:visible="visible" v-model:open="visible"
title="提示" title="提示"
width="480px" width="480px"
@close="onClose" @close="onClose"
modal-class="upload-success11-modal" centered
wrapClassName="upload-success11-modal"
:footer="null" :footer="null"
> >
<div class="flex items-center flex-col justify-center"> <div class="flex items-center flex-col justify-center">
@ -13,14 +14,12 @@
<p class="text-14px lh-22px font-400 color-#737478 ld">想让内容更抓眼球更吸流量吗</p> <p class="text-14px lh-22px font-400 color-#737478 ld">想让内容更抓眼球更吸流量吗</p>
<p class="text-14px lh-22px font-400 color-#737478 ld">试试内容稿件分析功能吧</p> <p class="text-14px lh-22px font-400 color-#737478 ld">试试内容稿件分析功能吧</p>
</div> </div>
<!-- <template #footer> </Modal>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">内容稿件分析</a-button>
</template> -->
</a-modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-feedback-success.png'; import icon1 from '@/assets/img/media-account/icon-feedback-success.png';
const router = useRouter(); const router = useRouter();

View File

@ -1,5 +1,5 @@
<script lang="jsx"> <script lang="jsx">
import { Drawer, Image } from '@arco-design/web-vue'; import { Drawer, Image } from 'ant-design-vue';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/error-img.png'; import icon1 from '@/assets/img/error-img.png';
@ -32,12 +32,12 @@ export default {
return () => ( return () => (
<Drawer <Drawer
title="审核列表" title="审核列表"
visible={visible.value} v-model:open={visible.value}
width={420} width={420}
class="check-list-drawer-xt" rootClassName="check-list-drawer-xt"
footer={false} footer={null}
header={false} closable={false}
onCancel={onClose} onClose={onClose}
> >
<div class="flex justify-between items-center h-56px px-24px"> <div class="flex justify-between items-center h-56px px-24px">
<div class="flex items-center"> <div class="flex items-center">
@ -61,13 +61,12 @@ export default {
height={48} height={48}
preview={false} preview={false}
src={item.cover} src={item.cover}
class="!rounded-4px mr-8px" class="!rounded-4px"
fit="cover"
v-slots={{ v-slots={{
error: () => <img src={icon1} class="w-full h-full" />, error: () => <img src={icon1} class="w-full h-full" />,
}} }}
/> />
<div class="flex-1 overflow-hidden flex flex-col items-start"> <div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px !text-14px`} /> <TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px !text-14px`} />
<p class="cts !text-14px">{`合规程度:${ <p class="cts !text-14px">{`合规程度:${
item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-' item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'

View File

@ -1,10 +1,12 @@
.check-list-drawer-xt { .check-list-drawer-xt {
.arco-drawer-mask { box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15);
.ant-drawer-mask {
background-color: transparent; background-color: transparent;
} }
.arco-drawer { .ant-drawer-header {
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.15); display: none;
.arco-drawer-body { }
.ant-drawer-body {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -42,4 +44,3 @@
} }
} }
} }
}

View File

@ -14,10 +14,10 @@ export const TAB_LIST = [
label: '文本', label: '文本',
value: enumTab.TEXT, value: enumTab.TEXT,
}, },
// { {
// label: '图片', label: '图片',
// value: enumTab.IMAGE, value: enumTab.IMAGE,
// }, },
]; ];
export enum Enum_Level { export enum Enum_Level {

View File

@ -24,6 +24,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Input } from 'ant-design-vue';
const {TextArea} = Input
import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue'; import { ref, computed, watch, defineProps, defineEmits, onMounted, onUnmounted } from 'vue';
import { escapeRegExp } from './constants'; import { escapeRegExp } from './constants';

View File

@ -1,23 +1,13 @@
<script lang="jsx"> <script lang="jsx">
import axios from 'axios'; import axios from 'axios';
import { Swiper, SwiperSlide } from 'swiper/vue'; import { Swiper, SwiperSlide } from 'swiper/vue';
import { Button, Form, Input, FormItem, Tabs, message, Image, Upload, Spin } from 'ant-design-vue';
import { IconLoading } from '@arco-design/web-vue/es/icon'; import { IconLoading } from '@arco-design/web-vue/es/icon';
import {
Image,
Form,
FormItem,
Input,
Textarea,
Button,
Tabs,
Upload,
TabPane,
Spin,
Message as AMessage,
} from '@arco-design/web-vue';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import HighlightTextarea from './highlight-textarea'; import HighlightTextarea from './highlight-textarea';
const { TabPane } = Tabs;
import 'swiper/css'; import 'swiper/css';
import 'swiper/css/navigation'; import 'swiper/css/navigation';
import { Navigation } from 'swiper/modules'; import { Navigation } from 'swiper/modules';
@ -87,7 +77,7 @@ export default {
const onAgainCheck = () => { const onAgainCheck = () => {
if (!isTextTab.value && !props.modelValue.files?.length) { if (!isTextTab.value && !props.modelValue.files?.length) {
AMessage.warning('请先上传需审核图片'); message.warning('请先上传需审核图片');
return; return;
} }
emit('againCheck'); emit('againCheck');
@ -99,15 +89,7 @@ export default {
activeTab.value = key; activeTab.value = key;
}; };
const validate = () => { const validate = () => {
return new Promise((resolve, reject) => { return formRef.value?.validate();
formRef.value?.validate((errors) => {
if (errors) {
reject();
} else {
resolve();
}
});
});
}; };
const reset = () => { const reset = () => {
formRef.value?.resetFields?.(); formRef.value?.resetFields?.();
@ -139,24 +121,19 @@ export default {
<Upload <Upload
ref={uploadRef} ref={uploadRef}
action="/" action="/"
draggable
class="w-fit" class="w-fit"
custom-request={(option) => uploadImage(option, action)} customRequest={(option) => uploadImage(option, action)}
accept=".jpg,.jpeg,.png,.gif,.webp" accept=".jpg,.jpeg,.png,.gif,.webp"
show-file-list={false} showUploadList={false}
multiple multiple
> >
{{ <UploadBtn />
'upload-button': () => <UploadBtn />,
}}
</Upload> </Upload>
); );
}; };
const uploadImage = async (option, action = 'upload') => { const uploadImage = async (option, action = 'upload') => {
const { const { file } = option;
fileItem: { file },
} = option;
const { name, size, type } = file; const { name, size, type } = file;
const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) }); const response = await getImagePreSignedUrl({ suffix: getFileExtension(name) });
@ -190,11 +167,11 @@ export default {
const renderFooterRow = () => { const renderFooterRow = () => {
return ( return (
<> <>
<Button class="mr-12px" size="medium" onClick={onAgainCheck} disabled={isDisabled.value}> <Button class="mr-12px" onClick={onAgainCheck} disabled={isDisabled.value}>
再次审核 再次审核
</Button> </Button>
{isTextTab.value ? ( {isTextTab.value ? (
<Button size="medium" type="outline" class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}> <Button type="primary" ghost class="w-123px check-btn" onClick={onAiReplace} disabled={isDisabled.value}>
{aiReplaceLoading.value ? ( {aiReplaceLoading.value ? (
<> <>
<IconLoading size={14} /> <IconLoading size={14} />
@ -210,7 +187,7 @@ export default {
) : ( ) : (
<div class="w-88px"> <div class="w-88px">
{renderUpload( {renderUpload(
<Button size="medium" type="outline"> <Button type="primary" ghost>
图片替换 图片替换
</Button>, </Button>,
'replaceImage', 'replaceImage',
@ -223,17 +200,17 @@ export default {
const renderTextForm = () => { const renderTextForm = () => {
return ( return (
<Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width> <Form ref={formRef} model={props.modelValue} rules={FORM_RULES} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required> <FormItem label="标题" name="title" required>
<Input <Input
v-model={props.modelValue.title} v-model:value={props.modelValue.title}
placeholder="请输入标题" placeholder="请输入标题"
size="large" size="large"
maxLength={30} maxlength={30}
show-word-limit showCount
disabled={isDisabled.value} disabled={isDisabled.value}
/> />
</FormItem> </FormItem>
<FormItem label="作品描述" field="content" class="flex-1 content-form-item"> <FormItem label="作品描述" name="content" class="flex-1 content-form-item">
<HighlightTextarea <HighlightTextarea
v-model={props.modelValue.content} v-model={props.modelValue.content}
disabled={isDisabled.value} disabled={isDisabled.value}
@ -409,9 +386,9 @@ export default {
<div class="right-box"> <div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p> <p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
<Spin <Spin
loading={true} spinning={true}
tip={`${isTextTab.value ? '文本' : '图片'}检测中`} tip={`${isTextTab.value ? '文本' : '图片'}检测中`}
size={72} size="large"
class="h-298px !flex flex-col justify-center items-center" class="h-298px !flex flex-col justify-center items-center"
/> />
</div> </div>
@ -421,7 +398,7 @@ export default {
<div class="right-box"> <div class="right-box">
<p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p> <p class="cts bold !text-16px !lh-24px !color-#211F24 mb-16px">审核结果</p>
{props.getDataLoading ? ( {props.getDataLoading ? (
<Spin loading={true} size={72} class="h-298px !flex justify-center items-center" /> <Spin spinning={true} size="large" class="h-298px !flex justify-center items-center" />
) : ( ) : (
renderCheckSuccessBox() renderCheckSuccessBox()
)} )}
@ -440,12 +417,11 @@ export default {
<div class="h-full w-full px-24px pt-16px pb-24px content-wrap flex"> <div class="h-full w-full px-24px pt-16px pb-24px content-wrap flex">
<div class="flex-2 left-box mr-24px flex flex-col"> <div class="flex-2 left-box mr-24px flex flex-col">
<div class="flex-1 mb-12px rounded-8px border-1px pt-8px flex flex-col pb-16px bg-#F7F8FA border-#E6E6E8 border-solid"> <div class="flex-1 mb-12px rounded-8px border-1px pt-8px flex flex-col pb-16px bg-#F7F8FA border-#E6E6E8 border-solid">
<Tabs v-model={activeTab.value} onTabClick={handleTabClick} class="mb-16px"> <Tabs activeKey={activeTab.value} onChange={handleTabClick} class="mb-16px">
{TAB_LIST.map((item) => ( {TAB_LIST.map((item) => (
<TabPane <TabPane
key={item.value} key={item.value}
v-slots={{ tab={
title: () => (
<div class="flex items-center relative"> <div class="flex items-center relative">
<span>{item.label}</span> <span>{item.label}</span>
{ {
@ -454,8 +430,7 @@ export default {
// ) // )
} }
</div> </div>
), }
}}
/> />
))} ))}
</Tabs> </Tabs>

View File

@ -23,47 +23,36 @@
} }
.left-box { .left-box {
:deep(.arco-tabs) { :deep(.ant-form) {
.arco-tabs-nav {
.arco-tabs-tab {
height: 40px;
// padding: 0 8px;
margin: 0 16px;
}
&::before {
display: none;
}
}
.arco-tabs-content {
display: none;
}
}
:deep(.arco-form) {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.arco-form-item { .ant-form-item {
margin-bottom: 24px; margin-bottom: 24px;
.arco-form-item-label-col { .ant-form-item-label-col {
.arco-form-item-label { .ant-form-item-label {
color: #939499; color: #939499;
} }
} }
} }
.content-form-item { .content-form-item {
margin-bottom: 0; margin-bottom: 0;
.ant-row {
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.arco-form-item-wrapper-col { .ant-form-item-control-input {
flex: 1; flex: 1;
.arco-form-item-content-wrapper, .ant-form-item-control-input-content,
.arco-form-item-content, .ant-form-item-control-input-content,
.arco-textarea-wrapper { .ant-textarea-wrapper {
height: 100%; height: 100%;
} }
} }
} }
} }
}
.upload-box { .upload-box {
display: flex; display: flex;
width: 100px; width: 100px;

View File

@ -1,5 +1,5 @@
<script lang="jsx"> <script lang="jsx">
import { Image } from '@arco-design/web-vue'; import { Image } from 'ant-design-vue';
import { Swiper, SwiperSlide } from 'swiper/vue'; import { Swiper, SwiperSlide } from 'swiper/vue';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
@ -56,13 +56,13 @@ export default {
height={48} height={48}
preview={false} preview={false}
src={item.cover} src={item.cover}
class="!rounded-4px mr-8px" class="!rounded-4px"
fit="cover" fit="cover"
v-slots={{ v-slots={{
error: () => <img src={icon1} class="w-full h-full" />, error: () => <img src={icon1} class="w-full h-full" />,
}} }}
/> />
<div class="flex-1 overflow-hidden flex flex-col items-start"> <div class="flex-1 overflow-hidden flex flex-col items-start ml-8px">
<TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px`} /> <TextOverTips context={item.title} class={`cts !color-#211F24 title mb-4px`} />
<p class="cts">{`合规程度:${item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'}`}</p> <p class="cts">{`合规程度:${item.ai_review?.compliance_level ? `${item.ai_review?.compliance_level}%` : '-'}`}</p>
</div> </div>

View File

@ -1,5 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue'; import { Button, message } from 'ant-design-vue';
import { Spin } from 'ant-design-vue';
import CancelCheckModal from './cancel-check-modal.vue'; import CancelCheckModal from './cancel-check-modal.vue';
import CheckSuccessModal from './check-success-modal.vue'; import CheckSuccessModal from './check-success-modal.vue';
import HeaderCard from './components/header-card'; import HeaderCard from './components/header-card';
@ -135,14 +136,14 @@ export default {
}; };
const onSave = async () => { const onSave = async () => {
if (!selectCardInfo.value.title) { if (!selectCardInfo.value.title) {
AMessage.warning('标题不能为空'); message.warning('标题不能为空');
} }
contentCardRef.value?.validate().then(async () => { contentCardRef.value?.validate().then(async () => {
const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value); const { code, data } = await putWorkAuditsUpdate(selectCardInfo.value);
if (code === 200) { if (code === 200) {
isSaved.value = true; isSaved.value = true;
AMessage.success('当前内容稿件已保存'); message.success('当前内容稿件已保存');
} }
}); });
}; };
@ -181,13 +182,13 @@ export default {
const renderFooterRow = () => { const renderFooterRow = () => {
return ( return (
<> <>
<Button size="medium" type="outline" class="mr-12px" onClick={onExit}> <Button type="primary" ghost class="mr-12px" onClick={onExit}>
退出 退出
</Button> </Button>
<Button size="medium" type="outline" class="mr-12px" onClick={onSave}> <Button type="primary" ghost class="mr-12px" onClick={onSave}>
保存 保存
</Button> </Button>
<Button type="primary" size="medium" onClick={onSubmit} loading={submitLoading.value}> <Button type="primary" onClick={onSubmit} loading={submitLoading.value}>
{submitLoading.value ? '通过审核中...' : '通过审核'} {submitLoading.value ? '通过审核中...' : '通过审核'}
</Button> </Button>
</> </>

View File

@ -1,5 +1,5 @@
<template> <template>
<a-form-item field="files"> <FormItem name="files">
<template #label> <template #label>
<div class="flex items-center"> <div class="flex items-center">
<span class="cts !color-#211F24 mr-4px">图片</span> <span class="cts !color-#211F24 mr-4px">图片</span>
@ -23,32 +23,31 @@
/> />
</div> </div>
</div> </div>
<a-upload <Upload
v-if="files.length < 18" v-if="files.length < 18"
ref="uploadRef" ref="uploadRef"
action="/" action="/"
draggable :customRequest="(option) => emit('upload', option)"
:custom-request="(option) => emit('upload', option)"
accept=".jpg,.jpeg,.png,.gif,.webp" accept=".jpg,.jpeg,.png,.gif,.webp"
:show-file-list="false" :showUploadList="false"
multiple multiple
class="!flex !items-center" class="!flex !items-center"
:limit="18 - files.length"
> >
<template #upload-button> <template #default>
<div class="upload-box"> <div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" /> <icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传图片</span> <span class="cts !color-#211F24">上传图片</span>
</div> </div>
</template> </template>
</a-upload> </Upload>
</VueDraggable> </VueDraggable>
</div> </div>
</a-form-item> </FormItem>
</template> </template>
<script setup> <script setup>
import { VueDraggable } from 'vue-draggable-plus'; import { VueDraggable } from 'vue-draggable-plus';
import { FormItem, Upload } from 'ant-design-vue';
const props = defineProps({ const props = defineProps({
files: { files: {

View File

@ -1,6 +1,6 @@
<script lang="jsx"> <script lang="jsx">
import axios from 'axios'; import axios from 'axios';
import { Form, FormItem, Input, Textarea, Upload, Message as AMessage, Button } from '@arco-design/web-vue'; import { Button, Form, FormItem, Input, message, Upload } from 'ant-design-vue';
// import CommonSelect from '@/components/common-select'; // import CommonSelect from '@/components/common-select';
// import { VueDraggable } from 'vue-draggable-plus'; // import { VueDraggable } from 'vue-draggable-plus';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
@ -11,6 +11,8 @@ import { formatFileSize, getVideoInfo, formatDuration, formatUploadSpeed } from
import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants.ts'; import { EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants.ts';
import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common'; import { getImagePreSignedUrl, getVideoPreSignedUrl } from '@/api/all/common';
const { TextArea } = Input;
// import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png'; // import icon1 from '@/assets/img/creative-generation-workshop/icon-close.png';
// 表单验证规则 // 表单验证规则
@ -128,9 +130,7 @@ export default {
formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.UPLOADING; formData.value.videoInfo.uploadStatus = ENUM_UPLOAD_STATUS.UPLOADING;
emit('updateVideoInfo', formData.value.videoInfo); emit('updateVideoInfo', formData.value.videoInfo);
const { const { file } = option;
fileItem: { file },
} = option;
setVideoInfo(file); setVideoInfo(file);
const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) }); const response = await getVideoPreSignedUrl({ suffix: getFileExtension(file.name) });
@ -161,13 +161,11 @@ export default {
}; };
// 文件上传处理 // 文件上传处理
const uploadImage = async (option) => { const uploadImage = async (option) => {
const { const { file } = option;
fileItem: { file },
} = option;
// 验证文件数量 // 验证文件数量
if (formData.value.files?.length >= 18) { if (formData.value.files?.length >= 18) {
AMessage.error('最多只能上传18张图片'); message.error('最多只能上传18张图片');
return; return;
} }
const { name, size, type } = file; const { name, size, type } = file;
@ -189,15 +187,7 @@ export default {
}; };
const validate = () => { const validate = () => {
return new Promise((resolve, reject) => { return formRef.value?.validate();
formRef.value?.validate((errors) => {
if (errors) {
reject(formData.value);
} else {
resolve();
}
});
});
}; };
const resetForm = () => { const resetForm = () => {
@ -211,25 +201,18 @@ export default {
<Upload <Upload
ref={uploadRef} ref={uploadRef}
action="/" action="/"
draggable customRequest={uploadVideo}
custom-request={uploadVideo}
accept=".mp4,.mov,.avi,.flv,.wmv" accept=".mp4,.mov,.avi,.flv,.wmv"
show-file-list={false} showUploadList={false}
> >
{{ {formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT ? (
'upload-button': () => {
if (formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.DEFAULT) {
return (
<div class="upload-box"> <div class="upload-box">
<icon-plus size="14" class="mb-16px color-#3C4043" /> <icon-plus size="14" class="mb-16px color-#3C4043" />
<span class="cts !color-#211F24">上传视频</span> <span class="cts !color-#211F24">上传视频</span>
</div> </div>
); ) : (
} else { <Button type="text">替换视频</Button>
return <Button type="text">替换视频</Button>; )}
}
},
}}
</Upload> </Upload>
); );
}; };
@ -238,7 +221,7 @@ export default {
const isEnd = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.END; const isEnd = formData.value.videoInfo.uploadStatus === ENUM_UPLOAD_STATUS.END;
return ( return (
<FormItem <FormItem
field="files" name="files"
v-slots={{ v-slots={{
label: () => ( label: () => (
<div class="flex items-center"> <div class="flex items-center">
@ -315,30 +298,31 @@ export default {
return () => ( return () => (
<Form ref={formRef} model={formData.value} rules={props.rules} layout="vertical" auto-label-width> <Form ref={formRef} model={formData.value} rules={props.rules} layout="vertical" auto-label-width>
<FormItem label="标题" field="title" required> <FormItem label="标题" name="title" required>
<Input <Input
v-model={formData.value.title} v-model:value={formData.value.title}
onInput={() => { onInput={() => {
console.log('onInput');
onChange(); onChange();
emit('reValidate'); emit('reValidate');
}} }}
placeholder="请输入标题" placeholder="请输入标题"
size="large" size="large"
class="!w-500px" class="!w-500px"
maxLength={30} maxlength={30}
show-word-limit showCount
/> />
</FormItem> </FormItem>
<FormItem label="作品描述" field="content"> <FormItem label="作品描述" name="content">
<Textarea <TextArea
v-model={formData.value.content} v-model:value={formData.value.content}
onInput={onChange} onInput={onChange}
placeholder="请输入作品描述" placeholder="请输入作品描述"
size="large" size="large"
class="textarea-box !w-784px" class="textarea-box !w-784px"
show-word-limit showCount
max-length={1000} maxlength={1000}
/> />
</FormItem> </FormItem>
{isVideo.value ? ( {isVideo.value ? (
@ -352,7 +336,7 @@ export default {
/> />
)} )}
{/* <FormItem label="所属项目" field="project_ids"> {/* <FormItem label="所属项目" name="project_ids">
<CommonSelect <CommonSelect
v-model={formData.value.project_ids} v-model={formData.value.project_ids}
onChange={() => emit('change')} onChange={() => emit('change')}

View File

@ -31,8 +31,6 @@
} }
} }
.textarea-box { .textarea-box {
:deep(.arco-textarea) {
height: 140px; height: 140px;
max-height: 298px; max-height: 298px;
} }
}

View File

@ -1,5 +1,5 @@
export const INITIAL_FORM = { export const INITIAL_FORM = {
audit_status: '', audit_status: undefined,
sort_column: undefined, sort_column: undefined,
sort_order: undefined, sort_order: undefined,
}; };

View File

@ -1,22 +1,15 @@
<script lang="jsx"> <script lang="jsx">
import { import { Button, Modal, Tooltip, Table, Pagination } from 'ant-design-vue';
Input,
Table,
Modal,
TableColumn,
Checkbox,
Pagination,
Button,
Tooltip,
Notification,
} from '@arco-design/web-vue';
import CommonSelect from '@/components/common-select'; import CommonSelect from '@/components/common-select';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal'; import ShareModal from '@/views/material-center/components/finished-products/manuscript/components/share-manuscript-modal/share-modal';
import { INITIAL_FORM, TABLE_COLUMNS } from './constants'; import { INITIAL_FORM, TABLE_COLUMNS } from './constants';
import { formatTableField, exactFormatTime } from '@/utils/tools'; import { formatTableField, exactFormatTime } from '@/utils/tools';
import { CHECK_STATUS, EnumManuscriptType } from '@/views/material-center/components/finished-products/manuscript/list/constants'; import {
CHECK_STATUS,
EnumManuscriptType,
} from '@/views/material-center/components/finished-products/manuscript/list/constants';
import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination'; import { useTableSelectionWithPagination } from '@/hooks/useTableSelectionWithPagination';
import { getWorksPage, getWriterLinksGenerate } from '@/api/all/generationWorkshop.ts'; import { getWorksPage, getWriterLinksGenerate } from '@/api/all/generationWorkshop.ts';
@ -31,8 +24,6 @@ export default {
dataSource, dataSource,
pageInfo, pageInfo,
onPageChange, onPageChange,
onPageSizeChange,
rowSelection,
handleSelect, handleSelect,
handleSelectAll, handleSelectAll,
DEFAULT_PAGE_INFO, DEFAULT_PAGE_INFO,
@ -40,9 +31,6 @@ export default {
onPageChange: () => { onPageChange: () => {
getData(); getData();
}, },
onPageSizeChange: () => {
getData();
},
}); });
const visible = ref(false); const visible = ref(false);
const query = ref(cloneDeep(INITIAL_FORM)); const query = ref(cloneDeep(INITIAL_FORM));
@ -86,42 +74,44 @@ export default {
reset(); reset();
}; };
const renderColumn = () => { // const renderColumn = () => {
return TABLE_COLUMNS.map((column) => ( // return TABLE_COLUMNS.map((column) => (
<TableColumn // <TableColumn
key={column.dataIndex} // key={column.dataIndex}
data-index={column.dataIndex} // data-index={column.dataIndex}
fixed={column.fixed} // fixed={column.fixed}
width={column.width} // width={column.width}
min-width={column.minWidth} // min-width={column.minWidth}
sortable={column.sortable} // sortable={column.sortable}
align={column.align} // align={column.align}
ellipsis // ellipsis
tooltip // tooltip
v-slots={{ // v-slots={{
title: () => ( // title: () => (
<div class="flex items-center"> // <>
<span class="cts mr-4px">{column.title}</span> // <span class="cts mr-4px">{column.title}</span>
{column.tooltip && ( // {column.tooltip && (
<Tooltip content={column.tooltip} position="top"> // <Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} /> // <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip> // </Tooltip>
)} // )}
</div> // </>
), // ),
cell: ({ record }) => renderCell(record), // cell: ({ record }) => renderCell(record),
}} // }}
/> // />
)); // ));
}; // };
const onShare = () => { const onShare = () => {
shareModalRef.value?.open(selectedRowKeys.value); shareModalRef.value?.open(selectedRowKeys.value);
}; };
const handleSorterChange = (column, order) => { const handleSorterChange = (pagination, filters, sorter) => {
query.value.sort_column = column; if (sorter && !Array.isArray(sorter) && sorter.columnKey) {
query.value.sort_order = order === 'ascend' ? 'asc' : 'desc'; query.value.sort_column = sorter.columnKey;
query.value.sort_order = sorter.order === 'ascend' ? 'asc' : 'desc';
reload(); reload();
}
}; };
const getStatusInfo = (audit_status) => { const getStatusInfo = (audit_status) => {
return CHECK_STATUS.find((v) => v.id === audit_status) ?? {}; return CHECK_STATUS.find((v) => v.id === audit_status) ?? {};
@ -132,12 +122,13 @@ export default {
return () => ( return () => (
<> <>
<Modal <Modal
v-model:visible={visible.value} v-model:open={visible.value}
title="分享内容稿件" title="分享内容稿件"
width="920px" width="920px"
onClose={onClose} onCancel={onClose}
unmount-on-close centered
modal-class="share-manuscript-modal" destroyOnClose
wrapClassName="share-manuscript-modal"
v-slots={{ v-slots={{
footer: () => ( footer: () => (
<div class="flex justify-between w-full items-center"> <div class="flex justify-between w-full items-center">
@ -145,16 +136,8 @@ export default {
已选择 <span class="cts color-#211F24 bold">{selectedRows.value.length}</span> 已选择 <span class="cts color-#211F24 bold">{selectedRows.value.length}</span>
</p> </p>
<div class="flex items-center"> <div class="flex items-center">
<Button size="medium" onClick={onClose}> <Button onClick={onClose}>取消</Button>
取消 <Button type="primary" class="ml-16px" onClick={onShare} disabled={!selectedRows.value.length}>
</Button>
<Button
type="primary"
class="ml-16px"
size="medium"
onClick={onShare}
disabled={!selectedRows.value.length}
>
分享 分享
</Button> </Button>
</div> </div>
@ -176,45 +159,44 @@ export default {
</div> </div>
<Table <Table
ref={tableRef} ref={tableRef}
data={dataSource.value} dataSource={dataSource.value}
row-key="id" rowKey="id"
column-resizable rowSelection={{
row-selection={rowSelection.value} selectedRowKeys: selectedRowKeys.value,
selected-keys={selectedRowKeys.value} onSelect: handleSelect,
onSelectAll: handleSelectAll,
}}
pagination={false} pagination={false}
scroll={{ x: '100%', y: '100%' }} scroll={{ x: '100%', y: '100%' }}
class="overflow-hidden" class="overflow-hidden"
bordered bordered
onSorterChange={handleSorterChange} showSorterTooltip={false}
onSelect={handleSelect} onChange={handleSorterChange}
onSelectAll={handleSelectAll}
v-slots={{ v-slots={{
empty: () => <NoData />, emptyText: () => <NoData />,
columns: () => ( }}
<> >
{TABLE_COLUMNS.map((column) => ( {TABLE_COLUMNS.map((column) => (
<TableColumn <Table.Column
key={column.dataIndex} key={column.dataIndex}
data-index={column.dataIndex} dataIndex={column.dataIndex}
fixed={column.fixed} fixed={column.fixed}
width={column.width} width={column.width}
min-width={column.minWidth} minWidth={column.minWidth}
sortable={column.sortable} sorter={column.sortable}
align={column.align} align={column.align}
ellipsis ellipsis
tooltip title={() => (
v-slots={{ <>
title: () => (
<div class="flex items-center">
<span class="cts mr-4px bold color-#211F24">{column.title}</span> <span class="cts mr-4px bold color-#211F24">{column.title}</span>
{column.tooltip && ( {column.tooltip && (
<Tooltip content={column.tooltip} position="top"> <Tooltip title={column.tooltip} placement="top">
<IconQuestionCircle class="tooltip-icon color-#737478" size={16} /> <IconQuestionCircle class="tooltip-icon color-#737478" size={16} />
</Tooltip> </Tooltip>
)} )}
</div> </>
), )}
cell: ({ record }) => { customRender={({ record }) => {
if (column.dataIndex === 'audit_status') { if (column.dataIndex === 'audit_status') {
return ( return (
<div <div
@ -227,7 +209,7 @@ export default {
</div> </div>
); );
} else if (column.dataIndex === 'title') { } else if (column.dataIndex === 'title') {
return <TextOverTips context={record.title} />; return <TextOverTips context={record.title} class="!text-12px" />;
} else if (column.dataIndex === 'type') { } else if (column.dataIndex === 'type') {
return ( return (
<div class="flex items-center"> <div class="flex items-center">
@ -249,36 +231,27 @@ export default {
} else if (column.dataIndex === 'last_modified_at') { } else if (column.dataIndex === 'last_modified_at') {
return ( return (
<span class="cts num"> <span class="cts num">
{exactFormatTime( {exactFormatTime(record.last_modified_at, 'YYYY-MM-DD HH:mm:ss', 'YYYY-MM-DD HH:mm:ss')}
record.last_modified_at,
'YYYY-MM-DD HH:mm:ss',
'YYYY-MM-DD HH:mm:ss',
)}
</span> </span>
); );
} else { } else {
return formatTableField(column, record, true); return formatTableField(column, record, true);
} }
},
}} }}
/> />
))} ))}
</> </Table>
),
}}
/>
{pageInfo.value.total > 0 && ( {pageInfo.value.total > 0 && (
<div class="flex justify-end mt-16px"> <div class="flex justify-end mt-16px">
<Pagination <Pagination
total={pageInfo.value.total} total={pageInfo.value.total}
size="mini" size="small"
show-total showTotal={(total, range) => `${total}`}
show-jumper showQuickJumper
show-page-size showSizeChanger
current={pageInfo.value.page} current={pageInfo.value.page}
page-size={pageInfo.value.page_size} pageSize={pageInfo.value.page_size}
onChange={onPageChange} onChange={onPageChange}
onPageSizeChange={onPageSizeChange}
/> />
</div> </div>
)} )}

View File

@ -1,5 +1,5 @@
<script lang="jsx"> <script lang="jsx">
import { Modal, Form, FormItem, Input, Button, Message as AMessage } from '@arco-design/web-vue'; import { Button, Modal, Form, FormItem, Input, Tooltip, message } from 'ant-design-vue';
import CommonSelect from '@/components/common-select'; import CommonSelect from '@/components/common-select';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
@ -65,8 +65,7 @@ export default {
}; };
const onGenerateLink = () => { const onGenerateLink = () => {
formRef.value.validate().then(async (errors) => { formRef.value.validate().then(async () => {
if (!errors) {
try { try {
loading.value = true; loading.value = true;
const { code, data } = await postShareLinksGenerate(formData.value); const { code, data } = await postShareLinksGenerate(formData.value);
@ -77,13 +76,12 @@ export default {
path: `/explore/list/${data.code}`, path: `/explore/list/${data.code}`,
}).href; }).href;
copy(generateFullUrl(url)); copy(generateFullUrl(url));
AMessage.success('链接已复制!'); message.success('链接已复制!');
emit('close'); emit('close');
} }
} finally { } finally {
loading.value = false; loading.value = false;
} }
}
}); });
}; };
const open = (workIds) => { const open = (workIds) => {
@ -96,27 +94,33 @@ export default {
}); });
return () => ( return () => (
<Modal <Modal
v-model:visible={visible.value} v-model:open={visible.value}
title="分享内容稿件" title="分享内容稿件"
width="480px" width="480px"
onClose={onClose} onCancel={onClose}
unmount-on-close destroyOnClose
centered
auto-label-width auto-label-width
v-slots={{ v-slots={{
footer: () => ( footer: () => (
<> <>
<Button size="medium" onClick={onClose}> <Button onClick={onClose}>取消</Button>
取消 <Button type="primary" class="ml-16px" onClick={onGenerateLink} disabled={loading.value}>
</Button>
<Button type="primary" class="ml-16px" size="medium" onClick={onGenerateLink} disabled={loading.value}>
{loading.value ? '生成中...' : '生成链接'} {loading.value ? '生成中...' : '生成链接'}
</Button> </Button>
</> </>
), ),
}} }}
> >
<Form ref={formRef} rules={rules} model={formData.value} auto-label-width> <Form
<FormItem label="有效期" prop="days" row-class="!items-center"> ref={formRef}
rules={rules}
model={formData.value}
labelAlign="right"
labelCol={{ span: 5 }}
wrapperCol={{ span: 19 }}
>
<FormItem label="有效期" name="days" row-class="!items-center">
<CommonSelect <CommonSelect
v-model={formData.value.days} v-model={formData.value.days}
options={OPTIONS} options={OPTIONS}
@ -128,21 +132,20 @@ export default {
/> />
</FormItem> </FormItem>
<FormItem <FormItem
label="分享对象" name="receiver"
prop="receiver"
row-class="!items-center" row-class="!items-center"
v-slots={{ v-slots={{
label: () => ( label: () => (
<div class="flex items-center"> <div class="flex items-center">
<span>分享对象</span> <span>分享对象</span>
<a-tooltip content="可填写客户名称、昵称等,非必填" position="top"> <Tooltip title="可填写客户名称、昵称等,非必填" placement="top">
<icon-question-circle class="tooltip-icon color-#737478 ml-4px" size="14" /> <icon-question-circle class="tooltip-icon color-#737478 ml-4px" size="14" />
</a-tooltip> </Tooltip>
</div> </div>
), ),
}} }}
> >
<Input v-model={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" /> <Input v-model:value={formData.value.receiver} class="!w-240px" size="large" placeholder="请输入分享对象" />
</FormItem> </FormItem>
</Form> </Form>
</Modal> </Modal>

View File

@ -18,49 +18,36 @@
font-size: 14px; font-size: 14px;
} }
} }
.arco-modal-body { .ant-modal-body {
height: 464px; height: 464px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
.arco-scrollbar-track { .ant-scrollbar-track {
display: none !important; display: none !important;
} }
.arco-table { .ant-table {
.arco-table-container { .ant-table-thead {
.arco-table-element { .ant-table-cell {
thead {
.arco-table-tr {
.arco-table-th {
.arco-table-cell {
padding: 10px 16px !important; padding: 10px 16px !important;
} }
} }
} .ant-table-body {
} .ant-table-cell {
tbody { padding: 6px 16px !important;
.arco-table-tr { .ant-table-cell-content {
.arco-table-td {
.arco-table-cell {
padding: 6px 16px;
.arco-table-cell-content,
.arco-table-td-content {
font-size: 12px; font-size: 12px;
line-height: 20px; line-height: 20px;
} }
} }
} }
} }
} .ant-pagination {
} .ant-pagination-total-text,
} .ant-pagination-options-quick-jumper {
}
.arco-pagination {
.arco-pagination-total,
.arco-pagination-jumper-prepend {
font-size: 14px; font-size: 14px;
} }
.arco-pagination-jumper-prepend { .ant-pagination-options-quick-jumper {
font-family: $font-family-regular; font-family: $font-family-regular;
} }
} }

View File

@ -1,16 +1,5 @@
<script lang="jsx"> <script lang="jsx">
import { import { Modal, Button, Form, FormItem, RadioGroup, Radio, Input, message, Upload } from 'ant-design-vue';
Modal,
Form,
FormItem,
Input,
RadioGroup,
Radio,
Upload,
Button,
Message as AMessage,
Textarea,
} from '@arco-design/web-vue';
import { useClipboard } from '@vueuse/core'; import { useClipboard } from '@vueuse/core';
import { getWriterLinksGenerate, getTemplateUrl, postWorksByLink, postWorksByFile } from '@/api/all/generationWorkshop'; import { getWriterLinksGenerate, getTemplateUrl, postWorksByLink, postWorksByFile } from '@/api/all/generationWorkshop';
import { generateFullUrl } from '@/utils/tools'; import { generateFullUrl } from '@/utils/tools';
@ -18,6 +7,9 @@ import { slsWithCatch } from '@/utils/stroage.ts';
import TextOverTips from '@/components/text-over-tips'; import TextOverTips from '@/components/text-over-tips';
import icon1 from '@/assets/img/media-account/icon-feedback-fail.png'; import icon1 from '@/assets/img/media-account/icon-feedback-fail.png';
import icon2 from '@/assets/img/media-account/icon-download.png';
const { TextArea } = Input;
// 状态枚举 // 状态枚举
const TASK_STATUS = { const TASK_STATUS = {
@ -41,7 +33,7 @@ const INITIAL_FORM = {
export default { export default {
setup(props, { emit, expose }) { setup(props, { emit, expose }) {
const update = inject('update'); // const update = inject('update');
const router = useRouter(); const router = useRouter();
// 响应式状态 // 响应式状态
@ -119,8 +111,7 @@ export default {
handleHandwriteSubmit(); handleHandwriteSubmit();
return; return;
} }
formRef.value?.validate(async (errors) => { formRef.value?.validate().then(async () => {
if (!errors) {
taskStatus.value = TASK_STATUS.LOADING; taskStatus.value = TASK_STATUS.LOADING;
const { link } = form.value; const { link } = form.value;
const { code, data } = await postWorksByLink({ link }); const { code, data } = await postWorksByLink({ link });
@ -128,7 +119,6 @@ export default {
taskStatus.value = TASK_STATUS.SUCCESS; taskStatus.value = TASK_STATUS.SUCCESS;
works.value = data ? [data] : []; works.value = data ? [data] : [];
} }
}
}); });
}, 300); }, 300);
@ -140,27 +130,25 @@ export default {
// 手写提交处理 // 手写提交处理
const handleHandwriteSubmit = () => { const handleHandwriteSubmit = () => {
if (!form.value.writerLink) { if (!form.value.writerLink) {
AMessage.warning('请输入上传链接!'); message.warning('请输入上传链接!');
return; return;
} }
copy(form.value.writerLink); copy(form.value.writerLink);
AMessage.success('复制成功!'); message.success('复制成功!');
onClose(); onClose();
}; };
// 取消上传 // 取消上传
const onCancelUpload = () => { const onCancelUpload = () => {
taskStatus.value = TASK_STATUS.DEFAULT; taskStatus.value = TASK_STATUS.DEFAULT;
AMessage.info('已取消上传'); message.info('已取消上传');
}; };
// 文件上传处理 // 文件上传处理
const handleUpload = async (option) => { const handleUpload = async (option) => {
taskStatus.value = TASK_STATUS.LOADING; taskStatus.value = TASK_STATUS.LOADING;
const { const { file } = option;
fileItem: { file },
} = option;
const formData = new FormData(); const formData = new FormData();
formData.append('file', file); formData.append('file', file);
@ -222,9 +210,9 @@ export default {
// 渲染链接上传表单 // 渲染链接上传表单
const renderLinkForm = () => ( const renderLinkForm = () => (
<FormItem label="链接地址" field="link" required> <FormItem label="链接地址" name="link" required>
<Textarea <TextArea
v-model={form.value.link} v-model:value={form.value.link}
size="large" size="large"
placeholder="请输入飞书链接地址" placeholder="请输入飞书链接地址"
autoSize={{ minRows: 5, maxRows: 8 }} autoSize={{ minRows: 5, maxRows: 8 }}
@ -234,8 +222,8 @@ export default {
// 渲染手写上传表单 // 渲染手写上传表单
const renderHandwriteForm = () => ( const renderHandwriteForm = () => (
<FormItem label="上传链接" field="writerLink"> <FormItem label="上传链接" name="writerLink">
<Input v-model={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" /> <Input v-model:value={form.value.writerLink} placeholder="请输入上传链接" disabled size="large" />
</FormItem> </FormItem>
); );
@ -245,24 +233,20 @@ export default {
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">
<Upload <Upload
action="/" action="/"
draggable
multiple multiple
customRequest={handleUpload} customRequest={handleUpload}
class="w-full"
accept=".xlsx,.xls,.docx,.doc,.mp4,.mov,.avi,.flv,.wmv,.m4v" accept=".xlsx,.xls,.docx,.doc,.mp4,.mov,.avi,.flv,.wmv,.m4v"
show-file-list={false} showUploadList={false}
> >
{{
'upload-button': () => (
<div class="upload-box"> <div class="upload-box">
<icon-plus size="14" class="mb-16px" /> <icon-plus size="14" class="mb-16px" />
<span class="text mb-4px">点击或拖拽文件到此处上传</span> <span class="text mb-4px">点击或拖拽文件到此处上传</span>
<span class="tip">支持文档文本+, 视频批量上传</span> <span class="tip">支持文档文本+, 视频批量上传</span>
</div> </div>
),
}}
</Upload> </Upload>
<div class="flex items-center cursor-pointer mt-8px" onClick={handleDownloadTemplate}> <div class="flex items-center cursor-pointer mt-8px" onClick={handleDownloadTemplate}>
<icon-download size="14" class="mr-4px !color-#6D4CFE" /> <img src={icon2} width="16" height="16" class="mr-4px" />
<span class="cts color-#6D4CFE">下载示例文档</span> <span class="cts color-#6D4CFE">下载示例文档</span>
</div> </div>
</div> </div>
@ -332,36 +316,30 @@ export default {
const renderFooterButtons = () => { const renderFooterButtons = () => {
const buttonMap = { const buttonMap = {
[TASK_STATUS.LOADING]: () => ( [TASK_STATUS.LOADING]: () => (
<Button type="primary" size="medium" onClick={onCancelUpload}> <Button type="primary" onClick={onCancelUpload}>
取消上传 取消上传
</Button> </Button>
), ),
[TASK_STATUS.DEFAULT]: () => ( [TASK_STATUS.DEFAULT]: () => (
<> <>
<Button size="medium" onClick={onClose}> <Button onClick={onClose}>取消</Button>
取消 <Button type="primary" onClick={onSubmit}>
</Button>
<Button type="primary" size="medium" onClick={onSubmit}>
{isHandwrite.value ? '复制链接' : '确认'} {isHandwrite.value ? '复制链接' : '确认'}
</Button> </Button>
</> </>
), ),
[TASK_STATUS.FAILED]: () => ( [TASK_STATUS.FAILED]: () => (
<> <>
<Button size="medium" onClick={onClose}> <Button onClick={onClose}>取消</Button>
取消 <Button type="primary" onClick={onClose}>
</Button>
<Button type="primary" size="medium" onClick={onClose}>
重新上传 重新上传
</Button> </Button>
</> </>
), ),
[TASK_STATUS.SUCCESS]: () => ( [TASK_STATUS.SUCCESS]: () => (
<> <>
<Button size="medium" onClick={onClose}> <Button onClick={onClose}>取消</Button>
取消 <Button type="primary" onClick={goUpload}>
</Button>
<Button type="primary" size="medium" onClick={goUpload}>
确认 确认
</Button> </Button>
</> </>
@ -375,17 +353,16 @@ export default {
return () => ( return () => (
<Modal <Modal
v-model:visible={visible.value} centered
v-model:open={visible.value}
title={getTitle()} title={getTitle()}
modal-class="upload-manuscript-modal" wrapClassName="upload-manuscript-modal"
width="500px" width="500px"
mask-closable={false} maskClosable={false}
unmount-on-close destroyOnClose
onClose={onClose} centered
footer={!(isDefault.value && isLocal.value)} onCancel={onClose}
v-slots={{ footer={isDefault.value && isLocal.value ? null : renderFooterButtons()}
footer: () => renderFooterButtons(),
}}
> >
<Form <Form
ref={formRef} ref={formRef}
@ -398,7 +375,7 @@ export default {
> >
{isDefault.value && ( {isDefault.value && (
<FormItem label="上传方式"> <FormItem label="上传方式">
<RadioGroup v-model={uploadType.value} onChange={onUploadTypeChange}> <RadioGroup v-model:value={uploadType.value} onChange={onUploadTypeChange}>
<Radio value={UPLOAD_TYPE.LINK}>链接上传</Radio> <Radio value={UPLOAD_TYPE.LINK}>链接上传</Radio>
<Radio value={UPLOAD_TYPE.LOCAL}>本地上传</Radio> <Radio value={UPLOAD_TYPE.LOCAL}>本地上传</Radio>
<Radio value={UPLOAD_TYPE.HANDWRITE}>写手上传</Radio> <Radio value={UPLOAD_TYPE.HANDWRITE}>写手上传</Radio>

View File

@ -28,4 +28,8 @@
border: 1px dashed var(--Border-1, #d7d7d9); border: 1px dashed var(--Border-1, #d7d7d9);
background: var(--BG-200, #f2f3f5); background: var(--BG-200, #f2f3f5);
} }
.ant-upload {
cursor: pointer;
width: 100%;
}
} }

View File

@ -1,5 +1,5 @@
<script lang="jsx"> <script lang="jsx">
import { Button, Message as AMessage, Spin } from '@arco-design/web-vue'; import { Button, Spin, message } from 'ant-design-vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { AuditStatus } from '@/views/material-center/components/finished-products/constants'; import { AuditStatus } from '@/views/material-center/components/finished-products/constants';
@ -119,12 +119,12 @@ export default {
return ( return (
<> <>
<Button size="medium" type="outline" class="mr-12px" onClick={onBack}> <Button type="primary" ghost class="mr-12px" onClick={onBack}>
退出 退出
</Button> </Button>
<Button <Button
size="medium" type="primary"
type="outline" ghost
class="mr-12px" class="mr-12px"
onClick={() => onClick={() =>
router.push({ router.push({
@ -138,7 +138,7 @@ export default {
编辑 编辑
</Button> </Button>
{audit_status !== AuditStatus.Passed && ( {audit_status !== AuditStatus.Passed && (
<Button type="primary" size="medium" onClick={_fn}> <Button type="primary" onClick={_fn}>
去审核 去审核
</Button> </Button>
)} )}
@ -157,7 +157,7 @@ export default {
}); });
return () => ( return () => (
<Spin loading={loading.value} class="manuscript-detail-wrap" size={50}> <Spin spinning={loading.value} wrapperClassName="manuscript-detail-wrap" size="large">
<div class="h-full w-full flex flex-col"> <div class="h-full w-full flex flex-col">
<div class="flex items-center mb-8px"> <div class="flex items-center mb-8px">
<span class="cts color-#4E5969 cursor-pointer" onClick={onBack}> <span class="cts color-#4E5969 cursor-pointer" onClick={onBack}>

View File

@ -1,18 +1,19 @@
<template> <template>
<a-modal v-model:visible="visible" title="退出编辑" width="480px" @close="onClose"> <Modal v-model:open="visible" title="退出编辑" centered width="480px" @cancel="onClose">
<div class="flex items-center"> <div class="flex items-center">
<img :src="icon1" width="20" height="20" class="mr-12px" /> <img :src="icon1" width="20" height="20" class="mr-12px" />
<span>内容已修改尚未保存若退出编辑本次修改将不保存</span> <span>内容已修改尚未保存若退出编辑本次修改将不保存</span>
</div> </div>
<template #footer> <template #footer>
<a-button size="medium" @click="onClose">继续编辑</a-button> <Button size="medium" @click="onClose">继续编辑</Button>
<a-button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</a-button> <Button type="primary" class="ml-8px" size="medium" @click="onConfirm">确认退出</Button>
</template> </template>
</a-modal> </Modal>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { Button, Modal } from 'ant-design-vue';
import icon1 from '@/assets/img/media-account/icon-warn-1.png'; import icon1 from '@/assets/img/media-account/icon-warn-1.png';
const router = useRouter(); const router = useRouter();

Some files were not shown because too many files have changed in this diff Show More