Compare commits
97 Commits
ed3116773e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 34c866b221 | |||
| 545c90f13b | |||
| db228d91ed | |||
| 2b3f00ca2b | |||
| d2816a7f78 | |||
| f058a6f4e2 | |||
| d1327abeae | |||
| 0dc9f602f1 | |||
| 9dc618b255 | |||
| 60a3cba975 | |||
| 64868b47b3 | |||
| 7146a0330e | |||
| 4b9277282a | |||
| 513819ddce | |||
| 2b66a28446 | |||
| 4e56b498b8 | |||
| 743abaebb5 | |||
| a0d8823b02 | |||
| afaa76be91 | |||
| 99c692b048 | |||
| baea20728e | |||
| b1a91f3888 | |||
| b994378c36 | |||
| 713da3a160 | |||
| 94bfc567f2 | |||
| b7933f8cdf | |||
| d928642652 | |||
| 0a1bcb3cd4 | |||
| f73418546e | |||
| 586056413b | |||
| 278a1dd763 | |||
| fc6d7b9c5c | |||
| bf2ca554d4 | |||
| 5130bfd12e | |||
| 1bf29eaa65 | |||
| 0710268420 | |||
| e23e4cf248 | |||
| c13ee7acd6 | |||
| ba989b88f7 | |||
| d0d8235d27 | |||
| 2bfdf5a9eb | |||
| 22c9665932 | |||
| e76407cee2 | |||
| ea179428b7 | |||
| f3bbb6e25c | |||
| 268fbf5c97 | |||
| b07fe063a1 | |||
| 8190e8d689 | |||
| 0479451acb | |||
| 227450566e | |||
| acfb4c3c8d | |||
| 4f890c8b7f | |||
| 97ee58f8da | |||
| b0e5c01730 | |||
| 4eddad3250 | |||
| c9ed02baf8 | |||
| 02251c738e | |||
| d9e98355cf | |||
| 989f2afe4d | |||
| 0988bdadaf | |||
| 75cf9ccbd9 | |||
| 8ce3c85f9b | |||
| e284574c7e | |||
| 1bb83df40b | |||
| 82879fc920 | |||
| 013b122a96 | |||
| a967254fbc | |||
| d8853a5db1 | |||
| 1f7d4a7896 | |||
| 96bc818eb2 | |||
| d56f375217 | |||
| eed5775c1d | |||
| 95c46ebe67 | |||
| 710ff3b6f7 | |||
| 4d227a9fce | |||
| 61eff62996 | |||
| 181a04639c | |||
| fa0ef783e6 | |||
| 23e77c5186 | |||
| 376f701422 | |||
| 34c74d9ad8 | |||
| 4dfe0a10bf | |||
| e5a2dd2288 | |||
| 2ad870b61e | |||
| fa174858db | |||
| 0bec040309 | |||
| 532b34784a | |||
| 546bc4008d | |||
| acaed047ce | |||
| 944998ce59 | |||
| 8d50c6dadf | |||
| 80ddefec0f | |||
| 2ae07c2ba3 | |||
| c7fc744e16 | |||
| 127d9ed592 | |||
| b8974a7c6a | |||
| d593ac5c7f |
@@ -1,3 +1,3 @@
|
||||
# 开发环境配置
|
||||
VITE_API_BASE_URL=/api
|
||||
VITE_UPLOAD_BASE_URL=http://localhost:3000/api/upload
|
||||
VITE_API_BASE_URL=https://minio.zrbjr.com/
|
||||
VITE_UPLOAD_BASE_URL=/api/upload
|
||||
@@ -1,5 +1,5 @@
|
||||
# 生产环境配置
|
||||
VITE_API_BASE_URL=https://www.zrbjr.com/api
|
||||
VITE_UPLOAD_BASE_URL=https://www.zrbjr.com/api/upload
|
||||
VITE_API_BASE_URL=https://minio.zrbjr.com/
|
||||
VITE_UPLOAD_BASE_URL=/api/upload
|
||||
# VITE_API_BASE_URL=http://114.55.111.44:3001/api
|
||||
# VITE_UPLOAD_BASE_URL=http://114.55.111.44:3001/api/upload
|
||||
|
||||
306
package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"element-plus": "^2.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode": "^1.5.4",
|
||||
"swiper": "^8.4.7",
|
||||
"vue": "^3.3.11",
|
||||
"vue-awesome-swiper": "^5.0.1",
|
||||
@@ -1306,6 +1307,30 @@
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/async-validator": {
|
||||
"version": "4.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz",
|
||||
@@ -1363,6 +1388,15 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
@@ -1379,6 +1413,35 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^6.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorjs.io": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz",
|
||||
@@ -1410,6 +1473,15 @@
|
||||
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
@@ -1433,6 +1505,12 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dijkstrajs": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
|
||||
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/dom7": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmmirror.com/dom7/-/dom7-4.0.6.tgz",
|
||||
@@ -1498,6 +1576,12 @@
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
@@ -1620,6 +1704,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/find-up": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
|
||||
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"locate-path": "^5.0.0",
|
||||
"path-exists": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
@@ -1680,6 +1777,15 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
@@ -1796,6 +1902,15 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/is-glob": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||
@@ -1821,6 +1936,18 @@
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/locate-path": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
|
||||
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-locate": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
@@ -1942,6 +2069,51 @@
|
||||
"integrity": "sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-try": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/p-locate": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
|
||||
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"p-limit": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/p-try": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
|
||||
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/path-exists": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
|
||||
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||
@@ -1984,6 +2156,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
@@ -2018,6 +2199,23 @@
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/qrcode": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
|
||||
"integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dijkstrajs": "^1.0.1",
|
||||
"pngjs": "^5.0.0",
|
||||
"yargs": "^15.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"qrcode": "bin/qrcode"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
@@ -2032,6 +2230,21 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.44.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz",
|
||||
@@ -2416,6 +2629,12 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
@@ -2431,6 +2650,32 @@
|
||||
"integrity": "sha512-ISv/Ch+ig7SOtw7G2+qkwfVASzazUnvlDTwypdLoPoySv+6MqlOV10VwPSE6EWkGjhW50lUmghPmpYZXMu/+AQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
@@ -2699,6 +2944,67 @@
|
||||
"vue": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which-module": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
|
||||
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
|
||||
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^6.0.0",
|
||||
"decamelize": "^1.2.0",
|
||||
"find-up": "^4.1.0",
|
||||
"get-caller-file": "^2.0.1",
|
||||
"require-directory": "^2.1.1",
|
||||
"require-main-filename": "^2.0.0",
|
||||
"set-blocking": "^2.0.0",
|
||||
"string-width": "^4.2.0",
|
||||
"which-module": "^2.0.0",
|
||||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^18.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "18.1.3",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
|
||||
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/zrender": {
|
||||
"version": "5.6.1",
|
||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
"element-plus": "^2.4.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode": "^1.5.4",
|
||||
"swiper": "^8.4.7",
|
||||
"vue": "^3.3.11",
|
||||
"vue-awesome-swiper": "^5.0.1",
|
||||
|
||||
BIN
public/imgs/background.png
Normal file
|
After Width: | Height: | Size: 128 KiB |
BIN
public/imgs/bottomnav/gerenzhongxin-background.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/imgs/bottomnav/gerenzhongxin.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/imgs/bottomnav/jifenshangcheng-background.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/imgs/bottomnav/jifenshangcheng.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/imgs/bottomnav/moren-background.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/imgs/bottomnav/rongdoumingxi-background.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/imgs/bottomnav/rongdoumingxi.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/imgs/bottomnav/rongdoupipei-background.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/imgs/bottomnav/rongdoupipei.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/imgs/bottomnav/zhuye-background.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/imgs/bottomnav/zhuye.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/imgs/line.png
Normal file
|
After Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
public/imgs/mainpage/dinglianghuoqu1.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/imgs/mainpage/dinglianghuoqu2.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/imgs/mainpage/gengxintishi.png
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
public/imgs/mainpage/huoqurongdou1.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/imgs/mainpage/huoqurongdou2.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
BIN
public/imgs/mainpage/jifenyue1.png
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
public/imgs/mainpage/jifenyue2.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/imgs/mainpage/jingganglan-1.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
public/imgs/mainpage/jingganglan-2.png
Normal file
|
After Width: | Height: | Size: 450 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
BIN
public/imgs/mainpage/lunbotu/1.png
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
public/imgs/mainpage/lunbotu/2.png
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
public/imgs/mainpage/lunbotu/3.png
Normal file
|
After Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 383 B After Width: | Height: | Size: 383 B |
|
Before Width: | Height: | Size: 357 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
BIN
public/imgs/profile/icon/dizhi.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/imgs/profile/icon/gouwuche.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/imgs/profile/icon/kabaoguanli.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/imgs/profile/rongdou.png
Normal file
|
After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
BIN
public/imgs/shop/1.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
public/imgs/shop/10.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/imgs/shop/2.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/imgs/shop/3.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
public/imgs/shop/4.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
public/imgs/shop/5.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/imgs/shop/6.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/imgs/shop/7.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
public/imgs/shop/8.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
public/imgs/shop/9.png
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
public/imgs/shop/coupon/background.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
public/imgs/shop/coupon/bg_useful1.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/imgs/shop/coupon/bg_useful2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/imgs/shop/coupon/bg_useless1.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/imgs/shop/coupon/bg_useless2.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/imgs/shop/shumachanping/1.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
public/imgs/shop/shumachanping/2.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/imgs/shop/shumachanping/3.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
public/imgs/shop/shumachanping/4.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/imgs/shop/shumachanping/5.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/imgs/shop/shumachanping/6.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/imgs/shop/shumachanping/7.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/imgs/shop/shumachanping/8.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/imgs/shop/shumachanping/9.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/imgs/shop/style-change.png
Normal file
|
After Width: | Height: | Size: 260 B |
BIN
public/imgs/shop/tips.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 102 KiB |
@@ -19,13 +19,11 @@ const routesWithBottomNav = [
|
||||
'/',
|
||||
'/transfers',
|
||||
'/matching',
|
||||
'/points-history',
|
||||
'/profile',
|
||||
'/mypoints-history',
|
||||
'/shop',
|
||||
'/orders',
|
||||
'/mainpage',
|
||||
'/myprofile',
|
||||
'/mymatching',
|
||||
'/myshop',
|
||||
'/customerservice'
|
||||
]
|
||||
|
||||
@@ -1,76 +1,82 @@
|
||||
<template>
|
||||
<div class="bottom-nav">
|
||||
<div class="bottom-nav" :style="{ backgroundImage: 'url(' + backgroundImage + ')' }">
|
||||
<!-- 融豆匹配 -->
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/mainpage') }"
|
||||
@click="handleNavClick('/mainpage')"
|
||||
:class="{ active: isActive('/matching') }"
|
||||
@click="handleNavClick('/matching')"
|
||||
>
|
||||
<HomeFilled class="nav-icon" />
|
||||
<span class="nav-label">主页</span>
|
||||
</div>
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/mymatching') }"
|
||||
@click="handleNavClick('/mymatching')"
|
||||
>
|
||||
<Connection class="nav-icon" />
|
||||
<span class="nav-label">融豆匹配</span>
|
||||
</div>
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/myshop') }"
|
||||
@click="handleNavClick('/myshop')"
|
||||
>
|
||||
<Coin class="nav-icon" />
|
||||
<span class="nav-label">积分商城</span>
|
||||
<img
|
||||
src="/imgs/bottomnav/rongdoupipei.png"
|
||||
alt="融豆匹配"
|
||||
class="nav-image"
|
||||
:class="{ 'nav-image-active': isActive('/matching') }"
|
||||
/>
|
||||
<span class="nav-text" :class="{ 'nav-text-active': isActive('/matching') }">融豆匹配</span>
|
||||
</div>
|
||||
<!-- 融豆明细 -->
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/transfers') }"
|
||||
@click="handleNavClick('/transfers')"
|
||||
>
|
||||
<Money class="nav-icon" />
|
||||
<span class="nav-label">融豆明细</span>
|
||||
<img
|
||||
src="/imgs/bottomnav/rongdoumingxi.png"
|
||||
alt="融豆明细"
|
||||
class="nav-image"
|
||||
:class="{ 'nav-image-active': isActive('/transfers') }"
|
||||
/>
|
||||
<span class="nav-text" :class="{ 'nav-text-active': isActive('/transfers') }">融豆明细</span>
|
||||
</div>
|
||||
<!-- 主页 -->
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/mainpage') }"
|
||||
@click="handleNavClick('/mainpage')"
|
||||
>
|
||||
<img
|
||||
src="/imgs/bottomnav/zhuye.png"
|
||||
alt="主页"
|
||||
class="nav-image"
|
||||
:class="{ 'nav-image-active': isActive('/mainpage') }"
|
||||
/>
|
||||
<span class="nav-text" :class="{ 'nav-text-active': isActive('/mainpage') }">主页</span>
|
||||
</div>
|
||||
<!-- 积分商城 -->
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/shop') }"
|
||||
@click="handleNavClick('/shop')"
|
||||
>
|
||||
<img
|
||||
src="/imgs/bottomnav/jifenshangcheng.png"
|
||||
alt="积分商城"
|
||||
class="nav-image"
|
||||
:class="{ 'nav-image-active': isActive('/shop') }"
|
||||
/>
|
||||
<span class="nav-text" :class="{ 'nav-text-active': isActive('/shop') }">积分商城</span>
|
||||
</div>
|
||||
<!-- 个人中心 -->
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/myprofile') }"
|
||||
@click="handleNavClick('/myprofile')"
|
||||
>
|
||||
<User class="nav-icon" />
|
||||
<span class="nav-label">个人中心</span>
|
||||
<img
|
||||
src="/imgs/bottomnav/gerenzhongxin.png"
|
||||
alt="个人中心"
|
||||
class="nav-image"
|
||||
:class="{ 'nav-image-active': isActive('/myprofile') }"
|
||||
/>
|
||||
<span class="nav-text" :class="{ 'nav-text-active': isActive('/myprofile') }">个人中心</span>
|
||||
</div>
|
||||
<!-- <div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/matching') }"
|
||||
@click="handleNavClick('/matching')"
|
||||
>
|
||||
<Connection class="nav-icon" />
|
||||
<span class="nav-label">资金匹配</span>
|
||||
</div>
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/points-history') }"
|
||||
@click="handleNavClick('/points-history')"
|
||||
>
|
||||
<Coin class="nav-icon" />
|
||||
<span class="nav-label">积分记录</span>
|
||||
</div>
|
||||
<div
|
||||
class="nav-item"
|
||||
:class="{ active: isActive('/profile') }"
|
||||
@click="handleNavClick('/profile')"
|
||||
>
|
||||
<User class="nav-icon" />
|
||||
<span class="nav-label">个人中心</span>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { computed } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { Money, Coin, User, Connection, HomeFilled } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -87,6 +93,31 @@ const handleNavClick = (path) => {
|
||||
}
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
// 根据当前路由计算背景图片
|
||||
const backgroundImage = computed(() => {
|
||||
const currentPath = route.path
|
||||
switch (currentPath) {
|
||||
case '/matching':
|
||||
// return '/imgs/bottomnav/融豆匹配背景.png'
|
||||
return '/imgs/bottomnav/rongdoupipei-background.png'
|
||||
case '/transfers':
|
||||
// return '/imgs/bottomnav/融豆明细背景.png'
|
||||
return '/imgs/bottomnav/rongdoumingxi-background.png'
|
||||
case '/mainpage':
|
||||
// return '/imgs/bottomnav/主页背景.png'
|
||||
return '/imgs/bottomnav/zhuye-background.png'
|
||||
case '/shop':
|
||||
// return '/imgs/bottomnav/积分商城背景.png'
|
||||
return '/imgs/bottomnav/jifenshangcheng-background.png'
|
||||
case '/myprofile':
|
||||
// return '/imgs/bottomnav/个人中心背景.png'
|
||||
return '/imgs/bottomnav/gerenzhongxin-background.png'
|
||||
default:
|
||||
// return '/imgs/bottomnav/默认背景.png'
|
||||
return '/imgs/bottomnav/moren-background.png'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -95,116 +126,119 @@ const handleNavClick = (path) => {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 8px 0;
|
||||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
|
||||
padding: 20px 0;
|
||||
z-index: 1000;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 12px;
|
||||
min-width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
transform: translateY(-2px);
|
||||
background: rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.nav-item:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
background: rgba(64, 158, 255, 0.15);
|
||||
}
|
||||
|
||||
.nav-item.active .nav-icon {
|
||||
color: #409eff;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.nav-item.active .nav-label {
|
||||
color: #409eff;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 20px;
|
||||
color: #909399;
|
||||
margin-bottom: 4px;
|
||||
.nav-image {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: contain;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-item:hover .nav-icon {
|
||||
color: #409eff;
|
||||
transform: scale(1.1);
|
||||
.nav-image-active {
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
.nav-text {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
color: white;
|
||||
margin-top: 4px;
|
||||
transition: all 0.3s ease;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-item:hover .nav-label {
|
||||
color: #409eff;
|
||||
.nav-text-active {
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 最左边小球向右偏移 */
|
||||
.nav-item:first-child {
|
||||
transform: translateX(12px);
|
||||
}
|
||||
|
||||
.nav-item:nth-child(2) {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.nav-item:nth-child(4) {
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
/* 最右边小球向左偏移 */
|
||||
.nav-item:last-child {
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
|
||||
/* 移动端适配 */
|
||||
@media (max-width: 768px) {
|
||||
.bottom-nav {
|
||||
padding: 6px 0;
|
||||
padding-bottom: max(8px, env(safe-area-inset-bottom));
|
||||
padding: 15px 0;
|
||||
padding-bottom: max(15px, env(safe-area-inset-bottom));
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 6px 8px;
|
||||
min-width: 50px;
|
||||
min-height: 44px; /* 触摸友好的最小尺寸 */
|
||||
justify-content: center;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 18px;
|
||||
margin-bottom: 2px;
|
||||
.nav-image {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
.nav-image-active {
|
||||
transform: translateY(-40px);
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.bottom-nav {
|
||||
padding: 4px 0;
|
||||
padding-bottom: max(6px, env(safe-area-inset-bottom));
|
||||
padding: 10px 0;
|
||||
padding-bottom: max(10px, env(safe-area-inset-bottom));
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 4px 6px;
|
||||
min-width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 16px;
|
||||
margin-bottom: 1px;
|
||||
.nav-image {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
.nav-image-active {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
@@ -212,20 +246,25 @@ const handleNavClick = (path) => {
|
||||
/* 横屏适配 */
|
||||
@media (max-height: 500px) and (orientation: landscape) {
|
||||
.bottom-nav {
|
||||
padding: 4px 0;
|
||||
padding: 8px 0;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
padding: 4px 8px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
font-size: 16px;
|
||||
margin-bottom: 1px;
|
||||
.nav-image {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
font-size: 10px;
|
||||
.nav-image-active {
|
||||
transform: translateY(-30px);
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -44,7 +44,7 @@
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Refresh, Loading } from '@element-plus/icons-vue'
|
||||
import api from '@/utils/api'
|
||||
import api,{captchaAPI} from '@/utils/api'
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -81,7 +81,7 @@ const loading = ref(false)
|
||||
const getCaptcha = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await api.get('/captcha/generate')
|
||||
const response = await captchaAPI.generate()
|
||||
|
||||
if (response.data.success) {
|
||||
captchaImage.value = response.data.data.image
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// 环境配置
|
||||
const config = {
|
||||
development: {
|
||||
baseURL: 'http://localhost:3000',
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'https://minio.zrbjr.com/',
|
||||
uploadURL: import.meta.env.VITE_UPLOAD_BASE_URL || 'http://localhost:3000/api/upload'
|
||||
},
|
||||
production: {
|
||||
baseURL: window.location.origin,
|
||||
baseURL: 'https://minio.zrbjr.com/',
|
||||
uploadURL: import.meta.env.VITE_UPLOAD_BASE_URL || `${window.location.origin}/api/upload`
|
||||
}
|
||||
}
|
||||
@@ -21,20 +21,32 @@ export const { baseURL, uploadURL } = config[env]
|
||||
|
||||
// 获取完整的图片URL
|
||||
export const getImageUrl = (imagePath) => {
|
||||
// console.log('getImageUrl called with:', imagePath)
|
||||
if (!imagePath) return ''
|
||||
if (imagePath.startsWith('http')) return imagePath
|
||||
|
||||
// 在开发环境下,直接返回相对路径,让Vite代理处理
|
||||
// 如果图片路径以/uploads开头,直接返回原路径
|
||||
if (imagePath.startsWith('/uploads')) {
|
||||
const cleanBaseURL = baseURL.replace(/\/$/, '')
|
||||
// console.log('Image starts with /uploads, returning original path:', imagePath)
|
||||
return `${imagePath}`
|
||||
}
|
||||
|
||||
// 在开发环境下,也需要根据路径前缀处理
|
||||
if (env === 'development') {
|
||||
const cleanBaseURL = baseURL.replace(/\/$/, '')
|
||||
const cleanImagePath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
|
||||
return cleanImagePath
|
||||
const fullUrl = `${cleanBaseURL}${cleanImagePath}`
|
||||
// console.log('Development environment, returning:', fullUrl)
|
||||
return fullUrl
|
||||
}
|
||||
|
||||
// 生产环境下使用完整URL
|
||||
const cleanBaseURL = baseURL.replace(/\/$/, '')
|
||||
const cleanImagePath = imagePath.startsWith('/') ? imagePath : `/${imagePath}`
|
||||
const fullUrl = `${cleanBaseURL}${cleanImagePath}`
|
||||
|
||||
return `${cleanBaseURL}${cleanImagePath}`
|
||||
return fullUrl
|
||||
}
|
||||
|
||||
// 获取上传配置
|
||||
|
||||
@@ -1,376 +1,369 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import {createRouter, createWebHistory} from 'vue-router'
|
||||
import {useUserStore} from '@/stores/user'
|
||||
import NProgress from 'nprogress'
|
||||
import api from '@/utils/api'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
redirect: '/transfers',
|
||||
meta: {
|
||||
title: '首页'
|
||||
{
|
||||
path: '/',
|
||||
name: 'Home',
|
||||
redirect: '/mainpage',
|
||||
meta: {
|
||||
title: '首页'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mylogin',
|
||||
name: 'MyLogin',
|
||||
component: () => import('@/views/MyLogin.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
hideForAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mainpage',
|
||||
name: 'MainPage',
|
||||
component: () => import('@/views/MainPage.vue'),
|
||||
meta: {
|
||||
title: '主页'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/myshop',
|
||||
name: 'MyShop',
|
||||
component: () => import('@/views/MyShop.vue'),
|
||||
meta: {
|
||||
title: '积分商城'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/myprofile',
|
||||
name: 'MyProfile',
|
||||
component: () => import('@/views/MyProfile.vue'),
|
||||
meta: {
|
||||
title: '个人中心'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/distribution',
|
||||
name: 'Distribution',
|
||||
component: () => import('@/views/Distribution.vue'),
|
||||
meta: {
|
||||
title: '分享'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/loading',
|
||||
name: 'Loading',
|
||||
component: () => import('@/views/Loading.vue'),
|
||||
meta: {
|
||||
title: '维护中'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mypoints-history',
|
||||
name: 'MyPointsHistory',
|
||||
component: () => import('@/views/MyPointsHistory.vue'),
|
||||
meta: {
|
||||
title: '积分记录',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/editdetailspage',
|
||||
name: 'EditDetailsPage',
|
||||
component: () => import('@/views/EditDetailsPage.vue'),
|
||||
meta: {
|
||||
title: '编辑信息',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/editpasswordpage',
|
||||
name: 'EditPasswordPage',
|
||||
component: () => import('@/views/EditPasswordPage.vue'),
|
||||
meta: {
|
||||
title: '编辑密码',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/shop',
|
||||
name: 'Shop',
|
||||
component: () => import('@/views/Shop.vue'),
|
||||
meta: {
|
||||
title: '积分商城'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/productCategory',
|
||||
name: 'ProductCategory',
|
||||
component: () => import('@/views/ProductCategory.vue'),
|
||||
meta: {
|
||||
title: '商品分类'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/productCategoryFinal/:name',
|
||||
name: 'ProductCategoryFinal',
|
||||
component: () => import('@/views/ProductCategoryFinal.vue'),
|
||||
meta: {
|
||||
title: '商品分类'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/product/:id',
|
||||
name: 'ProductDetail',
|
||||
component: () => import('@/views/ProductDetail.vue'),
|
||||
meta: {
|
||||
title: '商品详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: () => import('@/views/Register.vue'),
|
||||
meta: {
|
||||
title: '注册',
|
||||
hideForAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/payment',
|
||||
name: 'Payment',
|
||||
component: () => import('@/views/Payment.vue'),
|
||||
meta: {
|
||||
title: '支付激活',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/orders',
|
||||
name: 'Orders',
|
||||
component: () => import('@/views/Orders.vue'),
|
||||
meta: {
|
||||
title: '我的订单',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/transfers',
|
||||
name: 'Transfers',
|
||||
component: () => import('@/views/Transfers.vue'),
|
||||
meta: {
|
||||
title: '转账',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/matching',
|
||||
name: 'Matching',
|
||||
component: () => import('@/views/Matching.vue'),
|
||||
meta: {
|
||||
title: '货款匹配',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/agent/login',
|
||||
name: 'AgentLogin',
|
||||
component: () => import('@/views/AgentLogin.vue'),
|
||||
meta: {
|
||||
title: '代理登录'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/agent/dashboard',
|
||||
name: 'AgentDashboard',
|
||||
component: () => import('@/views/AgentDashboard.vue'),
|
||||
meta: {
|
||||
title: '代理后台',
|
||||
requiresAuth: true,
|
||||
isAgent: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/customerservice',
|
||||
name: 'CustomerService',
|
||||
component: () => import('@/views/CustomerService.vue'),
|
||||
meta: {
|
||||
title: '客服中心'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/productsummary/:id',
|
||||
name: 'productSummary',
|
||||
component: () => import('@/views/ProductSummary.vue'),
|
||||
meta: {
|
||||
title: '商品汇总'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/buydetail',
|
||||
name: 'BuyDetail',
|
||||
component: () => import('../views/BuyDetails.vue'),
|
||||
meta: {title: '确认订单'}
|
||||
},
|
||||
{
|
||||
path: '/pay/:orderId',
|
||||
name: 'Pay',
|
||||
component: () => import('@/views/Pay.vue'),
|
||||
meta: {title: '确认支付'},
|
||||
props: route => ({orderId: route.query.orderId})
|
||||
},
|
||||
{
|
||||
path: '/cart',
|
||||
name: 'Cart',
|
||||
component: () => import('@/views/Cart.vue'),
|
||||
meta: {title: '购物车'}
|
||||
},
|
||||
{
|
||||
path: '/address',
|
||||
name: 'Address',
|
||||
component: () => import('@/views/Address.vue'),
|
||||
meta: {title: '地址管理', requiresAuth: true}
|
||||
},
|
||||
{
|
||||
path: '/payloading',
|
||||
name: 'PayLoading',
|
||||
component: () => import('@/views/PayLoading.vue'),
|
||||
meta: {title: '支付确认'},
|
||||
props: route => ({orderId: route.query.orderId})
|
||||
},
|
||||
{
|
||||
path: '/coupon',
|
||||
name: 'Coupon',
|
||||
component: () => import('@/views/CouponGet.vue'),
|
||||
meta: {title: '优惠券', requiresAuth: true}
|
||||
},
|
||||
{
|
||||
path: '/couponmanage',
|
||||
name: 'CouponManage',
|
||||
component: () => import('@/views/CouponManage.vue'),
|
||||
meta: {title: '优惠券管理', requiresAuth: true}
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('@/views/NotFound.vue'),
|
||||
meta: {
|
||||
title: '页面不存在'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mylogin',
|
||||
name: 'MyLogin',
|
||||
component: () => import('@/views/MyLogin.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
hideForAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mainpage',
|
||||
name: 'MainPage',
|
||||
component: () => import('@/views/MainPage.vue'),
|
||||
meta: {
|
||||
title: '主页'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mymatching',
|
||||
name: 'MyMatching',
|
||||
component: () => import('@/views/Matching.vue'),
|
||||
meta: {
|
||||
title: '货款匹配',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/myshop',
|
||||
name: 'MyShop',
|
||||
component: () => import('@/views/MyShop.vue'),
|
||||
meta: {
|
||||
title: '积分商城'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/myprofile',
|
||||
name: 'MyProfile',
|
||||
component: () => import('@/views/MyProfile.vue'),
|
||||
meta: {
|
||||
title: '个人中心'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/loading',
|
||||
name: 'Loading',
|
||||
component: () => import('@/views/Loading.vue'),
|
||||
meta: {
|
||||
title: '维护中'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/mypoints-history',
|
||||
name: 'MyPointsHistory',
|
||||
component: () => import('@/views/MyPointsHistory.vue'),
|
||||
meta: {
|
||||
title: '积分记录',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/editdetailspage',
|
||||
name: 'EditDetailsPage',
|
||||
component: () => import('@/views/EditDetailsPage.vue'),
|
||||
meta: {
|
||||
title: '编辑信息',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/editpasswordpage',
|
||||
name: 'EditPasswordPage',
|
||||
component: () => import('@/views/EditPasswordPage.vue'),
|
||||
meta: {
|
||||
title: '编辑密码',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'HomePage',
|
||||
component: () => import('@/views/Home.vue'),
|
||||
meta: {
|
||||
title: '首页'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/shop',
|
||||
name: 'Shop',
|
||||
component: () => import('@/views/Shop.vue'),
|
||||
meta: {
|
||||
title: '积分商城'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/product/:id',
|
||||
name: 'ProductDetail',
|
||||
component: () => import('@/views/ProductDetail.vue'),
|
||||
meta: {
|
||||
title: '商品详情'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'Login',
|
||||
component: () => import('@/views/Login.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
hideForAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/register',
|
||||
name: 'Register',
|
||||
component: () => import('@/views/Register.vue'),
|
||||
meta: {
|
||||
title: '注册',
|
||||
hideForAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'Profile',
|
||||
component: () => import('@/views/Profile.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/orders',
|
||||
name: 'Orders',
|
||||
component: () => import('@/views/Orders.vue'),
|
||||
meta: {
|
||||
title: '我的订单',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/points-history',
|
||||
name: 'PointsHistory',
|
||||
component: () => import('@/views/PointsHistory.vue'),
|
||||
meta: {
|
||||
title: '积分记录',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
path: '/task-center',
|
||||
name: 'TaskCenter',
|
||||
component: () => import('@/views/TaskCenter.vue'),
|
||||
meta: {
|
||||
title: '任务中心',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/transfers',
|
||||
name: 'Transfers',
|
||||
component: () => import('@/views/Transfers.vue'),
|
||||
meta: {
|
||||
title: '转账',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/matching',
|
||||
name: 'Matching',
|
||||
component: () => import('@/views/Matching.vue'),
|
||||
meta: {
|
||||
title: '货款匹配',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/agent/login',
|
||||
name: 'AgentLogin',
|
||||
component: () => import('@/views/AgentLogin.vue'),
|
||||
meta: {
|
||||
title: '代理登录'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/agent/dashboard',
|
||||
name: 'AgentDashboard',
|
||||
component: () => import('@/views/AgentDashboard.vue'),
|
||||
meta: {
|
||||
title: '代理后台',
|
||||
requiresAuth: true,
|
||||
isAgent: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/agent/withdrawals',
|
||||
name: 'AgentWithdrawals',
|
||||
component: () => import('@/views/AgentWithdrawals.vue'),
|
||||
meta: {
|
||||
title: '佣金提现',
|
||||
requiresAuth: true,
|
||||
isAgent: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'About',
|
||||
component: () => import('@/views/About.vue'),
|
||||
meta: {
|
||||
title: '关于我们'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/customerservice',
|
||||
name: 'CustomerService',
|
||||
component: () => import('@/views/CustomerService.vue'),
|
||||
meta: {
|
||||
title: '客服中心'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/productsummary/:id',
|
||||
name: 'productSummary',
|
||||
component: () => import('@/views/ProductSummary.vue'),
|
||||
meta: {
|
||||
title: '商品汇总'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/buydetail',
|
||||
name: 'BuyDetail',
|
||||
component: () => import('../views/BuyDetails.vue'),
|
||||
meta: { title: '确认订单' }
|
||||
},
|
||||
{
|
||||
path: '/pay/:orderId',
|
||||
name: 'Pay',
|
||||
component: () => import('@/views/Pay.vue'),
|
||||
meta: { title: '确认支付' },
|
||||
props: route => ({ orderId: route.query.orderId })
|
||||
},
|
||||
{
|
||||
path: '/cart',
|
||||
name: 'Cart',
|
||||
component: () => import('@/views/Cart.vue'),
|
||||
meta: { title: '购物车' }
|
||||
},
|
||||
{
|
||||
path: '/address',
|
||||
name: 'Address',
|
||||
component: () => import('@/views/Address.vue'),
|
||||
meta: { title: '地址管理', requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/payloading',
|
||||
name: 'PayLoading',
|
||||
component: () => import('@/views/PayLoading.vue'),
|
||||
meta: { title: '支付确认' },
|
||||
props: route => ({ orderId: route.query.orderId })
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'NotFound',
|
||||
component: () => import('@/views/NotFound.vue'),
|
||||
meta: {
|
||||
title: '页面不存在'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory('/frontend/'),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
return { top: 0 }
|
||||
history: createWebHistory('/frontend/'),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition
|
||||
} else {
|
||||
return {top: 0}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 路由守卫
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 设置页面标题
|
||||
if (to.meta.title) {
|
||||
document.title = `${to.meta.title} - 炬融圈`
|
||||
}
|
||||
|
||||
// 检查维护模式
|
||||
try {
|
||||
const {data} = await api.get('/system/maintenance-status', { showLoading: false })
|
||||
console.log(data,'data');
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.maintenance_mode) {
|
||||
// 维护模式开启,且不在维护页面,跳转到维护页面
|
||||
if (to.name !== 'Loading') {
|
||||
next({ name: 'Loading' })
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 维护模式关闭,且在维护页面,跳转到首页
|
||||
if (to.name === 'Loading') {
|
||||
next({ name: 'MainPage' })
|
||||
return
|
||||
}
|
||||
}
|
||||
NProgress.start()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 设置页面标题
|
||||
if (to.meta.title) {
|
||||
document.title = `${to.meta.title} - 炬融圈`
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果检查维护状态失败,继续正常流程
|
||||
console.warn('检查维护状态失败:', error)
|
||||
}
|
||||
|
||||
// 检查是否需要认证
|
||||
if (to.meta.requiresAuth) {
|
||||
// 检查是否是代理页面
|
||||
if (to.meta.isAgent) {
|
||||
// 代理页面认证逻辑
|
||||
const agentInfo = localStorage.getItem('agentInfo')
|
||||
const agentToken = localStorage.getItem('token')
|
||||
|
||||
if (!agentInfo || !agentToken) {
|
||||
next({
|
||||
path: '/agent/login',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
|
||||
// 检查维护模式
|
||||
try {
|
||||
const {data} = await api.get('/system/maintenance-status', {showLoading: false})
|
||||
console.log(data, 'data');
|
||||
|
||||
if (data.success) {
|
||||
if (data.data.maintenance_mode) {
|
||||
// 维护模式开启,且不在维护页面,跳转到维护页面
|
||||
if (to.name !== 'Loading') {
|
||||
next({name: 'Loading'})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 维护模式关闭,且在维护页面,跳转到首页
|
||||
if (to.name === 'Loading') {
|
||||
next({name: 'MainPage'})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 如果检查维护状态失败,继续正常流程
|
||||
console.warn('检查维护状态失败:', error)
|
||||
}
|
||||
|
||||
// 检查是否需要认证
|
||||
if (to.meta.requiresAuth) {
|
||||
// 检查是否是代理页面
|
||||
if (to.meta.isAgent) {
|
||||
// 代理页面认证逻辑
|
||||
const agentInfo = localStorage.getItem('agentInfo')
|
||||
const agentToken = localStorage.getItem('token')
|
||||
|
||||
if (!agentInfo || !agentToken) {
|
||||
next({
|
||||
path: '/mylogin',
|
||||
query: {redirect: to.fullPath}
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 普通用户页面认证逻辑
|
||||
// console.log(userStore.isAuthenticated, 'isAuthenticated');
|
||||
|
||||
if (!userStore.isAuthenticated) {
|
||||
// 尝试从本地存储恢复登录状态
|
||||
await userStore.checkAuth()
|
||||
|
||||
if (!userStore.isAuthenticated) {
|
||||
next({
|
||||
name: 'MyLogin',
|
||||
query: {redirect: to.fullPath}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 检查支付状态(管理员除外)
|
||||
// console.log(userStore.user);
|
||||
|
||||
if (userStore.user && userStore.user.role !== 'admin' && userStore.user.payment_status === 'unpaid') {
|
||||
// console.log('进来了');
|
||||
|
||||
// 如果当前不在支付页面,静默重定向到支付页面(不显示额外通知)
|
||||
if (to.name !== 'Payment') {
|
||||
next({
|
||||
name: 'Payment',
|
||||
query: {redirect: to.fullPath}
|
||||
})
|
||||
return
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录用户访问登录/注册页面,重定向到转账管理(改成了主页)
|
||||
if (to.meta.hideForAuth && userStore.isAuthenticated) {
|
||||
next({name: 'MainPage'})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// 普通用户页面认证逻辑
|
||||
if (!userStore.isAuthenticated) {
|
||||
// 尝试从本地存储恢复登录状态
|
||||
await userStore.checkAuth()
|
||||
|
||||
if (!userStore.isAuthenticated) {
|
||||
next({
|
||||
name: 'MyLogin',
|
||||
query: { redirect: to.fullPath }
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已登录用户访问登录/注册页面,重定向到转账管理(改成了主页)
|
||||
if (to.meta.hideForAuth && userStore.isAuthenticated) {
|
||||
next({ name: 'MainPage' })
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
|
||||
next()
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
NProgress.done()
|
||||
NProgress.done()
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import api from '@/utils/api'
|
||||
import api,{authAPI} from '@/utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
@@ -23,10 +23,9 @@ export const useUserStore = defineStore('user', () => {
|
||||
token.value = newToken
|
||||
if (newToken) {
|
||||
localStorage.setItem('token', newToken)
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${newToken}`
|
||||
} else {
|
||||
localStorage.removeItem('token')
|
||||
delete api.defaults.headers.common['Authorization']
|
||||
console.log('token已移除');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,17 +38,53 @@ export const useUserStore = defineStore('user', () => {
|
||||
const login = async (credentials) => {
|
||||
try {
|
||||
loading.value = true
|
||||
const response = await api.post('/auth/login', credentials)
|
||||
const response = await authAPI.login(credentials)
|
||||
|
||||
console.log('response',response);
|
||||
|
||||
if (response.data.token) {
|
||||
if (response.data.success && response.data.token) {
|
||||
setToken(response.data.token)
|
||||
setUser(response.data.user)
|
||||
startStatusCheck() // 登录成功后开始状态检查
|
||||
ElMessage.success(response.data.message || '登录成功')
|
||||
return { success: true, data: response.data }
|
||||
} else if (response.data.needPayment) {
|
||||
console.log(response.data.needPayment,'response.data.needPayment');
|
||||
console.log({
|
||||
success: false,
|
||||
needPayment: true,
|
||||
userId: response.data.userId,
|
||||
message: response.data.message
|
||||
});
|
||||
setToken(response.data.token)
|
||||
setUser(response.data.user)
|
||||
// 用户需要支付激活,不显示错误消息,由前端页面处理
|
||||
const returnData = {
|
||||
success: false,
|
||||
needPayment: true,
|
||||
userId: response.data.userId,
|
||||
message: response.data.message
|
||||
}
|
||||
console.log('即将返回needPayment数据:', returnData);
|
||||
return returnData
|
||||
} else {
|
||||
const message = response.data.message || '登录失败'
|
||||
ElMessage.error(message)
|
||||
return { success: false, message }
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.message || '登录失败'
|
||||
console.log(error,'error');
|
||||
const errorData = error.response?.data
|
||||
if (errorData?.needPayment) {
|
||||
// 处理403状态码返回的需要支付情况
|
||||
return {
|
||||
success: false,
|
||||
needPayment: true,
|
||||
userId: errorData.userId,
|
||||
message: errorData.message
|
||||
}
|
||||
}
|
||||
const message = errorData?.message || '登录失败'
|
||||
ElMessage.error(message)
|
||||
return { success: false, message }
|
||||
} finally {
|
||||
@@ -63,11 +98,30 @@ export const useUserStore = defineStore('user', () => {
|
||||
loading.value = true
|
||||
const response = await api.post('/auth/register', userData)
|
||||
|
||||
if (response.data.token) {
|
||||
setToken(response.data.token)
|
||||
setUser(response.data.user)
|
||||
ElMessage.success(response.data.message || '注册成功')
|
||||
return { success: true, data: response.data }
|
||||
if (response.data.success) {
|
||||
// 检查是否需要支付
|
||||
if (response.data.needPayment) {
|
||||
setToken(response.data.token)
|
||||
setUser(response.data.user)
|
||||
// 需要支付的情况,返回成功状态和支付相关信息
|
||||
return {
|
||||
success: true,
|
||||
needPayment: true,
|
||||
token: response.data.token,
|
||||
user: response.data.user,
|
||||
message: response.data.message
|
||||
}
|
||||
} else {
|
||||
// 直接注册成功的情况
|
||||
setToken(response.data.token)
|
||||
setUser(response.data.user)
|
||||
ElMessage.success(response.data.message || '注册成功')
|
||||
return { success: true, data: response.data }
|
||||
}
|
||||
} else {
|
||||
const message = response.data.message || '注册失败'
|
||||
ElMessage.error(message)
|
||||
return { success: false, message }
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error.response?.data?.message || '注册失败'
|
||||
@@ -94,15 +148,13 @@ export const useUserStore = defineStore('user', () => {
|
||||
|
||||
try {
|
||||
// 确保请求头已设置
|
||||
if (token.value && !api.defaults.headers.common['Authorization']) {
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${token.value}`
|
||||
}
|
||||
|
||||
const response = await api.get('/auth/me')
|
||||
setUser(response.data.user)
|
||||
return true
|
||||
} catch (error) {
|
||||
// token无效,清除本地存储
|
||||
console.log('token无效,清除本地存储',error);
|
||||
setToken('')
|
||||
setUser(null)
|
||||
return false
|
||||
|
||||
219
src/utils/api.js
@@ -1,65 +1,66 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage, ElLoading } from 'element-plus'
|
||||
import router from '@/router'
|
||||
import NProgress from 'nprogress'
|
||||
|
||||
// 创建axios实例
|
||||
const api = axios.create({
|
||||
baseURL: '/api',
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 工厂函数(复用拦截器逻辑)
|
||||
export const createRequest = (baseURL) => {
|
||||
const request = axios.create({
|
||||
baseURL,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化时设置token
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
request.defaults.headers.common['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
})
|
||||
|
||||
// 初始化时设置token
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
api.defaults.headers.common['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
let loadingInstance = null
|
||||
api.interceptors.request.use(
|
||||
// 请求拦截器
|
||||
request.interceptors.request.use(
|
||||
(config) => {
|
||||
// 从localStorage获取token
|
||||
// 开始进度条
|
||||
NProgress.start()
|
||||
|
||||
// 显示加载动画(除了某些不需要的请求)
|
||||
if (!config.hideLoading) {
|
||||
showLoading()
|
||||
}
|
||||
|
||||
// 添加认证token
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 显示加载动画(可选)
|
||||
if (config.showLoading !== false) {
|
||||
loadingInstance = ElLoading.service({
|
||||
text: '加载中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
}
|
||||
|
||||
return config
|
||||
},
|
||||
(error) => {
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close()
|
||||
}
|
||||
hideLoading()
|
||||
NProgress.done()
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 响应拦截器
|
||||
api.interceptors.response.use(
|
||||
// 响应拦截器
|
||||
request.interceptors.response.use(
|
||||
(response) => {
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close()
|
||||
}
|
||||
hideLoading()
|
||||
NProgress.done()
|
||||
return response
|
||||
},
|
||||
(error) => {
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close()
|
||||
}
|
||||
hideLoading()
|
||||
NProgress.done()
|
||||
|
||||
const { response } = error
|
||||
|
||||
// 处理不同的错误状态码
|
||||
if (error.response) {
|
||||
if (response) {
|
||||
const { status, data } = error.response
|
||||
|
||||
switch (status) {
|
||||
@@ -72,10 +73,10 @@ api.interceptors.response.use(
|
||||
// 判断当前是否在代理相关页面
|
||||
const currentPath = router.currentRoute.value.path
|
||||
if (currentPath.startsWith('/agent')) {
|
||||
router.push('/agent/login')
|
||||
router.push('/agent/mylogin')
|
||||
ElMessage.error('代理登录已过期,请重新登录')
|
||||
} else {
|
||||
router.push({ name: 'Login' })
|
||||
router.push({ name: 'MyLogin' })
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
}
|
||||
break
|
||||
@@ -86,8 +87,15 @@ api.interceptors.response.use(
|
||||
// 清除token并跳转到登录页
|
||||
localStorage.removeItem('token')
|
||||
delete api.defaults.headers.common['Authorization']
|
||||
router.push({ name: 'Login' })
|
||||
router.push({ name: 'MyLogin' })
|
||||
ElMessage.error(data.message || '账户已被拉黑,请联系管理员')
|
||||
} else if (data.code === 'PAYMENT_REQUIRED') {
|
||||
// 需要支付,跳转到支付页面
|
||||
// 只在不是支付页面时才跳转和显示消息,避免重复通知
|
||||
if (router.currentRoute.value.name !== 'Payment') {
|
||||
router.push({ name: 'Payment' })
|
||||
ElMessage.warning(data.message || '您的账户尚未激活,请完成支付后再使用')
|
||||
}
|
||||
} else {
|
||||
ElMessage.error(data.message || '权限不足')
|
||||
}
|
||||
@@ -129,13 +137,53 @@ api.interceptors.response.use(
|
||||
}
|
||||
)
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
let loadingInstance = null
|
||||
let requestCount = 0
|
||||
let isLoggingOut = false // 防止重复登出
|
||||
|
||||
// 显示加载
|
||||
const showLoading = () => {
|
||||
if (requestCount === 0) {
|
||||
loadingInstance = ElLoading.service({
|
||||
text: '加载中...',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
})
|
||||
}
|
||||
requestCount++
|
||||
}
|
||||
|
||||
// 隐藏加载
|
||||
const hideLoading = () => {
|
||||
requestCount--
|
||||
if (requestCount <= 0) {
|
||||
requestCount = 0
|
||||
if (loadingInstance) {
|
||||
loadingInstance.close()
|
||||
loadingInstance = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 生成不同的实例
|
||||
export const apiRequest = createRequest('/api')
|
||||
export const midRequest = createRequest('http://192.168.0.12:3005/mid')
|
||||
|
||||
// 初始化时设置token
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
apiRequest.defaults.headers.common['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 封装常用的请求方法
|
||||
export const request = {
|
||||
get: (url, config = {}) => api.get(url, config),
|
||||
post: (url, data = {}, config = {}) => api.post(url, data, config),
|
||||
put: (url, data = {}, config = {}) => api.put(url, data, config),
|
||||
delete: (url, config = {}) => api.delete(url, config),
|
||||
patch: (url, data = {}, config = {}) => api.patch(url, data, config)
|
||||
const api = {
|
||||
get: (url, config = {}) => apiRequest.get(url, config),
|
||||
post: (url, data = {}, config = {}) => apiRequest.post(url, data, config),
|
||||
put: (url, data = {}, config = {}) => apiRequest.put(url, data, config),
|
||||
delete: (url, config = {}) => apiRequest.delete(url, config),
|
||||
patch: (url, data = {}, config = {}) => apiRequest.patch(url, data, config)
|
||||
}
|
||||
|
||||
|
||||
@@ -143,43 +191,46 @@ export const request = {
|
||||
// 用户相关API
|
||||
export const userAPI = {
|
||||
// 获取用户列表
|
||||
getList: (params = {}) => request.get('/users', { params }),
|
||||
getList: (params = {}) => apiRequest.get('/users', { params }),
|
||||
|
||||
// 获取用户详情
|
||||
getDetail: (id) => request.get(`/users/${id}`),
|
||||
getDetail: (id) => apiRequest.get(`/users/${id}`),
|
||||
|
||||
// 更新用户信息
|
||||
update: (id, data) => request.put(`/users/${id}`, data),
|
||||
update: (id, data) => apiRequest.put(`/users/${id}`, data),
|
||||
|
||||
// 删除用户
|
||||
delete: (id) => request.delete(`/users/${id}`),
|
||||
delete: (id) => apiRequest.delete(`/users/${id}`),
|
||||
|
||||
// 获取用户统计
|
||||
getStats: () => request.get('/users/stats/overview')
|
||||
getStats: () => apiRequest.get('/users/stats/overview')
|
||||
}
|
||||
|
||||
// 认证相关API
|
||||
export const authAPI = {
|
||||
// 登录
|
||||
login: (data) => request.post('/auth/login', data),
|
||||
// login: (data) => midRequest.post('/auth/login', data),
|
||||
login: (data) => apiRequest.post('/auth/login', data),
|
||||
|
||||
// 注册
|
||||
register: (data) => request.post('/auth/register', data),
|
||||
register: (data) => midRequest.post('/auth/register', data),
|
||||
|
||||
// 获取当前用户信息
|
||||
me: () => request.get('/auth/me'),
|
||||
me: () => apiRequest.get('/auth/me'),
|
||||
|
||||
// 修改密码
|
||||
changePassword: (data) => request.put('/auth/change-password', data)
|
||||
changePassword: (data) => apiRequest.put('/auth/change-password', data)
|
||||
}
|
||||
|
||||
// 验证码相关API
|
||||
export const captchaAPI = {
|
||||
// 生成验证码
|
||||
generate: () => request.get('/captcha/generate'),
|
||||
// generate: () => midRequest.get('/captcha/generate'),
|
||||
generate: () => apiRequest.get('/captcha/generate'),
|
||||
|
||||
// 验证验证码
|
||||
verify: (data) => request.post('/captcha/verify', data)
|
||||
// verify: (data) => midRequest.post('/captcha/verify', data)
|
||||
verify: (data) => apiRequest.post('/captcha/verify', data)
|
||||
}
|
||||
|
||||
// 文件上传API
|
||||
@@ -188,7 +239,7 @@ export const uploadAPI = {
|
||||
uploadImage: (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('image', file)
|
||||
return request.post('/upload/image', formData, {
|
||||
return midRequest.post('/upload/image', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
@@ -199,7 +250,7 @@ export const uploadAPI = {
|
||||
uploadFile: (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request.post('/upload/file', formData, {
|
||||
return midRequest.post('/upload/file', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
@@ -207,10 +258,34 @@ export const uploadAPI = {
|
||||
}
|
||||
}
|
||||
|
||||
// 支付相关API
|
||||
export const paymentAPI = {
|
||||
// 获取支付方式
|
||||
getMethods: () => apiRequest.get('/payment/methods'),
|
||||
|
||||
// 创建支付订单
|
||||
createOrder: (data) => apiRequest.post('/payment/create-order', data),
|
||||
|
||||
// 查询支付状态
|
||||
queryStatus: (outTradeNo) => apiRequest.get(`/payment/query-status/${outTradeNo}`),
|
||||
|
||||
getOrder: () => apiRequest.get('/payment/check-status'),
|
||||
|
||||
// 获取支付记录
|
||||
getOrders: (params = {}) => apiRequest.get('/payment/orders', { params })
|
||||
}
|
||||
|
||||
// 购买商品
|
||||
export const buyAPI = {
|
||||
buy: (data) => midRequest.post('/payment/create-order', data),
|
||||
pay: (outTradeNo, params) => midRequest.get(`/payment/query-status/${outTradeNo}`, {params}),
|
||||
test : () => apiRequest.get('/payment/pay-product/test'),
|
||||
}
|
||||
|
||||
// 转账相关API
|
||||
export const transferAPI = {
|
||||
// 获取公户信息
|
||||
getPublicAccount: () => request.get('/transfers/public-account'),
|
||||
getPublicAccount: () => apiRequest.get('/transfers/public-account'),
|
||||
|
||||
// 创建转账记录
|
||||
create: (data) => {
|
||||
@@ -218,7 +293,7 @@ export const transferAPI = {
|
||||
Object.keys(data).forEach(key => {
|
||||
formData.append(key, data[key])
|
||||
})
|
||||
return request.post('/transfers', formData, {
|
||||
return apiRequest.post('/transfers', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
@@ -226,34 +301,38 @@ export const transferAPI = {
|
||||
},
|
||||
|
||||
// 确认转账
|
||||
confirm: (id) => request.put(`/transfers/${id}/confirm`),
|
||||
confirm: (id) => apiRequest.put(`/transfers/${id}/confirm`),
|
||||
|
||||
// 拒绝转账
|
||||
reject: (id) => request.put(`/transfers/${id}/reject`),
|
||||
reject: (id) => apiRequest.put(`/transfers/${id}/reject`),
|
||||
|
||||
// 确认收款
|
||||
confirmReceived: (id) => request.post('/transfers/confirm-received', { transfer_id: id }),
|
||||
confirmReceived: (id) => apiRequest.post('/transfers/confirm-received', { transfer_id: id }),
|
||||
|
||||
// 确认未收到款
|
||||
confirmNotReceived: (id) => request.post('/transfers/confirm-not-received', { transfer_id: id }),
|
||||
confirmNotReceived: (id) => apiRequest.post('/transfers/confirm-not-received', { transfer_id: id }),
|
||||
|
||||
// 获取用户转账记录
|
||||
getUserTransfers: (params = {}) => request.get('/transfers/user', { params }),
|
||||
getUserTransfers: (params = {}) => apiRequest.get('/transfers/user', { params }),
|
||||
|
||||
// 获取指定用户的转账记录
|
||||
getUserTransfersByUserId: (userId, params = {}) => request.get(`/transfers/user/${userId}`, { params }),
|
||||
getUserTransfersByUserId: (userId, params = {}) => apiRequest.get(`/transfers/user/${userId}`, { params }),
|
||||
|
||||
// 获取待确认转账
|
||||
getPendingTransfers: (params = {}) => request.get('/transfers/pending', { params }),
|
||||
getPendingTransfers: (params = {}) => apiRequest.get('/transfers/pending', { params }),
|
||||
|
||||
// 获取用户账户信息
|
||||
getUserAccount: () => request.get('/transfers/account'),
|
||||
getUserAccount: () => apiRequest.get('/transfers/account'),
|
||||
|
||||
// 获取转账列表(管理员)
|
||||
getList: (params = {}) => request.get('/transfers', { params }),
|
||||
getList: (params = {}) => apiRequest.get('/transfers', { params }),
|
||||
|
||||
// 获取转账统计
|
||||
getStats: () => request.get('/transfers/stats')
|
||||
getStats: () => apiRequest.get('/transfers/stats')
|
||||
}
|
||||
|
||||
export const distributionAPI = {
|
||||
getLowerUsers: (params) => apiRequest.get('/agents/distribution', { params }),
|
||||
}
|
||||
|
||||
export default api
|
||||
@@ -1,453 +0,0 @@
|
||||
<template>
|
||||
<div class="about-page">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar">
|
||||
<div class="nav-left">
|
||||
<el-button
|
||||
type="text"
|
||||
@click="$router.go(-1)"
|
||||
class="back-btn"
|
||||
>
|
||||
<el-icon><ArrowLeft /></el-icon>
|
||||
返回
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="nav-center">
|
||||
<h1 class="nav-title">关于我们</h1>
|
||||
</div>
|
||||
<div class="nav-right"></div>
|
||||
</nav>
|
||||
|
||||
<!-- 页面内容 -->
|
||||
<div class="about-content">
|
||||
<!-- 网站介绍 -->
|
||||
<section class="intro-section">
|
||||
<div class="intro-header">
|
||||
<div class="logo">
|
||||
<el-icon size="60"><Platform /></el-icon>
|
||||
</div>
|
||||
<h2>融互通</h2>
|
||||
<p class="tagline">专业的积分兑换与商品管理平台</p>
|
||||
</div>
|
||||
|
||||
<div class="intro-content">
|
||||
<p>
|
||||
积分商城系统是一个现代化的积分兑换与商品管理平台,致力于为用户提供丰富的商品选择和便捷的积分兑换体验。
|
||||
我们相信积分的价值在于为用户带来实际的收益和满足感,通过技术的力量让积分兑换变得更加简单高效。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 功能特色 -->
|
||||
<section class="features-section">
|
||||
<h3>功能特色</h3>
|
||||
<div class="features-grid">
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">
|
||||
<el-icon><ShoppingBag /></el-icon>
|
||||
</div>
|
||||
<h4>丰富商品</h4>
|
||||
<p>精选优质商品,涵盖生活用品、数码产品、虚拟服务等多个品类</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">
|
||||
<el-icon><Coin /></el-icon>
|
||||
</div>
|
||||
<h4>积分兑换</h4>
|
||||
<p>灵活的积分兑换机制,让您的积分发挥最大价值,享受购物乐趣</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">
|
||||
<el-icon><TrendCharts /></el-icon>
|
||||
</div>
|
||||
<h4>积分管理</h4>
|
||||
<p>完整的积分获取和消费记录,让您清楚了解每一分积分的来源和去向</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon">
|
||||
<el-icon><User /></el-icon>
|
||||
</div>
|
||||
<h4>个人中心</h4>
|
||||
<p>完善的个人资料管理,记录您的兑换历程和积分成长轨迹</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 技术栈 -->
|
||||
<section class="tech-section">
|
||||
<h3>技术栈</h3>
|
||||
<div class="tech-grid">
|
||||
<div class="tech-category">
|
||||
<h4>前端技术</h4>
|
||||
<ul>
|
||||
<li>Vue 3 + Composition API</li>
|
||||
<li>Element Plus UI 组件库</li>
|
||||
<li>Vue Router 路由管理</li>
|
||||
<li>Pinia 状态管理</li>
|
||||
<li>Vite 构建工具</li>
|
||||
<li>响应式设计</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tech-category">
|
||||
<h4>后端技术</h4>
|
||||
<ul>
|
||||
<li>Node.js + Express</li>
|
||||
<li>MySQL 数据库</li>
|
||||
<li>JWT 身份认证</li>
|
||||
<li>RESTful API 设计</li>
|
||||
<li>积分系统管理</li>
|
||||
<li>订单处理系统</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 联系我们 -->
|
||||
<section class="contact-section">
|
||||
<h3>联系我们</h3>
|
||||
<div class="contact-info">
|
||||
<div class="contact-item">
|
||||
<el-icon><Message /></el-icon>
|
||||
<div>
|
||||
<h4>邮箱</h4>
|
||||
<p>contact@example.com</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contact-item">
|
||||
<el-icon><Phone /></el-icon>
|
||||
<div>
|
||||
<h4>电话</h4>
|
||||
<p>400-123-4567</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contact-item">
|
||||
<el-icon><Location /></el-icon>
|
||||
<div>
|
||||
<h4>地址</h4>
|
||||
<p>北京市朝阳区科技园区</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- 版本信息 -->
|
||||
<section class="version-section">
|
||||
<div class="version-info">
|
||||
<p><strong>版本:</strong>v1.0.0</p>
|
||||
<p><strong>更新时间:</strong>{{ updateTime }}</p>
|
||||
<p><strong>开发团队:</strong>积分商城系统开发团队</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<footer class="about-footer">
|
||||
<p>© 2024 积分商城系统. All rights reserved.</p>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Platform,
|
||||
ShoppingBag,
|
||||
Coin,
|
||||
TrendCharts,
|
||||
User,
|
||||
Message,
|
||||
Phone,
|
||||
Location
|
||||
} from '@element-plus/icons-vue'
|
||||
|
||||
// 响应式数据
|
||||
const updateTime = ref('2024-01-15')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about-page {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
height: 56px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-left,
|
||||
.nav-right {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.about-content {
|
||||
padding: 20px 16px;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.intro-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 40px 30px;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.intro-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
color: #409eff;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.intro-header h2 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 28px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tagline {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.intro-content p {
|
||||
font-size: 16px;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.features-section,
|
||||
.tech-section,
|
||||
.contact-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 30px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.features-section h3,
|
||||
.tech-section h3,
|
||||
.contact-section h3 {
|
||||
margin: 0 0 24px 0;
|
||||
font-size: 20px;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
color: #409eff;
|
||||
font-size: 32px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.feature-item h4 {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.feature-item p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.tech-category h4 {
|
||||
margin: 0 0 16px 0;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #409eff;
|
||||
}
|
||||
|
||||
.tech-category ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.tech-category li {
|
||||
padding: 8px 0;
|
||||
color: #555;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
.tech-category li::before {
|
||||
content: '•';
|
||||
color: #409eff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.contact-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.contact-item .el-icon {
|
||||
color: #409eff;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.contact-item h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.contact-item p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.version-section {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px 30px;
|
||||
margin-bottom: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.version-info p {
|
||||
margin: 8px 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.about-footer {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.about-content {
|
||||
padding: 15px 10px;
|
||||
}
|
||||
|
||||
.intro-section,
|
||||
.features-section,
|
||||
.tech-section,
|
||||
.contact-section {
|
||||
padding: 20px 15px;
|
||||
}
|
||||
|
||||
.intro-header h2 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.features-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tech-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.intro-section,
|
||||
.features-section,
|
||||
.tech-section,
|
||||
.contact-section,
|
||||
.version-section {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.intro-header h2 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.features-section h3,
|
||||
.tech-section h3,
|
||||
.contact-section h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -234,80 +234,7 @@ const loadRegionOptions = async () => {
|
||||
} catch (error) {
|
||||
console.error('获取省市区数据失败:', error)
|
||||
ElMessage.error(error.message || '获取省市区数据失败')
|
||||
|
||||
// 如果API获取失败,使用浙江省数据作为默认数据
|
||||
await loadFallbackRegionData()
|
||||
}
|
||||
}
|
||||
|
||||
// 回退方案:加载浙江省数据
|
||||
const loadFallbackRegionData = async () => {
|
||||
try {
|
||||
const zhejiangResponse = await api.get('/regions/zhejiang')
|
||||
if (zhejiangResponse.data.success) {
|
||||
const zhejiangData = zhejiangResponse.data.data || []
|
||||
// 将浙江省数据转换为级联选择器格式
|
||||
const cityMap = new Map()
|
||||
|
||||
zhejiangData.forEach(item => {
|
||||
if (!cityMap.has(item.city_name)) {
|
||||
cityMap.set(item.city_name, {
|
||||
value: item.city_name,
|
||||
label: item.city_name,
|
||||
children: []
|
||||
})
|
||||
}
|
||||
|
||||
// 添加区县数据(移除is_available过滤条件以确保所有区县都显示)
|
||||
if (item.district_name) {
|
||||
cityMap.get(item.city_name).children.push({
|
||||
value: item.district_name,
|
||||
label: item.district_name,
|
||||
code: item.region_code
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
regionOptions.value = [{
|
||||
value: '浙江省',
|
||||
label: '浙江省',
|
||||
children: Array.from(cityMap.values())
|
||||
}]
|
||||
console.log('已加载浙江省地区数据作为默认选项')
|
||||
} else {
|
||||
throw new Error('获取浙江省数据也失败')
|
||||
}
|
||||
} catch (fallbackError) {
|
||||
console.error('浙江省数据获取失败,使用硬编码数据:', fallbackError)
|
||||
// 最终回退到硬编码数据
|
||||
regionOptions.value = [
|
||||
{
|
||||
value: '浙江省',
|
||||
label: '浙江省',
|
||||
children: [
|
||||
{
|
||||
value: '宁波市',
|
||||
label: '宁波市',
|
||||
children: [
|
||||
{ value: '鄞州区', label: '鄞州区' },
|
||||
{ value: '海曙区', label: '海曙区' },
|
||||
{ value: '江北区', label: '江北区' },
|
||||
{ value: '北仑区', label: '北仑区' }
|
||||
]
|
||||
},
|
||||
{
|
||||
value: '杭州市',
|
||||
label: '杭州市',
|
||||
children: [
|
||||
{ value: '西湖区', label: '西湖区' },
|
||||
{ value: '上城区', label: '上城区' },
|
||||
{ value: '拱墅区', label: '拱墅区' },
|
||||
{ value: '余杭区', label: '余杭区' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">¥{{ (Number(stats.total_commission) || 0).toFixed(2) }}</div>
|
||||
<div class="stat-label">总佣金</div>
|
||||
<div class="stat-label">总营收</div>
|
||||
<div class="stat-trend">¥{{ (Number(stats.monthly_commission) || 0).toFixed(2) }} 本月</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,7 +58,7 @@
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">¥{{ (Number(stats.daily_commission) || 0).toFixed(2) }}</div>
|
||||
<div class="stat-label">今日佣金</div>
|
||||
<div class="stat-label">今日营收</div>
|
||||
<div class="stat-trend">{{ stats.daily_commission_records || 0 }} 笔记录</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,10 +79,10 @@
|
||||
|
||||
<!-- 数据可视化图表 -->
|
||||
<div class="charts-section">
|
||||
<!-- 佣金趋势图 -->
|
||||
<!-- 营收趋势图 -->
|
||||
<div class="chart-card">
|
||||
<div class="card-header">
|
||||
<h3>佣金趋势</h3>
|
||||
<h3>营收趋势</h3>
|
||||
<el-select v-model="chartPeriod" size="small" @change="loadChartData">
|
||||
<el-option label="近7天" value="7d" />
|
||||
<el-option label="近30天" value="30d" />
|
||||
@@ -105,20 +105,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 快速操作 -->
|
||||
<div class="quick-actions">
|
||||
<div class="action-card" @click="goToWithdrawals">
|
||||
<div class="action-icon withdraw">
|
||||
<el-icon><CreditCard /></el-icon>
|
||||
</div>
|
||||
<div class="action-content">
|
||||
<div class="action-title">佣金提现</div>
|
||||
<div class="action-desc">快速提现到账</div>
|
||||
</div>
|
||||
<el-icon class="arrow"><ArrowRight /></el-icon>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标签页 -->
|
||||
<el-tabs v-model="activeTab" class="main-tabs">
|
||||
<!-- 商户管理 -->
|
||||
@@ -144,7 +130,7 @@
|
||||
<div class="stat-item">
|
||||
<span class="stat-label">早期商户:</span>
|
||||
<span class="stat-value early">{{ stats.early_merchants || 0 }}</span>
|
||||
<el-tooltip content="注册时间早于代理加入时间的商户,不参与佣金计算" placement="top">
|
||||
<el-tooltip content="注册时间早于代理加入时间的商户,不参与营收计算" placement="top">
|
||||
<el-icon class="info-icon"><InfoFilled /></el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
@@ -189,7 +175,7 @@
|
||||
</el-tag>
|
||||
<el-tooltip
|
||||
v-if="row.is_early_merchant"
|
||||
content="该商户注册时间早于代理加入时间,不参与佣金计算"
|
||||
content="该商户注册时间早于代理加入时间,不参与营收计算"
|
||||
placement="top"
|
||||
>
|
||||
<el-icon class="info-icon"><InfoFilled /></el-icon>
|
||||
@@ -240,11 +226,11 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 佣金记录 -->
|
||||
<el-tab-pane label="佣金记录" name="commissions">
|
||||
<!-- 营收记录 -->
|
||||
<el-tab-pane label="营收记录" name="commissions">
|
||||
<div class="commissions-section">
|
||||
<div class="section-header">
|
||||
<h3>佣金记录</h3>
|
||||
<h3>营收记录</h3>
|
||||
<el-button @click="loadCommissions" :loading="loadingCommissions">
|
||||
刷新
|
||||
</el-button>
|
||||
@@ -266,25 +252,18 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="real_name" label="真实姓名" :width="null" min-width="80" class-name="hide-on-small-mobile" />
|
||||
<el-table-column prop="commission_amount" label="佣金金额" :width="null" min-width="90">
|
||||
<el-table-column prop="commission_amount" label="营收金额" :width="null" min-width="90">
|
||||
<template #default="{ row }">
|
||||
¥{{ (Number(row.commission_amount) || 0).toFixed(2) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="commission_type" label="佣金类型" :width="null" min-width="90">
|
||||
<el-table-column prop="commission_type" label="营收类型" :width="null" min-width="90">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getCommissionTypeTagType(row.commission_type)">
|
||||
{{ getCommissionTypeText(row.commission_type) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="status" label="状态" :width="null" min-width="70">
|
||||
<template #default="{ row }">
|
||||
<el-tag size="small" :type="getCommissionStatusType(row.status)">
|
||||
{{ getCommissionStatusText(row.status) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="created_at" label="生成时间" :width="null" min-width="120" class-name="hide-on-mobile">
|
||||
<template #default="{ row }">
|
||||
{{ formatDateTime(row.created_at) }}
|
||||
@@ -318,43 +297,6 @@
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
|
||||
|
||||
<!-- 佣金提现 -->
|
||||
<el-tab-pane label="佣金提现" name="withdrawals">
|
||||
<div class="withdrawals-section">
|
||||
<div class="section-header">
|
||||
<h3>佣金提现</h3>
|
||||
<el-button type="primary" @click="goToWithdrawals">
|
||||
进入提现页面
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<div class="withdrawal-summary">
|
||||
<div class="summary-card">
|
||||
<div class="summary-item">
|
||||
<span class="label">可提现金额:</span>
|
||||
<span class="value amount">¥{{ (Number(stats.available_amount) || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">提现中金额:</span>
|
||||
<span class="value">¥{{ (Number(stats.pending_withdrawal) || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="summary-item">
|
||||
<span class="label">已提现金额:</span>
|
||||
<span class="value">¥{{ (Number(stats.withdrawn_amount) || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<el-button type="primary" size="large" @click="goToWithdrawals">
|
||||
立即提现
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 商户转账记录 -->
|
||||
<el-tab-pane label="商户转账记录" name="transfers">
|
||||
<div class="transfers-section">
|
||||
@@ -505,7 +447,7 @@ const merchantsPage = ref(1)
|
||||
const merchantsPageSize = ref(10)
|
||||
const merchantsTotal = ref(0)
|
||||
|
||||
// 佣金数据
|
||||
// 营收数据
|
||||
const commissions = ref([])
|
||||
const loadingCommissions = ref(false)
|
||||
const commissionsPage = ref(1)
|
||||
@@ -611,7 +553,7 @@ const loadCommissions = async () => {
|
||||
commissions.value = data.data.commissions
|
||||
commissionsTotal.value = data.data.summary.total_records
|
||||
} catch (error) {
|
||||
ElMessage.error('加载佣金记录失败')
|
||||
ElMessage.error('加载营收记录失败')
|
||||
} finally {
|
||||
loadingCommissions.value = false
|
||||
}
|
||||
@@ -699,8 +641,8 @@ const getAuditStatusText = (status) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金类型标签类型
|
||||
* @param {string} type - 佣金类型
|
||||
* 获取营收类型标签类型
|
||||
* @param {string} type - 营收类型
|
||||
* @returns {string} 标签类型
|
||||
*/
|
||||
const getCommissionTypeTagType = (type) => {
|
||||
@@ -712,37 +654,29 @@ const getCommissionTypeTagType = (type) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金类型文本
|
||||
* @param {string} type - 佣金类型
|
||||
* 获取营收类型文本
|
||||
* @param {string} type - 营收类型
|
||||
* @returns {string} 显示文本
|
||||
*/
|
||||
const getCommissionTypeText = (type) => {
|
||||
const texts = {
|
||||
registration: '注册佣金',
|
||||
matching: '匹配佣金'
|
||||
registration: '注册营收',
|
||||
matching: '匹配营收'
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金状态标签类型
|
||||
* @param {string} status - 佣金状态
|
||||
* 获取营收状态标签类型
|
||||
* @param {string} status - 营收状态
|
||||
* @returns {string} 标签类型
|
||||
*/
|
||||
const getCommissionStatusType = (status) => {
|
||||
// 由于agent_commission_records表没有status字段,所有佣金记录都是已生成状态
|
||||
return 'success'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取佣金状态文本
|
||||
* @param {string} status - 佣金状态
|
||||
* 获取营收状态文本
|
||||
* @param {string} status - 营收状态
|
||||
* @returns {string} 显示文本
|
||||
*/
|
||||
const getCommissionStatusText = (status) => {
|
||||
// 由于agent_commission_records表没有status字段,所有佣金记录都是已生成状态
|
||||
return '已生成'
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取转账状态标签类型
|
||||
@@ -805,17 +739,9 @@ const getTransferTypeType = (type) => {
|
||||
*/
|
||||
const getTransferTypeText = (type) => {
|
||||
const texts = {
|
||||
deposit: '充值',
|
||||
withdraw: '提现',
|
||||
transfer: '转账',
|
||||
commission: '佣金',
|
||||
refund: '退款',
|
||||
penalty: '罚金',
|
||||
initial: '初始转账',
|
||||
return: '返还转账',
|
||||
user_to_user: '用户转账',
|
||||
user_to_public: '用户转公户',
|
||||
public_to_user: '公户转用户'
|
||||
user_to_agent: '用户转代理',
|
||||
user_to_system: '用户转系统',
|
||||
}
|
||||
return texts[type] || type
|
||||
}
|
||||
@@ -874,13 +800,6 @@ const maskUsername = (username) => {
|
||||
return username[0] + '*'.repeat(username.length - 2) + username[username.length - 1]
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到提现页面
|
||||
*/
|
||||
const goToWithdrawals = () => {
|
||||
router.push('/agent/withdrawals')
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新所有数据
|
||||
*/
|
||||
@@ -933,7 +852,7 @@ const loadChartData = async () => {
|
||||
* 生成模拟图表数据(用于演示)
|
||||
*/
|
||||
const generateMockChartData = () => {
|
||||
// 模拟佣金趋势数据
|
||||
// 模拟营收趋势数据
|
||||
const days = chartPeriod.value === '7d' ? 7 : chartPeriod.value === '30d' ? 30 : 90
|
||||
const trendData = []
|
||||
const today = new Date()
|
||||
@@ -968,11 +887,11 @@ const updateChartOptions = () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 佣金趋势图配置
|
||||
// 营收趋势图配置
|
||||
commissionTrendOption.value = {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter: '{b}<br/>佣金: ¥{c}'
|
||||
formatter: '{b}<br/>营收: ¥{c}'
|
||||
},
|
||||
grid: {
|
||||
left: '3%',
|
||||
@@ -1208,6 +1127,7 @@ onMounted(async () => {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
@@ -1381,6 +1301,25 @@ onMounted(async () => {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__nav) {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 12px 8px;
|
||||
border-radius: 12px;
|
||||
margin: 0 2px;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -1421,10 +1360,17 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
padding: 12px 20px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 12px 8px;
|
||||
border-radius: 12px;
|
||||
margin: 0 4px;
|
||||
margin: 0 2px;
|
||||
transition: all 0.3s ease;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item.is-active) {
|
||||
@@ -1436,70 +1382,6 @@ onMounted(async () => {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 提现相关样式 */
|
||||
.withdrawals-section {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.withdrawal-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
background: linear-gradient(135deg, #f8f9ff 0%, #f0f4ff 100%);
|
||||
border-radius: 16px;
|
||||
padding: 20px;
|
||||
border: 1px solid rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.summary-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid rgba(64, 158, 255, 0.1);
|
||||
}
|
||||
|
||||
.summary-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.summary-item .label {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.summary-item .value {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.summary-item .value.amount {
|
||||
color: #f56c6c;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.withdrawals-section .quick-actions {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.withdrawals-section .quick-actions .el-button {
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* 表格样式 */
|
||||
:deep(.el-table) {
|
||||
border-radius: 12px;
|
||||
@@ -1543,7 +1425,8 @@ onMounted(async () => {
|
||||
|
||||
/* 商户统计信息样式 */
|
||||
.merchant-stats {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 24px;
|
||||
padding: 16px;
|
||||
background: #f8f9fa;
|
||||
@@ -1559,7 +1442,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
.merchant-stats .stat-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
@@ -1580,7 +1462,6 @@ onMounted(async () => {
|
||||
|
||||
/* 商户类型相关样式 */
|
||||
.info-icon {
|
||||
margin-left: 4px;
|
||||
color: #E6A23C;
|
||||
cursor: help;
|
||||
font-size: 14px;
|
||||
@@ -1659,8 +1540,17 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
:deep(.el-tabs__item) {
|
||||
padding: 8px 12px;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
padding: 8px 4px;
|
||||
font-size: 14px;
|
||||
border-radius: 8px;
|
||||
margin: 0 1px;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
|
||||
@@ -96,25 +96,34 @@
|
||||
label-width="100px"
|
||||
>
|
||||
<el-form-item label="选择区域" prop="region_id">
|
||||
<el-select
|
||||
v-model="applyForm.region_id"
|
||||
placeholder="请选择代理区域"
|
||||
style="width: 100%"
|
||||
filterable
|
||||
>
|
||||
<el-option-group
|
||||
v-for="city in groupedRegions"
|
||||
:key="city.name"
|
||||
:label="city.name"
|
||||
>
|
||||
<el-option
|
||||
v-for="region in city.districts"
|
||||
:key="region.id"
|
||||
:label="region.district_name"
|
||||
:value="region.id"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
<!-- <el-select-->
|
||||
<!-- v-model="applyForm.region_id"-->
|
||||
<!-- placeholder="请选择代理区域"-->
|
||||
<!-- style="width: 100%"-->
|
||||
<!-- filterable-->
|
||||
<!-- >-->
|
||||
<!-- <el-option-group-->
|
||||
<!-- v-for="city in groupedRegions"-->
|
||||
<!-- :key="city.name"-->
|
||||
<!-- :label="city.name"-->
|
||||
<!-- >-->
|
||||
<!-- <el-option-->
|
||||
<!-- v-for="region in city.districts"-->
|
||||
<!-- :key="region.id"-->
|
||||
<!-- :label="region.district_name"-->
|
||||
<!-- :value="region.id"-->
|
||||
<!-- />-->
|
||||
<!-- </el-option-group>-->
|
||||
<!-- </el-select>-->
|
||||
|
||||
<el-cascader v-model="applyForm.region_id"
|
||||
popper-class="custom-cascader"
|
||||
:show-all-levels="false"
|
||||
placeholder="请选择代理区域"
|
||||
:options="regions"
|
||||
:props="regionsProp"
|
||||
clearable />
|
||||
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="真实姓名" prop="real_name">
|
||||
@@ -208,6 +217,13 @@ const applyRules = {
|
||||
]
|
||||
}
|
||||
|
||||
// 设置省市区级联选择器配置
|
||||
const regionsProp = {
|
||||
value: 'code',
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
}
|
||||
|
||||
// 计算属性
|
||||
const groupedRegions = computed(() => {
|
||||
const grouped = {}
|
||||
@@ -226,7 +242,7 @@ const groupedRegions = computed(() => {
|
||||
// 方法
|
||||
const loadRegions = async () => {
|
||||
try {
|
||||
const { data } = await api.get('/agents/regions')
|
||||
const { data } = await api.get('/regions/provinces')
|
||||
regions.value = data.data
|
||||
} catch (error) {
|
||||
ElMessage.error('加载区域列表失败')
|
||||
@@ -262,11 +278,12 @@ const handleLogin = async () => {
|
||||
|
||||
const handleApply = async () => {
|
||||
if (!applyFormRef.value) return
|
||||
|
||||
try {
|
||||
await applyFormRef.value.validate()
|
||||
applyLoading.value = true
|
||||
|
||||
|
||||
// 将区id设置为最终id
|
||||
applyForm.region_id = applyForm.region_id[2]
|
||||
const { data } = await api.post('/agents/apply', applyForm)
|
||||
|
||||
if (data.success) {
|
||||
@@ -294,7 +311,7 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
/* 使用与主登录页面一致的样式 */
|
||||
.agent-login-page {
|
||||
min-height: 100vh;
|
||||
@@ -505,4 +522,14 @@ onMounted(() => {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.custom-cascader{
|
||||
.el-cascader-panel{
|
||||
.el-cascader-menu{
|
||||
min-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -30,8 +30,16 @@
|
||||
<div class="product-details">
|
||||
<div class="product-price">
|
||||
<span class="price-label">实付</span>
|
||||
<el-icon class="coin-icon"><Coin /></el-icon>
|
||||
<span class="price-value">{{ totalPrice }}</span>
|
||||
<div class="price-container">
|
||||
<div class="main-price">
|
||||
<img src='/imgs/profile/rongdou.png' alt="融豆" class="rongdou-icon" />
|
||||
<span class="rongdou-price">{{ totalPrice }}</span>
|
||||
</div>
|
||||
<div class="sub-price">
|
||||
<el-icon class="points-icon"><Coin /></el-icon>
|
||||
<span class="points-price">{{ totalPointsPrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="quantity-selector">
|
||||
<el-button size="small" @click="decreaseQuantity" :disabled="quantity <= 1">-</el-button>
|
||||
@@ -56,10 +64,10 @@
|
||||
class="spec-item"
|
||||
:class="{
|
||||
active: selectedSpecs[specName]?.id === option.id,
|
||||
disabled: availableSpecs[specName] && !availableSpecs[specName][option.id]
|
||||
disabled: availableSpecs[specName] && !availableSpecs[specName][option.id] && selectedSpecs[specName]?.id !== option.id
|
||||
}"
|
||||
@click="selectSpec(specName, option)"
|
||||
:disabled="availableSpecs[specName] && !availableSpecs[specName][option.id]"
|
||||
:disabled="availableSpecs[specName] && !availableSpecs[specName][option.id] && selectedSpecs[specName]?.id !== option.id"
|
||||
>
|
||||
<span class="spec-label">{{ option.name }}</span>
|
||||
</div>
|
||||
@@ -122,6 +130,7 @@ import {
|
||||
ArrowRight
|
||||
} from '@element-plus/icons-vue'
|
||||
import api from '@/utils/api'
|
||||
import { getImageUrl } from '@/config'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -141,12 +150,61 @@ const specIdToOrder = ref({}) // 规格ID到顺序编号的映射
|
||||
// 计算属性
|
||||
const totalPrice = computed(() => {
|
||||
if (!product.value) return 0
|
||||
return product.value.points * quantity.value
|
||||
return product.value.rongdou_price * quantity.value
|
||||
})
|
||||
|
||||
const totalPointsPrice = computed(() => {
|
||||
if (!product.value) return 0
|
||||
return product.value.points_price * quantity.value
|
||||
})
|
||||
|
||||
// 动态计算当前场景下必须选择的规格名称集合(基于已选项与后端可用组合)
|
||||
const requiredSpecNames = computed(() => {
|
||||
const allSpecNames = Object.keys(specGroups.value)
|
||||
if (!product.value || !Array.isArray(product.value.specifications)) {
|
||||
return allSpecNames
|
||||
}
|
||||
|
||||
const availableSpecs = product.value.specifications.filter(s => s.is_available)
|
||||
|
||||
// 已选的规格项ID
|
||||
const selectedIds = allSpecNames
|
||||
.map(name => selectedSpecs.value[name]?.id)
|
||||
.filter(Boolean)
|
||||
|
||||
// 能与当前已选项兼容的可用规格规则集合
|
||||
const candidateSpecs = availableSpecs.filter(spec => {
|
||||
const detailIds = spec.spec_details.map(d => d.id)
|
||||
return selectedIds.every(id => detailIds.includes(id))
|
||||
})
|
||||
|
||||
if (candidateSpecs.length === 0) {
|
||||
// 若暂无兼容组合,默认要求全部(防止误判)
|
||||
return allSpecNames
|
||||
}
|
||||
|
||||
// 计算每个候选组合涉及的规格名称集
|
||||
const nameSets = candidateSpecs.map(spec => {
|
||||
const names = spec.spec_details.map(d => d.spec_name)
|
||||
return Array.from(new Set(names))
|
||||
})
|
||||
|
||||
// 优先选择包含当前已选规格名称的最小集合
|
||||
const selectedNames = allSpecNames.filter(name => !!selectedSpecs.value[name])
|
||||
const filteredByIntent = nameSets
|
||||
.filter(set => selectedNames.every(n => set.includes(n)))
|
||||
.sort((a, b) => a.length - b.length)
|
||||
|
||||
if (filteredByIntent.length > 0) return filteredByIntent[0]
|
||||
|
||||
// 兜底:选择最小的集合
|
||||
nameSets.sort((a, b) => a.length - b.length)
|
||||
return nameSets[0]
|
||||
})
|
||||
|
||||
const canPurchase = computed(() => {
|
||||
const specNames = Object.keys(specGroups.value)
|
||||
const allSpecsSelected = specNames.every(specName => selectedSpecs.value[specName])
|
||||
const required = requiredSpecNames.value
|
||||
const allSpecsSelected = required.every(specName => selectedSpecs.value[specName])
|
||||
return allSpecsSelected && quantity.value > 0
|
||||
})
|
||||
|
||||
@@ -163,40 +221,38 @@ const decreaseQuantity = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查规格组合是否有效
|
||||
// 检查规格组合是否有效(忽略顺序,按ID集合匹配)
|
||||
const isValidCombination = (testSelection) => {
|
||||
const selectedIds = []
|
||||
const specNames = Object.keys(specGroups.value)
|
||||
|
||||
// 按规格名称顺序收集选中的规格ID,转换为顺序编号
|
||||
|
||||
// 收集已选规格的真实ID(不再转换顺序编号)
|
||||
specNames.forEach(specName => {
|
||||
if (testSelection[specName]) {
|
||||
const specId = testSelection[specName].id
|
||||
const orderNumber = specIdToOrder.value[specId]
|
||||
selectedIds.push(orderNumber)
|
||||
} else {
|
||||
selectedIds.push(null) // 未选择的规格用null占位
|
||||
selectedIds.push(testSelection[specName].id)
|
||||
}
|
||||
})
|
||||
|
||||
// 如果还没有选择完所有规格,检查部分选择是否与任何有效组合兼容
|
||||
if (selectedIds.includes(null)) {
|
||||
return validCombinations.value.some(combinationKey => {
|
||||
const keyParts = combinationKey.split('-').map(k => parseInt(k))
|
||||
|
||||
// 检查当前部分选择是否与这个combination_key兼容
|
||||
return selectedIds.every((selectedOrder, index) => {
|
||||
// 如果该位置未选择,则兼容
|
||||
if (selectedOrder === null) return true
|
||||
// 如果该位置已选择,检查是否匹配
|
||||
return selectedOrder === keyParts[index]
|
||||
})
|
||||
|
||||
if (!product.value || !Array.isArray(product.value.specifications)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// 仅考虑后端标记为可用的规格组合
|
||||
const availableSpecs = product.value.specifications.filter(s => s.is_available)
|
||||
|
||||
// 部分选择:只要存在一个规格规则,其spec_details包含所有已选ID即可
|
||||
if (selectedIds.length < specNames.length) {
|
||||
return availableSpecs.some(spec => {
|
||||
const detailIds = spec.spec_details.map(d => d.id)
|
||||
return selectedIds.every(id => detailIds.includes(id))
|
||||
})
|
||||
}
|
||||
|
||||
// 如果选择了所有规格,检查完整组合是否有效
|
||||
const combinationKey = selectedIds.join('-')
|
||||
return validCombinations.value.includes(combinationKey)
|
||||
|
||||
// 完整选择:必须存在一个规格规则,其ID集合与所选集合相等(忽略顺序)
|
||||
return availableSpecs.some(spec => {
|
||||
const detailIds = spec.spec_details.map(d => d.id)
|
||||
return detailIds.length === selectedIds.length && selectedIds.every(id => detailIds.includes(id))
|
||||
})
|
||||
}
|
||||
|
||||
// 更新可选规格状态
|
||||
@@ -218,16 +274,20 @@ const updateAvailableSpecs = () => {
|
||||
|
||||
// 选择规格
|
||||
const selectSpec = (specName, option) => {
|
||||
// 检查该选项是否被禁用
|
||||
// 二次点击已选中项则取消选择
|
||||
if (selectedSpecs.value[specName]?.id === option.id) {
|
||||
delete selectedSpecs.value[specName]
|
||||
updateAvailableSpecs()
|
||||
return
|
||||
}
|
||||
|
||||
// 检查该选项是否被禁用(但允许点击已选项取消)
|
||||
if (availableSpecs.value[specName] && !availableSpecs.value[specName][option.id]) {
|
||||
ElMessage.warning('该规格组合不可选,请选择其他规格')
|
||||
return
|
||||
}
|
||||
|
||||
selectedSpecs.value[specName] = option
|
||||
console.log(`选择${specName}:`, option)
|
||||
console.log('当前选中的所有规格:', selectedSpecs.value)
|
||||
|
||||
// 更新可选规格状态
|
||||
updateAvailableSpecs()
|
||||
}
|
||||
@@ -293,16 +353,15 @@ const parseSpecifications = (specifications) => {
|
||||
|
||||
// 转换Set为数组并解析JSON
|
||||
const finalSpecGroups = {}
|
||||
let orderCounter = 1
|
||||
|
||||
Object.keys(tempSpecGroups).forEach(specName => {
|
||||
finalSpecGroups[specName] = Array.from(tempSpecGroups[specName]).map(item => JSON.parse(item))
|
||||
// 按sort_order排序
|
||||
finalSpecGroups[specName].sort((a, b) => a.sort_order - b.sort_order)
|
||||
|
||||
// 为每个规格选项分配顺序编号(从1开始)
|
||||
// 使用规格项自身ID作为映射,避免与后端combination_key不一致
|
||||
finalSpecGroups[specName].forEach(option => {
|
||||
specIdToOrderMap[option.id] = orderCounter++
|
||||
specIdToOrderMap[option.id] = option.id
|
||||
})
|
||||
})
|
||||
|
||||
@@ -313,7 +372,7 @@ const parseSpecifications = (specifications) => {
|
||||
specIdToOrder.value = specIdToOrderMap
|
||||
|
||||
console.log('有效的规格组合键:', validCombinationKeys)
|
||||
console.log('规格ID到顺序编号映射:', specIdToOrderMap)
|
||||
console.log('规格ID映射(按ID本身):', specIdToOrderMap)
|
||||
|
||||
// 初始化可选规格状态
|
||||
updateAvailableSpecs()
|
||||
@@ -322,36 +381,31 @@ const parseSpecifications = (specifications) => {
|
||||
console.log('解析后的规格分组:', finalSpecGroups)
|
||||
}
|
||||
|
||||
// 根据选中的规格组合找到对应的规格规则ID
|
||||
// 根据选中的规格组合找到对应的规格规则ID(忽略顺序,按ID集合匹配)
|
||||
const getSelectedSpecificationId = () => {
|
||||
const specNames = Object.keys(specGroups.value)
|
||||
const selectedIds = []
|
||||
|
||||
// 按规格名称顺序收集选中的规格ID,转换为顺序编号
|
||||
specNames.forEach(specName => {
|
||||
if (selectedSpecs.value[specName]) {
|
||||
const specId = selectedSpecs.value[specName].id
|
||||
const orderNumber = specIdToOrder.value[specId]
|
||||
selectedIds.push(orderNumber)
|
||||
}
|
||||
const required = requiredSpecNames.value
|
||||
const selectedIds = required
|
||||
.map(name => selectedSpecs.value[name]?.id)
|
||||
.filter(Boolean)
|
||||
|
||||
// 只有在必选规格都已选择时才匹配具体规则
|
||||
if (selectedIds.length !== required.length) return null
|
||||
|
||||
const availableSpecs = product.value.specifications?.filter(s => s.is_available) || []
|
||||
|
||||
const specification = availableSpecs.find(spec => {
|
||||
const detailIds = spec.spec_details.map(d => d.id)
|
||||
return detailIds.length === selectedIds.length && selectedIds.every(id => detailIds.includes(id))
|
||||
})
|
||||
|
||||
// 生成combination_key
|
||||
const combinationKey = selectedIds.join('-')
|
||||
|
||||
// 在specifications数组中找到对应的规格规则
|
||||
const specification = product.value.specifications?.find(spec =>
|
||||
spec.combination_key === combinationKey
|
||||
)
|
||||
|
||||
|
||||
return specification ? specification.id : null
|
||||
}
|
||||
|
||||
// 立即购买功能
|
||||
const handlePurchase = async () => {
|
||||
// 检查是否选择了所有必需的规格
|
||||
const specNames = Object.keys(specGroups.value)
|
||||
for (const specName of specNames) {
|
||||
// 只校验当前场景下的“必选规格”是否已选择
|
||||
const required = requiredSpecNames.value
|
||||
for (const specName of required) {
|
||||
if (!selectedSpecs.value[specName]) {
|
||||
ElMessage.warning(`请选择${specName}`)
|
||||
return
|
||||
@@ -361,7 +415,7 @@ const handlePurchase = async () => {
|
||||
// 获取选中规格对应的规格规则ID
|
||||
const specificationId = getSelectedSpecificationId()
|
||||
if (!specificationId) {
|
||||
ElMessage.error('所选规格组合无效,请重新选择')
|
||||
ElMessage.error('所选规格组合无效或不可用,请重新选择')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -534,23 +588,58 @@ onMounted(() => {
|
||||
|
||||
.product-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.coin-icon {
|
||||
color: #ffae00;
|
||||
.price-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.price-value {
|
||||
.main-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rongdou-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.rongdou-price {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #ffae00;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.sub-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.points-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.points-price {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.quantity-selector {
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="item-image">
|
||||
<img :src="item.image || '/imgs/productdetail/商品主图.png'" :alt="item.name" />
|
||||
<img :src="getImageUrl(item.product.image_url) || '/imgs/productdetail/商品主图.png'" :alt="item.name" />
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<div class="item-name">{{ item.name }}</div>
|
||||
@@ -74,8 +74,16 @@
|
||||
<span v-if="item.size" class="item-size">{{ item.size }}</span>
|
||||
</div>
|
||||
<div class="item-price">
|
||||
<el-icon class="coin-icon"><Coin /></el-icon>
|
||||
<span class="price-value">{{ item.product.points_price }}</span>
|
||||
<div class="price-container">
|
||||
<div class="main-price">
|
||||
<img src='/imgs/profile/rongdou.png' alt="融豆" class="rongdou-icon" />
|
||||
<span class="rongdou-price">{{ Math.ceil((item.product.points_price || 0) / 10000) }}</span>
|
||||
</div>
|
||||
<div class="sub-price">
|
||||
<el-icon class="points-icon"><Coin /></el-icon>
|
||||
<span class="points-price">{{ item.product.points_price || 0 }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
@@ -113,8 +121,16 @@
|
||||
<div class="selected-count">已选{{ selectedCount }}件</div>
|
||||
<div class="total-price">
|
||||
<span class="total-label">合计:</span>
|
||||
<el-icon class="coin-icon"><Coin /></el-icon>
|
||||
<span class="total-value">{{ totalPrice }}</span>
|
||||
<div class="total-price-container">
|
||||
<div class="total-main-price">
|
||||
<img src='/imgs/profile/rongdou.png' alt="融豆" class="total-rongdou-icon" />
|
||||
<span class="total-rongdou-price">{{ Math.ceil(totalPrice / 10000) }}</span>
|
||||
</div>
|
||||
<div class="total-sub-price">
|
||||
<el-icon class="total-points-icon"><Coin /></el-icon>
|
||||
<span class="total-points-price">{{ totalPrice }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
@@ -140,6 +156,7 @@ import {
|
||||
Coin
|
||||
} from '@element-plus/icons-vue'
|
||||
import api from '@/utils/api'
|
||||
import { getImageUrl } from '@/config'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -319,7 +336,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #ff6b35;
|
||||
background: #72c9ff;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -483,16 +500,48 @@ onMounted(() => {
|
||||
.item-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.main-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rongdou-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.rongdou-price {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.coin-icon {
|
||||
margin-right: 4px;
|
||||
.sub-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.points-icon {
|
||||
font-size: 12px;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.points-price {
|
||||
color: #ff6b35;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -591,11 +640,46 @@ onMounted(() => {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.total-value {
|
||||
.total-price-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.total-main-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.total-rongdou-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.total-rongdou-price {
|
||||
color: #ff6b35;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.total-sub-price {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.total-points-icon {
|
||||
font-size: 14px;
|
||||
color: #ff6b35;
|
||||
}
|
||||
|
||||
.total-points-price {
|
||||
color: #ff6b35;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.checkout-btn {
|
||||
background: linear-gradient(135deg, #ff6b35 0%, #ff4757 100%);
|
||||
border: none;
|
||||
|
||||
306
src/views/CouponGet.vue
Normal file
@@ -0,0 +1,306 @@
|
||||
<template>
|
||||
<div class="coupon-container">
|
||||
<div class="header">
|
||||
<div class="back-btn" @click="$router.go(-1)"><</div>
|
||||
<div class="header-text">
|
||||
优惠券包
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="coupon-content">
|
||||
<div class="discount_for_a_amount_container">
|
||||
<div class="container-title">
|
||||
满减券限量
|
||||
</div>
|
||||
<div class="coupons-content">
|
||||
<div class="coupon-item" v-for="coupon in coupons" :key="coupon.id">
|
||||
<div class="coupon-filtered-item" v-if="coupon.type === 'discount_for_a_amount'"
|
||||
:style="{
|
||||
backgroundImage: coupon.got
|
||||
? 'url(/imgs/shop/coupon/bg_useless1.png)'
|
||||
: 'url(/imgs/shop/coupon/bg_useful1.png)'
|
||||
}">
|
||||
<div class="text-left">¥{{ coupon.discount }}</div>
|
||||
<div class="text-mid-w">满{{ coupon.for_a_amount }}可用</div>
|
||||
<div class="text-right" @click="getCoupon('discount_for_a_amount',coupon.id)" v-if="!coupon.got">立即领取</div>
|
||||
<div class="text-right" v-else>已领取</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="discount_container">
|
||||
<div class="container-title" style="margin-bottom: 10px;">
|
||||
抵扣券限量
|
||||
</div>
|
||||
<div class="coupons-content grid-container">
|
||||
<div class="coupon-item" v-for="coupon in coupons" :key="coupon.id">
|
||||
<div class="coupon-filtered-item2" v-if="coupon.type === 'deduction'"
|
||||
:style="{
|
||||
backgroundImage: coupon.got
|
||||
? 'url(/imgs/shop/coupon/bg_useless2.png)'
|
||||
: 'url(/imgs/shop/coupon/bg_useful2.png)'
|
||||
}">
|
||||
<div class="text-top">¥{{ coupon.price }}</div>
|
||||
<div class="text-mid-h">无门槛</div>
|
||||
<div class="text-bottom" @click="getCoupon('deduction',coupon.id)" v-if="!coupon.got">立即领取</div>
|
||||
<div class="text-bottom" v-else>已领取</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="deduction_container">
|
||||
<div class="container-title">
|
||||
折扣券限量
|
||||
</div>
|
||||
<div class="coupons-content">
|
||||
<div class="coupon-item" v-for="coupon in coupons" :key="coupon.id">
|
||||
<div class="coupon-filtered-item" v-if="coupon.type === 'discount'"
|
||||
:style="{
|
||||
backgroundImage: coupon.got
|
||||
? 'url(/imgs/shop/coupon/bg_useless1.png)'
|
||||
: 'url(/imgs/shop/coupon/bg_useful1.png)'
|
||||
}">
|
||||
<div class="text-left">{{ coupon.precent/10 }}折</div>
|
||||
<div class="text-right" @click="getCoupon('discount',coupon.id)" v-if="!coupon.got">立即领取</div>
|
||||
<div class="text-right" v-else>已领取</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import api from '@/utils/api'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const coupons = ref([])
|
||||
|
||||
const getAllCoupons = async () => {
|
||||
const {data} = await api.get('/coupon',{
|
||||
params: {
|
||||
user_id: userStore.user.id
|
||||
}
|
||||
})
|
||||
if (data.success) {
|
||||
coupons.value = data.coupon
|
||||
console.log(123,coupons.value)
|
||||
} else {
|
||||
ElMessage.error(data.message || '优惠券领取失败')
|
||||
}
|
||||
}
|
||||
|
||||
const getCoupon = async (coupon_type,coupon_id) => {
|
||||
try {
|
||||
const {data} = await api.get(`/coupon/${userStore.user.id}`,{
|
||||
params: {
|
||||
coupon_type: coupon_type,
|
||||
coupon_id: coupon_id
|
||||
}
|
||||
})
|
||||
if (data.success) {
|
||||
ElMessage.success('优惠券领取成功')
|
||||
getAllCoupons()
|
||||
} else {
|
||||
ElMessage.error(data.msg || '优惠券领取失败')
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error('优惠券领取失败')
|
||||
console.error('优惠券领取失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getAllCoupons()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coupon-container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-image: url(/imgs/shop/coupon/background.png);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 15px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: #000000;
|
||||
background: transparent;
|
||||
border: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin-left: 30px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
opacity: 1;
|
||||
font-family: SF Pro;
|
||||
font-weight: 650;
|
||||
font-style: Expanded Semibold;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
letter-spacing: 0%;
|
||||
color: #2F4FB5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.coupon-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
padding: 20px;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.discount_for_a_amount_container,
|
||||
.discount_container,
|
||||
.deduction_container {
|
||||
background: white;
|
||||
width: 348px;
|
||||
padding: 10px;
|
||||
top: 32px;
|
||||
opacity: 1;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.container-title {
|
||||
width: 96;
|
||||
height: 20;
|
||||
top: 324px;
|
||||
left: 105px;
|
||||
opacity: 1;
|
||||
font-family: SF Pro;
|
||||
font-weight: 700;
|
||||
font-style: Bold;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0%;
|
||||
color: #2F4FB5;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.coupon-filtered-item {
|
||||
width: 324px;
|
||||
height: 66px;
|
||||
top: 58px;
|
||||
left: 12px;
|
||||
opacity: 1;
|
||||
background-size: 100% 100%;
|
||||
margin-top: 10px;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: #305DEF;
|
||||
}
|
||||
|
||||
.text-mid-w {
|
||||
font-size: 14px;
|
||||
color: #305DEF;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-family: SF Pro;
|
||||
font-weight: 590;
|
||||
font-style: Semibold;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0%;
|
||||
}
|
||||
|
||||
.text-right {
|
||||
font-size: 14px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
background: #305DEF;
|
||||
transition: background-color 0.3s;
|
||||
margin-right: -10px;
|
||||
}
|
||||
|
||||
.text-right:hover {
|
||||
background-color: #305DEF;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.coupon-filtered-item2 {
|
||||
width: 83px;
|
||||
height: 94px;
|
||||
opacity: 1;
|
||||
border-radius: 4px;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.text-top {
|
||||
font-family: SF Pro;
|
||||
font-weight: 700;
|
||||
font-style: Bold;
|
||||
font-size: 18px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0%;
|
||||
color: #305DEF;
|
||||
}
|
||||
|
||||
.text-mid-h {
|
||||
font-family: SF Pro;
|
||||
font-weight: 400;
|
||||
font-style: Regular;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0%;
|
||||
color: #305DEF;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.text-bottom {
|
||||
font-size: 10px;
|
||||
color: #305DEF;
|
||||
cursor: pointer;
|
||||
padding: 5px 10px;
|
||||
border-radius: 100px;
|
||||
transition: background-color 0.3s;
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
267
src/views/CouponManage.vue
Normal file
@@ -0,0 +1,267 @@
|
||||
<template>
|
||||
<div class="coupon-container">
|
||||
<div class="header">
|
||||
<div class="back-btn" @click="$router.go(-1)"><</div>
|
||||
<div class="header-text">
|
||||
优惠券包
|
||||
</div>
|
||||
<div class="link-btn" @click="$router.push('/coupon')">领券中心</div>
|
||||
</div>
|
||||
|
||||
<div class="coupon-list">
|
||||
<div class="coupon-item" v-for="coupon in couponList" :key="coupon.id">
|
||||
<div class="coupon-info">
|
||||
<div class="coupon-name">{{ getCouponName(coupon.couponInfo.type) }}</div>
|
||||
<div class="right">
|
||||
<div class="coupon-title">
|
||||
{{ getCouponTitle(coupon.couponInfo.type)[0] }}
|
||||
<span class="value">
|
||||
{{ coupon.couponInfo.discount === "0.00" ? coupon.couponInfo.price === "0.00" ? coupon.couponInfo.precent/10 : coupon.couponInfo.price : coupon.couponInfo.discount }}
|
||||
</span>
|
||||
{{ getCouponTitle(coupon.couponInfo.type)[1] }}
|
||||
</div>
|
||||
<div class="coupon-describe">{{ getCouponDescribe(coupon.couponInfo.type, coupon.couponInfo.for_a_amount === "0.00" ? coupon.couponInfo.price === "0.00" ? coupon.couponInfo.precent : coupon.couponInfo.price : coupon.couponInfo.for_a_amount) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<img src="/imgs/line.png" alt="虚线" class="line-image">
|
||||
<div class="coupon-bottom">
|
||||
{{ coupon.use_time === null ? '未使用' : '已使用' }}
|
||||
<span class="text" @click="showCouponDetail(coupon)">适用商品</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-drawer
|
||||
title="优惠券详情"
|
||||
v-model="dialogVisible"
|
||||
width="50%"
|
||||
direction="btt"
|
||||
size="90%"
|
||||
show_close
|
||||
>
|
||||
<div class="product-item" v-for="(product, index) in couponDetail.products" :key="product.id" @click="$router.push(`/productsummary/${couponDetail.products_id[index]}`)">
|
||||
{{ product.name }}
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="dialogVisible = false">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import api from '@/utils/api'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const user = useUserStore().user
|
||||
|
||||
const couponList = ref([])
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const couponDetail = ref([])
|
||||
|
||||
const getCouponList = async () => {
|
||||
const response = await api.get(`/coupon/user/${user.id}`)
|
||||
couponList.value = response.data.coupon
|
||||
}
|
||||
|
||||
const getCouponName = (couponType) => {
|
||||
const couponName = {
|
||||
'discount_for_a_amount': '满减券',
|
||||
'deduction': '抵扣券',
|
||||
'discount': '折扣券',
|
||||
}
|
||||
return couponName[couponType] || couponType
|
||||
}
|
||||
|
||||
const getCouponTitle = (couponType) => {
|
||||
const couponTitle = {
|
||||
'discount_for_a_amount': ['¥',''],
|
||||
'deduction': ['¥',''],
|
||||
'discount': ['商品','折券'],
|
||||
}
|
||||
return couponTitle[couponType] || couponType
|
||||
}
|
||||
|
||||
const getCouponDescribe = (couponType, value) => {
|
||||
const couponDescribe = {
|
||||
'discount_for_a_amount': '满'+value+'可用',
|
||||
'deduction': '指定商品可用',
|
||||
'discount': '指定商品可用',
|
||||
}
|
||||
return couponDescribe[couponType] || couponType
|
||||
}
|
||||
|
||||
const showCouponDetail = (coupon) => {
|
||||
couponDetail.value = coupon.couponInfo
|
||||
dialogVisible.value = true
|
||||
console.log(coupon)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCouponList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.coupon-container {
|
||||
background: linear-gradient(180deg, #E3E8FF 0%, #ffffff00 100%);
|
||||
background-blend-mode: lighten;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-left: 20px;
|
||||
position: relative;
|
||||
background: #FFFFFF;
|
||||
height: 46px;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
font-size: 15px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
color: #000000;
|
||||
background: transparent;
|
||||
border: none;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
margin-left: 30px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
opacity: 1;
|
||||
font-family: SF Pro;
|
||||
font-weight: 650;
|
||||
font-style: Expanded Semibold;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
letter-spacing: 0%;
|
||||
text-align: center;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
.link-btn {
|
||||
background: #CADBFF;
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
gap: 10px;
|
||||
opacity: 1;
|
||||
border-radius: 99px;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
font-size: 10px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.coupon-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
background: #F5F7FF;
|
||||
width: 334px;
|
||||
height: 116px;
|
||||
padding-top: 8px;
|
||||
padding-right: 10px;
|
||||
padding-bottom: 8px;
|
||||
padding-left: 10px;
|
||||
gap: 10px;
|
||||
opacity: 1;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 15px;
|
||||
box-shadow: 2px 4px 4px 0px #00000040;
|
||||
}
|
||||
|
||||
.coupon-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 10px;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.coupon-name {
|
||||
background: #D4E2FF;
|
||||
width: 80px;
|
||||
height: 28px;
|
||||
padding-right: 16px;
|
||||
padding-left: 16px;
|
||||
gap: 10px;
|
||||
opacity: 1;
|
||||
border-radius: 99px;
|
||||
font-size: 15px;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
color: #2F4FB5;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.coupon-title,
|
||||
.coupon-describe {
|
||||
width: 300px;
|
||||
height: 28px;
|
||||
opacity: 1;
|
||||
font-family: SF Pro;
|
||||
font-size: 16px;
|
||||
line-height: 28px;
|
||||
vertical-align: middle;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.coupon-title {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 25px;
|
||||
font-weight: bold;
|
||||
color: #2F4FB5;
|
||||
}
|
||||
|
||||
.line-image {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.coupon-bottom {
|
||||
margin-top: 5px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 14px;
|
||||
margin-left: 170px;
|
||||
margin-top: 5px;
|
||||
background: #2954E0;
|
||||
color: #ffffff;
|
||||
height: 28px;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
gap: 10px;;
|
||||
opacity: 1;
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.product-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<!-- 客服二维码图片 -->
|
||||
<div class="customer-service-container">
|
||||
<img src="/imgs/mainpage/客服码.jpg" alt="客服码" class="customer-service-img">
|
||||
<img src="/imgs/mainpage/kefuma.jpg" alt="客服码" class="customer-service-img">
|
||||
<div class="customer-service-text">扫码联系客服(长按识别)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
553
src/views/Distribution.vue
Normal file
@@ -0,0 +1,553 @@
|
||||
<template>
|
||||
<div class="distribution-page">
|
||||
<!-- 导航栏 -->
|
||||
<nav class="navbar">
|
||||
<div class="nav-left">
|
||||
<el-button
|
||||
:text="true"
|
||||
@click="$router.go(-1)"
|
||||
class="back-btn"
|
||||
>
|
||||
<el-icon>
|
||||
<ArrowLeft/>
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="nav-center">
|
||||
<h1 class="nav-title">分享</h1>
|
||||
</div>
|
||||
<div class="nav-right">
|
||||
<!-- 占位元素,保持标题居中 -->
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-content">
|
||||
<!-- 分销说明 -->
|
||||
<div class="intro-section">
|
||||
<div class="intro-card">
|
||||
<h3 class="intro-title">邀请好友注册</h3>
|
||||
<p class="intro-desc">分享您的专属二维码,邀请好友注册</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二维码区域 -->
|
||||
<div class="qrcode-section">
|
||||
<div class="qrcode-card">
|
||||
<h4 class="qrcode-title">我的推广二维码</h4>
|
||||
<div class="qrcode-container">
|
||||
<canvas
|
||||
ref="qrcodeCanvas"
|
||||
class="qrcode-canvas"
|
||||
v-show="qrcodeGenerated"
|
||||
></canvas>
|
||||
<div v-show="!qrcodeGenerated" class="qrcode-loading">
|
||||
<el-icon class="loading-icon">
|
||||
<Loading/>
|
||||
</el-icon>
|
||||
<span>生成中...</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="qrcode-tip">扫描二维码,好友可直接注册并绑定您的推荐关系</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 推广链接 -->
|
||||
<div class="link-section">
|
||||
<div class="link-card">
|
||||
<h4 class="link-title">推广链接</h4>
|
||||
<div class="link-container">
|
||||
<el-input
|
||||
v-model="inviteLink"
|
||||
readonly
|
||||
class="link-input"
|
||||
>
|
||||
<template #append>
|
||||
<el-button @click="copyLink" type="primary">
|
||||
复制
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 下级用户 -->
|
||||
<el-button class="lower-users-btn" @click="handleLowerUser">查看下级用户</el-button>
|
||||
<el-drawer
|
||||
v-model="drawerLowerUsers"
|
||||
title="下级用户"
|
||||
direction="btt"
|
||||
:lockScroll="false"
|
||||
size="70%"
|
||||
@open="requestLowerUsers(params)"
|
||||
@closed="closeScrollerLowerUsers"
|
||||
>
|
||||
<!-- 一级 -->
|
||||
<ul v-if="!showScrollerLowerUsersByUser" v-infinite-scroll="loadScrollerLowerUsers" class="users-list"
|
||||
style="overflow: auto"
|
||||
:infinite-scroll-immediate="false"
|
||||
:infinite-scroll-disabled="scrollLowerUsers">
|
||||
<!-- lowerUsers 空数据处理 -->
|
||||
<el-empty v-if="lowerUsers.length==0" :image-size="200"/>
|
||||
<li v-else v-for="(item, index) in lowerUsers" :key="item.id">
|
||||
<el-divider v-if="index!=0" border-style="dashed"/>
|
||||
<div class="users-item" @click="handleLowerUserByUserId(item)">
|
||||
<el-image
|
||||
:src="getImageUrl(item.avatar)"
|
||||
:preview-src-list="[getImageUrl(item.avatar)]"
|
||||
class="user-avatar"
|
||||
fit="cover"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="user-text">
|
||||
<div class="user-username">用户名:{{ item.username }}</div>
|
||||
<div class="user-date">时间:{{ item.created_at }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<el-divider v-if="scrollLowerUsers" border-style="dashed" content-position="center">到底了~</el-divider>
|
||||
</ul>
|
||||
|
||||
<!-- 二级 -->
|
||||
<ul v-else v-infinite-scroll="loadScrollerLowerUsersByUser" class="users-list" style="overflow: auto"
|
||||
:infinite-scroll-immediate="false"
|
||||
:infinite-scroll-disabled="scrollLowerUsersByUser">
|
||||
<el-page-header style="margin-bottom: 20px" @back="onBack">
|
||||
<template #content>
|
||||
<span class="text-large font-600 mr-3"
|
||||
style="font-size: 14px"> 正在查看{{ currentUser.username }}的下级 </span>
|
||||
</template>
|
||||
<div class="mt-4 text-sm font-bold"></div>
|
||||
</el-page-header>
|
||||
<!-- lowerUsers 空数据处理 -->
|
||||
<el-empty v-if="lowerUsersByUser.length==0" :image-size="200"/>
|
||||
<li v-else v-for="(item, index) in lowerUsersByUser" :key="item.id">
|
||||
<el-divider v-if="index!=0" border-style="dashed"/>
|
||||
<div class="users-item">
|
||||
<el-image
|
||||
:src="getImageUrl(item.avatar)"
|
||||
:preview-src-list="[getImageUrl(item.avatar)]"
|
||||
class="user-avatar"
|
||||
fit="cover"
|
||||
>
|
||||
<template #error>
|
||||
<div class="image-slot">
|
||||
<el-icon>
|
||||
<Picture/>
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
</el-image>
|
||||
<div class="user-text">
|
||||
<div class="user-username">用户名:{{ item.username }}</div>
|
||||
<div class="user-date">时间:{{ item.created_at }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<el-divider v-if="scrollLowerUsersByUser" border-style="dashed" content-position="center">到底了~</el-divider>
|
||||
</ul>
|
||||
</el-drawer>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onMounted, nextTick, reactive} from 'vue'
|
||||
import {useRouter} from 'vue-router'
|
||||
import {useUserStore} from '@/stores/user'
|
||||
import {ElMessage} from 'element-plus'
|
||||
import {
|
||||
ArrowLeft,
|
||||
Loading, Picture
|
||||
} from '@element-plus/icons-vue'
|
||||
import QRCode from 'qrcode'
|
||||
import {distributionAPI} from "@/utils/api.js";
|
||||
import {getImageUrl} from "@/config/index.js";
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 响应式数据
|
||||
const qrcodeCanvas = ref(null)
|
||||
const qrcodeGenerated = ref(false)
|
||||
const generating = ref(false)
|
||||
const inviteLink = ref('')
|
||||
// 下级用户
|
||||
const drawerLowerUsers = ref(false)
|
||||
|
||||
// 生成邀请链接
|
||||
const generateInviteLink = () => {
|
||||
const userId = userStore.user?.id || 'guest'
|
||||
console.log(userId)
|
||||
const baseUrl = `${window.location.origin}/frontend`
|
||||
return `${baseUrl}/register?inviter=${userId}`// 实施用
|
||||
// return `http://192.168.1.250:5173/register?inviter=${userId}`// 测试用
|
||||
}
|
||||
|
||||
// 生成二维码
|
||||
const generateQRCode = async () => {
|
||||
try {
|
||||
generating.value = true
|
||||
qrcodeGenerated.value = false
|
||||
|
||||
// 生成邀请链接
|
||||
const link = generateInviteLink()
|
||||
inviteLink.value = link
|
||||
|
||||
// 等待DOM更新
|
||||
await nextTick()
|
||||
|
||||
if (qrcodeCanvas.value) {
|
||||
// 生成二维码到canvas
|
||||
await QRCode.toCanvas(qrcodeCanvas.value, link, {
|
||||
width: 200,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
}
|
||||
})
|
||||
|
||||
qrcodeGenerated.value = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成二维码失败:', error)
|
||||
ElMessage.error('生成二维码失败')
|
||||
} finally {
|
||||
generating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 复制链接
|
||||
const copyLink = async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(inviteLink.value)
|
||||
ElMessage.success('链接已复制到剪贴板')
|
||||
} catch (error) {
|
||||
// 降级方案
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.value = inviteLink.value
|
||||
document.body.appendChild(textArea)
|
||||
textArea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textArea)
|
||||
ElMessage.success('链接已复制到剪贴板')
|
||||
}
|
||||
}
|
||||
|
||||
// 查看下级用户
|
||||
const canMultiLevel = ref(false) // 是否有权查看更多级用户
|
||||
const showScrollerLowerUsersByUser = ref(false) // 一二级切换
|
||||
const scrollLowerUsers = ref(false) // 一级是否继续滚动
|
||||
const scrollLowerUsersByUser = ref(false) // 二级是否继续滚动
|
||||
|
||||
const lowerUsers = ref([]) // 一级下级
|
||||
const currentUser = ref({}) // 二级当前选中
|
||||
const lowerUsersByUser = ref([]) // 二级下级
|
||||
|
||||
// 一级传参
|
||||
const params = {
|
||||
page: 1,
|
||||
size: 10
|
||||
}
|
||||
|
||||
// 二级传参
|
||||
const paramsByUser = {
|
||||
page: 1,
|
||||
size: 10,
|
||||
user_id: ''
|
||||
}
|
||||
|
||||
const pages = ref(1)
|
||||
const pagesByUser = ref(1)
|
||||
|
||||
// 打开查看下级用户抽屉
|
||||
const handleLowerUser = async () => {
|
||||
// 判断是否为*代理查看下下级用户
|
||||
var role = JSON.parse(localStorage.getItem('role'))
|
||||
if (role.distribution || (role.user_type == 'agent' || role.user_type == 'agent_directly')) {
|
||||
canMultiLevel.value = true
|
||||
}
|
||||
lowerUsers.value = []
|
||||
drawerLowerUsers.value = true
|
||||
}
|
||||
|
||||
// 请求一级用户
|
||||
const requestLowerUsers = async (params) => {
|
||||
await distributionAPI.getLowerUsers(params).then((res) => {
|
||||
lowerUsers.value = lowerUsers.value.concat(res.data.data)
|
||||
pages.value = res.data.pagination.pages
|
||||
if (params.page >= pages.value) {
|
||||
scrollLowerUsers.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 请求二级用户
|
||||
const requestLowerUsersByUser = async (params) => {
|
||||
await distributionAPI.getLowerUsers(params).then((res) => {
|
||||
lowerUsersByUser.value = lowerUsersByUser.value.concat(res.data.data)
|
||||
pagesByUser.value = res.data.pagination.pages
|
||||
if (params.page >= pagesByUser.value) {
|
||||
scrollLowerUsersByUser.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 一级分页请求查看
|
||||
const loadScrollerLowerUsers = async () => {
|
||||
if (scrollLowerUsers.value) {
|
||||
return
|
||||
}
|
||||
params.page += 1
|
||||
await requestLowerUsers(params)
|
||||
}
|
||||
|
||||
// 二级分页请求查看
|
||||
const loadScrollerLowerUsersByUser = async () => {
|
||||
if (scrollLowerUsersByUser.value) {
|
||||
return
|
||||
}
|
||||
paramsByUser.page += 1
|
||||
await requestLowerUsersByUser(paramsByUser)
|
||||
}
|
||||
|
||||
// 查看二级用户
|
||||
const handleLowerUserByUserId = async (item) => {
|
||||
// 是否有权查看二级
|
||||
if (canMultiLevel.value) {
|
||||
paramsByUser.user_id = item.user_id
|
||||
currentUser.value = item
|
||||
await requestLowerUsersByUser(paramsByUser)
|
||||
showScrollerLowerUsersByUser.value = true
|
||||
}
|
||||
}
|
||||
|
||||
const onBack = () => {
|
||||
showScrollerLowerUsersByUser.value = false
|
||||
}
|
||||
|
||||
// 关闭查看下级用户抽屉
|
||||
const closeScrollerLowerUsers = () => {
|
||||
lowerUsers.value = []
|
||||
params.page = 0
|
||||
scrollLowerUsers.value = false
|
||||
}
|
||||
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
generateQRCode()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.distribution-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(to bottom, #72c9ffae, #f3f3f3);
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
height: 56px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #eee;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.nav-left,
|
||||
.nav-right {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
color: #409eff;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.page-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.intro-section,
|
||||
.qrcode-section,
|
||||
.link-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.intro-card,
|
||||
.qrcode-card,
|
||||
.link-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.intro-title,
|
||||
.qrcode-title,
|
||||
.link-title {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.intro-desc {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.qrcode-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20px 0;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.qrcode-canvas {
|
||||
border: 1px solid #eee;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.qrcode-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
font-size: 24px;
|
||||
animation: rotate 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.qrcode-tip {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.link-container {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.link-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.link-input .el-input-group__append) {
|
||||
padding: 0;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
:deep(.link-input .el-input-group__append .el-button) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
/* 确保按钮内文字垂直居中 */
|
||||
:deep(.link-input .el-input-group__append .el-button span) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.lower-users-btn {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.users-list {
|
||||
height: 100%;
|
||||
padding: 0 0 0 10px;;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.users-list .users-item {
|
||||
display: flex; /* 使用Flexbox布局 */
|
||||
align-items: center; /* 垂直居中对齐 */
|
||||
gap: 1rem; /* 图片与文字之间的间距 */
|
||||
|
||||
.user-avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-text {
|
||||
.user-username {
|
||||
margin-bottom: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.user-date {
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.users-list .users-item + .list-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -287,6 +287,12 @@ const saveProfile = async () => {
|
||||
await profileFormRef.value.validate()
|
||||
|
||||
saving.value = true
|
||||
form.wechatQr = form.wechatQr.replace('https://minio.zrbjr.com', '')
|
||||
form.alipayQr = form.alipayQr.replace('https://minio.zrbjr.com', '')
|
||||
form.unionpayQr = form.unionpayQr.replace('https://minio.zrbjr.com', '')
|
||||
form.businessLicense = form.businessLicense.replace('https://minio.zrbjr.com', '')
|
||||
form.idCardFront = form.idCardFront.replace('https://minio.zrbjr.com', '')
|
||||
form.idCardBack = form.idCardBack.replace('https://minio.zrbjr.com', '')
|
||||
const response = await api.put('/users/profile', form)
|
||||
|
||||
// 更新本地数据
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<!-- 导航栏 -->
|
||||
<el-header class="header">
|
||||
<div class="header-content">
|
||||
<div class="logo">
|
||||
<h2>前端H5系统</h2>
|
||||
</div>
|
||||
<div class="nav-menu">
|
||||
<el-menu
|
||||
mode="horizontal"
|
||||
:default-active="activeIndex"
|
||||
class="nav-menu-items"
|
||||
@select="handleMenuSelect"
|
||||
>
|
||||
<el-menu-item index="home">首页</el-menu-item>
|
||||
<el-menu-item index="shop">积分商城</el-menu-item>
|
||||
<el-menu-item index="about">关于</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="user-actions">
|
||||
<template v-if="userStore.isAuthenticated">
|
||||
<el-dropdown @command="handleUserCommand">
|
||||
<span class="user-info">
|
||||
<div class="user-avatar">
|
||||
{{ userStore.user?.username?.charAt(0)?.toUpperCase() }}
|
||||
</div>
|
||||
<span class="username">{{ userStore.user?.username }}</span>
|
||||
<el-icon><ArrowDown /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="profile">个人中心</el-dropdown-item>
|
||||
<el-dropdown-item command="orders">我的订单</el-dropdown-item>
|
||||
<el-dropdown-item command="points-history">积分记录</el-dropdown-item>
|
||||
<el-dropdown-item command="transfers">货款管理</el-dropdown-item>
|
||||
<el-dropdown-item divided command="logout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!-- 移除登录注册按钮 -->
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<el-main class="main-content">
|
||||
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="main-section">
|
||||
<div class="container">
|
||||
<div class="welcome-content">
|
||||
<h2 class="welcome-title">欢迎使用前端H5系统</h2>
|
||||
<p class="welcome-description">您的智能管理助手</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="stats-section" v-if="userStore.isAuthenticated">
|
||||
<div class="container">
|
||||
<h2 class="section-title">系统概览</h2>
|
||||
<el-row :gutter="20">
|
||||
<el-col :xs="12" :sm="6" v-for="stat in stats" :key="stat.key">
|
||||
<div class="stat-card">
|
||||
<div class="stat-icon">
|
||||
<el-icon :size="32"><component :is="stat.icon" /></el-icon>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-value">{{ stat.value }}</div>
|
||||
<div class="stat-label">{{ stat.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
|
||||
|
||||
|
||||
<!-- 页脚 -->
|
||||
<el-footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2024 前端H5系统. All rights reserved.</p>
|
||||
</div>
|
||||
</el-footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
import { ArrowDown, User, View, Clock, Document, Edit, Setting, DataAnalysis, Star } from '@element-plus/icons-vue'
|
||||
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 响应式数据
|
||||
const activeIndex = ref('home')
|
||||
const stats = ref([
|
||||
{ key: 'users', label: '用户数量', value: 0, icon: 'User' },
|
||||
{ key: 'orders', label: '订单总数', value: 0, icon: 'Document' },
|
||||
{ key: 'products', label: '商品数量', value: 0, icon: 'Star' },
|
||||
{ key: 'transfers', label: '转账记录', value: 0, icon: 'Clock' }
|
||||
])
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 方法
|
||||
const handleMenuSelect = (index) => {
|
||||
activeIndex.value = index
|
||||
switch (index) {
|
||||
case 'home':
|
||||
router.push('/transfers')
|
||||
break
|
||||
case 'shop':
|
||||
router.push('/shop')
|
||||
break
|
||||
case 'about':
|
||||
router.push('/about')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const handleUserCommand = (command) => {
|
||||
switch (command) {
|
||||
case 'profile':
|
||||
router.push('/profile')
|
||||
break
|
||||
case 'orders':
|
||||
router.push('/orders')
|
||||
break
|
||||
case 'points-history':
|
||||
router.push('/points-history')
|
||||
break
|
||||
case 'transfers':
|
||||
router.push('/transfers')
|
||||
break
|
||||
case 'logout':
|
||||
userStore.logout()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 获取统计数据
|
||||
const fetchStats = async () => {
|
||||
try {
|
||||
// 这里可以调用相关API获取统计数据
|
||||
// 暂时使用模拟数据
|
||||
stats.value = [
|
||||
{ key: 'users', label: '用户数量', value: 156, icon: 'User' },
|
||||
{ key: 'orders', label: '订单总数', value: 89, icon: 'Document' },
|
||||
{ key: 'products', label: '商品数量', value: 45, icon: 'Star' },
|
||||
{ key: 'transfers', label: '转账记录', value: 23, icon: 'Clock' }
|
||||
]
|
||||
} catch (error) {
|
||||
console.error('获取统计数据失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
if (userStore.isAuthenticated) {
|
||||
fetchStats()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.home {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.logo h2 {
|
||||
color: white;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-menu-items {
|
||||
background: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-menu-items .el-menu-item {
|
||||
color: white;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.nav-menu-items .el-menu-item:hover,
|
||||
.nav-menu-items .el-menu-item.is-active {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 0 0 80px 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.main-section,
|
||||
.stats-section {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.main-section {
|
||||
background-color: #f8f9fa;
|
||||
min-height: 50vh;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 32px;
|
||||
margin-bottom: 40px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
text-align: center;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 32px;
|
||||
margin-bottom: 16px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.welcome-description {
|
||||
font-size: 16px;
|
||||
color: #606266;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.stat-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
font-size: 14px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #303133;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer .container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
padding: 15px 20px;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.user-actions {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
|
||||
.section-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.welcome-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
padding-bottom: 80px;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -1,474 +0,0 @@
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<div class="login-card">
|
||||
<div class="login-header">
|
||||
<h2>用户登录</h2>
|
||||
<p>欢迎回到炬融圈</p>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginRules"
|
||||
class="login-form"
|
||||
@submit.prevent="handleLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入用户名或邮箱"
|
||||
size="large"
|
||||
:prefix-icon="User"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
size="large"
|
||||
:prefix-icon="Lock"
|
||||
show-password
|
||||
clearable
|
||||
@keyup.enter="handleLogin"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="captcha">
|
||||
<Captcha
|
||||
ref="captchaRef"
|
||||
v-model="loginForm.captcha"
|
||||
placeholder="请输入验证码"
|
||||
size="large"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<div class="form-options">
|
||||
<el-checkbox v-model="rememberMe">记住我</el-checkbox>
|
||||
<el-link type="primary" @click="showForgotPassword">
|
||||
忘记密码?
|
||||
</el-link>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
class="login-button"
|
||||
:loading="userStore.loading"
|
||||
@click="handleLogin"
|
||||
>
|
||||
{{ userStore.loading ? '登录中...' : '登录' }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- <div class="login-footer">
|
||||
<p>
|
||||
还没有账号?
|
||||
<el-link type="primary" @click="$router.push('/register')">
|
||||
立即注册
|
||||
</el-link>
|
||||
</p>
|
||||
</div> -->
|
||||
|
||||
<!-- 备案信息 -->
|
||||
<div class="icp-info">
|
||||
<div class="icp-item">
|
||||
<svg class="icp-icon" viewBox="0 0 1024 1024" width="16" height="16">
|
||||
<path d="M512 85.333333c-23.466667 0-42.666667 19.2-42.666667 42.666667v85.333333c0 23.466667 19.2 42.666667 42.666667 42.666667s42.666667-19.2 42.666667-42.666667V128c0-23.466667-19.2-42.666667-42.666667-42.666667z" fill="#909399"/>
|
||||
<path d="M512 256c-141.226667 0-256 114.773333-256 256 0 70.613333 28.586667 134.4 74.666667 180.48L512 874.666667l181.333333-182.186667C739.413333 646.4 768 582.613333 768 512c0-141.226667-114.773333-256-256-256z m0 341.333333c-47.146667 0-85.333333-38.186667-85.333333-85.333333s38.186667-85.333333 85.333333-85.333333 85.333333 38.186667 85.333333 85.333333-38.186667 85.333333-85.333333 85.333333z" fill="#909399"/>
|
||||
<path d="M170.666667 298.666667c-11.733333-20.48-37.546667-27.306667-58.026667-15.573334-20.48 11.733333-27.306667 37.546667-15.573333 58.026667l42.666666 74.24c11.733333 20.48 37.546667 27.306667 58.026667 15.573333 20.48-11.733333 27.306667-37.546667 15.573333-58.026666l-42.666666-74.24z" fill="#909399"/>
|
||||
<path d="M853.333333 298.666667l-42.666666 74.24c-11.733333 20.48-4.906667 46.293333 15.573333 58.026666 20.48 11.733333 46.293333 4.906667 58.026667-15.573333l42.666666-74.24c11.733333-20.48 4.906667-46.293333-15.573333-58.026667-20.48-11.733333-46.293333-4.906667-58.026667 15.573334z" fill="#909399"/>
|
||||
</svg>
|
||||
<a href="https://beian.miit.gov.cn/" target="_blank" class="icp-link">
|
||||
浙ICP备2025186895号
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 背景装饰 -->
|
||||
<div class="background-decoration">
|
||||
<div class="decoration-circle circle-1"></div>
|
||||
<div class="decoration-circle circle-2"></div>
|
||||
<div class="decoration-circle circle-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { User, Lock } from '@element-plus/icons-vue'
|
||||
import Captcha from '@/components/Captcha.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 表单引用
|
||||
const loginFormRef = ref()
|
||||
const captchaRef = ref()
|
||||
|
||||
// 表单数据
|
||||
const loginForm = reactive({
|
||||
username: '',
|
||||
password: '',
|
||||
captcha: ''
|
||||
})
|
||||
|
||||
// 其他状态
|
||||
const rememberMe = ref(false)
|
||||
|
||||
// 表单验证规则
|
||||
const loginRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名或邮箱', trigger: 'blur' },
|
||||
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{ min: 6, message: '密码至少6个字符', trigger: 'blur' }
|
||||
],
|
||||
captcha: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{ min: 4, max: 4, message: '验证码为4位字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 处理登录
|
||||
const handleLogin = async () => {
|
||||
if (!loginFormRef.value || !captchaRef.value) return
|
||||
|
||||
try {
|
||||
// 先验证表单
|
||||
const valid = await loginFormRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 验证验证码
|
||||
// const captchaValid = await captchaRef.value.verifyCaptcha(loginForm.captcha)
|
||||
// if (!captchaValid) {
|
||||
// loginForm.captcha = ''
|
||||
// return
|
||||
// }
|
||||
|
||||
// 获取验证码信息
|
||||
const captchaInfo = captchaRef.value.getCaptchaInfo()
|
||||
|
||||
// 提交登录请求(包含验证码信息)
|
||||
const loginData = {
|
||||
username: loginForm.username,
|
||||
password: loginForm.password,
|
||||
captchaId: captchaInfo.captchaId,
|
||||
captchaText: captchaInfo.captchaText
|
||||
}
|
||||
|
||||
const result = await userStore.login(loginData)
|
||||
|
||||
if (result.success) {
|
||||
// 登录成功,跳转到目标页面或转账管理
|
||||
const redirectPath = route.query.redirect || '/transfers'
|
||||
router.push(redirectPath)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
// 登录失败后刷新验证码
|
||||
if (captchaRef.value) {
|
||||
await captchaRef.value.refreshCaptcha()
|
||||
}
|
||||
loginForm.captcha = ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 忘记密码
|
||||
const showForgotPassword = () => {
|
||||
ElMessageBox.alert(
|
||||
'请联系管理员重置密码,或使用演示账号进行体验。',
|
||||
'忘记密码',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
type: 'info'
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 组件挂载时的处理
|
||||
onMounted(() => {
|
||||
// 如果已经登录,直接跳转
|
||||
if (userStore.isAuthenticated) {
|
||||
const redirectPath = route.query.redirect || '/transfers'
|
||||
router.push(redirectPath)
|
||||
}
|
||||
|
||||
// 从localStorage恢复记住我状态
|
||||
const savedUsername = localStorage.getItem('rememberedUsername')
|
||||
if (savedUsername) {
|
||||
loginForm.username = savedUsername
|
||||
rememberMe.value = true
|
||||
}
|
||||
})
|
||||
|
||||
// 监听记住我状态变化
|
||||
const handleRememberMe = () => {
|
||||
if (rememberMe.value && loginForm.username) {
|
||||
localStorage.setItem('rememberedUsername', loginForm.username)
|
||||
} else {
|
||||
localStorage.removeItem('rememberedUsername')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-page {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
border-radius: 16px;
|
||||
padding: 40px 30px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h2 {
|
||||
color: #303133;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-options {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.login-button {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.login-footer p {
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.icp-info {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
padding-top: 15px;
|
||||
border-top: 1px solid rgba(224, 224, 230, 0.3);
|
||||
}
|
||||
|
||||
.icp-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.icp-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icp-link {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.icp-link:hover {
|
||||
color: #409eff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.quick-login {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.demo-accounts {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: center;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.background-decoration {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.decoration-circle {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
animation: float 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.circle-1 {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
top: 10%;
|
||||
left: 10%;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.circle-2 {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
top: 60%;
|
||||
right: 10%;
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.circle-3 {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
bottom: 20%;
|
||||
left: 20%;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0px) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 480px) {
|
||||
.login-container {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
padding: 30px 20px;
|
||||
}
|
||||
|
||||
.demo-accounts {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-options {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.icp-info {
|
||||
margin-top: 15px;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.icp-link {
|
||||
font-size: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Element Plus 组件样式覆盖 */
|
||||
:deep(.el-input__wrapper) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-divider__text) {
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
/* 输入框聚焦效果 */
|
||||
:deep(.el-input__wrapper:hover),
|
||||
:deep(.el-input__wrapper.is-focus) {
|
||||
box-shadow: 0 0 0 1px #409eff inset;
|
||||
}
|
||||
|
||||
/* 加载状态样式 */
|
||||
.login-button.is-loading {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 动画效果 */
|
||||
.login-card {
|
||||
animation: slideInUp 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideInUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(30px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* 错误状态样式 */
|
||||
:deep(.el-form-item.is-error .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 1px #f56c6c inset;
|
||||
}
|
||||
|
||||
/* 成功状态样式 */
|
||||
:deep(.el-form-item.is-success .el-input__wrapper) {
|
||||
box-shadow: 0 0 0 1px #67c23a inset;
|
||||
}
|
||||
</style>
|
||||
@@ -3,6 +3,8 @@
|
||||
<div class="spacer"></div>
|
||||
|
||||
<div class="main-content">
|
||||
|
||||
<p class="welcome">{{userName}},欢迎来到炬融圈</p>
|
||||
<!-- 轮播图 -->
|
||||
<div class="carousel">
|
||||
<swiper
|
||||
@@ -13,7 +15,7 @@
|
||||
:pagination="{ clickable: true }"
|
||||
:loop="true"
|
||||
>
|
||||
<swiper-slide v-for="(item, index) in carouselItems" :key="index">
|
||||
<swiper-slide v-for="(item, index) in carouselItems" :key="index" @click="handleCarouselClick(item)" class="carousel-slide">
|
||||
<img :src="item.image" :alt="item.title" class="carousel-img" />
|
||||
<div class="carousel-title">{{ item.title }}</div>
|
||||
</swiper-slide>
|
||||
@@ -22,43 +24,35 @@
|
||||
|
||||
<!-- 头部导航 -->
|
||||
<div class="header">
|
||||
<router-link
|
||||
<div
|
||||
v-for="(item, index) in headerItems"
|
||||
:key="index"
|
||||
:to="item.path"
|
||||
class="header-item"
|
||||
custom
|
||||
v-slot="{ navigate }"
|
||||
>
|
||||
<div @click="navigate" class="header-item-content">
|
||||
<div @click="item.text === '系统公告' ? handleSystemAnnouncementClick() : $router.push(item.path)" class="header-item-content">
|
||||
<img :src="item.image" :alt="item.text" class="header-image" />
|
||||
<div class="header-text">{{ item.text }}</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- 修改后的操作区域 - 三个部分等宽 -->
|
||||
<div class="action-area">
|
||||
<div class="action-grid">
|
||||
<router-link to="/mymatching?autoStart=true" class="action-main">
|
||||
<div class="matching-text">
|
||||
<div>获取</div>
|
||||
<div>融豆</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<div class="action-stack">
|
||||
<div class="action-sub-item top">
|
||||
<div class="action-icon">💎</div>
|
||||
<div class="action-text">当前积分: {{ userPoints }}</div>
|
||||
</div>
|
||||
<router-link to="/myshop" class="action-sub-item bottom">
|
||||
<div class="action-icon">🛒</div>
|
||||
<div class="action-text">商城</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 修改后的操作区域 - 三个图片并排 -->
|
||||
<div class="action-area">
|
||||
<div class="action-grid-horizontal">
|
||||
<router-link to="/matching?quantitative=true" class="action-item">
|
||||
<img src="/imgs/mainpage/dinglianghuoqu1.png" alt="定量获取" class="action-image1" />
|
||||
</router-link>
|
||||
<router-link to="/matching" class="action-item">
|
||||
<img src="/imgs/mainpage/huoqurongdou1.png" alt="获取融豆" class="action-image2" />
|
||||
</router-link>
|
||||
<router-link to="/myshop" class="action-item">
|
||||
<img src="/imgs/mainpage/jifenyue1.png" alt="商城" class="action-image3" />
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">点击获得融豆,可兑换商城好物!</div>
|
||||
|
||||
<!-- 热门资讯 -->
|
||||
<div class="hot-news">
|
||||
<div class="news-title">热门资讯</div>
|
||||
@@ -73,18 +67,67 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 欢迎弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showWelcomeDialog"
|
||||
width="90%"
|
||||
:style="{ height: '515px' }"
|
||||
:show-close="true"
|
||||
:close-on-click-modal="true"
|
||||
:close-on-press-escape="true"
|
||||
@close="closeWelcomeDialog"
|
||||
center
|
||||
class="welcome-dialog"
|
||||
:lock-scroll="false"
|
||||
>
|
||||
<div class="welcome-content">
|
||||
<div class="welcome-icon">🎉</div>
|
||||
<h3>融汇通更新</h3>
|
||||
<div class="welcome-features">
|
||||
<div class="announcements-container" v-if="displayAnnouncements.length > 0">
|
||||
<div
|
||||
class="announcement-item"
|
||||
v-for="announcement in displayAnnouncements"
|
||||
:key="announcement.id"
|
||||
>
|
||||
<div class="announcement-header">
|
||||
<h4 class="announcement-title">{{ announcement.title }}</h4>
|
||||
<span class="announcement-priority" :class="announcement.priority">{{ announcement.priority }}</span>
|
||||
</div>
|
||||
<div class="announcement-content">{{ announcement.content }}</div>
|
||||
<div class="announcement-meta">
|
||||
<span class="announcement-time">{{ formatDate(announcement.created_at) }}</span>
|
||||
<span class="announcement-author">发布者: {{ announcement.creator_name }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-announcements">
|
||||
<span>{{ isSystemAnnouncementClick ? '暂无公告信息' : '暂无未读更新信息' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="closeWelcomeDialog">已读</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Swiper, SwiperSlide } from 'swiper/vue';
|
||||
import { Autoplay, Pagination } from 'swiper';
|
||||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { ElMessage, ElDialog, ElButton } from 'element-plus';
|
||||
import { transferAPI } from '../utils/api';
|
||||
import { useUserStore } from '../stores/user';
|
||||
import { useRouter } from 'vue-router';
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/autoplay';
|
||||
import 'swiper/css/pagination';
|
||||
import api from '@/utils/api'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -92,40 +135,51 @@ export default {
|
||||
SwiperSlide,
|
||||
},
|
||||
setup() {
|
||||
// 用户store
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
|
||||
// 响应式数据
|
||||
const userPoints = ref(0);
|
||||
const showWelcomeDialog = ref(false);
|
||||
|
||||
const updateNotice = ref('');
|
||||
const announcements = ref([]);
|
||||
|
||||
// 计算属性 - 获取用户名
|
||||
const userName = computed(() => {
|
||||
return userStore.user?.username || '用户';
|
||||
});
|
||||
const carouselItems = ref([
|
||||
{ image: '/imgs/top/1.jpg', title: '限时优惠活动' },
|
||||
{ image: '/imgs/top/2.jpg', title: '新用户专享' },
|
||||
{ image: '/imgs/top/3.jpg', title: '积分兑换' },
|
||||
{ image: '/imgs/mainpage/lunbotu/1.png', title: '招商中', path: '/' },
|
||||
{ image: '/imgs/mainpage/lunbotu/2.png', title: '代理合作', path: '/agent/login' },
|
||||
{ image: '/imgs/mainpage/lunbotu/3.png', title: '积分兑换', path: '/myshop' },
|
||||
]);
|
||||
const headerItems = ref([
|
||||
{
|
||||
image: '/imgs/mainpage/交易记录.png',
|
||||
image: '/imgs/mainpage/jiaoyijilu.png',
|
||||
text: '交易记录',
|
||||
path: '/transfers',
|
||||
},
|
||||
{
|
||||
image: '/imgs/mainpage/订单查询.png',
|
||||
image: '/imgs/mainpage/dingdanchaxun.png',
|
||||
text: '订单查询',
|
||||
path: '/points-history',
|
||||
path: '/orders',
|
||||
},
|
||||
{
|
||||
image: '/imgs/mainpage/客服中心.png',
|
||||
image: '/imgs/mainpage/kefuzhongxin.png',
|
||||
text: '客服中心',
|
||||
path: '/customerservice',
|
||||
},
|
||||
{
|
||||
image: '/imgs/mainpage/系统公告.png',
|
||||
image: '/imgs/mainpage/xitonggonggao.png',
|
||||
text: '系统公告',
|
||||
path: '#',
|
||||
},
|
||||
]);
|
||||
const newsItems = ref([
|
||||
'最新活动:双十一特惠',
|
||||
'最新活动:商城特惠',
|
||||
'商城新品上架',
|
||||
'积分兑换规则更新',
|
||||
'VIP会员特权升级',
|
||||
]);
|
||||
|
||||
// 获取用户积分
|
||||
@@ -144,26 +198,160 @@ export default {
|
||||
}
|
||||
};
|
||||
|
||||
// 计算未读公告
|
||||
const unreadAnnouncements = computed(() => {
|
||||
return announcements.value.filter(announcement => !announcement.is_read);
|
||||
});
|
||||
|
||||
// 标记是否通过系统公告按钮打开对话框
|
||||
const isSystemAnnouncementClick = ref(false);
|
||||
|
||||
// 计算要显示的公告列表
|
||||
const displayAnnouncements = computed(() => {
|
||||
if (isSystemAnnouncementClick.value) {
|
||||
// 如果是通过系统公告按钮打开,显示所有公告并按创建时间倒序排列(最新的在前)
|
||||
return [...announcements.value].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||
} else {
|
||||
// 否则只显示未读公告
|
||||
return unreadAnnouncements.value;
|
||||
}
|
||||
});
|
||||
|
||||
// 计算是否应该显示弹窗
|
||||
const shouldShowDialog = computed(() => {
|
||||
return unreadAnnouncements.value.length > 0;
|
||||
});
|
||||
|
||||
const getUpdateNotice = async () => {
|
||||
try {
|
||||
const response = await api.get('/announcements');
|
||||
console.log('获取更新信息',response);
|
||||
if (response.data.success && response.data.data.announcements) {
|
||||
announcements.value = response.data.data.announcements;
|
||||
// 设置第一个公告的标题作为默认显示
|
||||
if (announcements.value.length > 0) {
|
||||
updateNotice.value = announcements.value[0].title;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取更新信息失败', error);
|
||||
ElMessage.error('获取更新信息失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 定时刷新积分
|
||||
let refreshInterval;
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
getUserPoints();
|
||||
|
||||
await getUpdateNotice();
|
||||
|
||||
// 每5分钟刷新一次积分(可根据需求调整时间)
|
||||
refreshInterval = setInterval(getUserPoints, 5 * 60 * 1000);
|
||||
|
||||
// 根据是否有未读公告决定是否显示欢迎弹窗
|
||||
setTimeout(() => {
|
||||
if (shouldShowDialog.value) {
|
||||
showWelcomeDialog.value = true;
|
||||
}
|
||||
}, 500); // 延迟500ms显示,让页面先加载完成
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshInterval);
|
||||
});
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
};
|
||||
|
||||
// 标记公告为已读
|
||||
const markAnnouncementAsRead = async (announcementId) => {
|
||||
try {
|
||||
await api.post(`/announcements/${announcementId}/read`);
|
||||
console.log(`公告 ${announcementId} 已标记为已读`);
|
||||
} catch (error) {
|
||||
console.error('标记公告已读失败:', error);
|
||||
ElMessage.error('标记公告已读失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 标记所有未读公告为已读的标志
|
||||
const isMarkingAsRead = ref(false);
|
||||
|
||||
// 标记所有未读公告为已读
|
||||
const markAllAnnouncementsAsRead = async () => {
|
||||
if (isMarkingAsRead.value) return; // 防止重复调用
|
||||
isMarkingAsRead.value = true;
|
||||
|
||||
try {
|
||||
for (const announcement of unreadAnnouncements.value) {
|
||||
await markAnnouncementAsRead(announcement.id);
|
||||
}
|
||||
} finally {
|
||||
isMarkingAsRead.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭欢迎弹窗并标记所有未读公告为已读
|
||||
const closeWelcomeDialog = async () => {
|
||||
// 只有在非系统公告点击时才标记为已读
|
||||
if (!isSystemAnnouncementClick.value) {
|
||||
await markAllAnnouncementsAsRead();
|
||||
}
|
||||
showWelcomeDialog.value = false;
|
||||
isSystemAnnouncementClick.value = false; // 重置标志
|
||||
};
|
||||
|
||||
// 点击已读按钮
|
||||
const handleReadClick = () => {
|
||||
showWelcomeDialog.value = false;
|
||||
};
|
||||
|
||||
// 处理系统公告点击事件
|
||||
const handleSystemAnnouncementClick = () => {
|
||||
// console.log('触发')
|
||||
isSystemAnnouncementClick.value = true;
|
||||
showWelcomeDialog.value = true; // 无条件显示对话框
|
||||
};
|
||||
|
||||
// 处理轮播图点击事件
|
||||
const handleCarouselClick = (item) => {
|
||||
if (item.path && item.path !== '#') {
|
||||
// 使用Vue Router进行页面跳转
|
||||
router.push(item.path);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
modules: [Autoplay, Pagination],
|
||||
userPoints,
|
||||
userName,
|
||||
carouselItems,
|
||||
headerItems,
|
||||
newsItems,
|
||||
showWelcomeDialog,
|
||||
updateNotice,
|
||||
announcements,
|
||||
unreadAnnouncements,
|
||||
shouldShowDialog,
|
||||
formatDate,
|
||||
getUserPoints, // 如果需要外部调用可以暴露
|
||||
closeWelcomeDialog,
|
||||
handleReadClick,
|
||||
handleSystemAnnouncementClick,
|
||||
handleCarouselClick,
|
||||
displayAnnouncements,
|
||||
isSystemAnnouncementClick,
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -193,9 +381,12 @@ export default {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: linear-gradient(to bottom, #72c9ffae, #f3f3f3);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
position: relative;
|
||||
background-image: url('/imgs/mainpage/jingganglan-1.png'), url('/imgs/mainpage/jingganglan-2.png');
|
||||
background-size: 100% 10%, 100% 100%;
|
||||
background-position: center 28%, center 100%;
|
||||
background-repeat: no-repeat, no-repeat;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@@ -204,9 +395,16 @@ export default {
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
max-width: 375px;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* 欢迎文字 */
|
||||
.welcome {
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
@@ -215,7 +413,8 @@ export default {
|
||||
|
||||
/* 轮播图样式 */
|
||||
.carousel {
|
||||
width: 343px;
|
||||
width: 100%;
|
||||
max-width: 343px;
|
||||
height: 148px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
@@ -223,6 +422,16 @@ export default {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
opacity: 1;
|
||||
z-index: 1; /* 设置较低的z-index,让背景图显示在上面 */
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.carousel-slide:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.carousel-img {
|
||||
@@ -233,7 +442,7 @@ export default {
|
||||
|
||||
.carousel-title {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
bottom: 12px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
@@ -246,7 +455,8 @@ export default {
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between; /* 保持元素均匀分布 */
|
||||
width: 343px;
|
||||
width: 100%;
|
||||
max-width: 343px;
|
||||
height: 50px;
|
||||
padding: 0; /* 移除内边距 */
|
||||
background-color: transparent;
|
||||
@@ -286,95 +496,86 @@ export default {
|
||||
|
||||
/* 操作区域样式 */
|
||||
.action-area {
|
||||
width: 343px;
|
||||
width: 100%;
|
||||
max-width: 343px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
.action-grid-horizontal {
|
||||
display: flex;
|
||||
gap: 20px; /* 修改为20px间距 */
|
||||
height: 204px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
.action-main {
|
||||
width: 159px;
|
||||
height: 204px;
|
||||
|
||||
|
||||
.action-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to right, #4facfe 0%, #00f2fe 100%);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 18px;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--box-shadow);
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
text-decoration: none;
|
||||
padding: 63px 47px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-main:hover {
|
||||
transform: scale(1.02);
|
||||
box-shadow: 0 8px 15px rgba(67, 97, 238, 0.3);
|
||||
.action-item:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.action-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px; /* 修改为20px间距 */
|
||||
width: 165px;
|
||||
.action-image1 {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 180px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.action-sub-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--box-shadow);
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
text-decoration: none;
|
||||
opacity: 1;
|
||||
.action-image2 {
|
||||
width: 120%;
|
||||
height: auto;
|
||||
max-width: 180px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
margin-left: -5px;
|
||||
}
|
||||
|
||||
.action-sub-item.top {
|
||||
width: 165px;
|
||||
height: 92px;
|
||||
color: var(--primary-color);
|
||||
.action-image3 {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: 180px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.action-sub-item.bottom {
|
||||
width: 165px;
|
||||
height: 92px;
|
||||
color: var(--success-color);
|
||||
}
|
||||
|
||||
.action-sub-item:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.action-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
.tips {
|
||||
width: 100%;
|
||||
max-width: 343px;
|
||||
margin: 0 auto;
|
||||
padding: 12px 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--primary-color);
|
||||
background-color: rgba(67, 97, 238, 0.1);
|
||||
border: 1px solid var(--primary-color);
|
||||
border-radius: 25px;
|
||||
box-shadow: 0 2px 8px rgba(67, 97, 238, 0.15);
|
||||
color: #22a7ff;
|
||||
}
|
||||
|
||||
/* 热门资讯 */
|
||||
.hot-news {
|
||||
width: 343px;
|
||||
width: 100%;
|
||||
max-width: 343px;
|
||||
padding: 16px;
|
||||
background-color: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--box-shadow);
|
||||
border: 1px solid #e0e0e0;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
margin: 0 auto;
|
||||
opacity: 1;
|
||||
}
|
||||
@@ -412,4 +613,271 @@ export default {
|
||||
:deep(.swiper-pagination-bullet-active) {
|
||||
background: var(--primary-color) !important;
|
||||
}
|
||||
|
||||
|
||||
/* 欢迎弹窗样式 */
|
||||
:deep(.welcome-dialog) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-overlay) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog) {
|
||||
background: transparent !important;
|
||||
margin-top: 5vh !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__header) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__headerbtn) {
|
||||
top: 25px !important;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__body) {
|
||||
background-image: url('/imgs/mainpage/gengxintishi.png');
|
||||
background-size: 100% 100%;
|
||||
background-position: center bottom;
|
||||
background-repeat: no-repeat;
|
||||
background-color: transparent !important;
|
||||
position: relative;
|
||||
/* 调整背景图透明度,值范围0-1,0为完全透明,1为完全不透明 */
|
||||
opacity: 1;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__body::before) {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* 完全透明的遮罩层 */
|
||||
background: transparent;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.welcome-content {
|
||||
text-align: center;
|
||||
padding: 20px 20px 0 20px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: calc(100% - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
animation: bounce 2s infinite;
|
||||
}
|
||||
|
||||
.welcome-content h3 {
|
||||
color: #ff7b00;
|
||||
font-size: 26px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px 0;
|
||||
}
|
||||
|
||||
.welcome-content p {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
margin: 0 0 24px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.welcome-features {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.announcements-container {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding-right: 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--primary-color) #f1f1f1;
|
||||
}
|
||||
|
||||
.announcements-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.announcements-container::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.announcements-container::-webkit-scrollbar-thumb {
|
||||
background: var(--primary-color);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.announcements-container::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--secondary-color);
|
||||
}
|
||||
|
||||
.announcement-item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 12px;
|
||||
border-radius: 8px;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.announcement-item:hover {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.announcement-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.announcement-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--dark-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.announcement-priority {
|
||||
font-size: 10px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.announcement-priority.high {
|
||||
background: #ffebee;
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.announcement-priority.medium {
|
||||
background: #fff3e0;
|
||||
color: #ef6c00;
|
||||
}
|
||||
|
||||
.announcement-priority.low {
|
||||
background: #e8f5e8;
|
||||
color: #2e7d32;
|
||||
}
|
||||
|
||||
.announcement-content {
|
||||
font-size: 13px;
|
||||
color: #555;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.announcement-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.announcement-time,
|
||||
.announcement-author {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.no-announcements {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__footer) {
|
||||
padding: 15px 20px;
|
||||
border-top: none;
|
||||
background: transparent;
|
||||
position: absolute;
|
||||
bottom: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
bottom: 10;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: transparent;
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 20%, 50%, 80%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
40% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
60% {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗响应式样式 */
|
||||
@media (max-width: 480px) {
|
||||
.welcome-content {
|
||||
padding: 30px 15px 0 15px;
|
||||
height: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.welcome-icon {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.welcome-content h3 {
|
||||
font-size: 22px;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
|
||||
.welcome-content p {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.welcome-features {
|
||||
gap: 15px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__footer) {
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
:deep(.welcome-dialog .el-dialog__body) {
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<div class="card-header">
|
||||
<h3>融豆匹配</h3>
|
||||
<div class="toggle-container">
|
||||
<span class="toggle-label">开启大额匹配</span>
|
||||
<span class="toggle-label">开启定量获取</span>
|
||||
<label class="apple-switch">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -33,7 +33,7 @@
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">分配笔数:</span>
|
||||
<span class="value">3笔</span>
|
||||
<span class="value">3-4笔</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">单笔范围:</span>
|
||||
@@ -41,7 +41,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 大额匹配信息 -->
|
||||
<!-- 定量获取信息 -->
|
||||
<div v-if="matchingType === 'large'" class="matching-info">
|
||||
<div class="info-item">
|
||||
<span class="label">购买数量:</span>
|
||||
@@ -49,12 +49,12 @@
|
||||
<el-input
|
||||
v-model="customAmount"
|
||||
type="number"
|
||||
:min="5000"
|
||||
:min="3000"
|
||||
:max="50000"
|
||||
step="100"
|
||||
placeholder="请输入5000-50000之间的金额"
|
||||
placeholder="请输入3000-50000之间的融豆"
|
||||
>
|
||||
<template #prepend><img src="/imgs/profile/融豆.png" alt="融豆" class="bean-image"></template>
|
||||
<template #prepend><img src='/imgs/profile/rongdou.png' alt="融豆" class="bean-image"></template>
|
||||
</el-input>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,10 +62,6 @@
|
||||
<span class="label">分配规则:</span>
|
||||
<span class="value">{{ getLargeMatchingRule() }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">预计笔数:</span>
|
||||
<span class="value">{{ getLargeMatchingCount() }}笔</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -78,14 +74,14 @@
|
||||
|
||||
<!-- 小额匹配提示 -->
|
||||
<div v-if="matchingType === 'small'" class="tips">
|
||||
<p>• 系统将为您匹配3笔订单,总获取5000融豆</p>
|
||||
<p>• 系统将为您匹配3-4笔订单,总获取5000融豆</p>
|
||||
<p>• 优先匹配拥有融豆并自愿出售的用户</p>
|
||||
<!-- <p>• 每笔金额随机分配,确保货款循环</p> -->
|
||||
<!-- <p>• 每笔融豆随机分配,确保货款循环</p> -->
|
||||
</div>
|
||||
|
||||
<!-- 大额匹配提示 -->
|
||||
<!-- 定量获取提示 -->
|
||||
<div v-if="matchingType === 'large'" class="tips">
|
||||
<p>• 融豆数量:5000-50000</p>
|
||||
<p>• 融豆数量:3000-50000</p>
|
||||
<p>• 15000以下:分成多笔随机融豆</p>
|
||||
<p>• 15000以上:随机分拆,每笔100-10000</p>
|
||||
<p>• 优先匹配拥有融豆并自愿出售的用户</p>
|
||||
@@ -111,8 +107,8 @@
|
||||
</div>
|
||||
<div class="allocation-details">
|
||||
<p>转账给: <strong>{{ allocation.to_user_real_name }}</strong></p>
|
||||
<p>金额: <strong class="amount">¥{{ allocation.amount }}</strong></p>
|
||||
<p>总金额: ¥{{ allocation.total_amount }}</p>
|
||||
<p>融豆: <strong class="amount">¥{{ allocation.amount }}</strong></p>
|
||||
<p>总融豆: ¥{{ allocation.total_amount }}</p>
|
||||
<p class="deadline-info">
|
||||
转账时效:
|
||||
<span :class="['time-left', allocation.time_status]">
|
||||
@@ -156,7 +152,7 @@
|
||||
<span :class="['status', order.status]">{{ getStatusText(order.status) }}</span>
|
||||
</div>
|
||||
<div class="order-info">
|
||||
<p>金额: ¥{{ order.amount }}</p>
|
||||
<p>融豆: ¥{{ order.amount }}</p>
|
||||
<p>发起人: {{ order.initiator_real_name }}</p>
|
||||
<p v-if="!order.is_system_reverse">轮次: {{ order.cycle_count + 1 }}/{{ order.max_cycles }}</p>
|
||||
<p v-if="order.is_system_reverse" class="system-note">系统自动发起,向负余额用户补充货款</p>
|
||||
@@ -180,7 +176,7 @@
|
||||
<div class="modal-body" v-if="selectedOrder">
|
||||
<div class="order-summary">
|
||||
<p><strong>状态:</strong> {{ getStatusText(selectedOrder.order.status) }}</p>
|
||||
<p><strong>金额:</strong> ¥{{ selectedOrder.order.amount }}</p>
|
||||
<p><strong>融豆:</strong> ¥{{ selectedOrder.order.amount }}</p>
|
||||
<p><strong>发起人:</strong> {{ selectedOrder.order.initiator_real_name }}</p>
|
||||
<p><strong>轮次:</strong> {{ selectedOrder.order.cycle_count + 1 }}/{{ selectedOrder.order.max_cycles }}</p>
|
||||
</div>
|
||||
@@ -240,7 +236,7 @@
|
||||
<div class="transfer-info">
|
||||
<h4>转账信息</h4>
|
||||
<p><strong>收款人:</strong> {{ transferDialog.toUser.to_user_real_name }}</p>
|
||||
<p><strong>转账金额:</strong> ¥{{ transferDialog.amount }}</p>
|
||||
<p><strong>转账融豆:</strong> ¥{{ transferDialog.amount }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 收款码展示 -->
|
||||
@@ -306,7 +302,7 @@
|
||||
<div class="transfer-form">
|
||||
<h4>转账确认</h4>
|
||||
<el-form label-width="100px">
|
||||
<el-form-item label="转账金额">
|
||||
<el-form-item label="转账融豆">
|
||||
<el-input
|
||||
v-model="transferDialog.actualAmount"
|
||||
readonly
|
||||
@@ -349,12 +345,12 @@
|
||||
:src="getImageUrl(transferDialog.voucher)"
|
||||
:preview-src-list="[getImageUrl(transferDialog.voucher)]"
|
||||
alt="转账凭证"
|
||||
fit="cover"
|
||||
fit="contain"
|
||||
:lazy="true"
|
||||
>
|
||||
<template #placeholder>
|
||||
<div class="image-slot">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<el-icon class="image-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -393,15 +389,15 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 大额匹配确认弹窗 -->
|
||||
<!-- 定量获取确认弹窗 -->
|
||||
<el-dialog
|
||||
v-model="showLargeMatchingConfirm"
|
||||
title="开启大额匹配"
|
||||
title="开启定量获取"
|
||||
width="90%"
|
||||
:style="{ maxWidth: '500px' }"
|
||||
>
|
||||
<div class="confirm-dialog-content">
|
||||
<p>确认要开启大额匹配吗?</p>
|
||||
<p>确认要开启定量获取吗?</p>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
@@ -439,7 +435,7 @@ export default {
|
||||
showOrderDetail: false,
|
||||
selectedOrder: null,
|
||||
matchingType: 'small', // 匹配类型:small(小额) 或 large(大额)
|
||||
customAmount: '', // 大额匹配自定义金额
|
||||
customAmount: '', // 定量获取自定义融豆
|
||||
transferDialog: {
|
||||
visible: false,
|
||||
allocationId: null,
|
||||
@@ -462,13 +458,26 @@ export default {
|
||||
tempMatchingType: 'small' // 临时存储切换前的类型
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
// 处理定量获取参数
|
||||
if (this.$route.query.quantitative === 'true') {
|
||||
this.matchingType = 'large';
|
||||
this.tempMatchingType = 'large'; // 同步临时状态
|
||||
}
|
||||
|
||||
// 在处理完所有参数后清除query参数
|
||||
if (this.$route.query.quantitative) {
|
||||
this.$router.replace({ ...this.$route, query: {} });
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadData()
|
||||
},
|
||||
methods: {
|
||||
handleMatchingTypeChange() {
|
||||
if (this.tempMatchingType === 'large') {
|
||||
// 如果要切换到大额匹配,显示确认对话框
|
||||
// 如果要切换到定量获取,显示确认对话框
|
||||
this.showLargeMatchingConfirm = true
|
||||
} else {
|
||||
// 直接切换到小额匹配
|
||||
@@ -479,7 +488,7 @@ export default {
|
||||
confirmLargeMatching() {
|
||||
this.matchingType = 'large'
|
||||
this.showLargeMatchingConfirm = false
|
||||
this.$message.success('已开启大额匹配模式')
|
||||
this.$message.success('已开启定量获取模式')
|
||||
},
|
||||
|
||||
cancelLargeMatching() {
|
||||
@@ -537,15 +546,13 @@ export default {
|
||||
let confirmMessage = ''
|
||||
|
||||
if (this.matchingType === 'small') {
|
||||
confirmMessage = '确定要开始小额匹配吗?\n\n匹配成功后将生成3笔转账分配。'
|
||||
confirmMessage = '确定要开始小额匹配吗?\n\n匹配成功后将生成3-4笔转账分配。'
|
||||
} else {
|
||||
if (!this.isValidCustomAmount) {
|
||||
this.$message.error('请输入有效的匹配金额(5000-50000元)')
|
||||
this.$message.error('请输入有效的匹配融豆(3000-50000元)')
|
||||
return
|
||||
}
|
||||
const amount = parseFloat(this.customAmount)
|
||||
const count = this.getLargeMatchingCount()
|
||||
confirmMessage = `确定要开始大额匹配吗?\n\n匹配金额:${amount}元\n将生成${count}笔转账分配`
|
||||
}
|
||||
|
||||
// 二次确认对话框
|
||||
@@ -563,7 +570,7 @@ export default {
|
||||
matchingType: this.matchingType
|
||||
}
|
||||
|
||||
// 如果是大额匹配,添加自定义金额
|
||||
// 如果是定量获取,添加自定义融豆
|
||||
if (this.matchingType === 'large') {
|
||||
requestData.customAmount = parseFloat(this.customAmount)
|
||||
}
|
||||
@@ -572,7 +579,7 @@ export default {
|
||||
|
||||
const successMessage = this.matchingType === 'small'
|
||||
? '小额匹配成功!已为您生成3笔转账分配'
|
||||
: `大额匹配成功!已为您生成${this.getLargeMatchingCount()}笔转账分配`
|
||||
: `定量获取成功!已为您生成笔转账分配`
|
||||
|
||||
this.$message.success(successMessage)
|
||||
await this.loadData()
|
||||
@@ -595,8 +602,6 @@ export default {
|
||||
}).then(() => {
|
||||
this.$router.push('/myprofile')
|
||||
}).catch(() => {})
|
||||
} else {
|
||||
this.$message.error(errorMessage)
|
||||
}
|
||||
} finally {
|
||||
this.creating = false
|
||||
@@ -608,7 +613,7 @@ export default {
|
||||
/**
|
||||
* 确认分配并创建转账记录
|
||||
* @param {number} allocationId - 分配ID
|
||||
* @param {number} expectedAmount - 预期转账金额
|
||||
* @param {number} expectedAmount - 预期转账融豆
|
||||
*/
|
||||
async confirmAllocation(allocationId, expectedAmount) {
|
||||
try {
|
||||
@@ -709,9 +714,9 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 格式化金额显示,确保数字安全
|
||||
* @param {number|string|null|undefined} amount - 金额值
|
||||
* @returns {string} 格式化后的金额字符串
|
||||
* 格式化融豆显示,确保数字安全
|
||||
* @param {number|string|null|undefined} amount - 融豆值
|
||||
* @returns {string} 格式化后的融豆字符串
|
||||
*/
|
||||
formatAmount(amount) {
|
||||
const num = parseFloat(amount)
|
||||
@@ -719,41 +724,28 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取大额匹配的分配规则描述
|
||||
* 获取定量获取的分配规则描述
|
||||
* @returns {string} 规则描述
|
||||
*/
|
||||
getLargeMatchingRule() {
|
||||
const amount = parseFloat(this.customAmount) || 0
|
||||
if (amount <= 0) {
|
||||
return '请输入金额'
|
||||
} else if (amount < 5000) {
|
||||
return '金额不能少于5000元'
|
||||
return '请输入融豆'
|
||||
} else if (amount < 3000) {
|
||||
return '融豆不能少于3000元'
|
||||
} else if (amount > 50000) {
|
||||
return '金额不能超过50000元'
|
||||
return '融豆不能超过50000元'
|
||||
} else if (amount <= 15000) {
|
||||
return '分成多笔随机金额'
|
||||
return '分成多笔随机融豆'
|
||||
} else {
|
||||
return '随机分拆,每笔100-10000元'
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取大额匹配的预计笔数
|
||||
* 获取定量获取的预计笔数
|
||||
* @returns {string} 预计笔数描述
|
||||
*/
|
||||
getLargeMatchingCount() {
|
||||
const amount = parseFloat(this.customAmount) || 0
|
||||
if (amount <= 0 || amount < 5000 || amount > 50000) {
|
||||
return '0'
|
||||
} else if (amount <= 15000) {
|
||||
return '3'
|
||||
} else {
|
||||
// 15000以上随机分拆,估算笔数范围
|
||||
const minCount = Math.ceil(amount / 10000) // 按最大单笔10000计算最少笔数
|
||||
const maxCount = Math.floor(amount / 100) // 按最小单笔100计算最多笔数
|
||||
return `${minCount}-${Math.min(maxCount, 10)}` // 限制最大显示笔数为10
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭转账弹窗
|
||||
@@ -903,12 +895,18 @@ export default {
|
||||
|
||||
const actualAmount = parseFloat(this.transferDialog.actualAmount)
|
||||
|
||||
// 处理voucher,去掉开头的'https://minio.zrbjr.com'
|
||||
let processedVoucher = this.transferDialog.voucher
|
||||
if (processedVoucher.startsWith('https://minio.zrbjr.com')) {
|
||||
processedVoucher = processedVoucher.replace('https://minio.zrbjr.com', '')
|
||||
}
|
||||
|
||||
this.processing = true
|
||||
try {
|
||||
await api.post(`/matching/confirm-allocation/${this.transferDialog.allocationId}`, {
|
||||
transferAmount: actualAmount,
|
||||
description: this.transferDialog.description,
|
||||
voucher: this.transferDialog.voucher
|
||||
voucher: processedVoucher
|
||||
})
|
||||
this.$message.success('转账凭证已提交,转账记录已创建')
|
||||
this.closeTransferDialog()
|
||||
@@ -925,12 +923,12 @@ export default {
|
||||
|
||||
computed: {
|
||||
/**
|
||||
* 验证自定义金额是否有效
|
||||
* @returns {boolean} 金额是否有效
|
||||
* 验证自定义融豆是否有效
|
||||
* @returns {boolean} 融豆是否有效
|
||||
*/
|
||||
isValidCustomAmount() {
|
||||
const amount = parseFloat(this.customAmount)
|
||||
return !isNaN(amount) && amount >= 5000 && amount <= 50000
|
||||
return !isNaN(amount) && amount >= 3000 && amount <= 50000
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -946,6 +944,12 @@ export default {
|
||||
uploadHeaders() {
|
||||
return getUploadConfig().headers
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
matchingType(newVal, oldVal) {
|
||||
console.log('更改匹配模式', oldVal, 'to', newVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -954,7 +958,7 @@ export default {
|
||||
.matching-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
padding: 20px 10px;
|
||||
background: linear-gradient(to bottom, #72c9ffae, #f3f3f3);
|
||||
}
|
||||
|
||||
@@ -1449,7 +1453,7 @@ export default {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* 自定义金额输入框样式 */
|
||||
/* 自定义融豆输入框样式 */
|
||||
.custom-amount-input {
|
||||
flex: 1;
|
||||
margin-left: 10px;
|
||||
@@ -1841,7 +1845,7 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.is-loading {
|
||||
.image-loading {
|
||||
animation: rotating 2s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,6 @@
|
||||
<h2>用户登录</h2>
|
||||
<p>欢迎来到炬融圈</p>
|
||||
</div>
|
||||
|
||||
<div class="image">
|
||||
<img src="/imgs/login.png" alt="炬融圈">
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
@@ -21,7 +17,7 @@
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="loginForm.username"
|
||||
placeholder="请输入用户名或邮箱"
|
||||
placeholder="请输入手机号"
|
||||
size="large"
|
||||
:prefix-icon="User"
|
||||
clearable
|
||||
@@ -159,7 +155,7 @@ const rememberMe = ref(false)
|
||||
// 表单验证规则
|
||||
const loginRules = {
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名或邮箱', trigger: 'blur' },
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ min: 3, message: '用户名至少3个字符', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
@@ -181,7 +177,7 @@ const handleLogin = async () => {
|
||||
const valid = await loginFormRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// // 验证验证码
|
||||
// 验证验证码
|
||||
// const captchaValid = await captchaRef.value.verifyCaptcha(loginForm.captcha)
|
||||
// if (!captchaValid) {
|
||||
// loginForm.captcha = ''
|
||||
@@ -199,12 +195,40 @@ const handleLogin = async () => {
|
||||
captchaText: captchaInfo.captchaText
|
||||
}
|
||||
|
||||
const result = await userStore.login(loginData)
|
||||
|
||||
if (result.success) {
|
||||
// 登录成功,跳转到目标页面或转账管理
|
||||
const redirectPath = route.query.redirect || '/mainpage'
|
||||
router.push(redirectPath)
|
||||
console.log('开始调用登录接口');
|
||||
try {
|
||||
const result = await userStore.login(loginData)
|
||||
console.log('登录接口调用完成');
|
||||
console.log(result,'result');
|
||||
|
||||
if (result.success) {
|
||||
// 登录成功,跳转到目标页面或转账管理
|
||||
const redirectPath = route.query.redirect || '/mainpage'
|
||||
router.push(redirectPath)
|
||||
} else if (result.needPayment) {
|
||||
// 用户需要支付激活,直接跳转到支付页面
|
||||
ElMessage.info('账户尚未激活,正在跳转到支付页面...')
|
||||
router.push({
|
||||
path: '/payment',
|
||||
query: {
|
||||
userId: result.userId,
|
||||
from: 'login'
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (loginError) {
|
||||
console.error('登录调用异常:', loginError)
|
||||
// 如果是支付相关的错误,也要处理
|
||||
if (loginError.needPayment) {
|
||||
ElMessage.info('账户尚未激活,正在跳转到支付页面...')
|
||||
router.push({
|
||||
path: '/payment',
|
||||
query: {
|
||||
userId: loginError.userId,
|
||||
from: 'login'
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('登录失败:', error)
|
||||
@@ -216,21 +240,6 @@ const handleLogin = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 快速登录(演示用)
|
||||
const quickLogin = async (type) => {
|
||||
if (type === 'admin') {
|
||||
loginForm.username = 'admin'
|
||||
loginForm.password = 'admin123'
|
||||
} else {
|
||||
loginForm.username = 'user'
|
||||
loginForm.password = 'user123'
|
||||
}
|
||||
|
||||
// 清空验证码,让用户手动输入
|
||||
loginForm.captcha = ''
|
||||
ElMessage.info('请输入验证码后登录')
|
||||
}
|
||||
|
||||
// 忘记密码
|
||||
const showForgotPassword = () => {
|
||||
ElMessageBox.alert(
|
||||
@@ -333,33 +342,6 @@ const handleRememberMe = () => {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 图片容器样式 */
|
||||
.image {
|
||||
width: 375px; /* 固定宽度 */
|
||||
height: 287px; /* 固定高度 */
|
||||
margin: 0 auto 20px; /* 水平居中,底部留出间距 */
|
||||
overflow: hidden; /* 隐藏超出容器的部分 */
|
||||
border-radius: 8px; /* 可选:添加圆角增强视觉效果 */
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); /* 可选:添加轻微阴影 */
|
||||
}
|
||||
|
||||
/* 图片填充样式 */
|
||||
.image img {
|
||||
width: 100%; /* 宽度充满容器 */
|
||||
height: 100%; /* 高度充满容器 */
|
||||
object-fit: contain;
|
||||
display: block; /* 去除图片底部默认空白 */
|
||||
}
|
||||
|
||||
/* 响应式适配(小屏设备自动缩放) */
|
||||
@media (max-width: 375px) {
|
||||
.image {
|
||||
width: 100%; /* 在小于375px的屏幕上宽度自适应 */
|
||||
height: auto; /* 高度按比例自动计算 */
|
||||
aspect-ratio: 375 / 287; /* 保持原比例 */
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||