mirror of
https://gitee.com/myxzgzs/boyuehasfj-vue3-html.git
synced 2025-08-07 22:52:42 +08:00
boyuehasfj-vue3-html
This commit is contained in:
parent
320265cc07
commit
2bf74587de
6
env.d.ts
vendored
6
env.d.ts
vendored
@ -1 +1,7 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.9.0",
|
||||
"element-plus": "^2.7.7",
|
||||
"pinia": "^3.0.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"vue": "^3.5.13",
|
||||
|
172
pnpm-lock.yaml
generated
172
pnpm-lock.yaml
generated
@ -11,6 +11,9 @@ importers:
|
||||
axios:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
element-plus:
|
||||
specifier: ^2.7.7
|
||||
version: 2.10.3(vue@3.5.16(typescript@5.8.3))
|
||||
pinia:
|
||||
specifier: ^3.0.1
|
||||
version: 3.0.2(typescript@5.8.3)(vue@3.5.16(typescript@5.8.3))
|
||||
@ -218,6 +221,15 @@ packages:
|
||||
resolution: {integrity: sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@ctrl/tinycolor@3.6.1':
|
||||
resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@element-plus/icons-vue@2.3.1':
|
||||
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
|
||||
engines: {node: '>=12'}
|
||||
@ -394,6 +406,15 @@ packages:
|
||||
resolution: {integrity: sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@floating-ui/core@1.7.2':
|
||||
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
|
||||
|
||||
'@floating-ui/utils@0.2.10':
|
||||
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
||||
|
||||
'@humanfs/core@0.19.1':
|
||||
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||
engines: {node: '>=18.18.0'}
|
||||
@ -578,6 +599,9 @@ packages:
|
||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.7':
|
||||
resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==}
|
||||
|
||||
'@tsconfig/node22@22.0.2':
|
||||
resolution: {integrity: sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==}
|
||||
|
||||
@ -587,12 +611,21 @@ packages:
|
||||
'@types/json-schema@7.0.15':
|
||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||
|
||||
'@types/lodash@4.17.20':
|
||||
resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==}
|
||||
|
||||
'@types/node@22.15.29':
|
||||
resolution: {integrity: sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==}
|
||||
|
||||
'@types/qrcode@1.5.5':
|
||||
resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
|
||||
|
||||
'@types/web-bluetooth@0.0.16':
|
||||
resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.33.0':
|
||||
resolution: {integrity: sha512-CACyQuqSHt7ma3Ns601xykeBK/rDeZa3w6IS6UtMQbixO5DWy+8TilKkviGDH6jtWCo8FGRKEK5cLLkPvEammQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@ -767,6 +800,15 @@ packages:
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
'@vueuse/core@9.13.0':
|
||||
resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==}
|
||||
|
||||
'@vueuse/metadata@9.13.0':
|
||||
resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==}
|
||||
|
||||
'@vueuse/shared@9.13.0':
|
||||
resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==}
|
||||
|
||||
acorn-jsx@5.3.2:
|
||||
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||
peerDependencies:
|
||||
@ -798,6 +840,9 @@ packages:
|
||||
argparse@2.0.1:
|
||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||
|
||||
async-validator@4.2.5:
|
||||
resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
|
||||
|
||||
asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
|
||||
@ -887,6 +932,9 @@ packages:
|
||||
csstype@3.1.3:
|
||||
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
|
||||
|
||||
dayjs@1.11.13:
|
||||
resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==}
|
||||
|
||||
de-indent@1.0.2:
|
||||
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
|
||||
|
||||
@ -932,6 +980,11 @@ packages:
|
||||
electron-to-chromium@1.5.161:
|
||||
resolution: {integrity: sha512-hwtetwfKNZo/UlwHIVBlKZVdy7o8bIZxxKs0Mv/ROPiQQQmDgdm5a+KvKtBsxM8ZjFzTaCeLoodZ8jiBE3o9rA==}
|
||||
|
||||
element-plus@2.10.3:
|
||||
resolution: {integrity: sha512-OLpf0iekuvWJrz1+H9ybvem6TYTKSNk6L1QDA3tYq2YWbogKXJnWpHG1UAGKR1B7gx+vUH7M15VIH3EijE9Kgw==}
|
||||
peerDependencies:
|
||||
vue: ^3.2.0
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
@ -967,6 +1020,9 @@ packages:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
escape-html@1.0.3:
|
||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||
|
||||
escape-string-regexp@4.0.0:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
@ -1315,6 +1371,16 @@ packages:
|
||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
lodash-es@4.17.21:
|
||||
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
|
||||
|
||||
lodash-unified@1.0.3:
|
||||
resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==}
|
||||
peerDependencies:
|
||||
'@types/lodash-es': '*'
|
||||
lodash: '*'
|
||||
lodash-es: '*'
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
@ -1331,6 +1397,9 @@ packages:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
memoize-one@6.0.0:
|
||||
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
|
||||
|
||||
memorystream@0.3.1:
|
||||
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
@ -1387,6 +1456,9 @@ packages:
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
normalize-wheel-es@1.2.0:
|
||||
resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==}
|
||||
|
||||
npm-normalize-package-bin@4.0.0:
|
||||
resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==}
|
||||
engines: {node: ^18.17.0 || >=20.5.0}
|
||||
@ -1744,6 +1816,17 @@ packages:
|
||||
vscode-uri@3.1.0:
|
||||
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
|
||||
|
||||
vue-demi@0.14.10:
|
||||
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
|
||||
engines: {node: '>=12'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@vue/composition-api': ^1.0.0-rc.1
|
||||
vue: ^3.0.0-0 || ^2.6.0
|
||||
peerDependenciesMeta:
|
||||
'@vue/composition-api':
|
||||
optional: true
|
||||
|
||||
vue-eslint-parser@10.1.3:
|
||||
resolution: {integrity: sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
@ -2014,6 +2097,12 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.27.1
|
||||
|
||||
'@ctrl/tinycolor@3.6.1': {}
|
||||
|
||||
'@element-plus/icons-vue@2.3.1(vue@3.5.16(typescript@5.8.3))':
|
||||
dependencies:
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
|
||||
'@esbuild/aix-ppc64@0.21.5':
|
||||
optional: true
|
||||
|
||||
@ -2127,6 +2216,17 @@ snapshots:
|
||||
'@eslint/core': 0.14.0
|
||||
levn: 0.4.1
|
||||
|
||||
'@floating-ui/core@1.7.2':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/dom@1.7.2':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.2
|
||||
'@floating-ui/utils': 0.2.10
|
||||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
'@humanfs/node@0.16.6':
|
||||
@ -2245,12 +2345,20 @@ snapshots:
|
||||
|
||||
'@sindresorhus/merge-streams@4.0.0': {}
|
||||
|
||||
'@sxzz/popperjs-es@2.11.7': {}
|
||||
|
||||
'@tsconfig/node22@22.0.2': {}
|
||||
|
||||
'@types/estree@1.0.7': {}
|
||||
|
||||
'@types/json-schema@7.0.15': {}
|
||||
|
||||
'@types/lodash-es@4.17.12':
|
||||
dependencies:
|
||||
'@types/lodash': 4.17.20
|
||||
|
||||
'@types/lodash@4.17.20': {}
|
||||
|
||||
'@types/node@22.15.29':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
@ -2259,6 +2367,8 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/node': 22.15.29
|
||||
|
||||
'@types/web-bluetooth@0.0.16': {}
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.33.0(@typescript-eslint/parser@8.33.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@ -2528,6 +2638,25 @@ snapshots:
|
||||
typescript: 5.8.3
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
|
||||
'@vueuse/core@9.13.0(vue@3.5.16(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.16
|
||||
'@vueuse/metadata': 9.13.0
|
||||
'@vueuse/shared': 9.13.0(vue@3.5.16(typescript@5.8.3))
|
||||
vue-demi: 0.14.10(vue@3.5.16(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
'@vueuse/metadata@9.13.0': {}
|
||||
|
||||
'@vueuse/shared@9.13.0(vue@3.5.16(typescript@5.8.3))':
|
||||
dependencies:
|
||||
vue-demi: 0.14.10(vue@3.5.16(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
- vue
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
@ -2553,6 +2682,8 @@ snapshots:
|
||||
|
||||
argparse@2.0.1: {}
|
||||
|
||||
async-validator@4.2.5: {}
|
||||
|
||||
asynckit@0.4.0: {}
|
||||
|
||||
axios@1.9.0:
|
||||
@ -2643,6 +2774,8 @@ snapshots:
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
dayjs@1.11.13: {}
|
||||
|
||||
de-indent@1.0.2: {}
|
||||
|
||||
debug@4.4.1:
|
||||
@ -2674,6 +2807,27 @@ snapshots:
|
||||
|
||||
electron-to-chromium@1.5.161: {}
|
||||
|
||||
element-plus@2.10.3(vue@3.5.16(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@ctrl/tinycolor': 3.6.1
|
||||
'@element-plus/icons-vue': 2.3.1(vue@3.5.16(typescript@5.8.3))
|
||||
'@floating-ui/dom': 1.7.2
|
||||
'@popperjs/core': '@sxzz/popperjs-es@2.11.7'
|
||||
'@types/lodash': 4.17.20
|
||||
'@types/lodash-es': 4.17.12
|
||||
'@vueuse/core': 9.13.0(vue@3.5.16(typescript@5.8.3))
|
||||
async-validator: 4.2.5
|
||||
dayjs: 1.11.13
|
||||
escape-html: 1.0.3
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21)
|
||||
memoize-one: 6.0.0
|
||||
normalize-wheel-es: 1.2.0
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- '@vue/composition-api'
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
entities@4.5.0: {}
|
||||
@ -2723,6 +2877,8 @@ snapshots:
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-config-prettier@10.1.5(eslint@9.28.0(jiti@2.4.2)):
|
||||
@ -3055,6 +3211,14 @@ snapshots:
|
||||
dependencies:
|
||||
p-locate: 5.0.0
|
||||
|
||||
lodash-es@4.17.21: {}
|
||||
|
||||
lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.17.21)(lodash@4.17.21):
|
||||
dependencies:
|
||||
'@types/lodash-es': 4.17.12
|
||||
lodash: 4.17.21
|
||||
lodash-es: 4.17.21
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
@ -3069,6 +3233,8 @@ snapshots:
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
memoize-one@6.0.0: {}
|
||||
|
||||
memorystream@0.3.1: {}
|
||||
|
||||
merge2@1.4.1: {}
|
||||
@ -3108,6 +3274,8 @@ snapshots:
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
normalize-wheel-es@1.2.0: {}
|
||||
|
||||
npm-normalize-package-bin@4.0.0: {}
|
||||
|
||||
npm-run-all2@7.0.2:
|
||||
@ -3439,6 +3607,10 @@ snapshots:
|
||||
|
||||
vscode-uri@3.1.0: {}
|
||||
|
||||
vue-demi@0.14.10(vue@3.5.16(typescript@5.8.3)):
|
||||
dependencies:
|
||||
vue: 3.5.16(typescript@5.8.3)
|
||||
|
||||
vue-eslint-parser@10.1.3(eslint@9.28.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
|
@ -3,9 +3,11 @@ import { RouterView } from 'vue-router'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<keep-alive>
|
||||
<RouterView />
|
||||
</keep-alive>
|
||||
<router-view v-slot="{ Component }">
|
||||
<keep-alive>
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
const props = defineProps({
|
||||
@ -33,11 +33,16 @@ const getDisplayUrl = (url: string): string => {
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const generateQRCode = async (url: string) => {
|
||||
if (!url) {
|
||||
console.warn('QRCodeDisplay: URL 为空,无法生成二维码。');
|
||||
qrcodeImgUrl.value = ''; // 清空图片,显示备用文本
|
||||
return;
|
||||
}
|
||||
try {
|
||||
isLoading.value = true;
|
||||
// 使用 toDataURL 方法生成二维码数据 URL
|
||||
qrcodeImgUrl.value = await QRCode.toDataURL(props.url, {
|
||||
console.log('QRCodeDisplay: 尝试生成二维码,URL:', url);
|
||||
qrcodeImgUrl.value = await QRCode.toDataURL(url, {
|
||||
width: 180,
|
||||
margin: 1,
|
||||
errorCorrectionLevel: 'H',
|
||||
@ -46,15 +51,25 @@ onMounted(async () => {
|
||||
light: '#ffffff'
|
||||
}
|
||||
});
|
||||
console.log('QRCodeDisplay: 二维码生成成功,URL数据:', qrcodeImgUrl.value.substring(0, 50) + '...');
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error);
|
||||
// 如果生成失败,使用一个默认的二维码图片作为替代
|
||||
qrcodeImgUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(props.url)}`;
|
||||
console.error('QRCodeDisplay: 生成二维码失败:', error);
|
||||
qrcodeImgUrl.value = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(url)}`;
|
||||
console.log('QRCodeDisplay: 二维码生成失败,使用备用URL:', qrcodeImgUrl.value.substring(0, 50) + '...');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
generateQRCode(props.url);
|
||||
});
|
||||
|
||||
// 监听 props.url 的变化,并在变化时重新生成二维码
|
||||
watch(() => props.url, (newUrl) => {
|
||||
generateQRCode(newUrl);
|
||||
}, { immediate: true }); // immediate: true 确保在组件挂载时也立即执行一次
|
||||
|
||||
const onDownload = () => {
|
||||
try {
|
||||
if (!qrcodeImgUrl.value) {
|
||||
@ -82,7 +97,8 @@ const onDownload = () => {
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
<div v-else class="qrcode">
|
||||
<img :src="qrcodeImgUrl" :alt="title" />
|
||||
<img v-if="qrcodeImgUrl" :src="qrcodeImgUrl" :alt="title" />
|
||||
<p v-else class="qr-code-fallback">二维码加载失败</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="qrcode-info">
|
||||
@ -113,6 +129,12 @@ const onDownload = () => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qr-code-fallback {
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.qrcode img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
|
@ -76,6 +76,9 @@ onUnmounted(() => {
|
||||
<li class="nav-item">
|
||||
<router-link to="/hasfjform" class="nav-link" @click="closeMenu">表单下载</router-link>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<router-link to="/feedback-query" class="nav-link" @click="closeMenu">留言查询</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -60,6 +60,12 @@ const router = createRouter({
|
||||
name: 'searchResults',
|
||||
component: () => import('../views/SearchResultsView.vue'),
|
||||
},
|
||||
// 留言查询页面
|
||||
{
|
||||
path: '/feedback-query',
|
||||
name: 'feedbackQuery',
|
||||
component: () => import('../views/FeedbackQueryView.vue'),
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
|
@ -2,7 +2,7 @@ import axios from 'axios'
|
||||
|
||||
// 创建axios实例
|
||||
const apiClient = axios.create({
|
||||
baseURL: '', // 使用相对路径,将通过Vite代理
|
||||
baseURL: 'http://222.184.49.22:19696/', // 直接设置后端服务地址
|
||||
timeout: 15000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -239,7 +239,7 @@ async function requestWithCorrectType(type: string, formatId: string) {
|
||||
|
||||
// 如果上面的方法失败,尝试其他备用方法
|
||||
console.log(`尝试使用备用API路径获取详情`);
|
||||
const backupUrl = `/api/hasfj/hasfjpages/list`;
|
||||
const backupUrl = `/hasfj/hasfjpages/list`;
|
||||
const backupParams = { pageType: type, pageNum: 1, pageSize: 10 };
|
||||
|
||||
const backupResponse = await apiClient.get(backupUrl, { params: backupParams });
|
||||
@ -915,126 +915,98 @@ export const getBaseUrl = (): string => {
|
||||
// 更新页面浏览量
|
||||
export const updateViewCount = async (type: string, formatId: string) => {
|
||||
try {
|
||||
console.log(`开始更新${type}浏览量,formatId:`, formatId);
|
||||
|
||||
// 前端直接调用页面查询接口来更新浏览量,避开updateViewCount接口问题
|
||||
// 从README-浏览量更新.md可知,后端可能没有实现正确的updateViewCount接口
|
||||
try {
|
||||
console.log(`尝试使用getInfoByFormatId接口获取详情并更新浏览量`);
|
||||
// 使用format/{formatId}接口,这个接口会自动更新浏览量
|
||||
const response = await apiClient.get(`/hasfj/hasfjpages/format/${formatId}`);
|
||||
console.log(`获取页面详情响应:`, response);
|
||||
|
||||
if (response.status === 200 && response.data) {
|
||||
// 如果请求成功,从响应中提取浏览量数据
|
||||
const viewCount =
|
||||
(response.data.data && typeof response.data.data.viewCount === 'number') ? response.data.data.viewCount :
|
||||
(response.data.viewCount !== undefined) ? response.data.viewCount : null;
|
||||
|
||||
if (viewCount !== null) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: '更新成功',
|
||||
data: {
|
||||
viewCount: viewCount
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// 如果没有获得浏览量数据,但请求成功,依然返回成功
|
||||
return {
|
||||
code: 200,
|
||||
msg: '更新成功',
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`使用页面详情接口更新浏览量失败:`, error);
|
||||
console.log(`更新${type}浏览次数,formatId:`, formatId);
|
||||
const url = `/hasfj/hasfjpages/updateViewCount`; // 假设后端有此接口
|
||||
const data = { pageType: type, formatId: formatId };
|
||||
const response = await apiClient.post(url, data);
|
||||
if (response.status === 200) {
|
||||
console.log(`浏览次数更新成功:`, response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
console.error(`浏览次数更新失败:`, response);
|
||||
throw new Error(`浏览次数更新失败,状态码: ${response.status}`);
|
||||
}
|
||||
|
||||
// 退化方案:使用页面列表接口查询页面,然后记录浏览量
|
||||
try {
|
||||
console.log(`尝试使用页面列表接口查询页面`);
|
||||
const listResponse = await apiClient.get(`/hasfj/hasfjpages/list`, {
|
||||
params: {
|
||||
pageType: type,
|
||||
formatId: formatId,
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`页面列表响应:`, listResponse);
|
||||
|
||||
if (listResponse.status === 200 && listResponse.data && listResponse.data.rows) {
|
||||
// 从列表结果中查找匹配的记录
|
||||
const rows = listResponse.data.rows;
|
||||
let targetPage = null;
|
||||
|
||||
if (Array.isArray(rows)) {
|
||||
targetPage = rows.find((item: any) =>
|
||||
item.formatId === formatId && (!item.pageType || item.pageType === type));
|
||||
}
|
||||
|
||||
if (targetPage) {
|
||||
// 找到匹配的页面,获取其浏览量
|
||||
const viewCount = typeof targetPage.viewCount === 'number' ? targetPage.viewCount : null;
|
||||
|
||||
if (viewCount !== null) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: '更新成功',
|
||||
data: {
|
||||
viewCount: viewCount
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`使用页面列表接口查询失败:`, error);
|
||||
}
|
||||
|
||||
// 最后的方案:使用get接口,不更新浏览量,仅获取当前页面数据
|
||||
try {
|
||||
console.log(`尝试使用get接口获取页面数据`);
|
||||
const getResponse = await apiClient.get(`/api/hasfj/page`, {
|
||||
params: {
|
||||
type: type,
|
||||
formatId: formatId
|
||||
}
|
||||
});
|
||||
|
||||
if (getResponse.status === 200 && getResponse.data) {
|
||||
// 从响应中提取浏览量
|
||||
const viewCount =
|
||||
(getResponse.data.data && typeof getResponse.data.data.viewCount === 'number') ? getResponse.data.data.viewCount :
|
||||
(getResponse.data.viewCount !== undefined) ? getResponse.data.viewCount : null;
|
||||
|
||||
if (viewCount !== null) {
|
||||
return {
|
||||
code: 200,
|
||||
msg: '更新成功',
|
||||
data: {
|
||||
viewCount: viewCount
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`使用get接口获取页面数据失败:`, error);
|
||||
}
|
||||
|
||||
// 所有方法都失败,返回一个通用成功响应
|
||||
return {
|
||||
code: 200,
|
||||
msg: '操作完成',
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`更新浏览量失败:`, error);
|
||||
// 即使失败也返回成功,以免影响用户体验
|
||||
return {
|
||||
code: 200,
|
||||
msg: '操作完成'
|
||||
};
|
||||
console.error(`更新浏览次数异常:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交用户留言
|
||||
* @param {Object} feedbackData - 留言数据
|
||||
*/
|
||||
export const submitFeedback = async (feedbackData: any) => {
|
||||
try {
|
||||
console.log('提交用户留言:', feedbackData);
|
||||
const response = await apiClient.post('/hasfj/feedback', feedbackData);
|
||||
if (response.status === 200 && response.data.code === 200) {
|
||||
console.log('用户留言提交成功:', response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
console.error('用户留言提交失败:', response);
|
||||
throw new Error(response.data.msg || '留言提交失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('用户留言提交异常:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询用户留言回复结果
|
||||
* @param {Object} queryParams - 查询参数 (feedbackNo, phoneNumber, idCardNumber, userName)
|
||||
*/
|
||||
export const queryFeedback = async (queryParams: any) => {
|
||||
try {
|
||||
console.log('查询用户留言:', queryParams);
|
||||
const response = await apiClient.get('/hasfj/feedback/query', { params: queryParams });
|
||||
if (response.status === 200 && response.data.code === 200) {
|
||||
console.log('用户留言查询成功:', response.data);
|
||||
return response.data;
|
||||
} else {
|
||||
console.error('用户留言查询失败:', response);
|
||||
throw new Error(response.data.msg || '留言查询失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('用户留言查询异常:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 新增用户留言
|
||||
export const addFeedback = async (data: any) => {
|
||||
try {
|
||||
const response = await apiClient.post('/hasfj/feedback/user/submit', data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('提交留言失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 查询用户留言
|
||||
export const listFeedback = async (query: any) => {
|
||||
try {
|
||||
const response = await apiClient.get('/user/query', { params: query });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('列表留言失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 通过留言编号获取用户留言详情
|
||||
* @param {string} feedbackNo - 留言查询编号
|
||||
*/
|
||||
export const queryFeedbackDetail = async (queryParams: any) => {
|
||||
try {
|
||||
// 使用新的 /user/queryDetail 接口,并传递包含所有查询参数的对象
|
||||
const response = await apiClient.get('/hasfj/feedback/user/queryDetail', { params: queryParams });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('查询留言详情失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
@ -5,111 +5,111 @@ import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
content?: string;
|
||||
pageType?: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
updateTime?: string;
|
||||
status?: number;
|
||||
sortOrder?: number;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
keywords?: string;
|
||||
description?: string;
|
||||
attachmentUrl?: string;
|
||||
id: number
|
||||
formatId: string
|
||||
title: string
|
||||
content?: string
|
||||
pageType?: string
|
||||
pageUrl?: string
|
||||
createTime: string
|
||||
updateTime?: string
|
||||
status?: number
|
||||
sortOrder?: number
|
||||
viewCount: number
|
||||
author?: string
|
||||
keywords?: string
|
||||
description?: string
|
||||
attachmentUrl?: string
|
||||
}
|
||||
|
||||
const caseList = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(12); // 每页显示12条数据
|
||||
const caseList = ref<PageItem[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(12) // 每页显示12条数据
|
||||
|
||||
const fetchCases = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const result = await getPageList('case', currentPage.value, pageSize.value);
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
const result = await getPageList('case', currentPage.value, pageSize.value)
|
||||
|
||||
if (result.code === 200) {
|
||||
caseList.value = result.rows || [];
|
||||
total.value = result.total || caseList.value.length;
|
||||
|
||||
caseList.value = result.rows || []
|
||||
total.value = result.total || caseList.value.length
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (caseList.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchCases();
|
||||
currentPage.value = currentPage.value - 1
|
||||
await fetchCases()
|
||||
}
|
||||
} else {
|
||||
error.value = result.msg || '获取数据失败';
|
||||
caseList.value = [];
|
||||
error.value = result.msg || '获取数据失败'
|
||||
caseList.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
caseList.value = [];
|
||||
console.error(e)
|
||||
error.value = '获取数据失败,请稍后重试'
|
||||
caseList.value = []
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchCases();
|
||||
});
|
||||
fetchCases()
|
||||
})
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
currentPage.value = page
|
||||
pageSize.value = size
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchCases();
|
||||
});
|
||||
fetchCases()
|
||||
})
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('典型案例列表页面被激活,重新获取数据');
|
||||
fetchCases();
|
||||
});
|
||||
console.log('典型案例列表页面被激活,重新获取数据')
|
||||
fetchCases()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="case-list">
|
||||
<TheNavbar />
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>典型案例</h1>
|
||||
<p class="subtitle">查看劳动纠纷的典型处理案例</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="cases-section">
|
||||
@ -117,19 +117,19 @@ onActivated(() => {
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchCases" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="caseList.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有典型案例相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="case-grid">
|
||||
<div v-for="item in caseList" :key="item.formatId" class="case-card">
|
||||
@ -148,14 +148,14 @@ onActivated(() => {
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
:page-sizes="[8, 16, 24, 32, 150]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -240,7 +240,9 @@ onActivated(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
.loading,
|
||||
.error,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@ -260,10 +262,13 @@ onActivated(() => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
.btn-retry,
|
||||
.btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
@ -278,7 +283,8 @@ onActivated(() => {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
.btn-retry:hover,
|
||||
.btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
@ -356,13 +362,15 @@ onActivated(() => {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.case-date, .case-views {
|
||||
.case-date,
|
||||
.case-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.case-date::before, .case-views::before {
|
||||
.case-date::before,
|
||||
.case-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
@ -409,16 +417,16 @@ onActivated(() => {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
|
||||
.case-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.cases-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
@ -428,26 +436,26 @@ onActivated(() => {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.case-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.cases-section {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
.case-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,7 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getPageDetail, updateViewCount } from '../services/api'
|
||||
import {
|
||||
getPageDetail,
|
||||
updateViewCount,
|
||||
addFeedback,
|
||||
listFeedback,
|
||||
queryFeedbackDetail,
|
||||
} from '../services/api'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
@ -9,131 +15,281 @@ const loading = ref(true)
|
||||
const error = ref(false)
|
||||
const page = ref<any>(null)
|
||||
|
||||
const feedbackForm = reactive({
|
||||
legalArticle: '',
|
||||
issueDescription: '',
|
||||
userName: '',
|
||||
phoneNumber: '',
|
||||
idCardNumber: '',
|
||||
contactInfoType: 1, // 默认手机号
|
||||
})
|
||||
|
||||
const feedbackSubmitSuccess = ref(false)
|
||||
const feedbackNoReturned = ref('')
|
||||
|
||||
// 处理内容,将#标题#格式的文本转换为可折叠卡片
|
||||
const processedContent = computed(() => {
|
||||
if (!page.value || !page.value.content) return ''
|
||||
|
||||
// 正则表达式匹配 #标题# 格式
|
||||
const regex = /#([^#]+)#([\s\S]*?)(?=#[^#]+#|$)/g
|
||||
let index = 0
|
||||
|
||||
// 替换为可折叠卡片HTML,不使用内联onclick
|
||||
const result = page.value.content.replace(
|
||||
regex,
|
||||
(match: string, title: string, content: string) => {
|
||||
// 去除前后空白
|
||||
title = title.trim()
|
||||
content = content.trim()
|
||||
const currentIndex = index++
|
||||
|
||||
return `
|
||||
<div class="law-card" data-card-index="${currentIndex}">
|
||||
<div class="law-card-header" data-card-index="${currentIndex}">
|
||||
<span class="law-card-title">${title}</span>
|
||||
<span class="law-card-toggle">▼</span>
|
||||
</div>
|
||||
<div class="law-card-content">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
)
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 展开或折叠卡片的处理函数
|
||||
const expandedCards = ref<Set<number>>(new Set())
|
||||
|
||||
const toggleCard = (index: number) => {
|
||||
const card = document.querySelector(`.law-card[data-card-index="${index}"]`)
|
||||
if (card) {
|
||||
if (expandedCards.value.has(index)) {
|
||||
expandedCards.value.delete(index)
|
||||
card.classList.remove('expanded')
|
||||
} else {
|
||||
expandedCards.value.add(index)
|
||||
card.classList.add('expanded')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const formatId = route.query.Id as string
|
||||
if (!formatId) {
|
||||
console.error('缺少必要的formatId参数');
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
return;
|
||||
console.error('缺少必要的formatId参数')
|
||||
error.value = true
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
console.log('开始获取典型案例详情,formatId:', formatId);
|
||||
console.log('开始获取典型案例详情,formatId:', formatId)
|
||||
// 获取典型案例数据
|
||||
const result = await getPageDetail('case', formatId);
|
||||
console.log('典型案例详情请求结果:', result);
|
||||
const result = await getPageDetail('case', formatId)
|
||||
console.log('典型案例详情请求结果:', result)
|
||||
|
||||
if (result && result.code === 200 && result.data) {
|
||||
page.value = result.data;
|
||||
console.log('成功获取典型案例详情:', page.value);
|
||||
page.value = result.data
|
||||
console.log('成功获取典型案例详情:', page.value)
|
||||
|
||||
// 更新浏览量
|
||||
try {
|
||||
const viewResult = await updateViewCount('case', formatId) as any;
|
||||
console.log('更新浏览量结果:', viewResult);
|
||||
|
||||
const viewResult = (await updateViewCount('case', formatId)) as any
|
||||
console.log('更新浏览量结果:', viewResult)
|
||||
|
||||
// 确认服务器端更新成功
|
||||
if (viewResult && viewResult.code === 200) {
|
||||
// 如果后端返回了新的浏览量数据
|
||||
if (viewResult.data && typeof viewResult.data.viewCount === 'number') {
|
||||
page.value.viewCount = viewResult.data.viewCount;
|
||||
}
|
||||
// 否则本地递增浏览量
|
||||
else if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1;
|
||||
} else {
|
||||
page.value.viewCount = 1; // 初始浏览量
|
||||
page.value.viewCount = viewResult.data.viewCount
|
||||
}
|
||||
|
||||
console.log('浏览量更新为:', page.value.viewCount);
|
||||
// 否则本地递增浏览量
|
||||
else if (
|
||||
typeof page.value.viewCount === 'number' ||
|
||||
typeof page.value.viewCount === 'string'
|
||||
) {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||||
} else {
|
||||
page.value.viewCount = 1 // 初始浏览量
|
||||
}
|
||||
|
||||
console.log('浏览量更新为:', page.value.viewCount)
|
||||
} else {
|
||||
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误');
|
||||
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新浏览量失败:', e);
|
||||
console.error('更新浏览量失败:', e)
|
||||
// 出错时也尝试本地增加浏览量
|
||||
if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1;
|
||||
console.log('API出错,本地更新浏览量为:', page.value.viewCount);
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||||
console.log('API出错,本地更新浏览量为:', page.value.viewCount)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查返回的数据是否确实是典型案例类型
|
||||
if (page.value.pageType && page.value.pageType !== 'case') {
|
||||
console.error(`错误: 请求的是典型案例(case),但返回的是${page.value.pageType}类型的内容`);
|
||||
console.error(`错误: 请求的是典型案例(case),但返回的是${page.value.pageType}类型的内容`)
|
||||
// 不再尝试重新获取,而是直接显示错误
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
return;
|
||||
error.value = true
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 处理可能的中文乱码问题
|
||||
if (page.value.title && /\\u|%/.test(page.value.title)) {
|
||||
try {
|
||||
page.value.title = decodeURIComponent(page.value.title);
|
||||
page.value.title = decodeURIComponent(page.value.title)
|
||||
} catch (e) {
|
||||
console.error('标题解码失败:', e);
|
||||
console.error('标题解码失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查内容是否为HTML格式
|
||||
if (page.value.content) {
|
||||
console.log('内容类型:', typeof page.value.content);
|
||||
console.log('内容前50个字符:', page.value.content.substring(0, 50));
|
||||
console.log('内容类型:', typeof page.value.content)
|
||||
console.log('内容前50个字符:', page.value.content.substring(0, 50))
|
||||
|
||||
// 处理可能的中文乱码问题
|
||||
if (/\\u|%/.test(page.value.content)) {
|
||||
try {
|
||||
page.value.content = decodeURIComponent(page.value.content);
|
||||
page.value.content = decodeURIComponent(page.value.content)
|
||||
} catch (e) {
|
||||
console.error('内容解码失败:', e);
|
||||
console.error('内容解码失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 确保内容是HTML字符串
|
||||
if (typeof page.value.content !== 'string') {
|
||||
try {
|
||||
page.value.content = JSON.stringify(page.value.content);
|
||||
page.value.content = JSON.stringify(page.value.content)
|
||||
} catch (e) {
|
||||
console.error('内容格式转换失败:', e);
|
||||
console.error('内容格式转换失败:', e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('页面内容为空');
|
||||
page.value.content = '<p>暂无内容</p>';
|
||||
console.warn('页面内容为空')
|
||||
page.value.content = '<p>暂无内容</p>'
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
document.title = `${page.value.title || '典型案例详情'} - 典型案例`;
|
||||
document.title = `${page.value.title || '典型案例详情'} - 典型案例`
|
||||
} else {
|
||||
console.error('获取典型案例详情失败:', result?.msg || '未知错误');
|
||||
error.value = true;
|
||||
console.error('获取典型案例详情失败:', result?.msg || '未知错误')
|
||||
error.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取典型案例详情异常:', e);
|
||||
error.value = true;
|
||||
console.error('获取典型案例详情异常:', e)
|
||||
error.value = true
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
day: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个初始化折叠卡片功能的方法
|
||||
const initCollapsibleCards = () => {
|
||||
console.log('初始化折叠卡片...')
|
||||
// 使用事件委托在父元素上监听点击事件
|
||||
document.querySelector('.body')?.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement
|
||||
const header = target.closest('.law-card-header')
|
||||
|
||||
if (header) {
|
||||
const index = parseInt(header.getAttribute('data-card-index') || '-1')
|
||||
if (index >= 0) {
|
||||
toggleCard(index)
|
||||
console.log('切换卡片状态:', index)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log('折叠卡片初始化完成')
|
||||
}
|
||||
|
||||
// 在内容更新后初始化折叠卡片
|
||||
onMounted(() => {
|
||||
// 内容加载完成后初始化折叠卡片
|
||||
setTimeout(() => {
|
||||
initCollapsibleCards()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
const submitFeedback = async () => {
|
||||
try {
|
||||
// 前端验证
|
||||
if (!feedbackForm.legalArticle.trim()) {
|
||||
alert('有问题的案例不能为空。')
|
||||
return
|
||||
}
|
||||
if (!feedbackForm.issueDescription.trim()) {
|
||||
alert('问题描述不能为空。')
|
||||
return
|
||||
}
|
||||
|
||||
if (feedbackForm.contactInfoType === 1 && !feedbackForm.phoneNumber.trim()) {
|
||||
alert('手机号不能为空。')
|
||||
return
|
||||
} else if (feedbackForm.contactInfoType === 2 && !feedbackForm.idCardNumber.trim()) {
|
||||
alert('身份证号不能为空。')
|
||||
return
|
||||
} else if (feedbackForm.contactInfoType === 3 && !feedbackForm.userName.trim()) {
|
||||
alert('姓名不能为空。')
|
||||
return
|
||||
}
|
||||
|
||||
const dataToSend = { ...feedbackForm }
|
||||
// 根据选择的联系方式类型设置对应的值,其他值置空
|
||||
if (feedbackForm.contactInfoType === 1) {
|
||||
dataToSend.idCardNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackForm.contactInfoType === 2) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackForm.contactInfoType === 3) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.idCardNumber = ''
|
||||
}
|
||||
|
||||
const res = await addFeedback(dataToSend)
|
||||
if (res && res.code === 200) {
|
||||
feedbackSubmitSuccess.value = true
|
||||
feedbackNoReturned.value = res.data?.feedbackNo || '未知'
|
||||
// 清空表单
|
||||
feedbackForm.legalArticle = ''
|
||||
feedbackForm.issueDescription = ''
|
||||
feedbackForm.userName = ''
|
||||
feedbackForm.phoneNumber = ''
|
||||
feedbackForm.idCardNumber = ''
|
||||
feedbackForm.contactInfoType = 1
|
||||
console.log('留言提交成功,查询编号:', feedbackNoReturned.value)
|
||||
} else {
|
||||
alert('提交留言失败:' + (res.msg || '未知错误'))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('提交留言异常:', e)
|
||||
alert('提交留言异常,请稍后再试')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -168,12 +324,99 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body" v-html="page.content"></div>
|
||||
<div class="body" v-html="processedContent"></div>
|
||||
|
||||
<div class="footer">
|
||||
<router-link to="/hasfjcase" class="btn-more">查看更多典型案例</router-link>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
<!-- 用户留言模块 -->
|
||||
<div class="feedback-section">
|
||||
<h3><i class="icon-chat"></i> 案例咨询与留言</h3>
|
||||
<div v-if="!feedbackSubmitSuccess" class="feedback-form">
|
||||
<p class="section-description">如果您对典型案例有任何疑问或建议,欢迎在此留言。</p>
|
||||
<div class="form-group">
|
||||
<label for="legalArticle">有问题的案例:<span class="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="legalArticle"
|
||||
v-model="feedbackForm.legalArticle"
|
||||
placeholder="如:2023年某某案"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="issueDescription">问题描述: <span class="required">*</span></label>
|
||||
<textarea
|
||||
id="issueDescription"
|
||||
v-model="feedbackForm.issueDescription"
|
||||
rows="5"
|
||||
placeholder="请详细描述您的问题,字数在10-500字之间"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contactInfoType">联系方式类型:</label>
|
||||
<select id="contactInfoType" v-model="feedbackForm.contactInfoType">
|
||||
<option :value="1">手机号</option>
|
||||
<option :value="2">身份证号</option>
|
||||
<option :value="3">姓名</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phoneNumber"
|
||||
>手机号:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 1">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneNumber"
|
||||
v-model="feedbackForm.phoneNumber"
|
||||
placeholder="请输入手机号"
|
||||
:required="feedbackForm.contactInfoType === 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idCardNumber"
|
||||
>身份证号:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 2">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="idCardNumber"
|
||||
v-model="feedbackForm.idCardNumber"
|
||||
placeholder="请输入身份证号"
|
||||
:required="feedbackForm.contactInfoType === 2"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="userNameContact"
|
||||
>姓名:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 3">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="userNameContact"
|
||||
v-model="feedbackForm.userName"
|
||||
placeholder="请输入您的姓名"
|
||||
:required="feedbackForm.contactInfoType === 3"
|
||||
/>
|
||||
</div>
|
||||
<button @click="submitFeedback" class="submit-btn">提交留言</button>
|
||||
</div>
|
||||
<div v-else class="feedback-success">
|
||||
<h4>留言提交成功!</h4>
|
||||
<p>
|
||||
您的留言查询编号是:<span class="feedback-no">{{ feedbackNoReturned }}</span>
|
||||
</p>
|
||||
<p>请牢记此编号,以便查询回复。</p>
|
||||
<button
|
||||
@click="((feedbackSubmitSuccess = false), (feedbackNoReturned = ''))"
|
||||
class="submit-btn"
|
||||
>
|
||||
继续留言
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -210,7 +453,9 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
@ -350,18 +595,75 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
/* 添加法律卡片相关样式 */
|
||||
:deep(.law-card) {
|
||||
margin: 1.5rem 0;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
:deep(.law-card-header) {
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
user-select: none; /* 防止选中文本 */
|
||||
}
|
||||
|
||||
:deep(.law-card-header:hover) {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-toggle) {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
:deep(.law-card-content) {
|
||||
padding: 0;
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-height 0.5s ease,
|
||||
padding 0.5s ease;
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-content) {
|
||||
padding: 1.5rem;
|
||||
max-height: 2000px;
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-toggle) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.btn-home,
|
||||
.btn-more {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 1.5rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
transform 0.3s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-home:hover, .btn-more:hover {
|
||||
.btn-home:hover,
|
||||
.btn-more:hover {
|
||||
background-color: #0069d9;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
@ -390,6 +692,10 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
.content .body {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
@ -426,10 +732,23 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
.btn-home,
|
||||
.btn-more {
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-header) {
|
||||
padding: 0.8rem 1rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-content) {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
@ -446,4 +765,249 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-section {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
margin-top: 2rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.feedback-section h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feedback-section .icon-chat::before {
|
||||
content: '💬'; /* Chat bubble icon */
|
||||
margin-right: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.feedback-section .icon-search::before {
|
||||
content: '🔍'; /* Search icon */
|
||||
margin-right: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.feedback-section .section-description {
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feedback-form .form-group,
|
||||
.feedback-query .form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feedback-form label,
|
||||
.feedback-query label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.feedback-form input[type='text'],
|
||||
.feedback-form textarea,
|
||||
.feedback-form select,
|
||||
.feedback-query input[type='text'],
|
||||
.feedback-query select {
|
||||
width: calc(100% - 20px);
|
||||
padding: 0.8rem 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.feedback-form textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.feedback-form .required,
|
||||
.feedback-query .required {
|
||||
color: var(--color-danger);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn,
|
||||
.feedback-query .submit-btn {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 2rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn:hover,
|
||||
.feedback-query .submit-btn:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
|
||||
.feedback-query .submit-btn:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.feedback-success {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background-color: #e6ffe6;
|
||||
border: 1px solid #aaffaa;
|
||||
border-radius: 8px;
|
||||
color: #336633;
|
||||
}
|
||||
|
||||
.feedback-success h4 {
|
||||
color: #28a745;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.feedback-success .feedback-no {
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-success button {
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.7rem 1.5rem;
|
||||
background-color: var(--color-success);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.feedback-success button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.query-title {
|
||||
margin-top: 3rem;
|
||||
border-top: 1px dashed #eee;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.query-result {
|
||||
background-color: #e9f7fe;
|
||||
border: 1px solid #b3e0ff;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.query-result h4 {
|
||||
color: #007bff;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: 1px dashed #cceeff;
|
||||
}
|
||||
|
||||
.query-result .result-item {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--color-text);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.query-result .result-item strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.query-result .admin-reply-content {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.6;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--color-danger);
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-replied {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
color: gray;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.feedback-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.feedback-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.feedback-section h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.feedback-section .icon-chat::before,
|
||||
.feedback-section .icon-search::before {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-form input[type='text'],
|
||||
.feedback-form textarea,
|
||||
.feedback-form select,
|
||||
.feedback-query input[type='text'],
|
||||
.feedback-query select {
|
||||
padding: 0.6rem 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn,
|
||||
.feedback-query .submit-btn {
|
||||
padding: 0.6rem 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feedback-success h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.feedback-success .feedback-no {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.query-result h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.query-result .result-item {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
515
src/views/FeedbackQueryView.vue
Normal file
515
src/views/FeedbackQueryView.vue
Normal file
@ -0,0 +1,515 @@
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from 'vue'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
import { queryFeedbackDetail } from '../services/api'
|
||||
import QRCodeDisplay from '../components/QRCodeDisplay.vue'
|
||||
import { onMounted } from 'vue'
|
||||
|
||||
// 留言查询表单数据
|
||||
const feedbackQueryForm = reactive({
|
||||
feedbackNo: '',
|
||||
userName: '',
|
||||
idCardNumber: '',
|
||||
phoneNumber: '',
|
||||
contactInfoType: 1, // 默认手机号
|
||||
})
|
||||
|
||||
const feedbackQueryResult = ref<any>(null)
|
||||
const queryErrorMessage = ref('')
|
||||
const isQuerying = ref(false)
|
||||
|
||||
// QR Code 相关变量
|
||||
const currentUrl = ref('')
|
||||
|
||||
// 在组件挂载时设置当前 URL
|
||||
onMounted(() => {
|
||||
currentUrl.value = window.location.href
|
||||
})
|
||||
|
||||
// 查询留言回复
|
||||
const queryFeedback = async () => {
|
||||
queryErrorMessage.value = ''
|
||||
feedbackQueryResult.value = null
|
||||
isQuerying.value = true
|
||||
|
||||
try {
|
||||
// 前端验证
|
||||
if (!feedbackQueryForm.feedbackNo.trim()) {
|
||||
queryErrorMessage.value = '留言编号不能为空。'
|
||||
return
|
||||
}
|
||||
|
||||
// 确保至少有一个联系方式被填写
|
||||
const hasContactInfo =
|
||||
(feedbackQueryForm.contactInfoType === 1 && feedbackQueryForm.phoneNumber.trim()) ||
|
||||
(feedbackQueryForm.contactInfoType === 2 && feedbackQueryForm.idCardNumber.trim()) ||
|
||||
(feedbackQueryForm.contactInfoType === 3 && feedbackQueryForm.userName.trim())
|
||||
|
||||
if (!hasContactInfo) {
|
||||
queryErrorMessage.value = '手机号、身份证号或姓名至少需要填写一项。'
|
||||
return
|
||||
}
|
||||
|
||||
const dataToSend = { ...feedbackQueryForm }
|
||||
// 根据选择的联系方式类型设置对应的值,其他值置空
|
||||
if (feedbackQueryForm.contactInfoType === 1) {
|
||||
dataToSend.idCardNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackQueryForm.contactInfoType === 2) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackQueryForm.contactInfoType === 3) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.idCardNumber = ''
|
||||
}
|
||||
|
||||
const res = await queryFeedbackDetail(dataToSend)
|
||||
if (res && res.code === 200 && res.data) {
|
||||
feedbackQueryResult.value = res.data
|
||||
console.log('留言查询结果:', feedbackQueryResult.value)
|
||||
} else {
|
||||
queryErrorMessage.value = res.msg || '未找到匹配的留言或查询失败。'
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('查询留言异常:', e)
|
||||
queryErrorMessage.value = '查询留言异常,请稍后再试。'
|
||||
} finally {
|
||||
isQuerying.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
|
||||
// 计算留言状态
|
||||
const getFeedbackStatus = (adminReplyContent: string | null | undefined): string => {
|
||||
return adminReplyContent && adminReplyContent.trim() !== '' ? '已回复' : '待处理'
|
||||
}
|
||||
|
||||
// 移除P标签
|
||||
const removePTags = (htmlString: string | null | undefined): string => {
|
||||
if (!htmlString) return ''
|
||||
// 使用正则表达式移除 <p> 和 </p> 标签
|
||||
return htmlString.replace(/<p[^>]*>|<\/p>/gi, '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="feedback-query-container">
|
||||
<TheNavbar />
|
||||
|
||||
<div class="content">
|
||||
<div class="container query-layout">
|
||||
<div class="query-card">
|
||||
<h3><i class="icon-search"></i> 留言查询</h3>
|
||||
<p class="section-description">请输入您的留言编号和联系方式以查询留言回复情况。</p>
|
||||
|
||||
<div class="feedback-query-form">
|
||||
<div class="form-group">
|
||||
<label for="feedbackNo">留言编号:<span class="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="feedbackNo"
|
||||
v-model="feedbackQueryForm.feedbackNo"
|
||||
placeholder="请输入留言编号"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contactInfoType">联系方式类型:</label>
|
||||
<select id="contactInfoType" v-model="feedbackQueryForm.contactInfoType">
|
||||
<option :value="1">手机号</option>
|
||||
<option :value="2">身份证号</option>
|
||||
<option :value="3">姓名</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" v-if="feedbackQueryForm.contactInfoType === 1">
|
||||
<label for="phoneNumber">手机号: <span class="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneNumber"
|
||||
v-model="feedbackQueryForm.phoneNumber"
|
||||
placeholder="请输入手机号"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group" v-if="feedbackQueryForm.contactInfoType === 2">
|
||||
<label for="idCardNumber">身份证号: <span class="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="idCardNumber"
|
||||
v-model="feedbackQueryForm.idCardNumber"
|
||||
placeholder="请输入身份证号"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group" v-if="feedbackQueryForm.contactInfoType === 3">
|
||||
<label for="userName">姓名: <span class="required">*</span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="userName"
|
||||
v-model="feedbackQueryForm.userName"
|
||||
placeholder="请输入您的姓名"
|
||||
/>
|
||||
</div>
|
||||
<button @click="queryFeedback" :disabled="isQuerying" class="submit-btn">
|
||||
{{ isQuerying ? '查询中...' : '查询留言' }}
|
||||
</button>
|
||||
<p v-if="queryErrorMessage" class="error-message">{{ queryErrorMessage }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="feedbackQueryResult" class="query-result-section">
|
||||
<h4>留言回复详情</h4>
|
||||
<div class="result-item">
|
||||
<strong>留言编号:</strong> {{ feedbackQueryResult.feedbackNo }}
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<strong>留言标题:</strong> {{ feedbackQueryResult.legalArticle }}
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<strong>问题描述:</strong> {{ feedbackQueryResult.issueDescription }}
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<strong>留言时间:</strong> {{ formatDate(feedbackQueryResult.createTime) }}
|
||||
</div>
|
||||
<div class="result-item">
|
||||
<strong>留言状态:</strong>
|
||||
<span
|
||||
:class="{
|
||||
'status-pending':
|
||||
getFeedbackStatus(feedbackQueryResult.adminReplyContent) === '待处理',
|
||||
'status-replied':
|
||||
getFeedbackStatus(feedbackQueryResult.adminReplyContent) === '已回复',
|
||||
}"
|
||||
>
|
||||
{{ getFeedbackStatus(feedbackQueryResult.adminReplyContent) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="feedbackQueryResult.adminReplyContent" class="result-item">
|
||||
<strong>回复内容:</strong>
|
||||
<div
|
||||
class="admin-reply-content"
|
||||
v-html="removePTags(feedbackQueryResult.adminReplyContent)"
|
||||
></div>
|
||||
</div>
|
||||
<div v-if="feedbackQueryResult.adminReplyTime" class="result-item">
|
||||
<strong>回复时间:</strong> {{ formatDate(feedbackQueryResult.adminReplyTime) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="qr-code-side-card">
|
||||
<h4>扫描二维码</h4>
|
||||
<p class="section-description">在手机上扫描此二维码,快速打开当前查询页面。</p>
|
||||
<QRCodeDisplay
|
||||
v-if="currentUrl"
|
||||
:url="currentUrl"
|
||||
title="留言查询页面"
|
||||
id="feedback-query-page-qr"
|
||||
/>
|
||||
<p v-else class="qr-code-loading-hint">正在生成二维码...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.feedback-query-container {
|
||||
min-height: 100vh;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 2rem 0;
|
||||
flex: 1;
|
||||
background-color: var(--color-background);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.container.query-layout {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: flex-start;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.query-card {
|
||||
flex: 2;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: 2.5rem;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.qr-code-side-card {
|
||||
flex: 1;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-md);
|
||||
padding: 1.5rem;
|
||||
margin-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
min-width: 250px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.qr-code-side-card h4 {
|
||||
font-size: 1.3rem;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: 1px dashed var(--color-border-light);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qr-code-side-card .section-description {
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.qr-code-display-wrapper {
|
||||
margin-top: 80px;
|
||||
width: 180px;
|
||||
height: 180px;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.qr-code-loading-hint {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.query-card h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.query-card .icon-search::before {
|
||||
content: '🔍';
|
||||
margin-right: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.query-card .section-description {
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feedback-query-form .form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-query-form label {
|
||||
display: block;
|
||||
margin-bottom: 0.6rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.feedback-query-form input[type='text'],
|
||||
.feedback-query-form select {
|
||||
width: 100%;
|
||||
padding: 0.8rem 12px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
border-color var(--transition-fast),
|
||||
box-shadow var(--transition-fast);
|
||||
}
|
||||
|
||||
.feedback-query-form input[type='text']:focus,
|
||||
.feedback-query-form select:focus {
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.feedback-query-form .required {
|
||||
color: var(--color-danger);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.feedback-query-form .submit-btn {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 2.5rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.05rem;
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
transform 0.2s ease,
|
||||
box-shadow 0.3s ease;
|
||||
margin-top: 1.5rem;
|
||||
font-weight: 600;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.feedback-query-form .submit-btn:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.feedback-query-form .submit-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.feedback-query-form .submit-btn:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--color-danger);
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
background-color: #ffebe8;
|
||||
border: 1px solid #ffccb8;
|
||||
padding: 0.8rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.query-result-section {
|
||||
background-color: #e9f7fe;
|
||||
border: 1px solid #b3e0ff;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.query-result-section h4 {
|
||||
color: #007bff;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: 1px dashed #cceeff;
|
||||
}
|
||||
|
||||
.query-result-section .result-item {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--color-text);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.query-result-section .result-item strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.query-result-section .admin-reply-content {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid var(--color-border-light);
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.6;
|
||||
color: #555;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-replied {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.container.query-layout {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
|
||||
.query-card,
|
||||
.qr-code-side-card {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.qr-code-side-card {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.query-card {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.query-card h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.query-card .icon-search::before {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-query-form input[type='text'],
|
||||
.feedback-query-form select {
|
||||
padding: 0.7rem 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feedback-query-form .submit-btn {
|
||||
padding: 0.7rem 2rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.query-result-section h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.query-result-section .result-item {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -5,107 +5,107 @@ import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
multiAttachments?: string;
|
||||
attachmentUrl?: string;
|
||||
id: number
|
||||
formatId: string
|
||||
title: string
|
||||
pageUrl?: string
|
||||
createTime: string
|
||||
viewCount: number
|
||||
author?: string
|
||||
multiAttachments?: string
|
||||
attachmentUrl?: string
|
||||
}
|
||||
|
||||
const formList = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(15);
|
||||
const formList = ref<PageItem[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(15)
|
||||
|
||||
const fetchForms = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const result = await getPageList('form', currentPage.value, pageSize.value);
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
const result = await getPageList('form', currentPage.value, pageSize.value)
|
||||
|
||||
if (result.code === 200) {
|
||||
formList.value = result.rows || [];
|
||||
total.value = result.total || formList.value.length;
|
||||
|
||||
formList.value = result.rows || []
|
||||
total.value = result.total || formList.value.length
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (formList.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchForms();
|
||||
currentPage.value = currentPage.value - 1
|
||||
await fetchForms()
|
||||
}
|
||||
} else {
|
||||
error.value = result.msg || '获取数据失败';
|
||||
formList.value = [];
|
||||
error.value = result.msg || '获取数据失败'
|
||||
formList.value = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
formList.value = [];
|
||||
console.error(e)
|
||||
error.value = '获取数据失败,请稍后重试'
|
||||
formList.value = []
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchForms();
|
||||
});
|
||||
fetchForms()
|
||||
})
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
currentPage.value = page
|
||||
pageSize.value = size
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchForms();
|
||||
});
|
||||
fetchForms()
|
||||
})
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('表单下载列表页面被激活,重新获取数据');
|
||||
fetchForms();
|
||||
});
|
||||
console.log('表单下载列表页面被激活,重新获取数据')
|
||||
fetchForms()
|
||||
})
|
||||
|
||||
// 判断是否有附件
|
||||
function hasAttachments(item: PageItem): boolean {
|
||||
// 首先检查是否有直接附件链接
|
||||
if (item.attachmentUrl) return true;
|
||||
|
||||
if (item.attachmentUrl) return true
|
||||
|
||||
// 再检查多附件数据
|
||||
if (!item.multiAttachments) return false;
|
||||
|
||||
if (!item.multiAttachments) return false
|
||||
|
||||
try {
|
||||
const attachments = JSON.parse(item.multiAttachments);
|
||||
return Array.isArray(attachments) && attachments.length > 0;
|
||||
const attachments = JSON.parse(item.multiAttachments)
|
||||
return Array.isArray(attachments) && attachments.length > 0
|
||||
} catch (e) {
|
||||
console.error('解析附件列表失败:', e);
|
||||
return false;
|
||||
console.error('解析附件列表失败:', e)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -113,14 +113,14 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
<template>
|
||||
<div class="form-list">
|
||||
<TheNavbar />
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>表单下载</h1>
|
||||
<p class="subtitle">下载各类表单模板,便捷办理相关业务</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="forms-section">
|
||||
@ -128,24 +128,24 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>内容加载失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchForms" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="formList.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有表单下载相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="form-grid">
|
||||
<div v-for="item in formList" :key="item.formatId" class="form-card">
|
||||
<router-link :to="`/table.html?Id=${item.formatId}`" class="form-link">
|
||||
<div class="form-icon" :class="{'has-download': hasAttachments(item)}"></div>
|
||||
<div class="form-icon" :class="{ 'has-download': hasAttachments(item) }"></div>
|
||||
<div class="form-content">
|
||||
<h3 class="form-title">{{ item.title }}</h3>
|
||||
<div class="form-meta">
|
||||
@ -160,14 +160,14 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
:page-sizes="[8, 16, 24, 32, 150]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -252,7 +252,9 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
.loading,
|
||||
.error,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@ -272,10 +274,13 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
.btn-retry,
|
||||
.btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
@ -290,7 +295,8 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
.btn-retry:hover,
|
||||
.btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
@ -376,13 +382,15 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-date, .form-views {
|
||||
.form-date,
|
||||
.form-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.form-date::before, .form-views::before {
|
||||
.form-date::before,
|
||||
.form-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
@ -449,16 +457,16 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.forms-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
@ -468,26 +476,26 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.form-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.forms-section {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
.form-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,118 +5,118 @@ import TheNavbar from '../components/TheNavbar.vue'
|
||||
import Pagination from '../components/Pagination.vue'
|
||||
|
||||
interface PageItem {
|
||||
id: number;
|
||||
formatId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
pageType: string;
|
||||
pageUrl?: string;
|
||||
createTime: string;
|
||||
updateTime: string;
|
||||
status: number;
|
||||
sortOrder: number;
|
||||
viewCount: number;
|
||||
author?: string;
|
||||
keywords?: string;
|
||||
description?: string;
|
||||
attachmentUrl?: string;
|
||||
id: number
|
||||
formatId: string
|
||||
title: string
|
||||
content: string
|
||||
pageType: string
|
||||
pageUrl?: string
|
||||
createTime: string
|
||||
updateTime: string
|
||||
status: number
|
||||
sortOrder: number
|
||||
viewCount: number
|
||||
author?: string
|
||||
keywords?: string
|
||||
description?: string
|
||||
attachmentUrl?: string
|
||||
}
|
||||
|
||||
interface PageData {
|
||||
code: number;
|
||||
rows: PageItem[];
|
||||
total: number;
|
||||
msg: string;
|
||||
code: number
|
||||
rows: PageItem[]
|
||||
total: number
|
||||
msg: string
|
||||
}
|
||||
|
||||
const laws = ref<PageItem[]>([]);
|
||||
const loading = ref(true);
|
||||
const error = ref('');
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = ref(15);
|
||||
const laws = ref<PageItem[]>([])
|
||||
const loading = ref(true)
|
||||
const error = ref('')
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(15)
|
||||
|
||||
const fetchLaws = async () => {
|
||||
try {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
|
||||
const response = await getPageList('law', currentPage.value, pageSize.value);
|
||||
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
|
||||
const response = await getPageList('law', currentPage.value, pageSize.value)
|
||||
|
||||
if (response.code === 200) {
|
||||
laws.value = response.rows || [];
|
||||
total.value = response.total || laws.value.length;
|
||||
|
||||
laws.value = response.rows || []
|
||||
total.value = response.total || laws.value.length
|
||||
|
||||
// 如果当前页没有数据且不是第一页,尝试请求前一页
|
||||
if (laws.value.length === 0 && currentPage.value > 1) {
|
||||
currentPage.value = currentPage.value - 1;
|
||||
await fetchLaws();
|
||||
currentPage.value = currentPage.value - 1
|
||||
await fetchLaws()
|
||||
}
|
||||
} else {
|
||||
error.value = response.msg || '获取数据失败';
|
||||
laws.value = [];
|
||||
error.value = response.msg || '获取数据失败'
|
||||
laws.value = []
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取法律规定列表失败:', err);
|
||||
error.value = '获取数据失败,请稍后重试';
|
||||
laws.value = [];
|
||||
console.error('获取法律规定列表失败:', err)
|
||||
error.value = '获取数据失败,请稍后重试'
|
||||
laws.value = []
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 页码或每页数量变化时重新获取数据
|
||||
watch([currentPage, pageSize], () => {
|
||||
fetchLaws();
|
||||
});
|
||||
fetchLaws()
|
||||
})
|
||||
|
||||
// 分页变化处理
|
||||
const handlePageChange = (page: number, size: number) => {
|
||||
currentPage.value = page;
|
||||
pageSize.value = size;
|
||||
};
|
||||
currentPage.value = page
|
||||
pageSize.value = size
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchLaws();
|
||||
});
|
||||
fetchLaws()
|
||||
})
|
||||
|
||||
// 添加页面激活时重新获取数据的处理函数
|
||||
onActivated(() => {
|
||||
console.log('法律规定列表页面被激活,重新获取数据');
|
||||
fetchLaws();
|
||||
});
|
||||
console.log('法律规定列表页面被激活,重新获取数据')
|
||||
fetchLaws()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="law-list-container">
|
||||
<TheNavbar />
|
||||
|
||||
|
||||
<div class="page-header">
|
||||
<div class="container">
|
||||
<h1>法律规定</h1>
|
||||
<p class="subtitle">查看并了解最新的法律法规内容</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="container">
|
||||
<div class="content-wrapper">
|
||||
<div class="law-list">
|
||||
@ -124,19 +124,19 @@ onActivated(() => {
|
||||
<div class="spinner"></div>
|
||||
<p>加载中...</p>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="error" class="error">
|
||||
<h3>获取数据失败</h3>
|
||||
<p>{{ error }}</p>
|
||||
<button @click="fetchLaws" class="btn-retry">重试</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else-if="laws.length === 0" class="empty">
|
||||
<h3>暂无内容</h3>
|
||||
<p>当前没有法律规定相关内容</p>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-else class="list-content">
|
||||
<div class="law-grid">
|
||||
<div v-for="law in laws" :key="law.id" class="law-card">
|
||||
@ -155,14 +155,14 @@ onActivated(() => {
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<Pagination
|
||||
<Pagination
|
||||
:total="total"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
@change="handlePageChange"
|
||||
:page-sizes="[8, 16, 24, 32]"
|
||||
:page-sizes="[8, 16, 24, 32, 150]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -247,7 +247,9 @@ onActivated(() => {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.loading, .error, .empty {
|
||||
.loading,
|
||||
.error,
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
@ -267,10 +269,13 @@ onActivated(() => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-retry, .btn-home {
|
||||
.btn-retry,
|
||||
.btn-home {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
@ -285,7 +290,8 @@ onActivated(() => {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-retry:hover, .btn-home:hover {
|
||||
.btn-retry:hover,
|
||||
.btn-home:hover {
|
||||
background-color: var(--color-primary-dark);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||||
@ -363,13 +369,15 @@ onActivated(() => {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.law-date, .law-views {
|
||||
.law-date,
|
||||
.law-views {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.law-date::before, .law-views::before {
|
||||
.law-date::before,
|
||||
.law-views::before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
@ -416,16 +424,16 @@ onActivated(() => {
|
||||
.page-header {
|
||||
padding: 2.5rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2.2rem;
|
||||
}
|
||||
|
||||
|
||||
.law-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.law-list {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
@ -435,26 +443,26 @@ onActivated(() => {
|
||||
.page-header {
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
|
||||
.law-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
.law-list {
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
|
||||
.law-link {
|
||||
padding: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
@ -1,139 +1,310 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed, reactive } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { getPageDetail, updateViewCount } from '../services/api'
|
||||
import {
|
||||
getPageDetail,
|
||||
updateViewCount,
|
||||
addFeedback,
|
||||
listFeedback,
|
||||
queryFeedbackDetail,
|
||||
} from '../services/api'
|
||||
import TheNavbar from '../components/TheNavbar.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const loading = ref(true)
|
||||
const error = ref(false)
|
||||
const page = ref<any>(null)
|
||||
const expandedCards = ref<Set<number>>(new Set())
|
||||
const cardCount = ref(0)
|
||||
|
||||
const feedbackForm = reactive({
|
||||
legalArticle: '',
|
||||
issueDescription: '',
|
||||
userName: '',
|
||||
phoneNumber: '',
|
||||
idCardNumber: '',
|
||||
contactInfoType: 1, // 默认手机号
|
||||
})
|
||||
|
||||
const feedbackSubmitSuccess = ref(false)
|
||||
const feedbackNoReturned = ref('')
|
||||
|
||||
const submitFeedback = async () => {
|
||||
try {
|
||||
// 前端验证
|
||||
if (!feedbackForm.legalArticle.trim()) {
|
||||
alert('有问题的法律条例或案例不能为空。')
|
||||
return
|
||||
}
|
||||
if (!feedbackForm.issueDescription.trim()) {
|
||||
alert('问题描述不能为空。')
|
||||
return
|
||||
}
|
||||
|
||||
if (feedbackForm.contactInfoType === 1 && !feedbackForm.phoneNumber.trim()) {
|
||||
alert('手机号不能为空。')
|
||||
return
|
||||
} else if (feedbackForm.contactInfoType === 2 && !feedbackForm.idCardNumber.trim()) {
|
||||
alert('身份证号不能为空。')
|
||||
return
|
||||
} else if (feedbackForm.contactInfoType === 3 && !feedbackForm.userName.trim()) {
|
||||
alert('姓名不能为空。')
|
||||
return
|
||||
}
|
||||
|
||||
const dataToSend = { ...feedbackForm }
|
||||
// 根据选择的联系方式类型设置对应的值,其他值置空
|
||||
if (feedbackForm.contactInfoType === 1) {
|
||||
dataToSend.idCardNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackForm.contactInfoType === 2) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.userName = ''
|
||||
} else if (feedbackForm.contactInfoType === 3) {
|
||||
dataToSend.phoneNumber = ''
|
||||
dataToSend.idCardNumber = ''
|
||||
}
|
||||
|
||||
const res = await addFeedback(dataToSend)
|
||||
if (res && res.code === 200) {
|
||||
feedbackSubmitSuccess.value = true
|
||||
feedbackNoReturned.value = res.data?.feedbackNo || '未知'
|
||||
// 清空表单
|
||||
feedbackForm.legalArticle = ''
|
||||
feedbackForm.issueDescription = ''
|
||||
feedbackForm.userName = ''
|
||||
feedbackForm.phoneNumber = ''
|
||||
feedbackForm.idCardNumber = ''
|
||||
feedbackForm.contactInfoType = 1
|
||||
console.log('留言提交成功,查询编号:', feedbackNoReturned.value)
|
||||
} else {
|
||||
alert('提交留言失败:' + (res.msg || '未知错误'))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('提交留言异常:', e)
|
||||
alert('提交留言异常,请稍后再试')
|
||||
}
|
||||
}
|
||||
|
||||
// 处理内容,将#标题#格式的文本转换为可折叠卡片
|
||||
const processedContent = computed(() => {
|
||||
if (!page.value || !page.value.content) return ''
|
||||
|
||||
// 正则表达式匹配 #标题# 格式
|
||||
const regex = /#([^#]+)#([\s\S]*?)(?=#[^#]+#|$)/g
|
||||
let index = 0
|
||||
|
||||
// 替换为可折叠卡片HTML,不使用内联onclick
|
||||
const result = page.value.content.replace(
|
||||
regex,
|
||||
(match: string, title: string, content: string) => {
|
||||
// 去除前后空白
|
||||
title = title.trim()
|
||||
content = content.trim()
|
||||
const currentIndex = index++
|
||||
|
||||
// 将卡片ID添加到expandedCards集合,使其默认展开
|
||||
expandedCards.value.add(currentIndex)
|
||||
|
||||
return `
|
||||
<div class="law-card expanded" data-card-index="${currentIndex}">
|
||||
<div class="law-card-header" data-card-index="${currentIndex}">
|
||||
<span class="law-card-title">${title}</span>
|
||||
<span class="law-card-toggle">▼</span>
|
||||
</div>
|
||||
<div class="law-card-content">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
},
|
||||
)
|
||||
|
||||
// 记录卡片总数以供后续初始化使用
|
||||
cardCount.value = index
|
||||
|
||||
return result
|
||||
})
|
||||
|
||||
// 展开或折叠卡片的处理函数
|
||||
const toggleCard = (index: number) => {
|
||||
const card = document.querySelector(`.law-card[data-card-index="${index}"]`)
|
||||
if (card) {
|
||||
if (expandedCards.value.has(index)) {
|
||||
expandedCards.value.delete(index)
|
||||
card.classList.remove('expanded')
|
||||
} else {
|
||||
expandedCards.value.add(index)
|
||||
card.classList.add('expanded')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const formatId = route.query.Id as string
|
||||
if (!formatId) {
|
||||
console.error('缺少必要的formatId参数');
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
return;
|
||||
console.error('缺少必要的formatId参数')
|
||||
error.value = true
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
console.log('开始获取法律规定详情,formatId:', formatId);
|
||||
console.log('开始获取法律规定详情,formatId:', formatId)
|
||||
// 获取法律规定数据
|
||||
const result = await getPageDetail('law', formatId);
|
||||
console.log('法律规定详情请求结果:', result);
|
||||
const result = await getPageDetail('law', formatId)
|
||||
console.log('法律规定详情请求结果:', result)
|
||||
|
||||
if (result && result.code === 200 && result.data) {
|
||||
page.value = result.data;
|
||||
console.log('成功获取法律规定详情:', page.value);
|
||||
page.value = result.data
|
||||
console.log('成功获取法律规定详情:', page.value)
|
||||
|
||||
// 更新浏览量
|
||||
try {
|
||||
const viewResult = await updateViewCount('law', formatId) as any;
|
||||
console.log('更新浏览量结果:', viewResult);
|
||||
|
||||
const viewResult = (await updateViewCount('law', formatId)) as any
|
||||
console.log('更新浏览量结果:', viewResult)
|
||||
|
||||
// 确认服务器端更新成功
|
||||
if (viewResult && viewResult.code === 200) {
|
||||
// 如果后端返回了新的浏览量数据
|
||||
if (viewResult.data && typeof viewResult.data.viewCount === 'number') {
|
||||
page.value.viewCount = viewResult.data.viewCount;
|
||||
}
|
||||
// 否则本地递增浏览量
|
||||
else if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1;
|
||||
} else {
|
||||
page.value.viewCount = 1; // 初始浏览量
|
||||
page.value.viewCount = viewResult.data.viewCount
|
||||
}
|
||||
|
||||
console.log('浏览量更新为:', page.value.viewCount);
|
||||
// 否则本地递增浏览量
|
||||
else if (
|
||||
typeof page.value.viewCount === 'number' ||
|
||||
typeof page.value.viewCount === 'string'
|
||||
) {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||||
} else {
|
||||
page.value.viewCount = 1 // 初始浏览量
|
||||
}
|
||||
|
||||
console.log('浏览量更新为:', page.value.viewCount)
|
||||
} else {
|
||||
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误');
|
||||
console.warn('浏览量更新API返回错误:', viewResult?.msg || '未知错误')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('更新浏览量失败:', e);
|
||||
console.error('更新浏览量失败:', e)
|
||||
// 出错时也尝试本地增加浏览量
|
||||
if (typeof page.value.viewCount === 'number' || typeof page.value.viewCount === 'string') {
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1;
|
||||
console.log('API出错,本地更新浏览量为:', page.value.viewCount);
|
||||
page.value.viewCount = Number(page.value.viewCount || 0) + 1
|
||||
console.log('API出错,本地更新浏览量为:', page.value.viewCount)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查返回的数据是否确实是法律规定类型
|
||||
if (page.value.pageType && page.value.pageType !== 'law') {
|
||||
console.error(`错误: 请求的是法律规定(law),但返回的是${page.value.pageType}类型的内容`);
|
||||
console.error(`错误: 请求的是法律规定(law),但返回的是${page.value.pageType}类型的内容`)
|
||||
// 不再尝试重新获取,而是直接显示错误
|
||||
error.value = true;
|
||||
loading.value = false;
|
||||
return;
|
||||
error.value = true
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 处理可能的中文乱码问题
|
||||
if (page.value.title && /\\u|%/.test(page.value.title)) {
|
||||
try {
|
||||
page.value.title = decodeURIComponent(page.value.title);
|
||||
page.value.title = decodeURIComponent(page.value.title)
|
||||
} catch (e) {
|
||||
console.error('标题解码失败:', e);
|
||||
console.error('标题解码失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查内容是否为HTML格式
|
||||
if (page.value.content) {
|
||||
console.log('内容类型:', typeof page.value.content);
|
||||
console.log('内容前50个字符:', page.value.content.substring(0, 50));
|
||||
console.log('内容类型:', typeof page.value.content)
|
||||
console.log('内容前50个字符:', page.value.content.substring(0, 50))
|
||||
|
||||
// 处理可能的中文乱码问题
|
||||
if (/\\u|%/.test(page.value.content)) {
|
||||
try {
|
||||
page.value.content = decodeURIComponent(page.value.content);
|
||||
page.value.content = decodeURIComponent(page.value.content)
|
||||
} catch (e) {
|
||||
console.error('内容解码失败:', e);
|
||||
console.error('内容解码失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
// 确保内容是HTML字符串
|
||||
if (typeof page.value.content !== 'string') {
|
||||
try {
|
||||
page.value.content = JSON.stringify(page.value.content);
|
||||
page.value.content = JSON.stringify(page.value.content)
|
||||
} catch (e) {
|
||||
console.error('内容格式转换失败:', e);
|
||||
console.error('内容格式转换失败:', e)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('页面内容为空');
|
||||
page.value.content = '<p>暂无内容</p>';
|
||||
console.warn('页面内容为空')
|
||||
page.value.content = '<p>暂无内容</p>'
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
document.title = `${page.value.title || '法律规定详情'} - 法律规定`;
|
||||
document.title = `${page.value.title || '法律规定详情'} - 法律规定`
|
||||
} else {
|
||||
console.error('获取法律规定详情失败:', result?.msg || '未知错误');
|
||||
error.value = true;
|
||||
console.error('获取法律规定详情失败:', result?.msg || '未知错误')
|
||||
error.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取法律规定详情异常:', e);
|
||||
error.value = true;
|
||||
console.error('获取法律规定详情异常:', e)
|
||||
error.value = true
|
||||
} finally {
|
||||
loading.value = false;
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 添加一个初始化折叠卡片功能的方法
|
||||
const initCollapsibleCards = () => {
|
||||
console.log('初始化折叠卡片...')
|
||||
|
||||
// 初始化时默认展开所有卡片
|
||||
for (let i = 0; i < cardCount.value; i++) {
|
||||
expandedCards.value.add(i)
|
||||
const card = document.querySelector(`.law-card[data-card-index="${i}"]`)
|
||||
if (card) {
|
||||
card.classList.add('expanded')
|
||||
}
|
||||
}
|
||||
|
||||
// 使用事件委托在父元素上监听点击事件
|
||||
document.querySelector('.body')?.addEventListener('click', (e) => {
|
||||
const target = e.target as HTMLElement
|
||||
const header = target.closest('.law-card-header')
|
||||
|
||||
if (header) {
|
||||
const index = parseInt(header.getAttribute('data-card-index') || '-1')
|
||||
if (index >= 0) {
|
||||
toggleCard(index)
|
||||
console.log('切换卡片状态:', index)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
console.log('折叠卡片初始化完成')
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 内容加载完成后初始化折叠卡片
|
||||
setTimeout(() => {
|
||||
initCollapsibleCards()
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateStr: string | null | undefined): string => {
|
||||
if (!dateStr) return '未知日期';
|
||||
if (!dateStr) return '未知日期'
|
||||
try {
|
||||
const date = new Date(dateStr);
|
||||
const date = new Date(dateStr)
|
||||
if (isNaN(date.getTime())) {
|
||||
return '未知日期';
|
||||
return '未知日期'
|
||||
}
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
day: '2-digit',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('日期格式化错误:', e);
|
||||
return '未知日期';
|
||||
console.error('日期格式化错误:', e)
|
||||
return '未知日期'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -168,12 +339,100 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body" v-html="page.content"></div>
|
||||
|
||||
<div class="body" v-html="processedContent"></div>
|
||||
<div class="footer">
|
||||
<router-link to="/hasfjlaw" class="btn-more">查看更多法律规定</router-link>
|
||||
<router-link to="/" class="btn-home">返回首页</router-link>
|
||||
</div>
|
||||
<!-- 用户留言模块 -->
|
||||
<div class="feedback-section">
|
||||
<h3><i class="icon-chat"></i> 法律咨询与留言</h3>
|
||||
<div v-if="!feedbackSubmitSuccess" class="feedback-form">
|
||||
<p class="section-description">如果您对法律规定有任何疑问或建议,欢迎在此留言。</p>
|
||||
<div class="form-group">
|
||||
<label for="legalArticle"
|
||||
>有问题的法律条例或案例:<span class="required">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="legalArticle"
|
||||
v-model="feedbackForm.legalArticle"
|
||||
placeholder="如:公司法第十五条"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="issueDescription">问题描述: <span class="required">*</span></label>
|
||||
<textarea
|
||||
id="issueDescription"
|
||||
v-model="feedbackForm.issueDescription"
|
||||
rows="5"
|
||||
placeholder="请详细描述您的问题,字数在10-500字之间"
|
||||
required
|
||||
></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="contactInfoType">联系方式类型:</label>
|
||||
<select id="contactInfoType" v-model="feedbackForm.contactInfoType">
|
||||
<option :value="1">手机号</option>
|
||||
<option :value="2">身份证号</option>
|
||||
<option :value="3">姓名</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phoneNumber"
|
||||
>手机号:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 1">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="phoneNumber"
|
||||
v-model="feedbackForm.phoneNumber"
|
||||
placeholder="请输入手机号"
|
||||
:required="feedbackForm.contactInfoType === 1"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="idCardNumber"
|
||||
>身份证号:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 2">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="idCardNumber"
|
||||
v-model="feedbackForm.idCardNumber"
|
||||
placeholder="请输入身份证号"
|
||||
:required="feedbackForm.contactInfoType === 2"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="userNameContact"
|
||||
>姓名:
|
||||
<span class="required" v-show="feedbackForm.contactInfoType === 3">*</span></label
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
id="userNameContact"
|
||||
v-model="feedbackForm.userName"
|
||||
placeholder="请输入您的姓名"
|
||||
:required="feedbackForm.contactInfoType === 3"
|
||||
/>
|
||||
</div>
|
||||
<button @click="submitFeedback" class="submit-btn">提交留言</button>
|
||||
</div>
|
||||
<div v-else class="feedback-success">
|
||||
<h4>留言提交成功!</h4>
|
||||
<p>
|
||||
您的留言查询编号是:<span class="feedback-no">{{ feedbackNoReturned }}</span>
|
||||
</p>
|
||||
<p>请牢记此编号,以便查询回复。</p>
|
||||
<button
|
||||
@click="((feedbackSubmitSuccess = false), (feedbackNoReturned = ''))"
|
||||
class="submit-btn"
|
||||
>
|
||||
继续留言
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -210,7 +469,9 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.error {
|
||||
@ -295,6 +556,11 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icon-user::before {
|
||||
content: '👤';
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.content .body {
|
||||
line-height: 1.8;
|
||||
color: var(--color-text);
|
||||
@ -340,6 +606,61 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
/* 添加法律卡片相关样式 */
|
||||
:deep(.law-card) {
|
||||
margin: 1.5rem 0;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
:deep(.law-card-header) {
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: #f0f0f0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
user-select: none; /* 防止选中文本 */
|
||||
}
|
||||
|
||||
:deep(.law-card-header:hover) {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-toggle) {
|
||||
transition: transform 0.3s;
|
||||
transform: rotate(180deg); /* 默认显示为向上箭头,表示展开状态 */
|
||||
}
|
||||
|
||||
:deep(.law-card-content) {
|
||||
padding: 1.5rem; /* 默认有内边距 */
|
||||
max-height: 2000px; /* 默认展开高度 */
|
||||
overflow: hidden;
|
||||
transition:
|
||||
max-height 0.5s ease,
|
||||
padding 0.5s ease;
|
||||
}
|
||||
|
||||
/* 非展开状态的样式 */
|
||||
:deep(.law-card:not(.expanded) .law-card-content) {
|
||||
padding: 0;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
:deep(.law-card:not(.expanded) .law-card-toggle) {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
.content .footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 1.5rem;
|
||||
@ -350,18 +671,22 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
.btn-home,
|
||||
.btn-more {
|
||||
display: inline-block;
|
||||
padding: 0.6rem 1.5rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s, transform 0.3s;
|
||||
transition:
|
||||
background-color 0.3s,
|
||||
transform 0.3s;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.btn-home:hover, .btn-more:hover {
|
||||
.btn-home:hover,
|
||||
.btn-more:hover {
|
||||
background-color: #0069d9;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
@ -390,6 +715,10 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
.content .body {
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
@ -426,10 +755,23 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
.btn-home, .btn-more {
|
||||
.btn-home,
|
||||
.btn-more {
|
||||
padding: 0.5rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-header) {
|
||||
padding: 0.8rem 1rem;
|
||||
}
|
||||
|
||||
:deep(.law-card-title) {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-content) {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
@ -446,4 +788,258 @@ const formatDate = (dateStr: string | null | undefined): string => {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-content) {
|
||||
padding: 1.5rem;
|
||||
max-height: 2000px;
|
||||
}
|
||||
|
||||
:deep(.law-card.expanded .law-card-toggle) {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.feedback-section {
|
||||
background-color: #f9f9f9;
|
||||
border-radius: 8px;
|
||||
margin-top: 2rem;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.feedback-section h3 {
|
||||
font-size: 1.5rem;
|
||||
color: var(--color-primary);
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.feedback-section .icon-chat::before {
|
||||
content: '💬'; /* Chat bubble icon */
|
||||
margin-right: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.feedback-section .icon-search::before {
|
||||
content: '🔍'; /* Search icon */
|
||||
margin-right: 10px;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.feedback-section .section-description {
|
||||
color: var(--color-text-light);
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.feedback-form .form-group,
|
||||
.feedback-query .form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.feedback-form label,
|
||||
.feedback-query label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.feedback-form input[type='text'],
|
||||
.feedback-form textarea,
|
||||
.feedback-form select,
|
||||
.feedback-query input[type='text'],
|
||||
.feedback-query select {
|
||||
width: calc(100% - 20px);
|
||||
padding: 0.8rem 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.feedback-form textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.feedback-form .required,
|
||||
.feedback-query .required {
|
||||
color: var(--color-danger);
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn,
|
||||
.feedback-query .submit-btn {
|
||||
display: inline-block;
|
||||
padding: 0.8rem 2rem;
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s ease;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn:hover,
|
||||
.feedback-query .submit-btn:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
|
||||
.feedback-query .submit-btn:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.feedback-success {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
background-color: #e6ffe6;
|
||||
border: 1px solid #aaffaa;
|
||||
border-radius: 8px;
|
||||
color: #336633;
|
||||
}
|
||||
|
||||
.feedback-success h4 {
|
||||
color: #28a745;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.feedback-success .feedback-no {
|
||||
font-weight: bold;
|
||||
color: var(--color-primary);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-success button {
|
||||
margin-top: 1.5rem;
|
||||
padding: 0.7rem 1.5rem;
|
||||
background-color: var(--color-success);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.feedback-success button:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.query-title {
|
||||
margin-top: 3rem;
|
||||
border-top: 1px dashed #eee;
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.query-result {
|
||||
background-color: #e9f7fe;
|
||||
border: 1px solid #b3e0ff;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.query-result h4 {
|
||||
color: #007bff;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.3rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-bottom: 1px dashed #cceeff;
|
||||
}
|
||||
|
||||
.query-result .result-item {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--color-text);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.query-result .result-item strong {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.query-result .admin-reply-content {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.6;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: var(--color-danger);
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: orange;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-replied {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
color: gray;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.feedback-section {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.feedback-section {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.feedback-section h3 {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.feedback-section .icon-chat::before,
|
||||
.feedback-section .icon-search::before {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.feedback-form input[type='text'],
|
||||
.feedback-form textarea,
|
||||
.feedback-form select,
|
||||
.feedback-query input[type='text'],
|
||||
.feedback-query select {
|
||||
padding: 0.6rem 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feedback-form .submit-btn,
|
||||
.feedback-query .submit-btn {
|
||||
padding: 0.6rem 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.feedback-success h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.feedback-success .feedback-no {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.query-result h4 {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.query-result .result-item {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
101
vite.config.ts
101
vite.config.ts
@ -19,11 +19,13 @@ export default defineConfig({
|
||||
return () => {
|
||||
server.middlewares.use((req, res, next) => {
|
||||
// 如果是前端路由路径,直接返回index.html
|
||||
if (req.url === '/hasfjlaw' || req.url === '/hasfjcase' || req.url === '/hasfjform' || req.url === '/qrcodes') {
|
||||
const indexHtml = fs.readFileSync(
|
||||
path.resolve(__dirname, 'index.html'),
|
||||
'utf-8'
|
||||
)
|
||||
if (
|
||||
req.url === '/hasfjlaw' ||
|
||||
req.url === '/hasfjcase' ||
|
||||
req.url === '/hasfjform' ||
|
||||
req.url === '/qrcodes'
|
||||
) {
|
||||
const indexHtml = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'text/html')
|
||||
res.end(indexHtml)
|
||||
@ -32,12 +34,12 @@ export default defineConfig({
|
||||
next()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
@ -46,79 +48,86 @@ export default defineConfig({
|
||||
proxy: {
|
||||
// 配置跨域
|
||||
'/api': {
|
||||
// target: 'http://127.0.0.1:9799', // 后端服务地址
|
||||
target: 'http://222.184.49.22:9799', // 后端服务地址
|
||||
// target: 'http://127.0.0.1:19696', // 后端服务地址
|
||||
target: 'http://222.184.49.22:19696', // 后端服务地址
|
||||
changeOrigin: true, // 支持跨域
|
||||
rewrite: (path) => path.replace(/^\/api/, ''), // 移除/api前缀
|
||||
configure: (proxy, options) => {
|
||||
// 添加代理错误处理
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('API代理错误:', err);
|
||||
console.error('API代理错误:', err)
|
||||
|
||||
// 返回友好的错误响应
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null,
|
||||
}),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
// 添加司法局后台管理接口代理
|
||||
'/hasfj': {
|
||||
// target: 'http://127.0.0.1:9799', // 后端服务地址
|
||||
target: 'http://222.184.49.22:9799', // 司法局后端服务
|
||||
// target: 'http://127.0.0.1:19696', // 后端服务地址
|
||||
target: 'http://222.184.49.22:19696', // 后端服务地址
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path, // 保持路径不变
|
||||
configure: (proxy, options) => {
|
||||
// 添加代理错误处理
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('后台管理接口代理错误:', err);
|
||||
console.error('后台管理接口代理错误:', err)
|
||||
|
||||
// 返回友好的错误响应
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
code: 500,
|
||||
msg: '后端服务暂时不可用,请稍后重试',
|
||||
data: null,
|
||||
}),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
// 添加文件下载代理,专门处理静态资源文件请求
|
||||
'/profile': {
|
||||
target: 'http://222.184.49.22:9799', // 后端静态资源服务地址
|
||||
// target: 'http://127.0.0.1:19696', // 后端服务地址
|
||||
target: 'http://222.184.49.22:19696', // 后端静态资源服务地址
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path, // 保持路径不变
|
||||
configure: (proxy, options) => {
|
||||
proxy.on('error', (err, req, res) => {
|
||||
console.error('文件下载代理错误:', err);
|
||||
console.error('文件下载代理错误:', err)
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.writeHead(500, {
|
||||
'Content-Type': 'application/json'
|
||||
});
|
||||
'Content-Type': 'application/json',
|
||||
})
|
||||
|
||||
res.end(JSON.stringify({
|
||||
code: 500,
|
||||
msg: '文件下载失败,请稍后重试',
|
||||
data: null
|
||||
}));
|
||||
res.end(
|
||||
JSON.stringify({
|
||||
code: 500,
|
||||
msg: '文件下载失败,请稍后重试',
|
||||
data: null,
|
||||
}),
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user