代理版
@ -9,6 +9,7 @@
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vant/area-data": "^2.0.0",
|
||||
"@vueuse/core": "^11.3.0",
|
||||
"axios": "^1.7.7",
|
||||
"crypto-js": "^4.2.0",
|
||||
@ -16,6 +17,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.2.6",
|
||||
"qrcode": "^1.5.4",
|
||||
"vant": "^4.9.9",
|
||||
"vue": "^3.5.12",
|
||||
"vue-echarts": "^7.0.3",
|
||||
|
168
pnpm-lock.yaml
@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@vant/area-data':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
'@vueuse/core':
|
||||
specifier: ^11.3.0
|
||||
version: 11.3.0(vue@3.5.13)
|
||||
@ -29,6 +32,9 @@ importers:
|
||||
pinia:
|
||||
specifier: ^2.2.6
|
||||
version: 2.2.6(vue@3.5.13)
|
||||
qrcode:
|
||||
specifier: ^1.5.4
|
||||
version: 1.5.4
|
||||
vant:
|
||||
specifier: ^4.9.9
|
||||
version: 4.9.9(vue@3.5.13)
|
||||
@ -521,6 +527,9 @@ packages:
|
||||
'@types/web-bluetooth@0.0.20':
|
||||
resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==}
|
||||
|
||||
'@vant/area-data@2.0.0':
|
||||
resolution: {integrity: sha512-zgP4AA8z09S9QTNgVCCHo9cHjcybrv22RJDYPjuCkecn4SB98T5EoPQh2TwqbQXmUhbaOGgiZGy3OUaUxnY7qg==}
|
||||
|
||||
'@vant/auto-import-resolver@1.2.1':
|
||||
resolution: {integrity: sha512-czGWW4UolNITkF3qQSQlpHDHAsI3/GHVKbRMmEEpry7NWdnU4p5a5jBi0VApbaLa5g80Hy10XVs3IB+UozoSUw==}
|
||||
|
||||
@ -691,6 +700,10 @@ packages:
|
||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
camelcase@5.3.1:
|
||||
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
caniuse-lite@1.0.30001680:
|
||||
resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==}
|
||||
|
||||
@ -698,6 +711,9 @@ packages:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
|
||||
cliui@6.0.0:
|
||||
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||
|
||||
color-convert@2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
@ -750,6 +766,10 @@ packages:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
decamelize@1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
default-browser-id@5.0.0:
|
||||
resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==}
|
||||
engines: {node: '>=18'}
|
||||
@ -769,6 +789,9 @@ packages:
|
||||
didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
||||
dijkstrajs@1.0.3:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
|
||||
dlv@1.1.3:
|
||||
resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==}
|
||||
|
||||
@ -828,6 +851,10 @@ packages:
|
||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
find-up@4.1.0:
|
||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
follow-redirects@1.15.9:
|
||||
resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==}
|
||||
engines: {node: '>=4.0'}
|
||||
@ -864,6 +891,10 @@ packages:
|
||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
||||
get-stream@8.0.1:
|
||||
resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==}
|
||||
engines: {node: '>=16'}
|
||||
@ -1002,6 +1033,10 @@ packages:
|
||||
resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
locate-path@5.0.0:
|
||||
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
|
||||
@ -1103,9 +1138,25 @@ packages:
|
||||
resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
p-limit@2.3.0:
|
||||
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
p-locate@4.1.0:
|
||||
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
p-try@2.2.0:
|
||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
path-exists@4.0.0:
|
||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1161,6 +1212,10 @@ packages:
|
||||
pkg-types@1.2.1:
|
||||
resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==}
|
||||
|
||||
pngjs@5.0.0:
|
||||
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
postcss-import@15.1.0:
|
||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@ -1205,6 +1260,11 @@ packages:
|
||||
proxy-from-env@1.1.0:
|
||||
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||
|
||||
qrcode@1.5.4:
|
||||
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
hasBin: true
|
||||
|
||||
queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
|
||||
@ -1215,6 +1275,13 @@ packages:
|
||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
||||
engines: {node: '>=8.10.0'}
|
||||
|
||||
require-directory@2.1.1:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
require-main-filename@2.0.0:
|
||||
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||
|
||||
resolve@1.22.8:
|
||||
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
|
||||
hasBin: true
|
||||
@ -1373,6 +1440,9 @@ packages:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
|
||||
set-blocking@2.0.0:
|
||||
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||
engines: {node: '>=8'}
|
||||
@ -1638,11 +1708,18 @@ packages:
|
||||
webpack-virtual-modules@0.6.2:
|
||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||
|
||||
which-module@2.0.1:
|
||||
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||
|
||||
which@2.0.2:
|
||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||
engines: {node: '>= 8'}
|
||||
hasBin: true
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||
engines: {node: '>=10'}
|
||||
@ -1651,6 +1728,9 @@ packages:
|
||||
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
y18n@4.0.3:
|
||||
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||
|
||||
yallist@3.1.1:
|
||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||
|
||||
@ -1659,6 +1739,14 @@ packages:
|
||||
engines: {node: '>= 14'}
|
||||
hasBin: true
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
yargs@15.4.1:
|
||||
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
zrender@5.6.0:
|
||||
resolution: {integrity: sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==}
|
||||
|
||||
@ -2042,6 +2130,8 @@ snapshots:
|
||||
|
||||
'@types/web-bluetooth@0.0.20': {}
|
||||
|
||||
'@vant/area-data@2.0.0': {}
|
||||
|
||||
'@vant/auto-import-resolver@1.2.1': {}
|
||||
|
||||
'@vant/popperjs@1.3.0': {}
|
||||
@ -2266,6 +2356,8 @@ snapshots:
|
||||
|
||||
camelcase-css@2.0.1: {}
|
||||
|
||||
camelcase@5.3.1: {}
|
||||
|
||||
caniuse-lite@1.0.30001680: {}
|
||||
|
||||
chokidar@3.6.0:
|
||||
@ -2280,6 +2372,12 @@ snapshots:
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
cliui@6.0.0:
|
||||
dependencies:
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
wrap-ansi: 6.2.0
|
||||
|
||||
color-convert@2.0.1:
|
||||
dependencies:
|
||||
color-name: 1.1.4
|
||||
@ -2318,6 +2416,8 @@ snapshots:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
decamelize@1.2.0: {}
|
||||
|
||||
default-browser-id@5.0.0: {}
|
||||
|
||||
default-browser@5.2.1:
|
||||
@ -2331,6 +2431,8 @@ snapshots:
|
||||
|
||||
didyoumean@1.2.2: {}
|
||||
|
||||
dijkstrajs@1.0.3: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
eastasianwidth@0.2.0: {}
|
||||
@ -2414,6 +2516,11 @@ snapshots:
|
||||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
find-up@4.1.0:
|
||||
dependencies:
|
||||
locate-path: 5.0.0
|
||||
path-exists: 4.0.0
|
||||
|
||||
follow-redirects@1.15.9: {}
|
||||
|
||||
foreground-child@3.3.0:
|
||||
@ -2442,6 +2549,8 @@ snapshots:
|
||||
|
||||
gensync@1.0.0-beta.2: {}
|
||||
|
||||
get-caller-file@2.0.5: {}
|
||||
|
||||
get-stream@8.0.1: {}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
@ -2548,6 +2657,10 @@ snapshots:
|
||||
mlly: 1.7.3
|
||||
pkg-types: 1.2.1
|
||||
|
||||
locate-path@5.0.0:
|
||||
dependencies:
|
||||
p-locate: 4.1.0
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
@ -2635,8 +2748,20 @@ snapshots:
|
||||
is-inside-container: 1.0.0
|
||||
is-wsl: 3.1.0
|
||||
|
||||
p-limit@2.3.0:
|
||||
dependencies:
|
||||
p-try: 2.2.0
|
||||
|
||||
p-locate@4.1.0:
|
||||
dependencies:
|
||||
p-limit: 2.3.0
|
||||
|
||||
p-try@2.2.0: {}
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
path-exists@4.0.0: {}
|
||||
|
||||
path-key@3.1.1: {}
|
||||
|
||||
path-key@4.0.0: {}
|
||||
@ -2674,6 +2799,8 @@ snapshots:
|
||||
mlly: 1.7.3
|
||||
pathe: 1.1.2
|
||||
|
||||
pngjs@5.0.0: {}
|
||||
|
||||
postcss-import@15.1.0(postcss@8.4.49):
|
||||
dependencies:
|
||||
postcss: 8.4.49
|
||||
@ -2713,6 +2840,12 @@ snapshots:
|
||||
|
||||
proxy-from-env@1.1.0: {}
|
||||
|
||||
qrcode@1.5.4:
|
||||
dependencies:
|
||||
dijkstrajs: 1.0.3
|
||||
pngjs: 5.0.0
|
||||
yargs: 15.4.1
|
||||
|
||||
queue-microtask@1.2.3: {}
|
||||
|
||||
read-cache@1.0.0:
|
||||
@ -2723,6 +2856,10 @@ snapshots:
|
||||
dependencies:
|
||||
picomatch: 2.3.1
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
require-main-filename@2.0.0: {}
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.15.1
|
||||
@ -2863,6 +3000,8 @@ snapshots:
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
set-blocking@2.0.0: {}
|
||||
|
||||
shebang-command@2.0.0:
|
||||
dependencies:
|
||||
shebang-regex: 3.0.0
|
||||
@ -3153,10 +3292,18 @@ snapshots:
|
||||
|
||||
webpack-virtual-modules@0.6.2: {}
|
||||
|
||||
which-module@2.0.1: {}
|
||||
|
||||
which@2.0.2:
|
||||
dependencies:
|
||||
isexe: 2.0.0
|
||||
|
||||
wrap-ansi@6.2.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
wrap-ansi@7.0.0:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
@ -3169,10 +3316,31 @@ snapshots:
|
||||
string-width: 5.1.2
|
||||
strip-ansi: 7.1.0
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
yallist@3.1.1: {}
|
||||
|
||||
yaml@2.6.1: {}
|
||||
|
||||
yargs-parser@18.1.3:
|
||||
dependencies:
|
||||
camelcase: 5.3.1
|
||||
decamelize: 1.2.0
|
||||
|
||||
yargs@15.4.1:
|
||||
dependencies:
|
||||
cliui: 6.0.0
|
||||
decamelize: 1.2.0
|
||||
find-up: 4.1.0
|
||||
get-caller-file: 2.0.5
|
||||
require-directory: 2.1.1
|
||||
require-main-filename: 2.0.0
|
||||
set-blocking: 2.0.0
|
||||
string-width: 4.2.3
|
||||
which-module: 2.0.1
|
||||
y18n: 4.0.3
|
||||
yargs-parser: 18.1.3
|
||||
|
||||
zrender@5.6.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
|
17
src/App.vue
@ -1,10 +1,20 @@
|
||||
<script setup>
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
const { isWeChat } = useEnv()
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
onMounted(() => {
|
||||
onBeforeMount(() => {
|
||||
RefreshToken()
|
||||
const token = localStorage.getItem("token")
|
||||
if (token) {
|
||||
agentStore.fetchAgentStatus()
|
||||
userStore.fetchUserInfo()
|
||||
}
|
||||
})
|
||||
|
||||
const RefreshToken = async () => {
|
||||
const token = localStorage.getItem("token")
|
||||
const refreshAfter = localStorage.getItem("refreshAfter")
|
||||
@ -20,6 +30,7 @@ const RefreshToken = async () => {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 如果没有 token,直接返回
|
||||
if (!token) {
|
||||
if (isWeChat.value) {
|
||||
@ -35,9 +46,9 @@ const RefreshToken = async () => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 如果没有 refreshAfter 或者时间超过 refreshAfter,执行刷新 token 的请求
|
||||
refreshToken()
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -103,6 +114,8 @@ const h5WeixinGetCode = () => {
|
||||
// 跳转到授权URL
|
||||
window.location.href = authUrl;
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
BIN
src/assets/images/head_shot.webp
Normal file
After Width: | Height: | Size: 2.1 KiB |
1
src/assets/images/icon_bg.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1740754594473" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="36608" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M102.4 256a102.4 102.4 0 0 1 102.4-102.4h340.2752a102.4 102.4 0 0 1 95.0784 64.3584L665.6 281.6c3.3792 8.3968 5.4272 17.0496 6.2464 25.6H819.2a51.2 51.2 0 0 1 51.2 51.2v512a51.2 51.2 0 0 1-51.2 51.2H153.6a51.2 51.2 0 0 1-51.2-51.2V256z" fill="#2357DF" opacity=".5" p-id="36609"></path><path d="M238.592 409.6h681.8304a51.2 51.2 0 0 1 50.176 61.44l-83.8656 409.6a51.2 51.2 0 0 1-50.176 40.96H154.7776a51.2 51.2 0 0 1-50.176-61.44l83.8656-409.6a51.2 51.2 0 0 1 50.176-40.96z" fill="#2357DF" p-id="36610"></path><path d="M358.4 640m25.6 0l307.2 0q25.6 0 25.6 25.6l0 0q0 25.6-25.6 25.6l-307.2 0q-25.6 0-25.6-25.6l0 0q0-25.6 25.6-25.6Z" fill="#FFFFFF" p-id="36611"></path></svg>
|
After Width: | Height: | Size: 1007 B |
1
src/assets/images/icon_bz.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1740754612192" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="39491" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M887.456923 331.598922C867.952667 146.308492 707.042557 2.464607 516.876064 0.026575 307.205315-2.411457 134.105045 163.374716 134.105045 368.169401c0 134.091758 73.140959 253.555324 190.166494 319.382188l7.314096 75.578991c2.438032 14.628192 14.628192 24.38032 29.256383 24.380319h299.877932c14.628192 0 26.818352-12.19016 29.256383-24.380319l7.314096-75.578991c131.653726-70.702927 204.794685-207.232717 190.166494-355.952667zM321.833507 360.855305c0 17.066224-14.628192 29.256384-29.256384 29.256384s-29.256384-12.19016-29.256383-29.256384c0-9.752128 0-17.066224 2.438031-26.818351 2.438032-17.066224 17.066224-26.818352 34.132448-26.818352 17.066224 2.438032 29.256384 17.066224 26.818352 31.694416-2.438032 9.752128-4.876064 14.628192-4.876064 21.942287z m190.166493-185.290429c-70.702927 0-134.091758 36.570479-165.786174 97.521279-4.876064 9.752128-14.628192 14.628192-26.818351 14.628191-4.876064 0-9.752128 0-14.628192-2.438032-14.628192-7.314096-19.504256-24.38032-12.19016-39.008511 43.884575-78.017023 126.777662-126.777662 219.422877-126.777662 17.066224 0 29.256384 12.19016 29.256384 29.256383s-12.19016 26.818352-29.256384 26.818352zM343.775794 833.833507c-2.438032 9.752128-2.438032 17.066224-2.438032 26.818351 0 90.207183 75.578991 163.348142 168.224206 163.348142 92.645215 0 168.224206-73.140959 168.224206-163.348142 0-9.752128 0-19.504256-2.438032-26.818351H343.775794z" fill="#F5B53A" p-id="39492"></path></svg>
|
After Width: | Height: | Size: 1.7 KiB |
BIN
src/assets/images/invitation.png
Normal file
After Width: | Height: | Size: 144 KiB |
BIN
src/assets/images/invitation_agent_apply.png
Normal file
After Width: | Height: | Size: 369 KiB |
BIN
src/assets/images/logo.jpg
Normal file
After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 36 KiB |
BIN
src/assets/images/promote_bg.png
Normal file
After Width: | Height: | Size: 170 KiB |
Before Width: | Height: | Size: 159 KiB |
BIN
src/assets/images/tg_qrcode_1.png
Normal file
After Width: | Height: | Size: 846 KiB |
BIN
src/assets/images/vip_banner.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
src/assets/images/vip_bg.png
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
src/assets/images/yq_qrcode_1.png
Normal file
After Width: | Height: | Size: 585 KiB |
3
src/auto-imports.d.ts
vendored
@ -114,6 +114,8 @@ declare global {
|
||||
const unrefElement: typeof import('@vueuse/core')['unrefElement']
|
||||
const until: typeof import('@vueuse/core')['until']
|
||||
const useActiveElement: typeof import('@vueuse/core')['useActiveElement']
|
||||
const useAgent: typeof import('./composables/useAgent.js')['useAgent']
|
||||
const useAgentStore: typeof import('./stores/agentStore.js')['useAgentStore']
|
||||
const useAnimate: typeof import('@vueuse/core')['useAnimate']
|
||||
const useApiFetch: typeof import('./composables/useApiFetch.js')['default']
|
||||
const useArrayDifference: typeof import('@vueuse/core')['useArrayDifference']
|
||||
@ -271,6 +273,7 @@ declare global {
|
||||
const useUni: typeof import('./composables/useUni.js')['useUni']
|
||||
const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams']
|
||||
const useUserMedia: typeof import('@vueuse/core')['useUserMedia']
|
||||
const useUserStore: typeof import('./stores/userStore.js')['useUserStore']
|
||||
const useVModel: typeof import('@vueuse/core')['useVModel']
|
||||
const useVModels: typeof import('@vueuse/core')['useVModels']
|
||||
const useVibrate: typeof import('@vueuse/core')['useVibrate']
|
||||
|
185
src/components/AgentApplicationForm.vue
Normal file
@ -0,0 +1,185 @@
|
||||
<template>
|
||||
<van-popup v-model:show="show" destroy-on-close round position="bottom">
|
||||
<div class=" h-8 bg-blue-200/10 flex items-center justify-center font-semibold">成为代理</div>
|
||||
<div v-if="ancestor" class="text-center text-xs my-2 text-gray-500">{{ maskName(ancestor) }}邀您成为天远数据代理方</div>
|
||||
<div class="p-4">
|
||||
<van-field label-width="56" v-model="form.region" is-link readonly label="地区" placeholder="请选择地区"
|
||||
@click="showCascader = true" />
|
||||
<van-popup v-model:show="showCascader" round position="bottom">
|
||||
<van-cascader v-model="cascaderValue" title="请选择所在地区" :options="options" @close="showCascader = false"
|
||||
@finish="onFinish" />
|
||||
</van-popup>
|
||||
<van-field label-width="56" v-model="form.wechat_id" label="微信号" name="wechat_id" placeholder="请输入微信号" />
|
||||
<van-field label-width="56" v-model="form.mobile" label="手机号" name="mobile" placeholder="请输入手机号" />
|
||||
|
||||
<!-- 获取验证码按钮 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<van-field label-width="56" v-model="form.code" label="验证码" name="code" placeholder="请输入验证码" />
|
||||
<button class="px-2 py-1 text-sm font-bold flex-shrink-0 rounded-lg transition duration-300"
|
||||
:class="isCountingDown || !isPhoneNumberValid ? 'cursor-not-allowed bg-gray-300 text-gray-500' : 'bg-blue-500 text-white hover:bg-blue-600'"
|
||||
@click="getSmsCode">
|
||||
{{ isCountingDown ? `${countdown}s重新获取` : '获取验证码' }}
|
||||
</button>
|
||||
</div>
|
||||
<!-- 同意条款的复选框 -->
|
||||
<div class="p-4">
|
||||
<div class="flex items-start">
|
||||
<van-checkbox v-model="isAgreed" name="agree" icon-size="16px" class=" flex-shrink-0 mr-2">
|
||||
</van-checkbox>
|
||||
<div class="text-xs text-gray-400 leading-tight">
|
||||
我已阅读并同意
|
||||
<a class="cursor-pointer text-blue-400" @click="toUserAgreement">《用户协议》</a><a
|
||||
class="cursor-pointer text-blue-400" @click="toServiceAgreement">《信息技术服务合同》</a><a
|
||||
class="cursor-pointer text-blue-400" @click="toAgentManageAgreement">《推广方管理制度协议》</a>
|
||||
<div class="text-xs text-gray-400 mt-1">点击勾选即代表您同意上述法律文书的相关条款并签署上述法律文书</div>
|
||||
<div class="text-xs text-gray-400 mt-1">手机号未在本平台注册账号则申请后将自动生成账号</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<van-button type="primary" round block @click="submit">提交申请</van-button>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<van-button type="default" round block @click="closePopup">取消</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const show = defineModel('show')
|
||||
import { useCascaderAreaData } from '@vant/area-data';
|
||||
import { showToast } from 'vant'; // 引入 showToast 方法
|
||||
const emit = defineEmits(); // 确保 emit 可以正确使用
|
||||
const props = defineProps({
|
||||
ancestor: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const { ancestor } = toRefs(props);
|
||||
const form = ref({
|
||||
region: '',
|
||||
mobile: '',
|
||||
wechat_id: '',
|
||||
code: '', // 增加验证码字段
|
||||
});
|
||||
const showCascader = ref(false);
|
||||
const cascaderValue = ref('');
|
||||
const options = useCascaderAreaData();
|
||||
const loadingSms = ref(false); // 控制验证码按钮的loading状态
|
||||
const isCountingDown = ref(false)
|
||||
const isAgreed = ref(false)
|
||||
const countdown = ref(60)
|
||||
const onFinish = ({ selectedOptions }) => {
|
||||
showCascader.value = false;
|
||||
form.value.region = selectedOptions.map((option) => option.text).join('/');
|
||||
};
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(form.value.mobile)
|
||||
})
|
||||
|
||||
const getSmsCode = async () => {
|
||||
if (!form.value.mobile) {
|
||||
showToast({ message: '请输入手机号' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: '手机号格式不正确' });
|
||||
return;
|
||||
}
|
||||
|
||||
loadingSms.value = true;
|
||||
|
||||
const { data, error } = await useApiFetch('auth/sendSms')
|
||||
.post({ mobile: form.value.mobile, actionType: 'agentApply' })
|
||||
.json();
|
||||
|
||||
loadingSms.value = false;
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showToast({ message: "获取成功" });
|
||||
startCountdown(); // 启动倒计时
|
||||
} else {
|
||||
showToast(data.value.msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
let timer = null
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true
|
||||
countdown.value = 60
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--
|
||||
} else {
|
||||
clearInterval(timer)
|
||||
isCountingDown.value = false
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer)
|
||||
}
|
||||
})
|
||||
const submit = () => {
|
||||
// 校验表单字段
|
||||
if (!form.value.region) {
|
||||
showToast({ message: '请选择地区' });
|
||||
return;
|
||||
}
|
||||
if (!form.value.mobile) {
|
||||
showToast({ message: '请输入手机号' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: '手机号格式不正确' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.value.wechat_id) {
|
||||
showToast({ message: '请输入微信号' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!form.value.code) {
|
||||
showToast({ message: '请输入验证码' });
|
||||
return;
|
||||
}
|
||||
if (!isAgreed.value) {
|
||||
showToast({ message: "请先阅读并同意用户协议及相关条款" });
|
||||
return
|
||||
}
|
||||
console.log("form", form.value)
|
||||
// 触发父组件提交申请
|
||||
emit('submit', form.value);
|
||||
};
|
||||
const maskName = computed(() => {
|
||||
return (name) => {
|
||||
return name.substring(0, 3) + "****" + name.substring(7);
|
||||
}
|
||||
})
|
||||
const closePopup = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const toUserAgreement = () => {
|
||||
router.push({ name: "userAgreement" })
|
||||
}
|
||||
const toServiceAgreement = () => {
|
||||
router.push({ name: "agentSerivceAgreement" })
|
||||
|
||||
}
|
||||
const toAgentManageAgreement = () => {
|
||||
router.push({ name: "agentManageAgreement" })
|
||||
|
||||
}
|
||||
</script>
|
511
src/components/InquireForm.vue
Normal file
@ -0,0 +1,511 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
|
||||
import { aesEncrypt } from "@/utils/crypto";
|
||||
import { useRoute } from "vue-router";
|
||||
import CarNumberInput from "@/components/CarNumberInput.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter()
|
||||
const showAuthorizationPopup = ref(false);
|
||||
const authorization = ref(true);
|
||||
const showPayment = ref(false);
|
||||
const queryId = ref(null);
|
||||
const name = ref("");
|
||||
const nameMan = ref("");
|
||||
const nameWoman = ref("");
|
||||
const idCard = ref("");
|
||||
const idCardMan = ref("");
|
||||
const idCardWoman = ref("");
|
||||
const mobile = ref("");
|
||||
const bankCard = ref("");
|
||||
const startDate = ref([])
|
||||
const dateVal = ref("")
|
||||
const showDatePicker = ref(false)
|
||||
// 当前日期
|
||||
const today = new Date();
|
||||
const maxDate = today; // 最大日期为当前日期
|
||||
// 最小日期为2000年1月1日
|
||||
const minDate = new Date('2000-01-01');
|
||||
const entName = ref("");
|
||||
const entCode = ref("");
|
||||
const verificationCode = ref("");
|
||||
const agreeToTerms = ref(false);
|
||||
const isCountingDown = ref(false);
|
||||
const countdown = ref(60);
|
||||
const feature = ref(route.params.feature);
|
||||
const featureData = ref({});
|
||||
const carLicense = ref("");
|
||||
const carType = ref("小型汽车");
|
||||
const carPickerVal = ref([{ value: "02", text: "小型汽车" }]);
|
||||
const showCarTypePicker = ref(false);
|
||||
const carTypeColumns = [
|
||||
{ value: "01", text: "大型汽车" },
|
||||
{ value: "02", text: "小型汽车" },
|
||||
{ value: "03", text: "使馆汽车" },
|
||||
{ value: "04", text: "领馆汽车" },
|
||||
{ value: "05", text: "境外汽车" },
|
||||
{ value: "06", text: "外籍汽车" },
|
||||
{ value: "07", text: "普通摩托车" },
|
||||
{ value: "08", text: "轻便摩托车" },
|
||||
{ value: "09", text: "使馆摩托车" },
|
||||
{ value: "10", text: "领馆摩托车" },
|
||||
{ value: "11", text: "境外摩托车" },
|
||||
{ value: "12", text: "外籍摩托车" },
|
||||
{ value: "13", text: "低速车" },
|
||||
{ value: "14", text: "拖拉机" },
|
||||
{ value: "15", text: "挂车" },
|
||||
{ value: "16", text: "教练汽车" },
|
||||
{ value: "17", text: "教练摩托车" },
|
||||
{ value: "20", text: "临时入境汽车" },
|
||||
{ value: "21", text: "临时入境摩托车" },
|
||||
{ value: "22", text: "临时行驶车" },
|
||||
{ value: "23", text: "警用汽车" },
|
||||
{ value: "24", text: "警用摩托车" },
|
||||
{ value: "51", text: "新能源大型车" },
|
||||
{ value: "52", text: "新能源小型车" },
|
||||
];
|
||||
const formatterDate = (type, option) => {
|
||||
if (type === 'year') {
|
||||
option.text += '年';
|
||||
}
|
||||
if (type === 'month') {
|
||||
option.text += '月';
|
||||
}
|
||||
if (type === 'day') {
|
||||
option.text += '日';
|
||||
}
|
||||
return option;
|
||||
};
|
||||
const onConfirmDate = ({ selectedValues, selectedOptions }) => {
|
||||
dateVal.value = selectedOptions.map(item => item.text).join('');
|
||||
showDatePicker.value = false
|
||||
}
|
||||
const carLicenseChange = (e) => {
|
||||
carLicense.value = e;
|
||||
};
|
||||
const onConfirmCarType = ({ selectedValues, selectedOptions }) => {
|
||||
showCarTypePicker.value = false;
|
||||
carPickerVal.value = selectedValues;
|
||||
carType.value = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
isFinishPayment()
|
||||
getProduct();
|
||||
initAuthorization();
|
||||
});
|
||||
const discountPrice = ref(false) // 是否应用折扣
|
||||
|
||||
function isFinishPayment() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
let orderNo = query.get("out_trade_no");
|
||||
if (orderNo) {
|
||||
router.push({ path: '/report', query: { orderNo } });
|
||||
}
|
||||
}
|
||||
async function getProduct() {
|
||||
const { data, error } = await useApiFetch(`/product/en/${feature.value}`)
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value) {
|
||||
featureData.value = data.value.data;
|
||||
}
|
||||
}
|
||||
function initAuthorization() {
|
||||
if (NeedAuthorization.includes(feature.value)) {
|
||||
authorization.value = false;
|
||||
}
|
||||
}
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(mobile.value);
|
||||
});
|
||||
const isIdCardValid = computed(() => /^\d{17}[\dX]$/i.test(idCard.value));
|
||||
const isIdCardManValid = computed(() => /^\d{17}[\dX]$/i.test(idCardMan.value));
|
||||
const isIdCardWomanValid = computed(() =>
|
||||
/^\d{17}[\dX]$/i.test(idCardWoman.value)
|
||||
);
|
||||
const isCreditCodeValid = computed(() => /^.{18}$/.test(entCode.value));
|
||||
const isCarLicense = computed(() => carLicense.value.trim().length > 6);
|
||||
const isBankCardValid = computed(() => {
|
||||
const card = bankCard.value.replace(/\D/g, ""); // 移除所有非数字字符
|
||||
if (card.length < 13 || card.length > 19) {
|
||||
return false; // 校验长度
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
let shouldDouble = false;
|
||||
|
||||
// 从卡号的右边开始遍历
|
||||
for (let i = card.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(card.charAt(i));
|
||||
|
||||
if (shouldDouble) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
shouldDouble = !shouldDouble; // 反转是否乘 2
|
||||
}
|
||||
|
||||
return sum % 10 === 0; // 如果最终和能被 10 整除,则银行卡号有效
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
if (!agreeToTerms.value) {
|
||||
showToast({ message: `请阅读并同意用户协议、隐私政策${!NeedAuthorization.includes(feature.value) ? '和授权书' : ''}` });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!validateField("name", name.value, (v) => v, "请输入姓名") ||
|
||||
!validateField("nameMan", nameMan.value, (v) => v, "请输入男方姓名") ||
|
||||
!validateField(
|
||||
"nameWoman",
|
||||
nameWoman.value,
|
||||
(v) => v,
|
||||
"请输入女方姓名"
|
||||
) ||
|
||||
!validateField(
|
||||
"mobile",
|
||||
mobile.value,
|
||||
(v) => isPhoneNumberValid.value,
|
||||
"请输入有效的手机号"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCard",
|
||||
idCard.value,
|
||||
(v) => isIdCardValid.value,
|
||||
"请输入有效的身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCardMan",
|
||||
idCardMan.value,
|
||||
(v) => isIdCardManValid.value,
|
||||
"请输入有效的男方身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCardWoman",
|
||||
idCardWoman.value,
|
||||
(v) => isIdCardWomanValid.value,
|
||||
"请输入有效的女方身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"bankCard",
|
||||
bankCard.value,
|
||||
(v) => isBankCardValid.value,
|
||||
"请输入有效的银行卡号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"verificationCode",
|
||||
verificationCode.value,
|
||||
(v) => v,
|
||||
"请输入验证码"
|
||||
) ||
|
||||
!validateField(
|
||||
"carPickerVal",
|
||||
carPickerVal.value,
|
||||
(v) => v,
|
||||
"请选择车辆类型"
|
||||
) ||
|
||||
!validateField(
|
||||
"carLicense",
|
||||
carLicense.value,
|
||||
(v) => isCarLicense.value,
|
||||
"请输入正确的车牌号"
|
||||
) ||
|
||||
!validateField("entName", entName.value, (v) => v, "请输入企业名称") ||
|
||||
!validateField(
|
||||
"entCode",
|
||||
entCode.value,
|
||||
(v) => isCreditCodeValid.value,
|
||||
"请输入统一社会信用代码"
|
||||
) ||
|
||||
!validateField(
|
||||
"date",
|
||||
dateVal.value,
|
||||
(v) => v,
|
||||
"请选择日期"
|
||||
)
|
||||
|
||||
) {
|
||||
return;
|
||||
}
|
||||
submitRequest();
|
||||
}
|
||||
const validateField = (field, value, validationFn, errorMessage) => {
|
||||
if (isHasInput(field) && !validationFn(value)) {
|
||||
showToast({ message: errorMessage });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const defaultInput = ["name", "idCard", "mobile", "verificationCode"];
|
||||
const specialProduct = {
|
||||
toc_EnterpriseLawsuit: ["entName", "entCode", "mobile", "verificationCode"],
|
||||
toc_PhoneThreeElements: ["name", "idCard", "mobile"],
|
||||
toc_IDCardTwoElements: ["name", "idCard"],
|
||||
toc_PhoneTwoElements: ["name", "mobile"],
|
||||
toc_PersonVehicleVerification: ["name", "carType", "carLicense"],
|
||||
toc_VehiclesUnderName: ["name", "idCard"],
|
||||
toc_DualMarriage: ["nameMan", "idCardMan", "nameWoman", "idCardWoman"],
|
||||
toc_BankCardBlacklist: ["name", "idCard", "mobile", "bankCard"],
|
||||
toc_BankCardFourElements: ["name", "idCard", "mobile", "bankCard"],
|
||||
toc_NaturalLifeStatus: ["name", "idCard"],
|
||||
toc_NetworkDuration: ["mobile"],
|
||||
toc_PhoneSecondaryCard: ["mobile", "date"],
|
||||
toc_PhoneNumberRisk: ["mobile"],
|
||||
};
|
||||
const NeedAuthorization = [
|
||||
"toc_Marriage",
|
||||
"marriage"
|
||||
];
|
||||
const isHasInput = (input) => {
|
||||
if (specialProduct[feature.value]) {
|
||||
return specialProduct[feature.value].includes(input);
|
||||
} else {
|
||||
return defaultInput.includes(input);
|
||||
}
|
||||
};
|
||||
async function submitRequest() {
|
||||
const req = {};
|
||||
if (isHasInput("name")) {
|
||||
req.name = name.value;
|
||||
}
|
||||
if (isHasInput("idCard")) {
|
||||
req.id_card = idCard.value;
|
||||
}
|
||||
if (isHasInput("nameMan")) {
|
||||
req.name_man = nameMan.value;
|
||||
}
|
||||
if (isHasInput("idCardMan")) {
|
||||
req.id_card_man = idCardMan.value;
|
||||
}
|
||||
if (isHasInput("nameWoman")) {
|
||||
req.name_woman = nameWoman.value;
|
||||
}
|
||||
if (isHasInput("idCardWoman")) {
|
||||
req.id_card_woman = idCardWoman.value;
|
||||
}
|
||||
if (isHasInput("bankCard")) {
|
||||
req.bank_card = bankCard.value.replace(/\D/g, "");
|
||||
}
|
||||
if (isHasInput("mobile")) {
|
||||
req.mobile = mobile.value;
|
||||
}
|
||||
if (isHasInput("verificationCode")) {
|
||||
req.code = verificationCode.value;
|
||||
}
|
||||
if (isHasInput("carType")) {
|
||||
req.car_type = carPickerVal.value[0].value;
|
||||
}
|
||||
if (isHasInput("carLicense")) {
|
||||
req.car_license = carLicense.value.trim();
|
||||
}
|
||||
if (isHasInput("date")) {
|
||||
req.start_date = startDate.value.map(item => item).join('')
|
||||
}
|
||||
if (isHasInput("entName")) {
|
||||
req.ent_name = entName.value;
|
||||
}
|
||||
if (isHasInput("entCode")) {
|
||||
req.ent_code = entCode.value;
|
||||
}
|
||||
const reqStr = JSON.stringify(req);
|
||||
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
|
||||
const { data, error } = await useApiFetch(`/query/service/${feature.value}`)
|
||||
.post({ data: encodeData })
|
||||
.json();
|
||||
if (data.value.code === 200) {
|
||||
queryId.value = data.value.data.id;
|
||||
if (authorization.value) {
|
||||
showPayment.value = true;
|
||||
} else {
|
||||
showAuthorizationPopup.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return;
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch("/auth/sendSms")
|
||||
.post({ mobile: mobile.value, actionType: "query" })
|
||||
.json();
|
||||
|
||||
if (!error.value && data.value.code === 200) {
|
||||
showToast({ message: "验证码发送成功", type: "success" });
|
||||
startCountdown();
|
||||
} else {
|
||||
showToast({ message: "验证码发送失败,请重试" });
|
||||
}
|
||||
}
|
||||
let timer = null;
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true;
|
||||
countdown.value = 60;
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--;
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
isCountingDown.value = false;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`)
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`)
|
||||
}
|
||||
|
||||
function toAuthorization() {
|
||||
router.push(`/authorization`)
|
||||
}
|
||||
const toExample = () => {
|
||||
router.push(`/example?feature=${feature.value}`)
|
||||
};
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="inquire-bg min-h-screen p-6">
|
||||
<div class="card">
|
||||
<div class="mb-4 text-lg font-semibold text-gray-800">基本信息</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('name')">
|
||||
<label for="name" class="form-label">姓名</label>
|
||||
<input v-model="name" id="name" type="text" placeholder="请输入姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCard')">
|
||||
<label for="idCard" class="form-label">身份证号</label>
|
||||
<input v-model="idCard" id="idCard" type="text" placeholder="请输入身份证号" class="form-input" />
|
||||
</div>
|
||||
<!-- 双人婚姻 -->
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('nameMan')">
|
||||
<label for="nameMan" class="form-label">男方姓名</label>
|
||||
<input v-model="nameMan" id="nameMan" type="text" placeholder="请输入男方姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCardMan')">
|
||||
<label for="idCardMan" class="form-label">男方身份证号</label>
|
||||
<input v-model="idCardMan" id="idCardMan" type="text" placeholder="请输入男方身份证号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('nameWoman')">
|
||||
<label for="nameWoman" class="form-label">女方姓名</label>
|
||||
<input v-model="nameWoman" id="nameWoman" type="text" placeholder="请输入女方姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCardWoman')">
|
||||
<label for="idCardWoman" class="form-label">女方身份证号</label>
|
||||
<input v-model="idCardWoman" id="idCardWoman" type="text" placeholder="请输入女方身份证号" class="form-input" />
|
||||
</div>
|
||||
<!-- 双人婚姻 -->
|
||||
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('entName')">
|
||||
<label for="entName" class="form-label">企业名称</label>
|
||||
<input v-model="entName" id="entName" type="text" placeholder="请输入企业名称" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('entCode')">
|
||||
<label for="entCode" class="form-label">统一社会信用代码</label>
|
||||
<input v-model="entCode" id="entCode" type="text" placeholder="请输入统一社会信用代码" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('carType')">
|
||||
<label for="carType" class="form-label">汽车类型</label>
|
||||
<van-field id="carType" v-model="carType" is-link readonly placeholder="点击选择汽车类型"
|
||||
@click="showCarTypePicker = true" class="form-input" />
|
||||
<van-popup v-model:show="showCarTypePicker" destroy-on-close round position="bottom">
|
||||
<van-picker :model-value="carPickerVal" :columns="carTypeColumns"
|
||||
@cancel="showCarTypePicker = false" @confirm="onConfirmCarType" />
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('carLicense')">
|
||||
<!-- <label for="entCode" class="form-label">车牌号</label> -->
|
||||
<CarNumberInput class="form-input" @number-input-result="carLicenseChange" :default-str="carLicense">
|
||||
</CarNumberInput>
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('bankCard')">
|
||||
<label for="bankCard" class="form-label">银行卡号</label>
|
||||
<input v-model="bankCard" id="bankCard" type="tel" placeholder="请输入银行卡号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('mobile')">
|
||||
<label for="mobile" class="form-label">手机号</label>
|
||||
<input v-model="mobile" id="mobile" type="tel" placeholder="请输入手机号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('date')">
|
||||
<label for="date" class="form-label">业务日期</label>
|
||||
<van-field id="date" v-model="dateVal" is-link readonly placeholder="点击选择日期"
|
||||
@click="showDatePicker = true" class="form-input" />
|
||||
<van-popup v-model:show="showDatePicker" destroy-on-close round position="bottom">
|
||||
<van-date-picker v-model="startDate" :formatter="formatterDate" :min-date="minDate"
|
||||
:max-date="maxDate" title="选择日期" @confirm="onConfirmDate" @cancel="showDatePicker = false" />
|
||||
</van-popup>
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('verificationCode')">
|
||||
<label for="verificationCode" class="form-label">验证码</label>
|
||||
<div class="flex-1 flex items-center">
|
||||
<input v-model="verificationCode" id="verificationCode" type="text" placeholder="请输入验证码"
|
||||
class="form-input flex-1" />
|
||||
<button class="ml-2 px-4 py-2 text-sm text-blue-500 disabled:text-gray-400"
|
||||
:disabled="isCountingDown || !isPhoneNumberValid" @click="sendVerificationCode">
|
||||
{{
|
||||
isCountingDown
|
||||
? `${countdown}s重新获取`
|
||||
: "获取验证码"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center">
|
||||
<input type="checkbox" v-model="agreeToTerms" />
|
||||
<span class="ml-2 text-xs text-gray-400">
|
||||
我已阅读并同意
|
||||
<span @click="toUserAgreement" class="text-blue-500 ">用户协议、</span>
|
||||
<span @click="toPrivacyPolicy" class="text-blue-500 ">隐私政策</span>
|
||||
<template v-if="!NeedAuthorization.includes(feature)">
|
||||
<span @click="toAuthorization" class="text-blue-500 ">、授权书</span>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<button class="w-24 rounded-l-xl bg-blue-400 py-2 text-white" @click="toExample">
|
||||
示例报告
|
||||
</button>
|
||||
<button class="flex-1 rounded-r-xl bg-blue-500 py-2 text-white" @click="handleSubmit">
|
||||
立即查询
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.form-label {
|
||||
@apply w-20 text-sm font-medium text-gray-700 flex-shrink-0;
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: var(--van-text-color-3);
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@apply w-full border-b border-gray-200 px-2 py-2 focus:outline-none;
|
||||
}
|
||||
</style>
|
@ -70,7 +70,7 @@ const props = defineProps({
|
||||
const show = defineModel()
|
||||
const orderId = ref("")
|
||||
const router = useRouter()
|
||||
const selectedPaymentMethod = ref(isWeChat ? 'wechat' : 'alipay')
|
||||
const selectedPaymentMethod = ref(isWeChat.value ? 'wechat' : 'alipay')
|
||||
const discountPrice = ref(false) // 是否应用折扣
|
||||
onMounted(() => {
|
||||
})
|
||||
@ -101,7 +101,6 @@ async function getPayment() {
|
||||
if (res.err_msg == "get_brand_wcpay_request:ok") {
|
||||
console.log("支付成功", res)
|
||||
router.push({ path: '/report', query: { orderId: data.value.data.order_id } });
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
161
src/components/PriceInputPopup.vue
Normal file
@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<van-popup v-model:show="show" destroy-on-close round position="bottom">
|
||||
<div class="min-h-[500px] bg-gray-50 text-gray-600">
|
||||
<div class="h-10 bg-white flex items-center justify-center font-semibold text-lg">设置客户查询价
|
||||
</div>
|
||||
<div class="card m-4">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-lg">
|
||||
客户查询价 (元)</div>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-gray-200">
|
||||
<van-field v-model="price" type="number" label="¥" label-width="28"
|
||||
:placeholder="`${productConfig.price_range_min} - ${productConfig.price_range_max}`"
|
||||
@blur="onBlurPrice" class="!text-3xl" />
|
||||
</div>
|
||||
<div class="flex items-center justify-between mt-2">
|
||||
<div>推广收益为<span class="text-orange-500"> {{ promotionRevenue }} </span>元</div>
|
||||
<div>我的成本为<span class="text-orange-500"> {{ costPrice }} </span>元</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card m-4">
|
||||
<div class="text-lg mb-2">收益与成本说明</div>
|
||||
|
||||
<div>推广收益 = 客户查询价 - 我的成本</div>
|
||||
<div>我的成本 = 提价成本 + 底价成本</div>
|
||||
<div class="mt-1">提价成本:超过平台标准定价部分,平台会收取部分成本价</div>
|
||||
<div class="">设定范围:<span class="text-orange-500">{{
|
||||
productConfig.price_range_min }}</span>元 - <span class="text-orange-500">{{
|
||||
productConfig.price_range_max }}</span>元</div>
|
||||
</div>
|
||||
<div class="px-4 pb-4">
|
||||
<van-button class="w-full" round type="primary" size="large" @click="onConfirm">确认</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
defaultPrice: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
productConfig: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
const { defaultPrice, productConfig } = toRefs(props)
|
||||
const emit = defineEmits(["change"])
|
||||
const show = defineModel("show")
|
||||
const price = ref(null)
|
||||
|
||||
|
||||
watch(show, () => {
|
||||
price.value = defaultPrice.value
|
||||
})
|
||||
|
||||
|
||||
const costPrice = computed(() => {
|
||||
if (!productConfig.value) return 0.00
|
||||
// 平台定价成本
|
||||
let platformPricing = 0
|
||||
platformPricing += productConfig.value.cost_price
|
||||
if (price.value > productConfig.value.p_pricing_standard) {
|
||||
platformPricing += (price.value - productConfig.value.p_pricing_standard) * productConfig.value.p_overpricing_ratio
|
||||
}
|
||||
|
||||
if (productConfig.value.a_pricing_standard > platformPricing && productConfig.value.a_pricing_end > platformPricing && productConfig.value.a_overpricing_ratio > 0) {
|
||||
if (price.value > productConfig.value.a_pricing_standard) {
|
||||
if (price.value > productConfig.value.a_pricing_end) {
|
||||
platformPricing += (productConfig.value.a_pricing_end - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
||||
} else {
|
||||
platformPricing += (price.value - productConfig.value.a_pricing_standard) * productConfig.value.a_overpricing_ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return safeTruncate(platformPricing)
|
||||
})
|
||||
|
||||
const promotionRevenue = computed(() => {
|
||||
return safeTruncate(price.value - costPrice.value)
|
||||
});
|
||||
|
||||
// 价格校验与修正逻辑
|
||||
const validatePrice = (currentPrice) => {
|
||||
const min = productConfig.value.price_range_min;
|
||||
const max = productConfig.value.price_range_max;
|
||||
let newPrice = Number(currentPrice);
|
||||
let message = '';
|
||||
|
||||
// 处理无效输入
|
||||
if (isNaN(newPrice)) {
|
||||
newPrice = defaultPrice.value;
|
||||
return { newPrice, message: '输入无效,请输入价格' };
|
||||
}
|
||||
|
||||
// 处理小数位数(兼容科学计数法)
|
||||
try {
|
||||
const priceString = newPrice.toString()
|
||||
const [_, decimalPart = ""] = priceString.split('.');
|
||||
console.log(priceString, decimalPart)
|
||||
// 当小数位数超过2位时处理
|
||||
if (decimalPart.length > 2) {
|
||||
newPrice = parseFloat(safeTruncate(newPrice));
|
||||
message = '价格已自动格式化为两位小数';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('价格格式化异常:', e);
|
||||
}
|
||||
|
||||
// 范围校验(基于可能格式化后的值)
|
||||
if (newPrice < min) {
|
||||
message = `价格不能低于 ${min} 元`;
|
||||
newPrice = min;
|
||||
} else if (newPrice > max) {
|
||||
message = `价格不能高于 ${max} 元`;
|
||||
newPrice = max;
|
||||
}
|
||||
console.log(newPrice, message)
|
||||
return { newPrice, message };
|
||||
}
|
||||
function safeTruncate(num, decimals = 2) {
|
||||
if (isNaN(num) || !isFinite(num)) return "0.00";
|
||||
|
||||
const factor = 10 ** decimals;
|
||||
const scaled = Math.trunc(num * factor);
|
||||
const truncated = scaled / factor;
|
||||
|
||||
return truncated.toFixed(decimals);
|
||||
}
|
||||
const isManualConfirm = ref(false)
|
||||
const onConfirm = () => {
|
||||
if (isManualConfirm.value) return
|
||||
const { newPrice, message } = validatePrice(price.value)
|
||||
if (message) {
|
||||
price.value = newPrice
|
||||
showToast({ message });
|
||||
} else {
|
||||
emit("change", price.value)
|
||||
show.value = false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
const onBlurPrice = () => {
|
||||
const { newPrice, message } = validatePrice(price.value)
|
||||
if (message) {
|
||||
isManualConfirm.value = true
|
||||
price.value = newPrice
|
||||
showToast({ message });
|
||||
}
|
||||
setTimeout(() => {
|
||||
isManualConfirm.value = false
|
||||
}, 0)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,39 +0,0 @@
|
||||
<template>
|
||||
<van-popup v-model:show="show" position="bottom">
|
||||
<div class="max-h-[calc(100vh-100px)] m-4 ">
|
||||
<div class="p-4 bg-gray-200">
|
||||
<!-- <van-swipe lazy-render>
|
||||
<van-swipe-item v-for="image in images" :key="image" class="">
|
||||
</van-swipe-item>
|
||||
</van-swipe> -->
|
||||
<img class="rounded-xl shadow h-[500px] m-auto" src="@/assets/images/tg_qrcode_1.jpg" />
|
||||
</div>
|
||||
<div class="m-2">分享到</div>
|
||||
<div class="flex items-center justify-around">
|
||||
<div class="flex flex-col items-center justify-center" @click="toPromote">
|
||||
<img src="@/assets/images/icon_share_wechat.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">微信好友</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img src="@/assets/images/icon_share_friends.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">微信朋友圈</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img src="@/assets/images/icon_share_img.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">保存图片</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img src="@/assets/images/icon_share_url.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">复制链接</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const show = defineModel()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
174
src/components/QRcode.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<van-popup v-model:show="show" round position="bottom">
|
||||
<div class="max-h-[calc(100vh-100px)] m-4 ">
|
||||
<div class="p-4">
|
||||
<canvas ref="posterCanvas" class="rounded-xl shadow h-[500px] m-auto"></canvas>
|
||||
</div>
|
||||
<!-- <div class="m-2"></div> -->
|
||||
<van-divider>分享到好友</van-divider>
|
||||
|
||||
<div class="flex items-center justify-around">
|
||||
<!-- <div class="flex flex-col items-center justify-center" @click="toPromote">
|
||||
<img src="@/assets/images/icon_share_wechat.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">微信好友</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<img src="@/assets/images/icon_share_friends.svg" class="w-12 h-12 rounded-full" />
|
||||
<div class="text-center mt-1">微信朋友圈</div>
|
||||
</div> -->
|
||||
<div class="flex flex-col items-center justify-center" @click="savePoster">
|
||||
<img src="@/assets/images/icon_share_img.svg" class="w-10 h-10 rounded-full" />
|
||||
<div class="text-center mt-1 text-gray-600 text-xs">保存图片</div>
|
||||
</div>
|
||||
<div class="flex flex-col items-center justify-center" @click="copyUrl">
|
||||
<img src="@/assets/images/icon_share_url.svg" class="w-10 h-10 rounded-full" />
|
||||
<div class="text-center mt-1 text-gray-600 text-xs">复制链接</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import QRCode from 'qrcode';
|
||||
const props = defineProps({
|
||||
linkIdentifier: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: "promote", // 例如 "promote" | "invitation"
|
||||
},
|
||||
});
|
||||
const { linkIdentifier, mode } = toRefs(props);
|
||||
const posterCanvas = ref(null); // 用于绘制海报的canvas
|
||||
let posterGenerated = ref(false); // 标记海报是否已经生成过
|
||||
const show = defineModel('show')
|
||||
const url = computed(() => {
|
||||
const baseUrl = window.location.origin; // 获取当前站点的域名
|
||||
return mode.value === "promote"
|
||||
? `${baseUrl}/agent/promotionInquire/` // 使用动态的域名
|
||||
: `${baseUrl}/agent/invitationAgentApply/`;
|
||||
});
|
||||
|
||||
const qrcodeImage = ref(null);
|
||||
const loadPosterImage = async () => {
|
||||
if (mode.value === "promote") {
|
||||
const module = await import("@/assets/images/tg_qrcode_1.png");
|
||||
return module.default;
|
||||
} else {
|
||||
const module = await import("@/assets/images/yq_qrcode_1.png");
|
||||
return module.default;
|
||||
}
|
||||
};
|
||||
onMounted(async () => {
|
||||
qrcodeImage.value = await loadPosterImage()
|
||||
});
|
||||
// 生成海报并合成二维码
|
||||
const generatePoster = async () => {
|
||||
// 如果已经生成过海报,就直接返回
|
||||
if (posterGenerated.value) return;
|
||||
|
||||
// 确保 DOM 已经渲染完成
|
||||
await nextTick();
|
||||
|
||||
const canvas = posterCanvas.value;
|
||||
if (!canvas) return; // 如果 canvas 元素为空则直接返回
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// 1. 加载海报图片
|
||||
const posterImg = new Image();
|
||||
posterImg.src = qrcodeImage.value;
|
||||
|
||||
posterImg.onload = () => {
|
||||
// 设置 canvas 尺寸与海报图一致
|
||||
canvas.width = posterImg.width;
|
||||
canvas.height = posterImg.height;
|
||||
|
||||
// 2. 绘制海报图片
|
||||
ctx.drawImage(posterImg, 0, 0);
|
||||
|
||||
// 3. 生成二维码
|
||||
QRCode.toDataURL(generalUrl(), { width: 150, margin: 0 }, (err, qrCodeUrl) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 加载二维码图片
|
||||
const qrCodeImg = new Image();
|
||||
qrCodeImg.src = qrCodeUrl;
|
||||
qrCodeImg.onload = () => {
|
||||
const qrX = mode.value === "promote" ? 180 : 360; // **根据模式调整二维码位置**
|
||||
const qrY = mode.value === "promote" ? posterImg.height - 480 : posterImg.height - 1370;
|
||||
const qrSize = mode.value === "promote" ? 300 : 360
|
||||
ctx.drawImage(qrCodeImg, qrX, qrY, qrSize, qrSize);
|
||||
|
||||
// 标记海报已生成
|
||||
posterGenerated.value = true;
|
||||
};
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
// 监听 show 变化,show 为 true 时生成海报
|
||||
watch(show, (newVal) => {
|
||||
if (newVal && !posterGenerated.value) {
|
||||
generatePoster(); // 当弹窗显示且海报未生成时生成海报
|
||||
}
|
||||
});
|
||||
|
||||
// 分享到微信
|
||||
const toPromote = () => {
|
||||
// 这里可以实现微信分享的功能,比如调用微信JS-SDK等
|
||||
console.log('分享到微信好友');
|
||||
};
|
||||
|
||||
// 保存海报图片
|
||||
const savePoster = () => {
|
||||
const canvas = posterCanvas.value;
|
||||
const dataURL = canvas.toDataURL('image/png'); // 获取 canvas 内容为图片
|
||||
const a = document.createElement('a');
|
||||
a.href = dataURL;
|
||||
a.download = '天远数据查询.png';
|
||||
a.click();
|
||||
};
|
||||
const generalUrl = () => {
|
||||
return url.value + encodeURIComponent(linkIdentifier.value)
|
||||
}
|
||||
const copyUrl = () => {
|
||||
copyToClipboard(generalUrl())
|
||||
}
|
||||
// 复制链接
|
||||
const copyToClipboard = (text) => {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// 支持 Clipboard API
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
showToast({ message: "链接已复制!" });
|
||||
}).catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
});
|
||||
} else {
|
||||
// 对于不支持 Clipboard API 的浏览器,使用 fallback 方法
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
showToast({ message: "链接已复制!" });
|
||||
} catch (err) {
|
||||
console.error('复制失败:', err);
|
||||
} finally {
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 这里是你自定义的样式 */
|
||||
</style>
|
14
src/components/VipBanner.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="mb-4" @click="toAgentVip">
|
||||
<img src="@/assets/images/vip_banner.png" class="rounded-xl shadow-lg" alt="">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const toAgentVip = () => {
|
||||
router.push({ name: "agentVip" })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -6,15 +6,19 @@ import router from "@/router"; // 假设你使用 Vue Router
|
||||
const useApiFetch = createFetch({
|
||||
baseUrl: "/api/v1", // 你的 API 基础路径
|
||||
options: {
|
||||
async beforeFetch({ options }) {
|
||||
async beforeFetch({ url, options }) {
|
||||
showLoadingToast({
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
duration: 0, // 设置为 0 表示不会自动关闭
|
||||
loadingType: "spinner",
|
||||
});
|
||||
// 获取平台信息
|
||||
|
||||
const timestamp = Date.now();
|
||||
const separator = url.includes("?") ? "&" : "?"; // 判断是否已有参数
|
||||
url += `${separator}t=${timestamp}`; // 追加时间戳
|
||||
|
||||
// 获取平台信息
|
||||
const brand = "tydata"; // 固定的品牌信息
|
||||
|
||||
// 在请求前添加通用的 Header,例如 Authorization
|
||||
@ -33,7 +37,7 @@ const useApiFetch = createFetch({
|
||||
Authorization: `${token}`,
|
||||
};
|
||||
}
|
||||
return { options };
|
||||
return { url, options };
|
||||
},
|
||||
async afterFetch({ data, response }) {
|
||||
closeToast();
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div class="home-layout min-h-screen flex flex-col">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<img class="logo rounded-full overflow-hidden" src="@/assets/images/logo.png" alt="Logo" />
|
||||
<img class="logo rounded-full overflow-hidden" src="@/assets/images/logo.jpg" alt="Logo" />
|
||||
<div class="title">天远数据</div>
|
||||
</div>
|
||||
|
||||
@ -31,7 +31,7 @@
|
||||
</div> -->
|
||||
<div>
|
||||
<a class="text-blue-500" href="https://beian.miit.gov.cn">
|
||||
琼ICP备2024048057号-2
|
||||
琼ICP备2024048057号-1
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,6 +4,9 @@ import GlobalLayout from "@/layouts/GlobalLayout.vue";
|
||||
import HomeLayout from "@/layouts/HomeLayout.vue";
|
||||
import PageLayout from "@/layouts/PageLayout.vue";
|
||||
import index from "@/views/index.vue";
|
||||
import { useAgentStore } from "@/stores/agentStore";
|
||||
import { storeToRefs } from "pinia";
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
@ -26,7 +29,7 @@ const router = createRouter({
|
||||
component: () => import("@/views/Ai.vue"),
|
||||
},
|
||||
{
|
||||
path: "/agent",
|
||||
path: "agent",
|
||||
name: "agent",
|
||||
component: () => import("@/views/Agent.vue"),
|
||||
},
|
||||
@ -45,20 +48,15 @@ const router = createRouter({
|
||||
path: "/historyQuery",
|
||||
name: "history",
|
||||
component: () => import("@/views/HistoryQuery.vue"),
|
||||
meta: { title: "历史报告" },
|
||||
meta: { title: "历史报告", requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/promote",
|
||||
name: "promote",
|
||||
component: () => import("@/views/Promote.vue"),
|
||||
meta: { title: "推广" },
|
||||
},
|
||||
{
|
||||
path: "/withdraw",
|
||||
name: "withdraw",
|
||||
component: () => import("@/views/Withdraw.vue"),
|
||||
meta: { title: "提现" },
|
||||
path: "/help",
|
||||
name: "help",
|
||||
component: () => import("@/views/Help.vue"),
|
||||
meta: { title: "帮助中心" },
|
||||
},
|
||||
|
||||
{
|
||||
path: "/service",
|
||||
name: "service",
|
||||
@ -75,7 +73,7 @@ const router = createRouter({
|
||||
path: "/report",
|
||||
name: "report",
|
||||
component: () => import("@/views/Report.vue"),
|
||||
meta: { title: "报告结果" },
|
||||
meta: { title: "报告结果", requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: "/example",
|
||||
@ -104,7 +102,20 @@ const router = createRouter({
|
||||
import("@/views/UserAgreement.vue"),
|
||||
meta: { title: "用户协议" },
|
||||
},
|
||||
|
||||
{
|
||||
path: "/agentManageAgreement",
|
||||
name: "agentManageAgreement",
|
||||
component: () =>
|
||||
import("@/views/AgentManageAgreement.vue"),
|
||||
meta: { title: "代理管理协议" },
|
||||
},
|
||||
{
|
||||
path: "/agentSerivceAgreement",
|
||||
name: "agentSerivceAgreement",
|
||||
component: () =>
|
||||
import("@/views/AgentServiceAgreement.vue"),
|
||||
meta: { title: "信息技术服务合同" },
|
||||
},
|
||||
{
|
||||
path: "/inquire/:feature",
|
||||
name: "inquire",
|
||||
@ -113,6 +124,103 @@ const router = createRouter({
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "agent",
|
||||
component: PageLayout,
|
||||
children: [
|
||||
{
|
||||
path: "promoteDetails",
|
||||
name: "promoteDetails",
|
||||
component: () =>
|
||||
import("@/views/AgentPromoteDetails.vue"),
|
||||
meta: {
|
||||
title: "直推报告收益明细",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "rewardsDetails",
|
||||
name: "rewardsDetails",
|
||||
component: () =>
|
||||
import("@/views/AgentRewardsDetails.vue"),
|
||||
meta: {
|
||||
title: "代理奖励收益明细",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "promote",
|
||||
name: "promote",
|
||||
component: () => import("@/views/Promote.vue"),
|
||||
meta: {
|
||||
title: "直推报告",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "invitation",
|
||||
name: "invitation",
|
||||
component: () => import("@/views/Invitation.vue"),
|
||||
meta: {
|
||||
title: "邀请下级",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "agentVip",
|
||||
name: "agentVip",
|
||||
component: () => import("@/views/AgentVip.vue"),
|
||||
meta: {
|
||||
title: "代理会员",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "vipConfig",
|
||||
name: "agentVipConfig",
|
||||
component: () =>
|
||||
import("@/views/AgentVipConfig.vue"),
|
||||
meta: {
|
||||
title: "代理会员报告配置",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "withdraw",
|
||||
name: "withdraw",
|
||||
component: () => import("@/views/Withdraw.vue"),
|
||||
meta: {
|
||||
title: "提现",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "withdrawDetails",
|
||||
name: "withdrawDetails",
|
||||
component: () =>
|
||||
import("@/views/WithdrawDetails.vue"),
|
||||
meta: {
|
||||
title: "提现记录",
|
||||
requiresAuth: true,
|
||||
requiresAgent: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "invitationAgentApply/self",
|
||||
name: "invitationAgentApplySelf",
|
||||
component: () =>
|
||||
import("@/views/InvitationAgentApply.vue"),
|
||||
meta: { title: "代理申请", requiresAuth: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@ -122,16 +230,16 @@ const router = createRouter({
|
||||
component: () => import("@/views/Login.vue"),
|
||||
},
|
||||
{
|
||||
path: "/promotionInquire/:feature",
|
||||
path: "/agent/promotionInquire/:linkIdentifier",
|
||||
name: "promotionInquire",
|
||||
component: () => import("@/views/PromotionInquire.vue"),
|
||||
},
|
||||
// {
|
||||
// path: '/home',
|
||||
// name: 'home',
|
||||
// component: () => import('@/views/Home.vue'),
|
||||
// },
|
||||
|
||||
{
|
||||
path: "/agent/invitationAgentApply/:linkIdentifier",
|
||||
name: "invitationAgentApply",
|
||||
component: () => import("@/views/InvitationAgentApply.vue"),
|
||||
meta: { title: "代理申请" },
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
name: "NotFound",
|
||||
@ -148,9 +256,25 @@ NProgress.configure({
|
||||
});
|
||||
|
||||
// 路由导航守卫
|
||||
router.beforeEach((to, from, next) => {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start(); // 启动进度条
|
||||
next();
|
||||
const isAuthenticated = localStorage.getItem("token");
|
||||
const agentStore = useAgentStore();
|
||||
const { isAgent, isLoaded } = storeToRefs(agentStore);
|
||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||
next("/login");
|
||||
} else if (to.meta.requiresAgent && !isAgent.value) {
|
||||
if (!isLoaded.value) {
|
||||
await agentStore.fetchAgentStatus();
|
||||
}
|
||||
if (!isAgent.value) {
|
||||
next("/agent/invitationAgentApply/self");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
router.afterEach(() => {
|
||||
|
33
src/stores/agentStore.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useAgentStore = defineStore("agent", {
|
||||
state: () => ({
|
||||
isLoaded: false,
|
||||
level: "",
|
||||
status: 3, // 0=待审核,1=审核通过,2=审核未通过,3=未申请
|
||||
isAgent: false,
|
||||
ancestorID: null,
|
||||
agentID: null,
|
||||
mobile: "",
|
||||
}),
|
||||
actions: {
|
||||
async fetchAgentStatus() {
|
||||
const { data, error } = await useApiFetch("/agent/info")
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
this.level = data.value.data.level;
|
||||
this.isAgent = data.value.data.is_agent; // 判断是否是代理
|
||||
this.status = data.value.data.status; // 获取代理状态 0=待审核,1=审核通过,2=审核未通过,3=未申请
|
||||
this.agentID = data.value.data.agent_id;
|
||||
this.mobile = data.value.data.mobile;
|
||||
} else {
|
||||
console.log("Error fetching agent info", data.value);
|
||||
}
|
||||
}
|
||||
this.isLoaded = true;
|
||||
},
|
||||
},
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
24
src/stores/userStore.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore("user", {
|
||||
state: () => ({
|
||||
userName: "",
|
||||
userAvatar: "",
|
||||
isLoggedIn: false,
|
||||
}),
|
||||
actions: {
|
||||
async fetchUserInfo() {
|
||||
const { data, error } = await useApiFetch("/user/detail")
|
||||
.get()
|
||||
.json();
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
const userinfo = data.value.data.userInfo;
|
||||
this.userName = userinfo.nickName || "";
|
||||
this.userAvatar = userinfo.userAvatar;
|
||||
this.isLoggedIn = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
@ -173,7 +173,7 @@ function totalStats(data) {
|
||||
bank: "银行失联异常",
|
||||
nbank_nsloan: "网络小额贷款",
|
||||
nbank_autofin: "汽车金融",
|
||||
nbank_sloan: "地方小戴",
|
||||
nbank_sloan: "地方小贷",
|
||||
nbank_finlea: "融资租赁",
|
||||
nbank_cons: "消费金融",
|
||||
nbank_other: "其他风险",
|
||||
@ -210,7 +210,7 @@ function lostSotalStats(data) {
|
||||
bank: "银行失联异常",
|
||||
nbank_nsloan: "网络小额贷款",
|
||||
nbank_autofin: "汽车金融",
|
||||
nbank_sloan: "地方小戴",
|
||||
nbank_sloan: "地方小贷",
|
||||
nbank_finlea: "融资租赁",
|
||||
nbank_cons: "消费金融",
|
||||
nbank_other: "其他风险",
|
||||
|
@ -1,62 +1,226 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<div class="p-4 bg-gradient-to-b from-blue-50/30 to-gray-50 min-h-screen">
|
||||
<!-- 资产卡片 -->
|
||||
<div class="bg-white bg-opacity-80 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl text-gray-900 font-semibold">可提现金额</span>
|
||||
<span class="text-3xl text-blue-500 font-bold">¥ 0.0</span>
|
||||
<div class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-blue-50/70 to-blue-100/50 p-6">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="balance-pay" class="text-blue-500 text-xl mr-2" />
|
||||
<span class="text-lg font-bold text-gray-800">余额</span>
|
||||
</div>
|
||||
<span class="text-3xl text-blue-600 font-bold">¥ {{ (data?.balance || 0).toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600">累计收益:¥ 0.0</div>
|
||||
<div class="mt-6 grid grid-cols-3 gap-4">
|
||||
<van-button type="primary" round icon="after-sale" @click="withDraw">前往提现</van-button>
|
||||
<van-button type="primary" round icon="todo-list-o">提现记录</van-button>
|
||||
<van-button type="primary" round icon="balance-list-o">收入明细</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 销售金额 -->
|
||||
<div class="bg-white bg-opacity-80 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg text-gray-900">销售金额</span>
|
||||
<span class="text-xl text-blue-500">¥ 0</span>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600">累计销售 0 分</div>
|
||||
</div>
|
||||
|
||||
<!-- 邀请下级收益 -->
|
||||
<div class="bg-white bg-opacity-80 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg text-gray-900">邀请下级收益</span>
|
||||
<span class="text-xl text-blue-500">¥ 0</span>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-600">累计邀请 1 位</div>
|
||||
</div>
|
||||
|
||||
<!-- 收益统计 -->
|
||||
<div class="bg-white bg-opacity-80 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg text-gray-900">收益统计</span>
|
||||
</div>
|
||||
<div class="mt-4 flex justify-between items-center">
|
||||
<button class="bg-blue-500 text-white px-6 py-2 rounded-lg shadow-md">
|
||||
近7天
|
||||
<div class="text-sm text-gray-500 mb-2">累计收益:¥ {{ (data?.total_earnings || 0).toFixed(2) }}</div>
|
||||
<div class="text-sm text-gray-500 mb-6">冻结余额:¥ {{ (data?.frozen_balance || 0).toFixed(2) }}</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button @click="toWithdraw" class="bg-gradient-to-r from-blue-500 to-blue-400 text-white rounded-full py-2 px-4
|
||||
shadow-md flex items-center justify-center">
|
||||
<van-icon name="gold-coin" class="mr-1" />
|
||||
提现
|
||||
</button>
|
||||
<button class="bg-gray-300 text-gray-700 px-6 py-2 rounded-lg shadow-md">
|
||||
近1个月
|
||||
<button @click="toWithdrawDetails" class="bg-white/90 text-gray-600 border border-gray-200/50 rounded-full py-2 px-4
|
||||
shadow-sm flex items-center justify-center">
|
||||
<van-icon name="notes" class="mr-1" />
|
||||
提现记录
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-6 text-xl text-gray-900">近7天累计收益:¥ 0.00 元</div>
|
||||
</div>
|
||||
|
||||
<!-- 直推报告收益 -->
|
||||
<div class="rounded-xl shadow-lg mb-4 bg-gradient-to-r from-blue-50/40 to-cyan-50/50 p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="balance-list" class="text-blue-400 text-xl mr-2" />
|
||||
<span class="text-lg font-bold text-gray-800">直推报告收益</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-2xl text-blue-600 font-bold">¥ {{ (data?.direct_push?.total_commission ||
|
||||
0).toFixed(2) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">有效报告 {{ data?.direct_push?.total_report || 0 }} 份</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<div class="grid grid-cols-3 gap-2 mb-6">
|
||||
<button v-for="item in promoteDateOptions" :key="item.value" @click="selectedPromoteDate = item.value"
|
||||
class="rounded-full transition-all py-1 px-4 text-sm" :class="[
|
||||
selectedPromoteDate === item.value
|
||||
? 'bg-blue-500 text-white shadow-md'
|
||||
: 'bg-white/90 text-gray-600 border border-gray-200/50'
|
||||
]">
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 mb-6">
|
||||
<div class="bg-blue-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="gold-coin" class="mr-1" />本日收益
|
||||
</div>
|
||||
<div class="text-xl text-blue-600 font-bold mt-1">¥ {{ currentPromoteData.commission?.toFixed(2) ||
|
||||
'0.00' }}</div>
|
||||
</div>
|
||||
<div class="bg-blue-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="description" class="mr-1" />有效报告
|
||||
</div>
|
||||
<div class="text-xl text-blue-600 font-bold mt-1">{{ currentPromoteData.report || 0 }} 份</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-blue-500 text-sm font-semibold cursor-pointer pt-4"
|
||||
@click="goToPromoteDetail">
|
||||
<span>查看收益明细</span>
|
||||
<span class="text-lg">→</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活跃下级奖励 -->
|
||||
<div class="rounded-xl shadow-lg bg-gradient-to-r from-green-50/40 to-cyan-50/30 p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div class="flex items-center">
|
||||
<van-icon name="friends" class="text-green-500 text-xl mr-2" />
|
||||
<span class="text-lg font-bold text-gray-800">活跃下级奖励</span>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="text-2xl text-green-600 font-bold">¥ {{ (data?.active_reward?.total_reward ||
|
||||
0).toFixed(2) }}</div>
|
||||
<div class="text-sm text-gray-500 mt-1">活跃下级 0 位</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 日期选择 -->
|
||||
<div class="grid grid-cols-3 gap-2 mb-6">
|
||||
<button v-for="item in activeDateOptions" :key="item.value" @click="selectedActiveDate = item.value"
|
||||
class="rounded-full transition-all py-1 px-4 text-sm" :class="[
|
||||
selectedActiveDate === item.value
|
||||
? 'bg-green-500 text-white shadow-md'
|
||||
: 'bg-white/90 text-gray-600 border border-gray-200/50'
|
||||
]">
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2 mb-6">
|
||||
<div class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="medal" class="mr-1" />本日奖励
|
||||
</div>
|
||||
<div class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.active_reward ||
|
||||
0).toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="discount" class="mr-1" />下级推广奖励
|
||||
</div>
|
||||
<div class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_promote_reward ||
|
||||
0).toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="contact" class="mr-1" />新增活跃奖励
|
||||
</div>
|
||||
<div class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_upgrade_reward ||
|
||||
0).toFixed(2) }}</div>
|
||||
</div>
|
||||
<div class="bg-green-50/60 p-3 rounded-lg backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-gray-500">
|
||||
<van-icon name="fire" class="mr-1" />下级转化奖励
|
||||
</div>
|
||||
<div class="text-xl text-green-600 font-bold mt-1">¥ {{ (currentActiveData.sub_withdraw_reward ||
|
||||
0).toFixed(2) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between text-green-500 text-sm font-semibold cursor-pointer pt-4"
|
||||
@click="goToActiveDetail">
|
||||
<span>查看奖励明细</span>
|
||||
<span class="text-lg">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const router = useRouter()
|
||||
const withDraw = () => {
|
||||
router.push({ name: "withdraw" })
|
||||
}
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const data = ref(null);
|
||||
|
||||
// 日期选项映射
|
||||
const dateRangeMap = {
|
||||
today: 'today',
|
||||
week: 'last7d',
|
||||
month: 'last30d'
|
||||
};
|
||||
|
||||
// 直推报告数据
|
||||
const promoteDateOptions = [
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '近7天', value: 'week' },
|
||||
{ label: '近1月', value: 'month' }
|
||||
];
|
||||
const selectedPromoteDate = ref('today');
|
||||
|
||||
// 活跃下级数据
|
||||
const activeDateOptions = [
|
||||
{ label: '今日', value: 'today' },
|
||||
{ label: '近7天', value: 'week' },
|
||||
{ label: '近1月', value: 'month' }
|
||||
];
|
||||
const selectedActiveDate = ref('today');
|
||||
|
||||
// 计算当前直推数据
|
||||
const currentPromoteData = computed(() => {
|
||||
const range = dateRangeMap[selectedPromoteDate.value];
|
||||
return data.value?.direct_push?.[range] || { commission: 0, report: 0 };
|
||||
});
|
||||
|
||||
// 计算当前活跃数据
|
||||
const currentActiveData = computed(() => {
|
||||
const range = dateRangeMap[selectedActiveDate.value];
|
||||
return data.value?.active_reward?.[range] || {
|
||||
active_reward: 0,
|
||||
sub_promote_reward: 0,
|
||||
sub_upgrade_reward: 0,
|
||||
sub_withdraw_reward: 0
|
||||
};
|
||||
});
|
||||
|
||||
const getData = async () => {
|
||||
const { data: res, error } = await useApiFetch("/agent/revenue")
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
data.value = res.value.data;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
const token = localStorage.getItem("token")
|
||||
if (token) {
|
||||
getData();
|
||||
}
|
||||
});
|
||||
|
||||
// 路由跳转
|
||||
const goToPromoteDetail = () => router.push({ name: "promoteDetails" });
|
||||
const goToActiveDetail = () => router.push({ name: "rewardsDetails" });
|
||||
|
||||
const toWithdraw = () => router.push({ name: "withdraw" });
|
||||
|
||||
const toWithdrawDetails = () => router.push({ name: "withdrawDetails" });
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 在这里你可以添加额外的样式 */
|
||||
<style>
|
||||
/* 添加按钮悬停效果 */
|
||||
button {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
</style>
|
477
src/views/AgentManageAgreement.vue
Normal file
@ -0,0 +1,477 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-4 text-gray-800">
|
||||
<div class="box">
|
||||
<p class="text-center font-bold text-xl mb-4">代理管理制度</p>
|
||||
|
||||
<p class="indent-8 mb-2"><span>一、</span><strong>前言</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
海南天远大数据科技有限公司为加强对全国代理的统一管理,规范各代理行为,确保"天远数据"的顺利推广,特依据如下原则制定代理管理制度,望各级代理认真贯彻、严格遵守。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">1.谨慎性原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
本着对双方负责的态度,请各级代理务必认真贯彻执行本管理制度的工作程序,不可草率行事。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">2.用心协助原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
海南天远大数据科技有限公司配合各代理的工作,对于代理在推广工作中遇到的问题用心配合解决。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">3.诚信的原则</p>
|
||||
<p class="indent-8 mb-2">双方务必诚实有信用,决不提供虚假信息。</p>
|
||||
<p class="indent-8 mb-2">4.严格管理原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
认真贯彻执行各项管理制度。对违反管理制度的代理,坚决按制度规定予以处罚,直至取消代理资格,决不姑息迁就。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">5.双方共赢原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
海南天远大数据科技有限公司的目标是与代理共赢,共同发展。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">6.长期性原则</p>
|
||||
<p class="indent-8 mb-2">
|
||||
立足市场,与代理长期协作,确保代理用心放心地进行市场推广工作。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>二、总则</strong></p>
|
||||
<p class="indent-8 mb-2">第一条 代理期限为一年,代理协议实行一年一签制。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第二条 本制度规定海南天远大数据科技有限公司代理(以下称代理)权限、运作及业务处理等相关事项,旨在使海南天远大数据科技有限公司与各代理之间持续良好合作关系,促进双方共同发展;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第三条 代理经海南天远大数据科技有限公司授权并自代理协议书生效之日起,应严格依照代理协议及本制度的规定履行义务,享受权利。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第四条 海南天远大数据科技有限公司确定的代理应遵循海南天远大数据科技有限公司的规定从事代理活动,不得做出损害海南天远大数据科技有限公司利益和形象的行为;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
第五条 代理在代理推广过程中,应妥善处理做好售前、售中、售后的咨询维护工作。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>三、开通代理账户要求</strong></p>
|
||||
<p class="indent-8 mb-2"><strong>个人类:</strong></p>
|
||||
<p class="indent-8 mb-2">1、完全民事行为能力人。</p>
|
||||
<p class="indent-8 mb-2">2、本人实名认证的手机号。</p>
|
||||
<p class="indent-8 mb-2">3、首次提现时必须进行本人实名认证并进行人脸识别。</p>
|
||||
<p class="indent-8 mb-2">4、全面赞同天远数据的各项制度,并能积极参加天远数据为各代理所举办的各种活动;</p>
|
||||
|
||||
<p class="indent-8 mb-2">企业类:</p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、具有独立法人资格,并能提供有效营业执照、组织代码证等相关文件复印件,经审查合格签定代理协议后即成为海南天远大数据科技有限公司认证代理。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、应具备良好的经营规模、办公条件、设备及人员,有固定的营业场所,良好的资信潜力和商业信誉。并提供以下资料:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">◆营业执照复印件</p>
|
||||
<p class="indent-8 mb-2">◆身份证复印件</p>
|
||||
<p class="indent-8 mb-2">◆代理合作协议</p>
|
||||
<p class="indent-8 mb-2">◆业务场景展示</p>
|
||||
<p class="indent-8 mb-2">3、全面赞同天远数据的各项制度,并能积极参加天远数据为各代理所举办的各种活动;</p>
|
||||
|
||||
<p class="indent-8 mb-4"><strong>四、代理权利和义务</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
在成为海南天远大数据科技有限公司的认证代理后,可享有如下权利并承担相应的义务:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">1、使用天远数据开展广告宣传、市场推广活动;</p>
|
||||
<p class="indent-8 mb-2">2、维护海南天远大数据科技有限公司及其产品的良好形象;</p>
|
||||
<p class="indent-8 mb-2">3、开拓下级业务推广并负责对其定期进行业务培训;</p>
|
||||
<p class="indent-8 mb-2">4、推广过程中做好售前、售中、售后工作。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、如用户需要开具发票,代理则需向用户开具(咨询费)发票。如代理未开具发票,天远数据有义务配合税务机关采取相关措施。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
6、代理业务推广过程中,未经海南天远大数据科技有限公司授权,不得使用"天远数据官方"词汇用于广告宣传。
|
||||
</p>
|
||||
|
||||
<p class="font-bold mb-2">五、推广管理</p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、天远数据负责建立与代理之间的沟通与联系渠道,不定期地向代理提供宣传资料、信息、政策以及推广方案与管理制度等方面的支持。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、海南天远大数据科技有限公司充分尊重代理代理推广权,但有下列状况之一时,海南天远大数据科技有限公司将保留或者取消该代理的权利:
|
||||
</p>
|
||||
<p class="indent-8 mb-2">a代理经营管理不善,造成工作无法正常开展的;</p>
|
||||
<p class="indent-8 mb-2">b国家政策变化等不可抗力发生时;</p>
|
||||
<p class="indent-8 mb-2">c遇有客户投诉,经确认属代理操作不当的;</p>
|
||||
<p class="indent-8 mb-2">d其他严重损害海南天远大数据科技有限公司形象与产品形象的行为发生时;</p>
|
||||
<p class="indent-8 mb-2">e违反国家法律法规时;</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、当代理名下发生投诉时,代理需配合相关的协调。否则海南天远大数据科技有限公司有权无条件取消其代理资格,终止其代理协议。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">4、代理应合规宣传海南天远大数据科技有限公司产品形象。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、市场运作过程中,各代理在接到市场投诉时,应及时做好记录,并报海南天远大数据科技有限公司相关部门妥善处理。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>六、违规处罚</strong></p>
|
||||
<p class="indent-8 mb-2">
|
||||
1、各代理在推广海南天远大数据科技有限公司过程中,有损害海南天远大数据科技有限公司产品信誉行为时,视情节轻重,海南天远大数据科技有限公司将对其提出书面警告直至取消其代理资格;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、未按海南天远大数据科技有限公司有关规定和本制度开展工作的,海南天远大数据科技有限公司将提出书面警告并限期整改;
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
3、不遵守海南天远大数据科技有限公司的相关规章制度,造成与其他推广代理纠纷时,海南天远大数据科技有限公司将视其情节轻重,处以20000元以上50000元以下的罚款,并取消其代理资格。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、违反保密义务,导致海南天远大数据科技有限公司重大损失的,海南天远大数据科技有限公司将对其处以5000-20000元罚款,情节严重者将直接取消其代理资格。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、代理如严重违反海南天远大数据科技有限公司相关规章制度,海南天远大数据科技有限公司可随时解除双方约定的部分或全部协议。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>七、投诉类处罚</strong></p>
|
||||
<p class="indent-8 mb-2">1、代理账户累计投诉率处罚措施</p>
|
||||
<p class="indent-8 mb-2">a.月查询报告数量≥200单</p>
|
||||
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">处理类型</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">限制修改查询售价</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥5%<8%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+1</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥8%<10%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+3</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥10%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+5</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">b.月查询报告数量<100单</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">处理类型</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">限制修改查询售价</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥6%<8%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+1</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥8%<15%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+5</td>
|
||||
<td class="border border-gray-300 p-2 text-center">49</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥15%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">c.月查询报告数量<50单</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">处理类型</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">限制修改查询售价</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥15%<20%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+3</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥20%<50%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">+5</td>
|
||||
<td class="border border-gray-300 p-2 text-center">39</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">投诉率≥50%</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">
|
||||
备注:针对客户自身原因投诉,对于产生投诉的代理账户只有三次加底价机会(底价只加不减),到第四次时直接封号。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">执行时间:每月1号出数据统计,2号执行。</p>
|
||||
|
||||
<p class="indent-8 mb-2">2、代理单笔投诉处罚措施</p>
|
||||
<div class="mb-4 overflow-x-auto">
|
||||
<table class="w-full border-collapse border border-gray-300">
|
||||
<thead>
|
||||
<tr class="bg-gray-100">
|
||||
<th class="border border-gray-300 p-2 w-40">处理类型</th>
|
||||
<th class="border border-gray-300 p-2">提高底价</th>
|
||||
<th class="border border-gray-300 p-2">冻结推广收益</th>
|
||||
<th class="border border-gray-300 p-2">单笔风险资金冻结</th>
|
||||
<th class="border border-gray-300 p-2">罚款</th>
|
||||
<th class="border border-gray-300 p-2">禁止提现</th>
|
||||
<th class="border border-gray-300 p-2">封号</th>
|
||||
<th class="border border-gray-300 p-2">黑名单</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">网络公开恶意投诉(非欺诈类可解)</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">支付宝投诉</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">政务部门投诉</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">(非产品质量类)客户一般退款</td>
|
||||
<td class="border border-gray-300 p-2 text-center">1+退款次数(最高10元)</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="border border-gray-300 p-2">受代理教唆客户恶意退款</td>
|
||||
<td class="border border-gray-300 p-2 text-center">5+退款次数</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">√</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
<td class="border border-gray-300 p-2 text-center">×</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p class="indent-8 mb-2">3、扬言给客户做"数据修复"类投诉措施</p>
|
||||
<p class="indent-8 mb-2">第一步:每接到此类投诉一次,所属代理底价+10元/次,并警告。</p>
|
||||
<p class="indent-8 mb-2">第二步:警告无效后,依旧发生则采取直接封号并加入黑名单。</p>
|
||||
<p class="indent-8 mb-2 text-red-600">备注:</p>
|
||||
<p class="indent-8 mb-2 text-red-600">1)√为执行项 ×为不执行项</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
2)一般投诉罚款投诉金额1倍(除退还投诉金额外,另行按投诉金额1倍的标准进行罚款)。
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
如:投诉金额为45元,则退还用户投诉金额:45元,代理罚款:45*1=45元
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
3)公开投诉,代理罚款2倍/封号(依据具体严重情况而定,除退还投诉金额外,另行按投诉金额2倍的标准进行罚款)。
|
||||
</p>
|
||||
<p class="indent-8 mb-2 text-red-600">如:投诉金额为45元,退还用户金额:45元,则代理罚款:45*2=90元</p>
|
||||
<p class="indent-8 mb-2 text-red-600">
|
||||
4)如代理发生单笔投诉涉及到本制度第六条所规定的事由,将按第六条、第七条处罚规则合并执行。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2">4、冻结、封禁类代理处罚措施</p>
|
||||
<p class="indent-8 mb-2">
|
||||
a.自冻结、封禁之日起,3个月之内无任何新的投诉、舆情升级、违法犯罪情形,代理可向平台提交"提现申请函",平台根据处罚规则先行处罚后,对可提现余额再追加10%的罚款。代理可在7个工作日内完成相关的提现操作。但该代理账户的推广功能,则进入到审核期。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
b.自冻结、封禁之日起,3个月之内有新的投诉、舆情升级、违法犯罪情形,则平台对该账户的审查期将会延长,审查期间无法提现。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-4"><strong>八、封号规则</strong></p>
|
||||
<p class="mb-2">
|
||||
1、同一个设备频繁更换账号登录,或同一个账号频繁在多个设备登陆,系统自动自动检测手机登录IP和设备信息,有封号风险(一机一号,不要频繁切换设备或者账户);
|
||||
</p>
|
||||
<p class="mb-2">2、欺诈用户(诱导用户);</p>
|
||||
<p class="mb-2">3、先付款后退款等承诺;</p>
|
||||
<p class="mb-2">4、保证高额下款;</p>
|
||||
<p class="mb-2">5、使用数据优化(征信优化)等骗取用户钱财。</p>
|
||||
<p class="mb-2">6、发布涉嫌性骚扰的文字、图片;</p>
|
||||
<p class="mb-2">7、使用含色情、淫秽意味或其他令人不适的头像或资料;</p>
|
||||
<p class="mb-2">8、触犯新广告法;</p>
|
||||
<p class="mb-2">9、在朋友圈中使用辱骂、恐吓、威胁等言论;</p>
|
||||
<p class="mb-2">10、发布各类垃圾广告、恶意信息、诱骗信息;</p>
|
||||
<p class="mb-2">11、盗用他人头像或资料,伪装他人身份;</p>
|
||||
<p class="mb-2">12、多人举报的账号并涉及恶意诈骗;</p>
|
||||
<p class="mb-2">13、频繁被举报,每月超过20次以上的代理账户。</p>
|
||||
<p class="mb-2">14、恶意投诉,比如没有异议非说有异议且无法提供有效证明材料,各种奇葩投诉。</p>
|
||||
<p class="mb-2">15、租用账号,发布不良言论,诈骗信息。</p>
|
||||
<p class="mb-4">16、发布不当政治言论或者任何违反国家法规政策的言论。</p>
|
||||
<p class="mb-4">更多详细内容请认真阅读天远数据《代理协议》。</p>
|
||||
|
||||
<h3 class="font-bold mb-2">退款的规则及途径</h3>
|
||||
<h4 class="font-bold mb-2">【退款规则】</h4>
|
||||
<p class="mb-2">1、自订单支付完成后30天内为有效期,在30天内可申请退款。</p>
|
||||
<p class="mb-2">2、超过报告有效期30天,则无法办理退款。</p>
|
||||
<p class="mb-2">3、符合相关退款条件的用户,退款时仅退还实付金额。</p>
|
||||
<p class="mb-2">
|
||||
4、用户购买报告成功后,因不可抗力等法定原因或平台原因,导致平台无法提供服务,用户可联系客服,发起退款。
|
||||
</p>
|
||||
<p class="mb-2">5、若因用户的失误重复付款,则支持退款重复金额。</p>
|
||||
<p class="mb-2">6、服务已发生且不符合退款情形的费用不予退款。</p>
|
||||
<p class="mb-2">7、如代理在市场推广中存在欺诈等相关行为,用户可提供有效的凭证办理退款事宜。</p>
|
||||
<p class="mb-4">8、产品呈现的情况与用户本人实际情况不符,用户可提供有效的凭证发起退款申请。</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【用户发起投诉】</h4>
|
||||
<p class="mb-2">1.当代理拒绝退款,用户与代理双方线下也未达成一致时,用户可联系客服发起投诉。</p>
|
||||
<p class="mb-2">2.用户提交投诉后,请用户和代理按照相关提示举证,完成举证后客服将介入处理纠纷。</p>
|
||||
<p class="mb-2">
|
||||
3.平台客服介入前,若用户与代理双方已对退款协商一致,商家可直接联系平台客服说明情况,或者用户联系平台提供撤销投诉函并说明情况即可,同时投诉会关闭。
|
||||
</p>
|
||||
<p class="mb-2">4.平台客服介入后,若需要用户与代理提供举证信息,可发送相关材料至邮箱,方便客服及时处理。</p>
|
||||
<p class="mb-4">5.平台客服会根据举证信息,联系用户与代理双方处理投诉。</p>
|
||||
|
||||
<h5 class="indent-8 font-bold mb-2">九、退款服务以及流程</h5>
|
||||
<p class="indent-8 mb-4">
|
||||
自用户购买查询报告成功之日起,无论由于何种原因,用户均可向平台申请退款(不适用退款服务的情况除外)。
|
||||
</p>
|
||||
|
||||
<div class="flex flex-col items-center mb-4">
|
||||
<p class="mb-2">用户发起退款申请</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">提供有效证明凭证</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">平台审核是否符合退款标准</p>
|
||||
<p class="mb-2">↓</p>
|
||||
<p class="mb-2">确认无误后退款完成</p>
|
||||
<p class="mb-2">(预计1-7个工作日内完成)</p>
|
||||
</div>
|
||||
|
||||
<h4 class="font-bold mb-2">【退款流程说明】</h4>
|
||||
<p class="indent-8 mb-2">
|
||||
原路返回----直接把金额退回到用户付款的来源方,包括但不限于支付宝帐户,暂不收取手续费。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">
|
||||
具体操作流程:在发生退款时,用户可在查询页面点击"联系客服"发起"申请退款"申请,并提供有效的证明凭证,客服提交至平台系统,系统通过审核后,相关的退款金额将在1-7个工作日内原路返回对应的付款账户中。通过网银或支付宝等第三方支付平台进行支付的费用将直接退到原账户。
|
||||
</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【不适用退款服务的情况】</h4>
|
||||
<p class="indent-8 mb-2">1、已超过退款期限;</p>
|
||||
<p class="indent-8 mb-2">2、恶意投诉;</p>
|
||||
<p class="indent-8 mb-4">3、违反《用户使用协议》相关规则。</p>
|
||||
|
||||
<h4 class="font-bold mb-2">【补充说明】</h4>
|
||||
<p class="indent-8 mb-4">
|
||||
如您需要退款的产品类型不在以上30天,或者超出了30 天限制,则无法办理退款。如您有产品使用方面的疑问,您可以通过联系客服进行反馈。
|
||||
</p>
|
||||
|
||||
<p class="indent-8 mb-2"><strong>十、附则</strong></p>
|
||||
<p class="indent-8 mb-2">1、本制度作为《代理协议》之附件与《代理协议》具有同等法律效力。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
2、海南天远大数据科技有限公司将本着"诚信为本、长期服务"的宗旨和"公平合理"的原则对代理进行合理布局和调整,以实现互利互惠、共同快速发展的目的。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">3、因其他原因需终止代理关系,需向海南天远大数据科技有限公司提出书面申请。</p>
|
||||
<p class="indent-8 mb-2">
|
||||
4、代理之间发生业务竞争和冲突,海南天远大数据科技有限公司将依据公平、公正、公开的原则按相关制度予以调解、处理。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
5、如海南天远大数据科技有限公司与各代理之间出现协议上的纠纷,由海南天远大数据科技有限公司所在地法院裁决。
|
||||
</p>
|
||||
<p class="indent-8 mb-2">
|
||||
6、本制度的制定、修改与废止皆经由海南天远大数据科技有限公司讨论决定,解释权归海南天远大数据科技有限公司所有。
|
||||
</p>
|
||||
<p class="indent-8 mb-4">7、本制度于2022年1月1日起实施,公司将根据实施情况对本制度进行修正和调整。</p>
|
||||
|
||||
<p class="indent-8 mb-8">本制度一经网上点击/勾选,即代表理解并同意勾选遵守。</p>
|
||||
|
||||
<p class="text-right">本制度于 {{ effectiveDate }} 生效。</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 获取当前日期并格式化为中文格式
|
||||
const getFormattedDate = () => {
|
||||
const date = new Date()
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
return `${year} 年 ${month} 月 ${day} 日`
|
||||
}
|
||||
|
||||
const effectiveDate = ref(getFormattedDate())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
117
src/views/AgentPromoteDetails.vue
Normal file
@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 收益列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
|
||||
<span class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getReportTypeStyle(item.product_name)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.product_name)"></span>
|
||||
{{ item.product_name }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 颜色配置(根据产品名称映射)
|
||||
const typeColors = {
|
||||
'老板企业报告': { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' },
|
||||
'人事背调': { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500' },
|
||||
'家政风险': { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' },
|
||||
'婚恋风险': { bg: 'bg-pink-100', text: 'text-pink-800', dot: 'bg-pink-500' },
|
||||
'贷前背调': { bg: 'bg-orange-100', text: 'text-orange-800', dot: 'bg-orange-500' },
|
||||
'租赁风险': { bg: 'bg-indigo-100', text: 'text-indigo-800', dot: 'bg-indigo-500' },
|
||||
'个人风险': { bg: 'bg-red-100', text: 'text-red-800', dot: 'bg-red-500' },
|
||||
// 默认类型
|
||||
'default': { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const data = ref({
|
||||
total: 0,
|
||||
list: []
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
// 获取颜色样式
|
||||
const getReportTypeStyle = (name) => {
|
||||
const color = typeColors[name] || typeColors.default
|
||||
return `${color.bg} ${color.text}`
|
||||
}
|
||||
|
||||
// 获取小圆点颜色
|
||||
const getDotColor = (name) => {
|
||||
return (typeColors[name] || typeColors.default).dot
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
await getData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/commission?page=${page.value}&page_size=${pageSize.value}`
|
||||
).get().json()
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
// 首次加载
|
||||
if (page.value === 1) {
|
||||
data.value = res.value.data
|
||||
} else {
|
||||
// 分页加载
|
||||
data.value.list.push(...res.value.data.list)
|
||||
}
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.list.length >= res.value.data.total ||
|
||||
res.value.data.list.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 列表项入场动画 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
/* 适配vant组件 */
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
137
src/views/AgentRewardsDetails.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 收益列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
|
||||
<span class="text-green-500 font-bold">+{{ item.amount.toFixed(2) }}</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getReportTypeStyle(item.type)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.type)"></span>
|
||||
{{ typeToChinese(item.type) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 类型映射配置
|
||||
const typeConfig = {
|
||||
descendant_promotion: {
|
||||
chinese: '下级推广奖励',
|
||||
color: { bg: 'bg-blue-100', text: 'text-blue-800', dot: 'bg-blue-500' }
|
||||
},
|
||||
descendant_upgrade_vip: {
|
||||
chinese: '下级升级VIP奖励',
|
||||
color: { bg: 'bg-green-100', text: 'text-green-800', dot: 'bg-green-500' }
|
||||
},
|
||||
descendant_upgrade_svip: {
|
||||
chinese: '下级升级SVIP奖励',
|
||||
color: { bg: 'bg-purple-100', text: 'text-purple-800', dot: 'bg-purple-500' }
|
||||
},
|
||||
descendant_stay_activedescendant: {
|
||||
chinese: '下级活跃奖励',
|
||||
color: { bg: 'bg-pink-100', text: 'text-pink-800', dot: 'bg-pink-500' }
|
||||
},
|
||||
new_active: {
|
||||
chinese: '新增活跃奖励',
|
||||
color: { bg: 'bg-orange-100', text: 'text-orange-800', dot: 'bg-orange-500' }
|
||||
},
|
||||
descendant_withdraw: {
|
||||
chinese: '下级提现奖励',
|
||||
color: { bg: 'bg-indigo-100', text: 'text-indigo-800', dot: 'bg-indigo-500' }
|
||||
},
|
||||
default: {
|
||||
chinese: '其他奖励',
|
||||
color: { bg: 'bg-gray-100', text: 'text-gray-800', dot: 'bg-gray-500' }
|
||||
}
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const data = ref({
|
||||
total: 0,
|
||||
list: []
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
// 类型转中文
|
||||
const typeToChinese = (type) => {
|
||||
return typeConfig[type]?.chinese || typeConfig.default.chinese
|
||||
}
|
||||
|
||||
// 获取颜色样式
|
||||
const getReportTypeStyle = (type) => {
|
||||
const config = typeConfig[type] || typeConfig.default
|
||||
return `${config.color.bg} ${config.color.text}`
|
||||
}
|
||||
|
||||
// 获取小圆点颜色
|
||||
const getDotColor = (type) => {
|
||||
return typeConfig[type]?.color.dot || typeConfig.default.color.dot
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
if (!finished.value) {
|
||||
page.value++
|
||||
await getData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据
|
||||
const getData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/rewards?page=${page.value}&page_size=${pageSize.value}`
|
||||
).get().json()
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
if (page.value === 1) {
|
||||
data.value = res.value.data
|
||||
} else {
|
||||
data.value.list.push(...res.value.data.list)
|
||||
}
|
||||
|
||||
if (data.value.list.length >= res.value.data.total ||
|
||||
res.value.data.list.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(() => {
|
||||
getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持原有样式不变 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
277
src/views/AgentServiceAgreement.vue
Normal file
@ -0,0 +1,277 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="container mx-auto p-4 text-gray-800">
|
||||
<p class="text-center font-bold text-lg">信息技术服务合同
|
||||
</p>
|
||||
<p class="text-left"><span></span></p>
|
||||
<p class="text-left"><span class="text-black">甲方:</span></p>
|
||||
<p class="text-left"><span class="text-black">乙方:</span><span
|
||||
class="text-black">海南天远大数据科技有限公司</span>
|
||||
</p>
|
||||
<p class="text-left"> </p>
|
||||
<p class="text-left"><span class="text-black">鉴于:</span></p>
|
||||
<p class="text-left"><span class="text-black">1.
|
||||
甲方为其合法合规经营之业务,依法需对甲方最终用户或有关交易和交往利害关系主体有关信息进行识别;</span></p>
|
||||
<p class="text-left"><span class="text-black">2.
|
||||
乙方具备相关信息技术之专业能力,能够为甲方提供相应服务;</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
现双方根据《中华人民共和国</span><span class="text-black">民法典</span><span
|
||||
class="text-black">》等相关法律法规,本着诚实、信用、公平,促进社会诚信发展为原则,经友好协商就</span><span
|
||||
class="text-black">海南天远大数据科技有限公司</span><span
|
||||
class="text-black">信息技术服务事宜达成一致,签订本合同。</span></p>
|
||||
<p class="text-left"><span></span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">一、
|
||||
释义</span></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
除上下文另有约定外,下列用语具有如下含义:</span></p>
|
||||
<p class="text-left"><span class="text-black">1.1
|
||||
</span><span class="text-black">海南天远大数据科技有限公司</span><span
|
||||
class="text-black">信息技术服务/服务
|
||||
指乙方通过信息化、人工智能和信息科技等技术手段对</span><span class="text-black">大数据</span><span
|
||||
class="text-black">进行以公众号、小程序、APP、web页面(以下简称平台)或标准接口形式为客户提供的服务,协助客户完成信息的整理、管理等业务流程。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">1.2
|
||||
本合同 指本文本协议,及其形成本合同不可分割的附件。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.3
|
||||
本合同标题仅供方便参考之用,不影响本合同的含义与解释。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.4
|
||||
本合同甲方、乙方单独称为“一方”,合称为“双方”。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.5
|
||||
法律法规 指中国法律、行政法规、部门规章、地方性法规、地方性政府部门规章以及由政府机构颁布的其他规范性文件。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.6
|
||||
工作日 指法定节假日、休息日之外的日期。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.7
|
||||
服务有效期 指乙方依据本合同提供服务的期限。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.8
|
||||
授权
|
||||
指甲方最终用户以书面签名或法律效力等同于书面的电子签名等方式明确同意甲方向第三方服务商(指乙方并含其关联方,下同)提供本人/单位相关数据信息(包括但不限于个人信息、行为、交易、设备、不良信息,下同),及同意该第三方服务提供商查询、核实、搜集、处理、共享、使用(含合法业务应用)其本人/单位相关数据的行为,但是法律法规规定可以不经同意的除外。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">1.9
|
||||
API(Application Programming Interface,应用程序编程接口)
|
||||
指一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.10
|
||||
Web应用 指一种可以通过Web访问的应用程序,由完成特定任务的各种Web组件(web
|
||||
components)构成的并通过Web将服务展示给客户。</span></p>
|
||||
<p class="text-left"><span class="text-black">1.11
|
||||
SFTP(Secure File Transfer Protocol,安全文件传送协议)
|
||||
指可以为传输文件提供一种安全的加密方法。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">二、
|
||||
合作内容与方式</span></p>
|
||||
<p class="text-left"><span class="text-black">2.1
|
||||
根据本合同约定的条件和条款,甲方使用乙方提供的【</span><span
|
||||
class="text-black">海南天远大数据科技有限公司</span><span class="text-black">相关</span><span
|
||||
class="text-black">信息技术】服务(简称“乙方服务”或“本服务”)。</span></p>
|
||||
<p class="text-left"><span class="text-black">2.2
|
||||
|
||||
乙方负责提供服务平台或接口,供甲方通过平台或接口使用本服务。乙方向甲方提供的平台或接口是使用本服务的重要凭证,除非另有约定或说明,甲方通过该平台或接口向乙方所发出的指令及相关行为均视为甲方的行为。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">2.3
|
||||
甲方通过乙方提供的平台或API/Web应用向乙方发起服务需求,乙方通过平台或API/Web应用将服务结果返回给甲方。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">三、
|
||||
费用及支付</span></p>
|
||||
<p class="text-left"><span class="text-black">3.1
|
||||
本合同采用先付费,后使用的计价模式。甲方依据自主选择的服务项目对应费用向乙方支付服务费用。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.2
|
||||
服务有效期及支付方式</span></p>
|
||||
<p class="text-left"><span class="text-black">3.2.1
|
||||
服务有效期以1年为准。
|
||||
除非另有约定,充值金额使用完毕/流量限额全部用完或服务有效期届满之日(以先到者为准)且未在服务有效期内续费或者续流量的,则本合同即终止;在服务有效期内继续充值的,则续充的服务有效期为该次充值之后12个月;如当期服务有效期届满而预付金额/流量限额未用完,乙方无需退还服务费并不予以延期使用。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">3.3
|
||||
支付方式:</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.1
|
||||
甲方向乙方一次性</span><span class="text-black">或分多次</span><span
|
||||
class="text-black">支付服务费。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.2
|
||||
乙方根据甲方使用功能和次数实时计费,并从甲方已支付费用中扣除。</span></p>
|
||||
<p class="text-left"><span class="text-black">3.3.3
|
||||
若甲方拟继续使用服务的,则须在预付费/流量使用完毕后,继续充值。具体充值的服务项目和价格见平台展示。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">四、
|
||||
权利与义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1
|
||||
甲方权利与义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.1
|
||||
甲方有权根据本合同约定向乙方提起服务需求</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.2
|
||||
甲方</span><span
|
||||
class="text-black">使用乙方服务,不得违反《中华人民共和国个人信息保护法》及《征信业务管理办法》的相关规定,</span><span
|
||||
class="text-black">对乙方所提供的服务信息予以保密,且严格遵守法律法规,不从事任何侵犯个人信息或商业秘密的活动。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.1.3
|
||||
甲方向乙方提交的服务需求及相关数据,均已经过被查询用户、最终用户充分有效的书面或电子授权,同意乙方及乙方的关联合作方对相关数据进行获取,并在乙方系统中或提交数据源方用于该合同项下的识别并进行储存、分析、处理及使用(含业务应用)等合法行为,甲方对此充分理解并认可。</span>
|
||||
</p>
|
||||
<p class="text-left"><span
|
||||
class="text-black">根据乙方要求,甲方应当向乙方提供由最终用户签字的授权文件,乙方仅予以形式审查;乙方对授权文件的审查不免除甲方就授权的真实性、合法性、完整性、与本合同约定的一致性所承担的责任。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.1.4
|
||||
甲方需向乙方提供经乙方服务识别有相关用户之信息及验证结果反馈。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.5
|
||||
未经乙方同意,甲方不得将乙方提供的服务进行宣传或使用乙方名称、业务介绍、标识、商标、知识产权等。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.6
|
||||
甲方应限于自身使用乙方提供的产品和服务,不得从事与乙方有竞争关系的业务。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.1.7
|
||||
除为实现本合同目的确有必要外,甲方不将获得的服务信息进行存储、复制、下载、打印。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2
|
||||
乙方的权利和义务</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.1
|
||||
负责提供服务,向甲方提供标准平台服务或接口文件,为甲方开通服务账号。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.2
|
||||
乙方负责其系统的设计、开发,使系统能够支撑合作业务正常运行,并有义务对甲方提出的维护请求给予支持。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.3
|
||||
乙方为优化对甲方提供的服务,可对其服务系统进行升级、调试等处理。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.4
|
||||
在乙方受甲方委托为甲方提供信息技术服务中所产生的后果(包括但不限于因甲方提供数据缺乏真实性或准确性导致服务偏差、乙方在受托服务中与甲方用户发生纠纷或遭投诉的),由甲方负责处理与承担。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.2.5
|
||||
如甲方未按约定支付服务费时,乙方有权中止服务。</span></p>
|
||||
<p class="text-left"><span class="text-black">4.2.6
|
||||
因甲方违反合同保密、授权条款或违反约定使用本服务(包括但不限于5.1.6、5.1.7、5.1.8)的,乙方可以中止或终止服务,并向甲方索赔对乙方造成损失的违约费用;</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">4.2.7
|
||||
乙方为履行本合同在不影响甲方权利的前提下可以通过乙方关联公司向甲方提供服务。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">五、
|
||||
知识产权</span></p>
|
||||
<p class="text-left"><span class="text-black">5.1
|
||||
|
||||
乙方享有本合同产品和服务相关内容之知识产权与所有权包括但不限于:软件、程序、源代码、文档、专利、商标、著作权、域名、专有技术、商业秘密、文字表达及其组合、数据、数据变量、数据算法、数据/模型、图标、图饰、图表、色彩、界面设计;除非经乙方许可,本合同并不赋予甲方享有乙方任何知识产权上的权利。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">5.2
|
||||
除非另有约定,任何一方均不可凭借本合同取得另一方所拥有的著作权、专利权、商标权或其他知识产权。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">六、
|
||||
保密与信息安全</span></p>
|
||||
<p class="text-left"><span class="text-black">
|
||||
除非双方签订《商业保密协议》对保密另有约定外,需履行以下保密约定:
|
||||
</span></p>
|
||||
<p class="text-left"><span class="text-black">6.1
|
||||
|
||||
保密信息:提供方(或其母公司、子公司、关联公司)向接受方披露的信息包括但不限于本合同内容、合作模式、商业计划、投资、经营方案、分析或计算方法、系统、数据、数据变量、数据算法、数据/模型、程序、装置、规格、序列、设计、研究或开发活动和方案、知识产权、专有技术、服务信息与服务说明、业务与营销方案、推广方法、销售额、客户名单、商业机会、成本、价格及其他财务信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.2
|
||||
|
||||
保密信息不包括:(1)在收到保密信息之时或之前已合法知悉的信息且该信息不受保密义务约束;(2)非因接受方违约而成为公众信息的信息;(3)接受方从披露方及其关联方以外的其他信息源所获知的信息;(4)提供方未明示为保密信息的信息;(5)接受方未利用任何保密信息而合法独立开发的信息,或通过接受方信息数据分析而获知的信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.3
|
||||
|
||||
上述保密信息可以以数据、文字及记载上述内容的书面资料、图书、录音资料、录像资料、光盘、软件、网页、客户端等有形媒介体现,也可通过口头等形式体现。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.4
|
||||
任何一方在提供保密信息时,如以书面形式提供,应注明‘保密’等相关字样;如以口头或可视形式透露,应在披露前明示接受方为保密信息。</span></p>
|
||||
<p class="text-left"><span class="text-black">6.5
|
||||
|
||||
除另有约定外,未经披露方书面同意,须将保密信息严格保密,并不得直接或间接导致、准许或容许向任何第三方披露、公布、转移、挪用或泄露保密信息;接受方均不向第三方及其不必要知悉的员工披露保密信息。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.6
|
||||
|
||||
除另有约定外,承担保密义务的范围不包括:(1)由乙方提供给数据源方的;(2)提供给乙方关联公司的;(3)将经适当汇总、编辑、修改、整理的保密信息在必要与合理范围内提供给律师和会计师及其他专业服务提供者;(4)按照法律、法规、监管、有管辖权的法院要求须提供的信息,但在不禁止的情况下应立即向保密信息提供方予以通报。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.7
|
||||
双方确认除非保密信息依法公开,保密义务自本合同签订时开始持续有效;甲方是否继续使用乙方的服务,不影响保密义务的承担。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">6.8
|
||||
|
||||
双方均应当遵守法律法规关于信息安全的管理规定,并采取有效措施保障信息安全包括但不限于保障计算机系统及其相关配套设备、设施(含网络)、运行环境、信息系统功能的安全运行等。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">七、
|
||||
反商业贿赂</span></p>
|
||||
<p class="text-left"><span class="text-black">7.1
|
||||
双方都清楚并愿意严格遵守反商业贿赂的法律规定,任何形式的贿赂和贪渎行为都将触犯法律,并将受到不利后果。</span></p>
|
||||
<p class="text-left"><span class="text-black">7.2
|
||||
|
||||
双方均不得向对方或对方经办人或其他相关人员索要、收受、提供给予本合同约定外的任何利益,包括但不限于明扣、暗扣、现金、购物卡、实物、有价证券旅游或其他非物质利益等,但如该等利益属于行业惯例或通常礼仪做法的除外。</span>
|
||||
</p>
|
||||
<p class="text-left"><span
|
||||
class="text-black">本款“相关人员”是指双方经办人以外的与本合同有直接或间接利益关系的人员,如经办人的亲友等。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">八、
|
||||
违约责任</span></p>
|
||||
<p class="text-left"><span class="text-black">8.1
|
||||
|
||||
除非另有约定,任何一方未履行本合同或违反约定且未及时改正或采取补救措施的,违约方应向守约方按照已发生合同金额的</span><span
|
||||
class="text-black">30</span><span
|
||||
class="text-black">%向守约方承担违约责任,并赔偿守约方因此受到的直接经济损失(包括调查、仲裁、诉讼、律师等合理费用)。</span></p>
|
||||
<p class="text-left"><span class="text-black">8.2
|
||||
|
||||
如因甲方原因导致本合同终止的,其无权要求乙方退回已收取而未使用的服务费;若一方因对方或其它非己方原因未能履行本合同下的义务,一方无需赔偿对方承受的损失。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">8.3
|
||||
一方未行使、迟延行使或部分行使其权利,并不意味该权利被放弃;某一权利不行使并不意味着其它权利被放弃。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">九、
|
||||
免责事由</span></p>
|
||||
<p class="text-left"><span class="text-black">除特别指明外,一方无须就下列情形承担责任:</span></p>
|
||||
<p class="text-left"><span class="text-black">9.1
|
||||
|
||||
不可抗力。不可抗力是指合同双方当事人不能预见、不能避免并不能克服的客观情况,包括但不限于:战争、骚乱、恐怖主义、洪水、地震、台风、国家公布的疫情等事件。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">9.2
|
||||
|
||||
因法律、法规、规章、规定、指引、通知、政策、命令及其他规范性文件或政府行为原因导致本合同不能履行的,适用关于不可抗力的规定。</span></p>
|
||||
<p class="text-left"><span class="text-black">9.3
|
||||
乙方为改善服务质量或数据源方对系统进行调整、升级、扩容等措施而导致服务中断、延时等情况。</span></p>
|
||||
<p class="text-left"><span class="text-black">9.4
|
||||
因网络、设备、黑客攻击、计算机病毒侵入或发作、通信或电力故障等不可预测因素造成不能提供服务的情形。</span></p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">十、
|
||||
争议解决与法律适用</span></p>
|
||||
<p class="text-left"><span class="text-black">10.1
|
||||
因履行本合同而发生的一切争议,甲乙双方应协商解决。协商不成的,任何一方均有权向乙方所在地有管辖权法院提起诉讼。</span></p>
|
||||
<p class="text-left"><span class="text-black">十一、
|
||||
通知与送达</span></p>
|
||||
<p class="text-left"><span class="text-black">11.1
|
||||
|
||||
本合同项下的通知应以电子邮件、快递、传真、专人递送按合同签署页所列示联系方式发出,除非任何一方已书面通知对方变更联系方式并经对方确认。</span></p>
|
||||
<p class="text-left"><span class="text-black">11.2
|
||||
|
||||
书面通知的形式包括在网站公告、电子邮件、站内信、微信、手机短信和传真等电子方式及纸质文件。通知在下列日期视为送达被通知方:如以电子邮件发送,显示成功发送确认时,或发送后第一个工作日内未被退回;如是以快递发送,以交邮后第五个工作日;如以传真发送,于发件人传真机记录传输确认时;如以专人递送,被通知方签收日。</span>
|
||||
</p>
|
||||
<p class="text-left"></p>
|
||||
<p class="text-left"><span class="text-black">十二、
|
||||
其他</span></p>
|
||||
<p class="text-left"><span class="text-black">12.1
|
||||
|
||||
本合同附件作为本合同的组成部分,与本合同具有同等的法律效力;未尽事宜经双方协商后签订补充合同,补充合同与本合同条款如有冲突,以补充合同为准。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">12.2
|
||||
本合同终止日与服务有效期终止日相同,在合同期限届满前由双方友好协商是否续约。</span></p>
|
||||
<p class="text-left"><span class="text-black">12.3
|
||||
|
||||
本合同终止不影响合同中有关授权、付款、保密、知识产权、反商业贿赂、违约责任、争议解决与法律适用等可以独立存在的条款的效力,一方未履行完毕的义务仍需继续履行。</span>
|
||||
</p>
|
||||
<p class="text-left"><span class="text-black">12.4
|
||||
本合同自甲方支付/充值费用之日</span><span class="text-black">计算服务期限</span><span
|
||||
class="text-black">,合同期限为12个月。</span></p>
|
||||
<p style="text-indent:24pt;"><span>本协议通过点击同意/勾选的方式签署,自签署之日生效。</span>
|
||||
</p>
|
||||
<p style="text-indent: 24pt; text-align: right;"><span>本协议于 {{ effectiveDate }}生效。</span></p>
|
||||
<p class="text-center"></p>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const getFormattedDate = () => {
|
||||
const date = new Date()
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
return `${year} 年 ${month} 月 ${day} 日`
|
||||
}
|
||||
|
||||
const effectiveDate = ref(getFormattedDate())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
@apply my-1
|
||||
}
|
||||
</style>
|
21
src/views/AgentVip.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div class="relative">
|
||||
<img class="" src="@/assets/images/vip_bg.png" alt="代理会员">
|
||||
<div @click="toService" class="absolute left-[50%] translate-x-[-50%] bottom-80
|
||||
bg-gradient-to-r from-gray-900 via-black to-gray-900 <!-- 按钮自身渐变 -->
|
||||
py-2 px-4 rounded-lg text-white text-[24px] font-bold
|
||||
shadow-[0_0_15px_rgba(255,255,255,0.3)] <!-- 发光效果 -->
|
||||
hover:scale-105 transition-transform"> <!-- 悬停动画 -->
|
||||
点击马上报名
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
function toService() {
|
||||
// window.location.href = '/service' // 跳转到客服页面
|
||||
window.location.href = 'https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9' // 跳转到客服页面
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
434
src/views/AgentVipConfig copy.vue
Normal file
@ -0,0 +1,434 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto min-h-screen">
|
||||
<!-- 标题部分 -->
|
||||
<div class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
|
||||
<h1 class="text-2xl font-extrabold mb-2">专业报告定价配置</h1>
|
||||
<p class="opacity-90">请选择报告类型并设置定价策略,助您实现精准定价</p>
|
||||
</div>
|
||||
|
||||
<!-- 报告选择器 -->
|
||||
<div class="mb-4">
|
||||
<van-field readonly clickable name="reportType" v-model="selectedReportText" label="报告类型"
|
||||
placeholder="点击选择报告" @click="showPicker = true" class="card">
|
||||
<template #label>
|
||||
<span class="text-blue-600 font-medium">📝 选择报告</span>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-icon name="arrow-down" class="text-gray-400" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-popup v-model:show="showPicker" position="bottom">
|
||||
<van-picker :columns="reportOptions" :default-index="0" @confirm="onConfirm"
|
||||
@cancel="showPicker = false" />
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedReportText" class="space-y-6">
|
||||
<!-- 配置卡片 -->
|
||||
<div class="card">
|
||||
<!-- 当前报告标题 -->
|
||||
<div class="flex items-center mb-6">
|
||||
<van-icon name="description" class="text-blue-500 text-xl mr-2" />
|
||||
<h2 class="text-xl font-semibold text-gray-800">
|
||||
{{ selectedReportText }}配置
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- 显示当前产品的基础成本信息 -->
|
||||
<div v-if="currentProductConf && currentProductConf.cost_price"
|
||||
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
|
||||
<div class="text-lg font-semibold text-gray-700">产品基础信息</div>
|
||||
<div class="mt-1 text-sm text-gray-600">
|
||||
<div>基础成本价:<span class="font-medium">{{ currentProductConf.cost_price }}</span> 元</div>
|
||||
<div>最高设定金额上限:<span class="font-medium">{{ currentProductConf.price_range_max }}</span> 元</div>
|
||||
<div>最高设定比例上限:<span class="font-medium">{{ priceRatioMax }}</span> %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">成本策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 加价金额 -->
|
||||
<van-field v-model.number="currentConfig.price_increase_amount" label="加价金额" type="number"
|
||||
placeholder="0" @blur="validateDecimal('price_increase_amount')" class="custom-field"
|
||||
:class="{ 'van-field--error': increaseError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">🚀 加价金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大加价金额为{{ priceIncreaseAmountMax }}元<br>
|
||||
说明:加价金额是在基础成本价上增加的额外费用,决定下级报告的最低定价,您将获得所有输入的金额利润。
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">定价策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 定价区间最低 -->
|
||||
<van-field v-model.number="currentConfig.price_range_from" label="定价区间最低" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_from'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最低金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最低金额不能低于(基础最低 {{ currentProductConf?.price_range_min || 0 }}元 + 加价金额)<br>
|
||||
说明:设定的最低金额为定价区间的起始值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 定价区间最高 -->
|
||||
<van-field v-model.number="currentConfig.price_range_to" label="定价区间最高" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_to'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最高金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最高金额不能超过上限({{ currentProductConf?.price_range_max || 0 }}元)且不得小于最低金额({{ priceIncreaseMax }}元)<br>
|
||||
说明:设定的最高金额为定价区间的结束值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 收取比例 -->
|
||||
<van-field v-model.number="currentConfig.price_ratio" label="收取比例" type="digit" placeholder="0"
|
||||
@blur="() => { validateRatio(); }" class="custom-field" :class="{ 'van-field--error': ratioError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">📈 收取比例</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">%</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大收取比例为{{ priceRatioMax }}%<br>
|
||||
说明:收取比例表示对定价区间内(即报告金额超过最低金额、小于最高金额部分)的金额,按此比例进行利润分成。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<van-button type="primary" block
|
||||
class="shadow-lg bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white rounded-xl h-12"
|
||||
@click="handleSubmit">
|
||||
<van-icon name="success" class="mr-2" />
|
||||
保存当前报告配置
|
||||
</van-button>
|
||||
</div>
|
||||
<!-- 未选择提示 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<van-icon name="warning" class="text-gray-400 text-4xl mb-4" />
|
||||
<p class="text-gray-500">请先选择需要配置的报告类型</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
// 报告类型选项
|
||||
const reportOptions = [
|
||||
{ text: '人事背调', value: 'backgroundcheck', id: 1 },
|
||||
{ text: '家政风险', value: 'homeservice', id: 3 },
|
||||
{ text: '婚恋风险', value: 'marriage', id: 4 },
|
||||
{ text: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
|
||||
{ text: '租赁风险', value: 'rentalrisk', id: 6 },
|
||||
{ text: '个人风险', value: 'riskassessment', id: 7 },
|
||||
{ text: '老板企业报告', value: 'companyinfo', id: 2 },
|
||||
];
|
||||
|
||||
// 状态管理
|
||||
const showPicker = ref(false);
|
||||
const selectedReport = ref(reportOptions[0]);
|
||||
const selectedReportText = ref(reportOptions[0].text);
|
||||
const selectedReportId = ref(reportOptions[0].id);
|
||||
|
||||
const rangeError = ref(false);
|
||||
const ratioError = ref(false);
|
||||
const increaseError = ref(false);
|
||||
|
||||
|
||||
// 缓存当前报告配置数据(agent_membership_user_config)
|
||||
const configCache = reactive({});
|
||||
// 缓存产品配置数据(product_config)
|
||||
const productConfigCache = reactive({});
|
||||
|
||||
// 监听 selectedReportId 变化,更新当前报告信息,并获取配置数据
|
||||
watch(selectedReportId, (newVal) => {
|
||||
const report = reportOptions.find(r => r.id === newVal);
|
||||
selectedReport.value = report;
|
||||
selectedReportText.value = report.text;
|
||||
getConfig();
|
||||
});
|
||||
|
||||
// 当前配置计算属性(agent_membership_user_config)
|
||||
const currentConfig = computed(() => {
|
||||
if (!configCache[selectedReportId.value]) {
|
||||
configCache[selectedReportId.value] = {
|
||||
price_range_from: null,
|
||||
price_range_to: null,
|
||||
price_ratio: null,
|
||||
price_increase_amount: null,
|
||||
};
|
||||
}
|
||||
return configCache[selectedReportId.value];
|
||||
});
|
||||
|
||||
// 当前产品配置计算属性
|
||||
const currentProductConf = computed(() => {
|
||||
return productConfigCache[selectedReportId.value] || {};
|
||||
});
|
||||
|
||||
// 定义动态限制值
|
||||
const priceIncreaseMax = ref(null);
|
||||
const priceIncreaseAmountMax = ref(null);
|
||||
const priceRatioMax = ref(null);
|
||||
|
||||
// 获取配置接口,根据当前报告的 product_id 作为 query 参数
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const { data } = await useApiFetch("/agent/membership/user_config?product_id=" + selectedReportId.value)
|
||||
.get()
|
||||
.json();
|
||||
if (data.value?.code === 200) {
|
||||
// 处理 agent_membership_user_config
|
||||
const cfg = data.value.agent_membership_user_config;
|
||||
if (!cfg || Number(cfg.product_id) === 0) {
|
||||
// 未配置状态,初始化为空数据
|
||||
configCache[selectedReportId.value] = {
|
||||
price_range_from: null,
|
||||
price_range_to: null,
|
||||
price_ratio: null,
|
||||
price_increase_amount: null,
|
||||
product_id: 0
|
||||
};
|
||||
} else {
|
||||
// 已配置,映射接口字段(示例映射)
|
||||
configCache[selectedReportId.value] = {
|
||||
price_range_from: cfg.a_pricing_standard,
|
||||
price_range_to: cfg.a_pricing_end,
|
||||
price_ratio: cfg.a_overpricing_ratio * 100,
|
||||
price_increase_amount: cfg.price_increase_amount ?? null,
|
||||
product_id: cfg.product_id
|
||||
};
|
||||
}
|
||||
// 保存产品配置数据
|
||||
productConfigCache[selectedReportId.value] = data.value.product_config;
|
||||
// 更新动态限制值
|
||||
priceIncreaseMax.value = data.value.price_increase_max;
|
||||
priceIncreaseAmountMax.value = data.value.price_increase_amount;
|
||||
priceRatioMax.value = data.value.price_ratio * 100;
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('配置加载失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getConfig();
|
||||
});
|
||||
|
||||
// 金额输入格式验证,确保最多两位小数
|
||||
const validateDecimal = (field) => {
|
||||
const value = currentConfig.value[field];
|
||||
if (value === null || value === undefined) return;
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
currentConfig.value[field] = null;
|
||||
return;
|
||||
}
|
||||
const fixedValue = parseFloat(numValue.toFixed(2));
|
||||
currentConfig.value[field] = fixedValue;
|
||||
if (field === 'price_increase_amount') {
|
||||
if (fixedValue > priceIncreaseAmountMax.value) {
|
||||
currentConfig.value[field] = priceIncreaseAmountMax.value;
|
||||
showToast(`加价金额最大为${priceIncreaseAmountMax.value}元`);
|
||||
increaseError.value = true;
|
||||
setTimeout(() => {
|
||||
increaseError.value = false;
|
||||
}, 2000);
|
||||
} else {
|
||||
increaseError.value = false;
|
||||
}
|
||||
// 当加价金额变化后,重新验证价格区间
|
||||
validateRange();
|
||||
}
|
||||
};
|
||||
|
||||
// 价格区间验证
|
||||
const validateRange = () => {
|
||||
if (currentConfig.value.price_range_from === null || currentConfig.value.price_range_to === null) {
|
||||
rangeError.value = false;
|
||||
return;
|
||||
}
|
||||
if (isNaN(currentConfig.value.price_range_from) || isNaN(currentConfig.value.price_range_to)) return;
|
||||
const productConf = currentProductConf.value;
|
||||
const additional = currentConfig.value.price_increase_amount || 0;
|
||||
const minAllowed = productConf.cost_price + additional;
|
||||
const maxAllowed = productConf.price_range_max;
|
||||
if (currentConfig.value.price_range_from < minAllowed) {
|
||||
currentConfig.value.price_range_from = minAllowed;
|
||||
showToast(`最低金额不能低于成本价 ${minAllowed}元`);
|
||||
rangeError.value = true;
|
||||
closeRangeError();
|
||||
currentConfig.value.price_range_to = currentConfig.value.price_range_from + priceIncreaseMax.value;
|
||||
return;
|
||||
}
|
||||
if (currentConfig.value.price_range_to < currentConfig.value.price_range_from) {
|
||||
showToast('最高金额不能低于最低金额');
|
||||
currentConfig.value.price_range_to = (currentConfig.value.price_range_from + priceIncreaseMax.value) > maxAllowed
|
||||
? maxAllowed
|
||||
: currentConfig.value.price_range_from + priceIncreaseMax.value;
|
||||
rangeError.value = true;
|
||||
closeRangeError();
|
||||
return;
|
||||
}
|
||||
const diff = currentConfig.value.price_range_to - currentConfig.value.price_range_from;
|
||||
if (diff > priceIncreaseMax.value) {
|
||||
showToast(`价格区间最大差值为${priceIncreaseMax.value}元`);
|
||||
currentConfig.value.price_range_to = currentConfig.value.price_range_from + priceIncreaseMax.value;
|
||||
closeRangeError();
|
||||
return;
|
||||
}
|
||||
if (currentConfig.value.price_range_to > maxAllowed) {
|
||||
currentConfig.value.price_range_to = maxAllowed;
|
||||
showToast(`最高金额不能超过 ${maxAllowed}元`);
|
||||
closeRangeError();
|
||||
}
|
||||
if (!rangeError.value) {
|
||||
rangeError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const closeRangeError = () => {
|
||||
setTimeout(() => {
|
||||
rangeError.value = false;
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
// 收取比例验证(保留两位小数)
|
||||
const validateRatio = () => {
|
||||
let value = currentConfig.value.price_ratio;
|
||||
if (value === null || value === undefined) return;
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
currentConfig.value.price_ratio = null;
|
||||
ratioError.value = true;
|
||||
return;
|
||||
}
|
||||
if (numValue > priceRatioMax.value) {
|
||||
currentConfig.value.price_ratio = priceRatioMax.value;
|
||||
showToast(`收取比例最大为${priceRatioMax.value}%`);
|
||||
ratioError.value = true;
|
||||
setTimeout(() => {
|
||||
ratioError.value = false;
|
||||
}, 1000);
|
||||
} else if (numValue < 0) {
|
||||
currentConfig.value.price_ratio = 0;
|
||||
ratioError.value = true;
|
||||
} else {
|
||||
currentConfig.value.price_ratio = parseFloat(numValue.toFixed(2));
|
||||
ratioError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 提交处理
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
const submitData = {
|
||||
product_id: selectedReportId.value,
|
||||
price_range_from: currentConfig.value.price_range_from || 0,
|
||||
price_range_to: currentConfig.value.price_range_to || 0,
|
||||
price_ratio: (currentConfig.value.price_ratio || 0) / 100,
|
||||
price_increase_amount: currentConfig.value.price_increase_amount || 0,
|
||||
};
|
||||
const isValid = Object.values(currentConfig.value).every(val => val !== null) &&
|
||||
currentConfig.value.price_range_to >= currentConfig.value.price_range_from;
|
||||
if (!isValid) return;
|
||||
// 提交请求示例,请根据实际接口修改
|
||||
// const { data } = await useApiFetch("/agent/membership/user_config")
|
||||
// .post({ configs: submitData })
|
||||
// .json();
|
||||
// if (data.value?.code === 200) {
|
||||
// showToast({ message: '配置保存成功', position: 'top' });
|
||||
// getConfig();
|
||||
// }
|
||||
console.log("submitData", submitData);
|
||||
} catch (error) {
|
||||
showToast('保存失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
// Picker确认
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
selectedReport.value = selectedOptions[0];
|
||||
selectedReportText.value = selectedOptions[0].text;
|
||||
selectedReportId.value = selectedOptions[0].id;
|
||||
showPicker.value = false;
|
||||
rangeError.value = false;
|
||||
ratioError.value = false;
|
||||
increaseError.value = false;
|
||||
// 切换报告后获取最新配置
|
||||
getConfig();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
getConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.custom-field .van-field__body {
|
||||
@apply bg-gray-50 rounded-lg px-3 py-2;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-field:focus-within .van-field__body {
|
||||
@apply ring-2 ring-blue-200;
|
||||
}
|
||||
|
||||
.van-picker__toolbar {
|
||||
@apply bg-gray-50 rounded-t-lg;
|
||||
}
|
||||
|
||||
.van-picker__confirm {
|
||||
@apply text-blue-500 font-medium;
|
||||
}
|
||||
|
||||
.van-divider {
|
||||
@apply before:bg-gray-100 after:bg-gray-100;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__control {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__label {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__body {
|
||||
@apply ring-2 ring-red-200 bg-red-50;
|
||||
}
|
||||
</style>
|
457
src/views/AgentVipConfig.vue
Normal file
@ -0,0 +1,457 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto min-h-screen">
|
||||
<!-- 标题部分 -->
|
||||
<div class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
|
||||
<h1 class="text-2xl font-extrabold mb-2">专业报告定价配置</h1>
|
||||
<p class=" opacity-90">请选择报告类型并设置定价策略,助您实现精准定价</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<van-field readonly clickable name="reportType" v-model="selectedReportText" label="报告类型"
|
||||
placeholder="点击选择报告" @click="showPicker = true" class="card">
|
||||
<template #label>
|
||||
<span class="text-blue-600 font-medium">📝 选择报告</span>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-icon name="arrow-down" class="text-gray-400" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-popup v-model:show="showPicker" position="bottom">
|
||||
<van-picker :columns="reportOptions" :default-index="0" @confirm="onConfirm"
|
||||
@cancel="showPicker = false" />
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedReportText" class="space-y-6">
|
||||
<!-- 配置卡片 -->
|
||||
<div class="card">
|
||||
<!-- 当前报告标题 -->
|
||||
<div class="flex items-center mb-6">
|
||||
<van-icon name="description" class="text-blue-500 text-xl mr-2" />
|
||||
<h2 class="text-xl font-semibold text-gray-800">
|
||||
{{ selectedReportText }}配置
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- 显示当前产品的基础成本信息 -->
|
||||
<div v-if="productConfigData && productConfigData.cost_price"
|
||||
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
|
||||
<div class="text-lg font-semibold text-gray-700">报告基础配置信息</div>
|
||||
<div class="mt-1 text-sm text-gray-600">
|
||||
<div>基础成本价:<span class="font-medium">{{ productConfigData.cost_price }}</span> 元</div>
|
||||
<!-- <div>区间起始价:<span class="font-medium">{{ productConfigData.price_range_min }}</span> 元</div> -->
|
||||
<div>最高设定金额上限:<span class="font-medium">{{ productConfigData.price_range_max }}</span> 元</div>
|
||||
<div>最高设定比例上限:<span class="font-medium">{{ priceRatioMax }}</span> %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">成本策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 加价金额 -->
|
||||
<van-field v-model.number="configData.price_increase_amount" label="加价金额" type="number" placeholder="0"
|
||||
@blur="validateDecimal('price_increase_amount')" class="custom-field"
|
||||
:class="{ 'van-field--error': increaseError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">🚀 加价金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大加价金额为{{ priceIncreaseAmountMax }}元<br>
|
||||
说明:加价金额是在基础成本价上增加的额外费用,决定下级报告的最低定价,您将获得所有输入的金额利润。
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">定价策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 定价区间最低 -->
|
||||
<van-field v-model.number="configData.price_range_from" label="定价区间最低" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_from'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最低金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最低金额不能低于(基础最低 {{ productConfigData?.price_range_min || 0 }}元 + 加价金额)<br>
|
||||
说明:设定的最低金额为定价区间的起始值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 定价区间最高 -->
|
||||
<van-field v-model.number="configData.price_range_to" label="定价区间最高" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_to'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最高金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最高金额不能超过上限({{ productConfigData?.price_range_max || 0 }}元)和大于最低金额({{ priceIncreaseMax
|
||||
}}元)<br>
|
||||
说明:设定的最高金额为定价区间的结束值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 收取比例 -->
|
||||
<van-field v-model.number="configData.price_ratio" label="收取比例" type="digit" placeholder="0"
|
||||
@blur="() => { validateRatio(); }" class="custom-field" :class="{ 'van-field--error': ratioError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">📈 收取比例</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">%</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大收取比例为{{ priceRatioMax }}%<br>
|
||||
说明:收取比例表示对定价区间内(即报告金额超过最低金额,小于最高金额的部分)的金额,按此比例进行利润分成。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<van-button type="primary" block
|
||||
class="shadow-lg bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white rounded-xl h-12"
|
||||
@click="handleSubmit">
|
||||
<van-icon name="success" class="mr-2" />
|
||||
保存当前报告配置
|
||||
</van-button>
|
||||
</div>
|
||||
<!-- 未选择提示 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<van-icon name="warning" class="text-gray-400 text-4xl mb-4" />
|
||||
<p class="text-gray-500">请先选择需要配置的报告类型</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
import { settings } from 'nprogress';
|
||||
|
||||
// 报告类型选项
|
||||
const reportOptions = [
|
||||
{ text: '人事背调', value: 'backgroundcheck', id: 1 },
|
||||
{ text: '老板企业报告', value: 'companyinfo', id: 2 },
|
||||
{ text: '家政风险', value: 'homeservice', id: 3 },
|
||||
{ text: '婚恋风险', value: 'marriage', id: 4 },
|
||||
{ text: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
|
||||
{ text: '租赁风险', value: 'rentalrisk', id: 6 },
|
||||
{ text: '个人风险', value: 'riskassessment', id: 7 },
|
||||
|
||||
];
|
||||
|
||||
// 状态管理
|
||||
const showPicker = ref(false);
|
||||
const selectedReport = ref(reportOptions[0]);
|
||||
const selectedReportText = ref(reportOptions[0].text);
|
||||
const selectedReportId = ref(reportOptions[0].id);
|
||||
|
||||
const configData = ref({});
|
||||
const productConfigData = ref({});
|
||||
const priceIncreaseMax = ref(null);
|
||||
const priceIncreaseAmountMax = ref(null);
|
||||
const priceRatioMax = ref(null);
|
||||
const rangeError = ref(false);
|
||||
const ratioError = ref(false);
|
||||
const increaseError = ref(false);
|
||||
|
||||
// 金额输入格式验证:确保最多两位小数
|
||||
const validateDecimal = (field) => {
|
||||
const value = configData.value[field];
|
||||
if (value === null || value === undefined) return;
|
||||
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
configData.value[field] = null;
|
||||
return;
|
||||
}
|
||||
const fixedValue = parseFloat(numValue.toFixed(2));
|
||||
configData.value[field] = fixedValue;
|
||||
|
||||
if (field === 'price_increase_amount') {
|
||||
if (fixedValue > priceIncreaseAmountMax.value) {
|
||||
configData.value[field] = priceIncreaseAmountMax.value;
|
||||
showToast(`加价金额最大为${priceIncreaseAmountMax.value}元`);
|
||||
increaseError.value = true;
|
||||
setTimeout(() => {
|
||||
increaseError.value = false;
|
||||
}, 2000);
|
||||
} else {
|
||||
increaseError.value = false;
|
||||
}
|
||||
// 当加价金额改变后,重新验证价格区间
|
||||
validateRange();
|
||||
}
|
||||
};
|
||||
|
||||
// 价格区间验证(在 @blur 中调用)
|
||||
const validateRange = () => {
|
||||
console.log("configData.value.price_range_from", configData.value.price_range_from)
|
||||
console.log("configData.value.price_range_to", configData.value.price_range_to)
|
||||
|
||||
if (configData.value.price_range_from === null || configData.value.price_range_to === null) {
|
||||
rangeError.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(configData.value.price_range_from) || isNaN(configData.value.price_range_to)) return;
|
||||
const additional = configData.value.price_increase_amount || 0;
|
||||
const minAllowed = parseFloat(
|
||||
(Number(productConfigData.value.cost_price) + Number(additional)).toFixed(2)
|
||||
); // 使用成本价作为最小值
|
||||
const maxAllowed = productConfigData.value.price_range_max; // 使用产品配置中的最大价格作为最大值
|
||||
if (configData.value.price_range_from < minAllowed) {
|
||||
configData.value.price_range_from = minAllowed;
|
||||
showToast(`最低金额不能低于成本价 ${minAllowed}元`);
|
||||
rangeError.value = true;
|
||||
closeRangeError()
|
||||
|
||||
configData.value.price_range_to = parseFloat(
|
||||
(Number(configData.value.price_range_from) + Number(priceIncreaseMax.value)).toFixed(2)
|
||||
);
|
||||
return
|
||||
}
|
||||
|
||||
if (configData.value.price_range_to < configData.value.price_range_from) {
|
||||
showToast('最高金额不能低于最低金额');
|
||||
if (configData.value.price_range_from + priceIncreaseMax.value > maxAllowed) {
|
||||
configData.value.price_range_to = maxAllowed
|
||||
} else {
|
||||
configData.value.price_range_to = configData.value.price_range_from + priceIncreaseMax.value
|
||||
}
|
||||
rangeError.value = true;
|
||||
closeRangeError()
|
||||
return;
|
||||
}
|
||||
const diff = parseFloat(
|
||||
(configData.value.price_range_to - configData.value.price_range_from).toFixed(2)
|
||||
)
|
||||
if (diff > priceIncreaseMax.value) {
|
||||
showToast(`价格区间最大差值为${priceIncreaseMax.value}元`);
|
||||
configData.value.price_range_to = configData.value.price_range_from + priceIncreaseMax.value
|
||||
closeRangeError()
|
||||
return;
|
||||
}
|
||||
|
||||
if (configData.value.price_range_to > maxAllowed) {
|
||||
configData.value.price_range_to = maxAllowed;
|
||||
showToast(`最高金额不能超过 ${maxAllowed}元`);
|
||||
closeRangeError()
|
||||
}
|
||||
|
||||
if (!rangeError.value) {
|
||||
rangeError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 收取比例验证(修改为保留两位小数,不再四舍五入取整)
|
||||
const validateRatio = () => {
|
||||
let value = configData.value.price_ratio;
|
||||
if (value === null || value === undefined) return;
|
||||
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
configData.value.price_ratio = null;
|
||||
ratioError.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (numValue > priceRatioMax.value) {
|
||||
configData.value.price_ratio = priceRatioMax.value;
|
||||
showToast(`收取比例最大为${priceRatioMax.value}%`);
|
||||
ratioError.value = true;
|
||||
setTimeout(() => {
|
||||
ratioError.value = false;
|
||||
}, 1000);
|
||||
} else if (numValue < 0) {
|
||||
configData.value.price_ratio = 0;
|
||||
ratioError.value = true;
|
||||
} else {
|
||||
configData.value.price_ratio = parseFloat(numValue.toFixed(2));
|
||||
ratioError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取配置
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const { data, error } = await useApiFetch("/agent/membership/user_config?product_id=" + selectedReportId.value)
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value?.code === 200) {
|
||||
const respConfigData = data.value.data.agent_membership_user_config
|
||||
configData.value = {
|
||||
id: respConfigData.product_id,
|
||||
price_range_from: respConfigData.price_range_from || null,
|
||||
price_range_to: respConfigData.price_range_to || null,
|
||||
price_ratio: respConfigData.price_ratio * 100 || null, // 转换为百分比
|
||||
price_increase_amount: respConfigData.price_increase_amount || null,
|
||||
}
|
||||
console.log("configData", configData.value)
|
||||
// const respProductConfigData = data.value.data.product_config
|
||||
productConfigData.value = data.value.data.product_config
|
||||
// 设置动态限制值
|
||||
priceIncreaseMax.value = data.value.data.price_increase_max;
|
||||
priceIncreaseAmountMax.value = data.value.data.price_increase_amount;
|
||||
priceRatioMax.value = data.value.data.price_ratio * 100;
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('配置加载失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 提交处理
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
if (!finalValidation()) {
|
||||
return;
|
||||
}
|
||||
// 前端数据转换
|
||||
const submitData = {
|
||||
product_id: configData.value.id,
|
||||
price_range_from: configData.value.price_range_from || 0,
|
||||
price_range_to: configData.value.price_range_to || 0,
|
||||
price_ratio: (configData.value.price_ratio || 0) / 100, // 转换为小数
|
||||
price_increase_amount: configData.value.price_increase_amount || 0,
|
||||
};
|
||||
console.log("submitData", submitData)
|
||||
const { data, error } = await useApiFetch("/agent/membership/save_user_config")
|
||||
.post(submitData)
|
||||
.json();
|
||||
|
||||
if (data.value?.code === 200) {
|
||||
setTimeout(() => {
|
||||
showToast('保存成功');
|
||||
}, 500);
|
||||
getConfig();
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('保存失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
// 最终验证函数
|
||||
const finalValidation = () => {
|
||||
// 校验最低金额:不能为空且大于0
|
||||
if (!configData.value.price_range_from || configData.value.price_range_from <= 0) {
|
||||
showToast("最低金额不能为空");
|
||||
return false;
|
||||
}
|
||||
// 校验最高金额:不能为空且大于0
|
||||
if (!configData.value.price_range_to || configData.value.price_range_to <= 0) {
|
||||
showToast("最高金额不能为空");
|
||||
return false;
|
||||
}
|
||||
// 校验收取比例:不能为空且大于0
|
||||
if (!configData.value.price_ratio || configData.value.price_ratio <= 0) {
|
||||
showToast("收取比例不能为空");
|
||||
return false;
|
||||
}
|
||||
// 验证最低金额必须小于最高金额
|
||||
if (configData.value.price_range_from >= configData.value.price_range_to) {
|
||||
showToast("最低金额必须小于最高金额");
|
||||
return false;
|
||||
}
|
||||
// 验证价格区间差值不能超过最大允许差值
|
||||
const finalDiff = parseFloat(
|
||||
(configData.value.price_range_to - configData.value.price_range_from).toFixed(2)
|
||||
);
|
||||
if (finalDiff > priceIncreaseMax.value) {
|
||||
showToast(`价格区间最大差值为${priceIncreaseMax.value}元`);
|
||||
return false;
|
||||
}
|
||||
// 验证最高金额不能超过产品配置中设定的上限
|
||||
if (configData.value.price_range_to > productConfigData.value.price_range_max) {
|
||||
showToast(`最高金额不能超过${productConfigData.value.price_range_max}元`);
|
||||
return false;
|
||||
}
|
||||
// 验证最低金额不能低于成本价+加价金额(加价金额允许为空)
|
||||
const additional = configData.value.price_increase_amount || 0;
|
||||
if (configData.value.price_range_from < productConfigData.value.cost_price + additional) {
|
||||
showToast(`最低金额不能低于成本价${productConfigData.value.cost_price + additional}元`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// 选择器确认
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
selectedReport.value = selectedOptions[0];
|
||||
selectedReportText.value = selectedOptions[0].text;
|
||||
selectedReportId.value = selectedOptions[0].id;
|
||||
showPicker.value = false;
|
||||
// 重置错误状态
|
||||
rangeError.value = false;
|
||||
ratioError.value = false;
|
||||
increaseError.value = false;
|
||||
|
||||
getConfig()
|
||||
};
|
||||
const closeRangeError = () => {
|
||||
setTimeout(() => {
|
||||
rangeError.value = false;
|
||||
}, 2000)
|
||||
}
|
||||
onMounted(() => {
|
||||
getConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.custom-field .van-field__body {
|
||||
@apply bg-gray-50 rounded-lg px-3 py-2;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-field:focus-within .van-field__body {
|
||||
@apply ring-2 ring-blue-200;
|
||||
}
|
||||
|
||||
.van-picker__toolbar {
|
||||
@apply bg-gray-50 rounded-t-lg;
|
||||
}
|
||||
|
||||
.van-picker__confirm {
|
||||
@apply text-blue-500 font-medium;
|
||||
}
|
||||
|
||||
.van-divider {
|
||||
@apply before:bg-gray-100 after:bg-gray-100;
|
||||
}
|
||||
|
||||
/* 错误状态样式 */
|
||||
.van-field--error .van-field__control {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__label {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__body {
|
||||
@apply ring-2 ring-red-200 bg-red-50;
|
||||
}
|
||||
</style>
|
@ -209,7 +209,7 @@ const maskValue = computed(() => {
|
||||
<div class="flex justify-between border-b pb-2 pl-2" v-if="reportParams?.name">
|
||||
<span class="text-gray-700 font-bold">姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name", reportParams?.name)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.id_card">
|
||||
@ -221,65 +221,65 @@ const maskValue = computed(() => {
|
||||
v-if="reportParams?.nameMan">
|
||||
<span class="text-gray-700 font-bold">男方姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name", reportParams?.nameMan)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.idCardMan">
|
||||
<span class="text-gray-700 font-bold">男方身份证号</span>
|
||||
<span class="text-gray-600">{{ maskValue("id_card", reportParams?.idCardMan)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.nameWoman">
|
||||
<span class="text-gray-700 font-bold">女方姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name", reportParams?.nameWoman)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.idCardWoman">
|
||||
<span class="text-gray-700 font-bold">女方身份证号</span>
|
||||
<span class="text-gray-600">{{ maskValue("id_card",
|
||||
reportParams?.idCardWoman)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.bank_card">
|
||||
<span class="text-gray-700 font-bold">银行卡号</span>
|
||||
<span class="text-gray-600">{{ maskValue("bank_card",
|
||||
reportParams?.bank_card)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.mobile">
|
||||
<span class="text-gray-700 font-bold">手机号</span>
|
||||
<span class="text-gray-600">{{ maskValue("mobile", reportParams?.mobile)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.verification_code">
|
||||
<span class="text-gray-700 font-bold">验证码</span>
|
||||
<span class="text-gray-600">{{ maskValue("code",
|
||||
reportParams?.verification_code)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.car_license">
|
||||
<span class="text-gray-700 font-bold">车牌号</span>
|
||||
<span class="text-gray-600">{{ maskValue("car_license",
|
||||
reportParams?.car_license)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.ent_name">
|
||||
<span class="text-gray-700 font-bold">企业名称</span>
|
||||
<span class="text-gray-600">{{ maskValue("ent_name", reportParams?.ent_name)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.ent_code">
|
||||
<span class="text-gray-700 font-bold">企业代码</span>
|
||||
<span class="text-gray-600">{{ maskValue("ent_code", reportParams?.ent_code)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center bg-blue-100 rounded-xl px-4 py-2 flex-1">
|
||||
@ -348,7 +348,7 @@ const maskValue = computed(() => {
|
||||
</div> -->
|
||||
<div>
|
||||
<a class="text-blue-500" href="https://beian.miit.gov.cn">
|
||||
琼ICP备2024048057号-2
|
||||
琼ICP备2024048057号-1
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
11
src/views/Help.vue
Normal file
@ -0,0 +1,11 @@
|
||||
<template>
|
||||
<div>
|
||||
帮助中心
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -59,7 +59,7 @@
|
||||
class="w-6 h-6" /> -->
|
||||
<p class="text-sm">
|
||||
<!-- 琼公网安备 46010002000443 号 | -->
|
||||
琼ICP备2024048057号-2
|
||||
琼ICP备2024048057号-1
|
||||
</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -80,29 +80,19 @@ const formatterDate = (type, option) => {
|
||||
return option;
|
||||
};
|
||||
const onConfirmDate = ({ selectedValues, selectedOptions }) => {
|
||||
console.log("selectedValues", selectedValues)
|
||||
console.log("startDate", startDate.value)
|
||||
dateVal.value = selectedOptions.map(item => item.text).join('');
|
||||
showDatePicker.value = false
|
||||
}
|
||||
const carLicenseChange = (e) => {
|
||||
console.log("carLicenseChange", e);
|
||||
carLicense.value = e;
|
||||
};
|
||||
const onConfirmCarType = ({ selectedValues, selectedOptions }) => {
|
||||
console.log(
|
||||
"selectedValues, selectedOptions",
|
||||
selectedValues,
|
||||
selectedOptions
|
||||
);
|
||||
showCarTypePicker.value = false;
|
||||
carPickerVal.value = selectedValues;
|
||||
carType.value = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
isFinishPayment()
|
||||
getProduct();
|
||||
|
35
src/views/Invitation.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<img src="@/assets/images/invitation.png" alt="邀请下级">
|
||||
<div @click="showQRcode = true"
|
||||
class="bg-gradient-to-t from-orange-500 to-orange-300 fixed bottom-0 h-12 w-full bg-orange-400 shadow-xl text-white rounded-t-xl flex items-center justify-center font-bold">
|
||||
立即邀请好友</div>
|
||||
</div>
|
||||
<QRcode v-model:show="showQRcode" mode="invitation" :linkIdentifier="linkIdentifier" />
|
||||
</template>
|
||||
<script setup>
|
||||
import { storeToRefs } from "pinia";
|
||||
import { aesEncrypt } from "@/utils/crypto";
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
const agentStore = useAgentStore()
|
||||
const { mobile, agentID } = storeToRefs(agentStore); // 响应式解构
|
||||
|
||||
const showQRcode = ref(false);
|
||||
const linkIdentifier = ref("")
|
||||
onBeforeMount(() => {
|
||||
encryptIdentifire(agentID.value, mobile.value)
|
||||
})
|
||||
|
||||
const encryptIdentifire = (agentID, mobile) => {
|
||||
const linkIdentifierJSON = {
|
||||
agentID,
|
||||
mobile
|
||||
}
|
||||
const linkIdentifierStr = JSON.stringify(linkIdentifierJSON);
|
||||
const encodeData = aesEncrypt(linkIdentifierStr, "8e3e7a2f60edb49221e953b9c029ed10");
|
||||
linkIdentifier.value = encodeURIComponent(encodeData)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
156
src/views/InvitationAgentApply.vue
Normal file
@ -0,0 +1,156 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-[#D1D6FF]">
|
||||
|
||||
<img src="@/assets/images/invitation_agent_apply.png" alt="邀请代理申请">
|
||||
<!-- 统一状态处理容器 -->
|
||||
<div class="flex flex-col items-center justify-centerx">
|
||||
<!-- 审核中状态 -->
|
||||
<div v-if="displayStatus === 0" class="text-center">
|
||||
<span class="text-xs text-gray-500">您的申请正在审核中</span>
|
||||
<div class="bg-gray-200 p-1 rounded-3xl shadow-xl mt-1">
|
||||
<div
|
||||
class="text-xl font-bold px-8 py-2 bg-gray-400 text-white rounded-3xl shadow-lg cursor-not-allowed">
|
||||
审核进行中
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核通过状态 -->
|
||||
<div v-if="displayStatus === 1" class="text-center">
|
||||
<span class="text-xs text-gray-500">您已成为认证代理方</span>
|
||||
<div class="bg-green-100 p-1 rounded-3xl shadow-xl mt-1" @click="goToHome">
|
||||
<div
|
||||
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-green-500 to-green-300 text-white rounded-3xl shadow-lg cursor-pointer">
|
||||
进入应用首页
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 审核未通过状态 -->
|
||||
<div v-if="displayStatus === 2" class="text-center">
|
||||
<span class="text-xs text-red-500">审核未通过,请重新提交</span>
|
||||
<div class="bg-red-100 p-1 rounded-3xl shadow-xl mt-1" @click="agentApply">
|
||||
<div
|
||||
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-red-500 to-red-300 text-white rounded-3xl shadow-lg cursor-pointer">
|
||||
重新提交申请
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 未申请状态(包含邀请状态) -->
|
||||
<div v-if="displayStatus === 3" class="text-center">
|
||||
<span class="text-xs text-gray-500">{{ isSelf ? '立即申请成为代理人' : '邀您注册代理人' }}</span>
|
||||
<div class="bg-gray-100 p-1 rounded-3xl shadow-xl mt-1" @click="agentApply">
|
||||
<div
|
||||
class="text-xl font-bold px-8 py-2 bg-gradient-to-t from-blue-500 to-blue-300 text-white rounded-3xl shadow-lg cursor-pointer">
|
||||
立即成为代理方
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AgentApplicationForm v-model:show="showApplyPopup" @submit="submitApplication" @close="showApplyPopup = false"
|
||||
:ancestor="ancestor" />
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { aesDecrypt } from "@/utils/crypto"
|
||||
const showApplyPopup = ref(false)
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
const store = useAgentStore();
|
||||
const { status } = storeToRefs(store); // 响应式解构
|
||||
const ancestor = ref("")
|
||||
const isSelf = ref(false)
|
||||
const agentApply = () => {
|
||||
showApplyPopup.value = true
|
||||
}
|
||||
// 计算显示状态(当isSelf为false时强制显示为3)
|
||||
const displayStatus = computed(() => {
|
||||
return isSelf.value ? 3 : status.value
|
||||
})
|
||||
|
||||
// 跳转到首页
|
||||
const goToHome = () => {
|
||||
clearInterval(intervalId);
|
||||
router.push('/')
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
if (route.name === "invitationAgentApplySelf") {
|
||||
isSelf.value = true
|
||||
} else {
|
||||
const linkIdentifier = route.params.linkIdentifier
|
||||
const decryptDataStr = aesDecrypt(decodeURIComponent(linkIdentifier), "8e3e7a2f60edb49221e953b9c029ed10")
|
||||
const decryptData = JSON.parse(decryptDataStr)
|
||||
ancestor.value = decryptData.mobile
|
||||
}
|
||||
const token = localStorage.getItem("token")
|
||||
if (token) {
|
||||
store.fetchAgentStatus();
|
||||
}
|
||||
|
||||
})
|
||||
const submitApplication = async (formData) => {
|
||||
// 提交代理申请的数据
|
||||
const { region, mobile, wechat_id, code } = formData;
|
||||
let postData = {
|
||||
region,
|
||||
mobile,
|
||||
wechat_id,
|
||||
code,
|
||||
};
|
||||
if (!isSelf.value) {
|
||||
postData.ancestor = ancestor.value
|
||||
}
|
||||
const { data, error } = await useApiFetch("/agent/apply")
|
||||
.post(postData)
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showApplyPopup.value = false;
|
||||
showToast({ message: "已提交申请" });
|
||||
// refreshAgentStatus()
|
||||
if (data.value.data.accessToken) {
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
refreshAgentStatus()
|
||||
}
|
||||
|
||||
} else {
|
||||
console.log('申请失败', data.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
let intervalId = null; // 保存定时器 ID
|
||||
|
||||
const refreshAgentStatus = () => {
|
||||
// 当 status.value 变化时(如通过监听或事件)
|
||||
if (status.value === 3) {
|
||||
// 清除已有定时器(避免重复创建)
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
// 每次执行前检查状态是否仍为 3
|
||||
if (status.value !== 3) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
return;
|
||||
}
|
||||
store.fetchAgentStatus();
|
||||
}, 2000);
|
||||
} else {
|
||||
// 状态不是 3 时主动清除定时器
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -89,9 +89,7 @@ async function handleLogin() {
|
||||
if (data.value.code === 200) {
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
// if (phoneNumber.value === "18276151590") {
|
||||
// localStorage.setItem('m', "shifenliangzai")
|
||||
// }
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
window.location.href = '/'
|
||||
}
|
||||
}
|
||||
@ -121,7 +119,7 @@ const onClickLeft = () => {
|
||||
<div class="login px-8">
|
||||
<div class="mb-8 pt-8 text-left">
|
||||
<div class="flex flex-col items-center">
|
||||
<img class="h-16 w-16 rounded-full shadow" src="@/assets/images/logo.png" alt="Logo" />
|
||||
<img class="h-16 w-16 rounded-full shadow" src="@/assets/images/logo.jpg" alt="Logo" />
|
||||
<div class="text-3xl mt-4 text-slate-700 font-bold">天远数据</div>
|
||||
</div>
|
||||
</div>
|
||||
|
195
src/views/Me.vue
@ -1,63 +1,103 @@
|
||||
<template>
|
||||
<div class="box-border min-h-screen from-blue-100 to-white bg-gradient-to-b">
|
||||
<div class="flex flex-col p-4">
|
||||
<!-- Profile Section -->
|
||||
<div class="profile-section mb-4 flex items-center gap-4 rounded-md bg-white p-4 shadow-md"
|
||||
<div class="box-border min-h-screen from-blue-50 to-white bg-gradient-to-b">
|
||||
<div class="flex flex-col p-4 space-y-6">
|
||||
<!-- 用户信息卡片 -->
|
||||
<div class="profile-section group relative flex items-center gap-4 rounded-xl bg-white p-6 shadow-lg transition-all hover:shadow-xl"
|
||||
@click="!isLoggedIn ? redirectToLogin() : null">
|
||||
<img :src="userAvatar" alt="User Avatar" class="rounded-full" width="100" height="100" />
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">
|
||||
<div class="relative">
|
||||
<!-- 头像容器添加overflow-hidden解决边框问题 -->
|
||||
<div class="overflow-hidden rounded-full p-0.5" :class="levelGradient.border">
|
||||
<img :src="userAvatar || headShot" alt="User Avatar"
|
||||
class="h-24 w-24 rounded-full border-4 border-white">
|
||||
</div>
|
||||
|
||||
<!-- 代理标识 -->
|
||||
<div v-if="isAgent" class="absolute -bottom-2 -right-2">
|
||||
<div class="flex items-center justify-center rounded-full px-3 py-1 text-xs font-bold text-white shadow-sm"
|
||||
:class="levelGradient.badge">
|
||||
{{ levelNames[level] }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-2xl font-bold text-gray-800">
|
||||
{{ isWeChat ? '微信用户' : isLoggedIn ? maskName(userName) : '点击登录' }}
|
||||
</h2>
|
||||
<p v-if="isAgent" class="text-sm font-medium" :class="levelGradient.text">
|
||||
🎖️ {{ levelText[level] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<VipBanner v-if="isAgent && level === 'normal'" />
|
||||
<!-- 功能菜单 -->
|
||||
<div class="features-section space-y-3">
|
||||
<template v-if="isAgent && ['VIP', 'SVIP'].includes(level)">
|
||||
<button
|
||||
class="feature-item bg-gradient-to-r from-purple-200/80 to-pink-200/80 hover:from-purple-300/80 hover:to-pink-300/80 text-purple-700"
|
||||
@click="toVipConfig">
|
||||
⚙️ 代理报告配置
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- Divider (Optional) -->
|
||||
<hr />
|
||||
|
||||
<!-- Features Section -->
|
||||
<div class="features-section flex flex-col gap-2">
|
||||
<button class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm" @click="toHistory">
|
||||
我的报告
|
||||
<button class="feature-item hover:bg-blue-50" @click="toHistory">
|
||||
📃 我的报告
|
||||
</button>
|
||||
<!-- <button class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm" @click="">
|
||||
联系客服
|
||||
</button> -->
|
||||
<button class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm"
|
||||
@click="toUserAgreement">
|
||||
用户协议
|
||||
<button class="feature-item hover:bg-blue-50" @click="toUserAgreement">
|
||||
📜 用户协议
|
||||
</button>
|
||||
<!-- <button v-if="isWeChat" class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm"
|
||||
type="default" open-type="contact">
|
||||
联系客服
|
||||
</button> -->
|
||||
<button class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm" @click="toService">
|
||||
联系客服
|
||||
<button class="feature-item hover:bg-blue-50" @click="toService">
|
||||
💬 联系客服
|
||||
</button>
|
||||
<button class="feature-item w-full cursor-pointer rounded-md bg-white p-3 shadow-sm"
|
||||
@click="handleLogout" v-if="!isWeChat">
|
||||
退出登录
|
||||
<button v-if="!isWeChat" class="feature-item hover:bg-red-50 text-red-600" @click="handleLogout">
|
||||
⏏️ 退出登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const router = useRouter()
|
||||
const userName = ref('点击登录')
|
||||
const userAvatar = ref('https://img0.baidu.com/it/u=1240274933,2284862568&fm=253&fmt=auto&app=138&f=PNG?w=180&h=180')
|
||||
const isLoggedIn = ref(false)
|
||||
// const features = ref([
|
||||
// { title: '我的报告', icon: 'list', action: () => toHistory() },
|
||||
// { title: '联系客服', icon: 'service-o', action: () => toService() },
|
||||
// { title: '用户协议', icon: 'file', action: () => toUserAgreement() },
|
||||
// { title: '', icon: 'logout', action: () => handleLogout() },
|
||||
// ])
|
||||
import headShot from "@/assets/images/head_shot.webp"
|
||||
import { useAgentStore } from '@/stores/agentStore'
|
||||
import { useUserStore } from '@/stores/userStore'
|
||||
const agentStore = useAgentStore()
|
||||
const userStore = useUserStore()
|
||||
const { isAgent, level } = storeToRefs(agentStore)
|
||||
const { userName, userAvatar, isLoggedIn } = storeToRefs(userStore)
|
||||
const { isWeChat } = useEnv()
|
||||
const levelNames = {
|
||||
normal: '普通代理',
|
||||
VIP: 'VIP代理',
|
||||
SVIP: 'SVIP代理'
|
||||
}
|
||||
|
||||
const levelText = {
|
||||
normal: '基础代理特权',
|
||||
VIP: '高级代理特权',
|
||||
SVIP: '尊享代理特权'
|
||||
}
|
||||
|
||||
const levelGradient = computed(() => ({
|
||||
border: {
|
||||
'normal': 'bg-green-300',
|
||||
'VIP': 'bg-gradient-to-r from-yellow-400 to-amber-500',
|
||||
'SVIP': 'bg-gradient-to-r from-purple-400 to-pink-400 shadow-[0_0_15px_rgba(163,51,200,0.2)]'
|
||||
}[level.value],
|
||||
|
||||
badge: {
|
||||
'normal': 'bg-green-500',
|
||||
'VIP': 'bg-gradient-to-r from-yellow-500 to-amber-600',
|
||||
'SVIP': 'bg-gradient-to-r from-purple-500 to-pink-500'
|
||||
}[level.value],
|
||||
|
||||
text: {
|
||||
'normal': 'text-green-600',
|
||||
'VIP': 'text-amber-600',
|
||||
'SVIP': 'text-purple-600'
|
||||
}[level.value]
|
||||
}))
|
||||
const maskName = computed(() => {
|
||||
return (name) => {
|
||||
return name.substring(0, 3) + "****" + name.substring(7);
|
||||
@ -73,14 +113,13 @@ function toUserAgreement() {
|
||||
|
||||
function redirectToLogin() {
|
||||
router.push(`/login`)
|
||||
|
||||
}
|
||||
|
||||
function handleLogout() {
|
||||
localStorage.removeItem('token') // 使用浏览器的 localStorage 来存储 token
|
||||
isLoggedIn.value = false
|
||||
userName.value = '点击登录'
|
||||
userAvatar.value = 'https://img0.baidu.com/it/u=1240274933,2284862568&fm=253&fmt=auto&app=138&f=PNG?w=180&h=180'
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('refreshAfter')
|
||||
localStorage.removeItem('accessExpire')
|
||||
location.reload()
|
||||
}
|
||||
|
||||
function toService() {
|
||||
@ -88,59 +127,43 @@ function toService() {
|
||||
window.location.href = 'https://work.weixin.qq.com/kfid/kfc5c19b2b93a5e73b9' // 跳转到客服页面
|
||||
}
|
||||
|
||||
async function fetchUserInfo() {
|
||||
const { data, error } = await useApiFetch('/user/detail')
|
||||
.get()
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
const userinfo = data.value.data.userInfo
|
||||
userName.value = userinfo.nickName || ''
|
||||
userAvatar.value = userinfo.userAvatar || 'https://img0.baidu.com/it/u=1240274933,2284862568&fm=253&fmt=auto&app=138&f=PNG?w=180&h=180'
|
||||
isLoggedIn.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const safeAreaTop = ref(0)
|
||||
onMounted(() => {
|
||||
const token = localStorage.getItem('token') // 使用 localStorage 获取 token
|
||||
if (token) {
|
||||
isLoggedIn.value = true
|
||||
fetchUserInfo()
|
||||
} else {
|
||||
isLoggedIn.value = false
|
||||
}
|
||||
onBeforeMount(() => {
|
||||
// const token = localStorage.getItem('token') // 使用 localStorage 获取 token
|
||||
// if (token) {
|
||||
// fetchUserInfo()
|
||||
// agentStore.fetchAgentStatus()
|
||||
// }
|
||||
})
|
||||
const toVipConfig = () => {
|
||||
router.push({ name: "agentVipConfig" })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.profile-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
background: linear-gradient(135deg, #ffffff 50%, rgba(236, 253, 245, 0.3));
|
||||
border: 1px solid rgba(209, 213, 219, 0.2);
|
||||
}
|
||||
|
||||
.features-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
.profile-section .relative>div:first-child {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
|
||||
.feature-item {
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
text-align: left;
|
||||
transition:
|
||||
transform 0.2s ease,
|
||||
background 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
@apply flex w-full items-center shadow space-x-3 rounded-xl bg-white px-6 py-4 text-left text-gray-700 transition-all duration-300 hover:scale-[1.02] hover:shadow-md focus:outline-none;
|
||||
}
|
||||
|
||||
.feature-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
.border-gradient-to-r {
|
||||
border-image: linear-gradient(to right, var(--tw-gradient-from), var(--tw-gradient-to)) 1;
|
||||
}
|
||||
|
||||
.shadow-glow {
|
||||
filter: drop-shadow(0 0 8px rgba(163, 51, 200, 0.2));
|
||||
}
|
||||
</style>
|
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<div class="p-4 ">
|
||||
<div class="card">
|
||||
|
||||
<div class="min-h-screen p-4 promote">
|
||||
<div class="mb-4 card !bg-gradient-to-b from-orange-200 to-orange-200/80">
|
||||
<div class="">
|
||||
<div class="text-lg font-bold text-orange-500">直推用户查询</div>
|
||||
<div class="font-bold text-orange-400 mt-1">
|
||||
@ -11,71 +10,261 @@
|
||||
|
||||
<div class="mt-6">
|
||||
<div class="mt-2 text-gray-600 bg-orange-100 rounded-xl px-4 py-2">
|
||||
在下方 “自定义价格” 处选择报告类型,设置客户查询价(元)即可了解推广收益及我的成本价。
|
||||
在下方 “自定义价格” 处选择报告类型,设置客户查询价(元)即可立即推广。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-6">
|
||||
<div class="">
|
||||
<h2 class="text-xl font-semibold mb-2">生成推广码</h2>
|
||||
<van-cell-group inset>
|
||||
<van-field v-model="pickerFieldVal" is-link readonly label="报告类型" placeholder="请选择报告类型"
|
||||
@click="showPicker = true" />
|
||||
<van-popup v-model:show="showPicker" destroy-on-close round position="bottom">
|
||||
<van-picker :model-value="selectedReportType" :columns="reportTypes"
|
||||
@cancel="showPicker = false" @confirm="onConfirm" />
|
||||
</van-popup>
|
||||
<van-field v-model="clientPrice" type="number" label="客户查询价" placeholder="请输入价格" />
|
||||
<div class="flex items-center justify-between my-2">
|
||||
<div class="text-sm text-gray-500">推广收益为 {{ promotionRevenue }} 元</div>
|
||||
<div class="text-sm text-gray-500">我的成本为 {{ costPrice }} 元</div>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
<VipBanner />
|
||||
<!-- 判断是否是代理 -->
|
||||
<div>
|
||||
<div class="card mb-4">
|
||||
<div class="">
|
||||
<h2 class="text-xl font-semibold mb-2">生成推广码</h2>
|
||||
<van-cell-group inset>
|
||||
<!-- 报告类型 -->
|
||||
<van-field v-model="pickerFieldText" is-link readonly label="报告类型" placeholder="请选择报告类型"
|
||||
@click="showTypePicker = true" />
|
||||
<van-popup v-model:show="showTypePicker" destroy-on-close round position="bottom">
|
||||
<van-picker :model-value="selectedReportType" :columns="reportTypes"
|
||||
@cancel="showTypePicker = false" @confirm="onConfirmType" />
|
||||
</van-popup>
|
||||
<!-- 定价 -->
|
||||
<van-field v-model="clientPrice" is-link readonly label="客户查询价" placeholder="请输入价格"
|
||||
@click="showPricePicker = true" />
|
||||
<PriceInputPopup v-model:show="showPricePicker" :default-price="clientPrice"
|
||||
:product-config="pickerProductConfig" @change="onPriceChange" />
|
||||
<div class="flex items-center justify-between my-2">
|
||||
<div class="text-sm text-gray-500">推广收益为 <span class="text-orange-500">{{ promotionRevenue
|
||||
}}</span> 元</div>
|
||||
<div class="text-sm text-gray-500">我的成本为 <span class="text-orange-500">{{ costPrice
|
||||
}}</span> 元</div>
|
||||
</div>
|
||||
</van-cell-group>
|
||||
</div>
|
||||
|
||||
<div class="mt-6">
|
||||
<van-button type="primary" class="w-full" @click="generatePromotionCode">点击立即推广</van-button>
|
||||
<div class="mt-6">
|
||||
<van-button type="primary" class="w-full" @click="generatePromotionCode">点击立即推广</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 如果不是代理,展示根据status显示不同内容 -->
|
||||
<!-- <div>
|
||||
<div v-if="status === 0" class="card mt-6">
|
||||
<div class="font-semibold text-lg text-gray-700">申请审核中</div>
|
||||
<div class="text-sm text-gray-500 mt-4 mb-8">
|
||||
您的申请正在审核中,请耐心等待。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status === 2" class="card mt-6">
|
||||
<div class="font-semibold text-lg text-gray-700">申请未通过</div>
|
||||
<div class="text-sm text-gray-500 mt-4 mb-8">
|
||||
很抱歉,您的代理申请未通过。请检查您的信息或重新申请。
|
||||
</div>
|
||||
<van-button type="primary" round class="w-full" @click="showApplyPopup = true">
|
||||
申请成为代理
|
||||
</van-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="status === 3" class="card mt-6">
|
||||
<div class="font-semibold text-lg text-gray-700">未申请成为代理</div>
|
||||
<div class="text-sm text-gray-500 mt-4 mb-8">
|
||||
您还没有申请成为代理,立即申请即可开始推广。
|
||||
</div>
|
||||
<van-button type="primary" round class="w-full" @click="showApplyPopup = true">
|
||||
申请成为代理
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
<AgentApplicationForm v-model:show="showApplyPopup" @submit="submitApplication"
|
||||
@close="showApplyPopup = false" /> -->
|
||||
|
||||
<QRcode v-model:show="showQRcode" :linkIdentifier="linkIdentifier" />
|
||||
</div>
|
||||
<PromoteQRcode v-model:show="showQRcode" />
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
|
||||
import PriceInputPopup from '@/components/PriceInputPopup.vue';
|
||||
import VipBanner from '@/components/VipBanner.vue';
|
||||
const reportTypes = [
|
||||
{ text: '个人风险', value: 'personalRisk' },
|
||||
{ text: '婚恋风险', value: 'marriageRisk' },
|
||||
{ text: '家政服务', value: 'domesticService' },
|
||||
{ text: '租赁风险', value: 'rentalRisk' },
|
||||
{ text: '人事背调', value: 'hrBackgroundCheck' },
|
||||
{ text: '企业报告', value: 'enterpriseReport' },
|
||||
{ text: '贷前背调', value: 'preLoanBackgroundCheck' }
|
||||
{ text: '人事背调', value: 'backgroundcheck', id: 1 },
|
||||
{ text: '老板企业报告', value: 'companyinfo', id: 2 },
|
||||
{ text: '家政风险', value: 'homeservice', id: 3 },
|
||||
{ text: '婚恋风险', value: 'marriage', id: 4 },
|
||||
{ text: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
|
||||
{ text: '租赁风险', value: 'rentalrisk', id: 6 },
|
||||
{ text: '个人风险', value: 'riskassessment', id: 7 },
|
||||
];
|
||||
const showPicker = ref(false);
|
||||
const pickerFieldVal = ref('')
|
||||
const showTypePicker = ref(false);
|
||||
const showApplyPopup = ref(false); // 用来控制申请代理弹窗的显示
|
||||
const showPricePicker = ref(false);
|
||||
const pickerFieldText = ref('')
|
||||
const pickerFieldVal = ref(null)
|
||||
const pickerProductConfig = ref(null)
|
||||
const selectedReportType = ref([]);
|
||||
const onConfirm = ({ selectedValues, selectedOptions }) => {
|
||||
showPicker.value = false;
|
||||
selectedReportType.value = selectedValues;
|
||||
pickerFieldVal.value = selectedOptions[0].text;
|
||||
};
|
||||
const clientPrice = ref(null);
|
||||
const productConfig = ref(null);
|
||||
const linkIdentifier = ref("")
|
||||
|
||||
const clientPrice = ref(49.9);
|
||||
const costPrice = ref(10.31);
|
||||
|
||||
|
||||
// const costPrice = computed(() => {
|
||||
// if (!pickerProductConfig.value) return 0.00
|
||||
// // 平台定价成本
|
||||
// let platformPricing = 0
|
||||
// if (clientPrice.value > pickerProductConfig.value.p_pricing_standard) {
|
||||
// platformPricing = (clientPrice.value - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
|
||||
// }
|
||||
// return (pickerProductConfig.value.cost_price + platformPricing).toFixed(2)
|
||||
// })
|
||||
|
||||
const costPrice = computed(() => {
|
||||
if (!pickerProductConfig.value) return 0.00
|
||||
// 平台定价成本
|
||||
let platformPricing = 0
|
||||
platformPricing += pickerProductConfig.value.cost_price
|
||||
if (clientPrice.value > pickerProductConfig.value.p_pricing_standard) {
|
||||
platformPricing += (clientPrice.value - pickerProductConfig.value.p_pricing_standard) * pickerProductConfig.value.p_overpricing_ratio
|
||||
}
|
||||
|
||||
if (pickerProductConfig.value.a_pricing_standard > platformPricing && pickerProductConfig.value.a_pricing_end > platformPricing && pickerProductConfig.value.a_overpricing_ratio > 0) {
|
||||
if (clientPrice.value > pickerProductConfig.value.a_pricing_standard) {
|
||||
if (clientPrice.value > pickerProductConfig.value.a_pricing_end) {
|
||||
platformPricing += (pickerProductConfig.value.a_pricing_end - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
||||
} else {
|
||||
platformPricing += (clientPrice.value - pickerProductConfig.value.a_pricing_standard) * pickerProductConfig.value.a_overpricing_ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return safeTruncate(platformPricing)
|
||||
})
|
||||
|
||||
const promotionRevenue = computed(() => {
|
||||
// 计算推广收益 (客户查询价 - 我的成本)
|
||||
return (clientPrice.value - costPrice.value).toFixed(2);
|
||||
return safeTruncate(clientPrice.value - costPrice.value)
|
||||
});
|
||||
const showQRcode = ref(false)
|
||||
const showQRcode = ref(false);
|
||||
function safeTruncate(num, decimals = 2) {
|
||||
if (isNaN(num) || !isFinite(num)) return "0.00";
|
||||
|
||||
const generatePromotionCode = () => {
|
||||
const factor = 10 ** decimals;
|
||||
const scaled = Math.trunc(num * factor);
|
||||
const truncated = scaled / factor;
|
||||
|
||||
return truncated.toFixed(decimals);
|
||||
}
|
||||
const generatePromotionCode = async () => {
|
||||
if (selectedReportType.value.length === 0) {
|
||||
showToast({ message: '请选择报告类型' });
|
||||
return;
|
||||
}
|
||||
if (!clientPrice.value) {
|
||||
showToast({ message: '请输入查询价格' });
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch("/agent/generating_link")
|
||||
.post({ product: pickerFieldVal.value, price: clientPrice.value })
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
linkIdentifier.value = data.value.data.link_identifier
|
||||
} else {
|
||||
console.log("Error fetching agent info", data.value);
|
||||
}
|
||||
}
|
||||
if (!linkIdentifier.value) return
|
||||
showQRcode.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
onMounted(() => {
|
||||
getPromoteConfig();
|
||||
// getAgentInfo();
|
||||
});
|
||||
const SelectTypePicker = (reportType) => {
|
||||
selectedReportType.value = [reportType];
|
||||
pickerFieldText.value = reportType.text;
|
||||
pickerFieldVal.value = reportType.value;
|
||||
for (let i of productConfig.value) {
|
||||
if (i.product_id === reportType.id) {
|
||||
pickerProductConfig.value = i
|
||||
clientPrice.value = i.cost_price
|
||||
}
|
||||
}
|
||||
};
|
||||
const getPromoteConfig = async () => {
|
||||
const { data, error } = await useApiFetch("/agent/product_config")
|
||||
.get()
|
||||
.json()
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
productConfig.value = data.value.data.AgentProductConfig;
|
||||
SelectTypePicker(reportTypes[0])
|
||||
} else {
|
||||
console.log("Error fetching agent info", data.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
const onPriceChange = (price) => {
|
||||
clientPrice.value = price
|
||||
}
|
||||
|
||||
// const getAgentInfo = async () => {
|
||||
// const { data, error } = await useApiFetch("/agent/info")
|
||||
// .get()
|
||||
// .json()
|
||||
|
||||
// if (data.value && !error.value) {
|
||||
// if (data.value.code === 200) {
|
||||
// isAgent.value = data.value.data.is_agent; // 判断是否是代理
|
||||
// status.value = data.value.data.status; // 获取代理状态
|
||||
// agentID.value = data.value.data.agent_id
|
||||
// } else {
|
||||
// console.log("Error fetching agent info", data.value);
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
|
||||
const onConfirmType = ({ selectedValues, selectedOptions }) => {
|
||||
SelectTypePicker(selectedOptions[0])
|
||||
showTypePicker.value = false;
|
||||
};
|
||||
|
||||
|
||||
const submitApplication = async (formData) => {
|
||||
// 提交代理申请的数据
|
||||
const { region, mobile, wechat_id, code } = formData;
|
||||
const { data, error } = await useApiFetch("/agent/apply")
|
||||
.post({ region, mobile, wechat_id, code })
|
||||
.json();
|
||||
|
||||
if (data.value && !error.value) {
|
||||
if (data.value.code === 200) {
|
||||
showApplyPopup.value = false;
|
||||
// 这里可以提示成功,或者刷新代理状态
|
||||
showToast({ message: "已提交申请" });
|
||||
} else {
|
||||
console.log('申请失败', data.value);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
/* 自定义样式可以添加在这里 */
|
||||
/* .promote {
|
||||
background-color: #ffeee0;
|
||||
background-image: linear-gradient(45deg,
|
||||
rgba(255, 235, 205, 0.3) 25%,
|
||||
transparent 25%,
|
||||
transparent 50%,
|
||||
rgba(255, 235, 205, 0.3) 50%,
|
||||
rgba(255, 235, 205, 0.3) 75%,
|
||||
transparent 75%,
|
||||
transparent);
|
||||
background-size: 40px 40px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
*/
|
||||
</style>
|
@ -1,14 +1,595 @@
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted, onUnmounted } from "vue";
|
||||
import { aesEncrypt } from "@/utils/crypto";
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
import Authorization from "@/components/Authorization.vue";
|
||||
import Payment from "@/components/Payment.vue";
|
||||
import CarNumberInput from "@/components/CarNumberInput.vue";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter()
|
||||
const showAuthorizationPopup = ref(false);
|
||||
const authorization = ref(true);
|
||||
const showPayment = ref(false);
|
||||
const queryId = ref(null);
|
||||
const name = ref("");
|
||||
const nameMan = ref("");
|
||||
const nameWoman = ref("");
|
||||
const idCard = ref("");
|
||||
const idCardMan = ref("");
|
||||
const idCardWoman = ref("");
|
||||
const mobile = ref("");
|
||||
const bankCard = ref("");
|
||||
const startDate = ref([])
|
||||
const dateVal = ref("")
|
||||
const showDatePicker = ref(false)
|
||||
// 当前日期
|
||||
const today = new Date();
|
||||
const maxDate = today; // 最大日期为当前日期
|
||||
// 最小日期为2000年1月1日
|
||||
const minDate = new Date('2000-01-01');
|
||||
const entName = ref("");
|
||||
const entCode = ref("");
|
||||
const verificationCode = ref("");
|
||||
const agreeToTerms = ref(false);
|
||||
const isCountingDown = ref(false);
|
||||
const countdown = ref(60);
|
||||
const feature = ref("");
|
||||
const featureData = ref({});
|
||||
const carLicense = ref("");
|
||||
const carType = ref("小型汽车");
|
||||
const carPickerVal = ref([{ value: "02", text: "小型汽车" }]);
|
||||
const linkIdentifier = ref("")
|
||||
const showCarTypePicker = ref(false);
|
||||
const carTypeColumns = [
|
||||
{ value: "01", text: "大型汽车" },
|
||||
{ value: "02", text: "小型汽车" },
|
||||
{ value: "03", text: "使馆汽车" },
|
||||
{ value: "04", text: "领馆汽车" },
|
||||
{ value: "05", text: "境外汽车" },
|
||||
{ value: "06", text: "外籍汽车" },
|
||||
{ value: "07", text: "普通摩托车" },
|
||||
{ value: "08", text: "轻便摩托车" },
|
||||
{ value: "09", text: "使馆摩托车" },
|
||||
{ value: "10", text: "领馆摩托车" },
|
||||
{ value: "11", text: "境外摩托车" },
|
||||
{ value: "12", text: "外籍摩托车" },
|
||||
{ value: "13", text: "低速车" },
|
||||
{ value: "14", text: "拖拉机" },
|
||||
{ value: "15", text: "挂车" },
|
||||
{ value: "16", text: "教练汽车" },
|
||||
{ value: "17", text: "教练摩托车" },
|
||||
{ value: "20", text: "临时入境汽车" },
|
||||
{ value: "21", text: "临时入境摩托车" },
|
||||
{ value: "22", text: "临时行驶车" },
|
||||
{ value: "23", text: "警用汽车" },
|
||||
{ value: "24", text: "警用摩托车" },
|
||||
{ value: "51", text: "新能源大型车" },
|
||||
{ value: "52", text: "新能源小型车" },
|
||||
];
|
||||
const formatterDate = (type, option) => {
|
||||
if (type === 'year') {
|
||||
option.text += '年';
|
||||
}
|
||||
if (type === 'month') {
|
||||
option.text += '月';
|
||||
}
|
||||
if (type === 'day') {
|
||||
option.text += '日';
|
||||
}
|
||||
return option;
|
||||
};
|
||||
const onConfirmDate = ({ selectedValues, selectedOptions }) => {
|
||||
dateVal.value = selectedOptions.map(item => item.text).join('');
|
||||
showDatePicker.value = false
|
||||
}
|
||||
const carLicenseChange = (e) => {
|
||||
carLicense.value = e;
|
||||
};
|
||||
const onConfirmCarType = ({ selectedValues, selectedOptions }) => {
|
||||
showCarTypePicker.value = false;
|
||||
carPickerVal.value = selectedValues;
|
||||
carType.value = selectedOptions[0].text;
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
getProduct();
|
||||
})
|
||||
onMounted(() => {
|
||||
isFinishPayment()
|
||||
initAuthorization();
|
||||
});
|
||||
const discountPrice = ref(false) // 是否应用折扣
|
||||
|
||||
function isFinishPayment() {
|
||||
const query = new URLSearchParams(window.location.search);
|
||||
let orderNo = query.get("out_trade_no");
|
||||
if (orderNo) {
|
||||
router.push({ path: '/report', query: { orderNo } });
|
||||
}
|
||||
}
|
||||
async function getProduct() {
|
||||
linkIdentifier.value = route.params.linkIdentifier
|
||||
// linkIdentifier.value = decodeURIComponent(linkIdentifier.value)
|
||||
console.log("linkIdentifier", linkIdentifier.value)
|
||||
const { data: agentLinkData, error: agentLinkError } = await useApiFetch(`/agent/link?link_identifier=${linkIdentifier.value}`)
|
||||
.get()
|
||||
.json();
|
||||
if (agentLinkData.value && !agentLinkError.value) {
|
||||
if (agentLinkData.value.code === 200) {
|
||||
feature.value = agentLinkData.value.data.product_en;
|
||||
featureData.value = agentLinkData.value.data;
|
||||
console.log("feature", feature.value)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
function initAuthorization() {
|
||||
if (NeedAuthorization.includes(feature.value)) {
|
||||
authorization.value = false;
|
||||
}
|
||||
}
|
||||
const isPhoneNumberValid = computed(() => {
|
||||
return /^1[3-9]\d{9}$/.test(mobile.value);
|
||||
});
|
||||
const isIdCardValid = computed(() => /^\d{17}[\dX]$/i.test(idCard.value));
|
||||
const isIdCardManValid = computed(() => /^\d{17}[\dX]$/i.test(idCardMan.value));
|
||||
const isIdCardWomanValid = computed(() =>
|
||||
/^\d{17}[\dX]$/i.test(idCardWoman.value)
|
||||
);
|
||||
const isCreditCodeValid = computed(() => /^.{18}$/.test(entCode.value));
|
||||
const isCarLicense = computed(() => carLicense.value.trim().length > 6);
|
||||
const isBankCardValid = computed(() => {
|
||||
const card = bankCard.value.replace(/\D/g, ""); // 移除所有非数字字符
|
||||
if (card.length < 13 || card.length > 19) {
|
||||
return false; // 校验长度
|
||||
}
|
||||
|
||||
let sum = 0;
|
||||
let shouldDouble = false;
|
||||
|
||||
// 从卡号的右边开始遍历
|
||||
for (let i = card.length - 1; i >= 0; i--) {
|
||||
let digit = parseInt(card.charAt(i));
|
||||
|
||||
if (shouldDouble) {
|
||||
digit *= 2;
|
||||
if (digit > 9) {
|
||||
digit -= 9;
|
||||
}
|
||||
}
|
||||
|
||||
sum += digit;
|
||||
shouldDouble = !shouldDouble; // 反转是否乘 2
|
||||
}
|
||||
|
||||
return sum % 10 === 0; // 如果最终和能被 10 整除,则银行卡号有效
|
||||
});
|
||||
|
||||
function handleSubmit() {
|
||||
if (!agreeToTerms.value) {
|
||||
showToast({ message: `请阅读并同意用户协议、隐私政策${!NeedAuthorization.includes(feature.value) ? '和授权书' : ''}` });
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!validateField("name", name.value, (v) => v, "请输入姓名") ||
|
||||
!validateField("nameMan", nameMan.value, (v) => v, "请输入男方姓名") ||
|
||||
!validateField(
|
||||
"nameWoman",
|
||||
nameWoman.value,
|
||||
(v) => v,
|
||||
"请输入女方姓名"
|
||||
) ||
|
||||
!validateField(
|
||||
"mobile",
|
||||
mobile.value,
|
||||
(v) => isPhoneNumberValid.value,
|
||||
"请输入有效的手机号"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCard",
|
||||
idCard.value,
|
||||
(v) => isIdCardValid.value,
|
||||
"请输入有效的身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCardMan",
|
||||
idCardMan.value,
|
||||
(v) => isIdCardManValid.value,
|
||||
"请输入有效的男方身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"idCardWoman",
|
||||
idCardWoman.value,
|
||||
(v) => isIdCardWomanValid.value,
|
||||
"请输入有效的女方身份证号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"bankCard",
|
||||
bankCard.value,
|
||||
(v) => isBankCardValid.value,
|
||||
"请输入有效的银行卡号码"
|
||||
) ||
|
||||
!validateField(
|
||||
"verificationCode",
|
||||
verificationCode.value,
|
||||
(v) => v,
|
||||
"请输入验证码"
|
||||
) ||
|
||||
!validateField(
|
||||
"carPickerVal",
|
||||
carPickerVal.value,
|
||||
(v) => v,
|
||||
"请选择车辆类型"
|
||||
) ||
|
||||
!validateField(
|
||||
"carLicense",
|
||||
carLicense.value,
|
||||
(v) => isCarLicense.value,
|
||||
"请输入正确的车牌号"
|
||||
) ||
|
||||
!validateField("entName", entName.value, (v) => v, "请输入企业名称") ||
|
||||
!validateField(
|
||||
"entCode",
|
||||
entCode.value,
|
||||
(v) => isCreditCodeValid.value,
|
||||
"请输入统一社会信用代码"
|
||||
) ||
|
||||
!validateField(
|
||||
"date",
|
||||
dateVal.value,
|
||||
(v) => v,
|
||||
"请选择日期"
|
||||
)
|
||||
|
||||
) {
|
||||
return;
|
||||
}
|
||||
submitRequest();
|
||||
}
|
||||
const validateField = (field, value, validationFn, errorMessage) => {
|
||||
if (isHasInput(field) && !validationFn(value)) {
|
||||
showToast({ message: errorMessage });
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const defaultInput = ["name", "idCard", "mobile", "verificationCode"];
|
||||
const specialProduct = {
|
||||
toc_EnterpriseLawsuit: ["entName", "entCode", "mobile", "verificationCode"],
|
||||
toc_PhoneThreeElements: ["name", "idCard", "mobile"],
|
||||
toc_IDCardTwoElements: ["name", "idCard"],
|
||||
toc_PhoneTwoElements: ["name", "mobile"],
|
||||
toc_PersonVehicleVerification: ["name", "carType", "carLicense"],
|
||||
toc_VehiclesUnderName: ["name", "idCard"],
|
||||
toc_DualMarriage: ["nameMan", "idCardMan", "nameWoman", "idCardWoman"],
|
||||
toc_BankCardBlacklist: ["name", "idCard", "mobile", "bankCard"],
|
||||
toc_BankCardFourElements: ["name", "idCard", "mobile", "bankCard"],
|
||||
toc_NaturalLifeStatus: ["name", "idCard"],
|
||||
toc_NetworkDuration: ["mobile"],
|
||||
toc_PhoneSecondaryCard: ["mobile", "date"],
|
||||
toc_PhoneNumberRisk: ["mobile"],
|
||||
};
|
||||
const NeedAuthorization = [
|
||||
"toc_Marriage",
|
||||
"marriage"
|
||||
];
|
||||
const isHasInput = (input) => {
|
||||
if (specialProduct[feature.value]) {
|
||||
return specialProduct[feature.value].includes(input);
|
||||
} else {
|
||||
return defaultInput.includes(input);
|
||||
}
|
||||
};
|
||||
async function submitRequest() {
|
||||
const req = {};
|
||||
if (isHasInput("name")) {
|
||||
req.name = name.value;
|
||||
}
|
||||
if (isHasInput("idCard")) {
|
||||
req.id_card = idCard.value;
|
||||
}
|
||||
if (isHasInput("nameMan")) {
|
||||
req.name_man = nameMan.value;
|
||||
}
|
||||
if (isHasInput("idCardMan")) {
|
||||
req.id_card_man = idCardMan.value;
|
||||
}
|
||||
if (isHasInput("nameWoman")) {
|
||||
req.name_woman = nameWoman.value;
|
||||
}
|
||||
if (isHasInput("idCardWoman")) {
|
||||
req.id_card_woman = idCardWoman.value;
|
||||
}
|
||||
if (isHasInput("bankCard")) {
|
||||
req.bank_card = bankCard.value.replace(/\D/g, "");
|
||||
}
|
||||
if (isHasInput("mobile")) {
|
||||
req.mobile = mobile.value;
|
||||
}
|
||||
if (isHasInput("verificationCode")) {
|
||||
req.code = verificationCode.value;
|
||||
}
|
||||
if (isHasInput("carType")) {
|
||||
req.car_type = carPickerVal.value[0].value;
|
||||
}
|
||||
if (isHasInput("carLicense")) {
|
||||
req.car_license = carLicense.value.trim();
|
||||
}
|
||||
if (isHasInput("date")) {
|
||||
req.start_date = startDate.value.map(item => item).join('')
|
||||
}
|
||||
if (isHasInput("entName")) {
|
||||
req.ent_name = entName.value;
|
||||
}
|
||||
if (isHasInput("entCode")) {
|
||||
req.ent_code = entCode.value;
|
||||
}
|
||||
const reqStr = JSON.stringify(req);
|
||||
const encodeData = aesEncrypt(reqStr, "ff83609b2b24fc73196aac3d3dfb874f");
|
||||
|
||||
const { data, error } = await useApiFetch(`/query/service_agent/${feature.value}`)
|
||||
.post({ data: encodeData, agent_identifier: linkIdentifier.value })
|
||||
.json();
|
||||
if (data.value.code === 200) {
|
||||
queryId.value = data.value.data.id;
|
||||
localStorage.setItem('token', data.value.data.accessToken)
|
||||
localStorage.setItem('refreshAfter', data.value.data.refreshAfter)
|
||||
localStorage.setItem('accessExpire', data.value.data.accessExpire)
|
||||
if (authorization.value) {
|
||||
showPayment.value = true;
|
||||
} else {
|
||||
showAuthorizationPopup.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function sendVerificationCode() {
|
||||
if (isCountingDown.value || !isPhoneNumberValid.value) return;
|
||||
if (!isPhoneNumberValid.value) {
|
||||
showToast({ message: "请输入有效的手机号" });
|
||||
return;
|
||||
}
|
||||
|
||||
const { data, error } = await useApiFetch("/auth/sendSms")
|
||||
.post({ mobile: mobile.value, actionType: "query" })
|
||||
.json();
|
||||
|
||||
if (!error.value && data.value.code === 200) {
|
||||
showToast({ message: "验证码发送成功", type: "success" });
|
||||
startCountdown();
|
||||
} else {
|
||||
showToast({ message: "验证码发送失败,请重试" });
|
||||
}
|
||||
}
|
||||
let timer = null;
|
||||
|
||||
function startCountdown() {
|
||||
isCountingDown.value = true;
|
||||
countdown.value = 60;
|
||||
timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--;
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
isCountingDown.value = false;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
function toUserAgreement() {
|
||||
router.push(`/userAgreement`)
|
||||
}
|
||||
|
||||
function toPrivacyPolicy() {
|
||||
router.push(`/privacyPolicy`)
|
||||
}
|
||||
|
||||
function toAuthorization() {
|
||||
router.push(`/authorization`)
|
||||
}
|
||||
// 用户同意
|
||||
const agreed = () => {
|
||||
showAuthorizationPopup.value = false;
|
||||
authorization.value = true;
|
||||
showPayment.value = true;
|
||||
};
|
||||
|
||||
// 用户取消
|
||||
const cancel = () => {
|
||||
showAuthorizationPopup.value = false;
|
||||
};
|
||||
const toExample = () => {
|
||||
router.push(`/example?feature=${feature.value}`)
|
||||
};
|
||||
onUnmounted(() => {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Inquire />
|
||||
<div class="inquire-bg min-h-screen p-6">
|
||||
<div class="mb-6 text-center text-3xl font-bold text-blue-700">
|
||||
{{ featureData.product_name }}
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="mb-4 text-lg font-semibold text-gray-800">基本信息</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('name')">
|
||||
<label for="name" class="form-label">姓名</label>
|
||||
<input v-model="name" id="name" type="text" placeholder="请输入姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCard')">
|
||||
<label for="idCard" class="form-label">身份证号</label>
|
||||
<input v-model="idCard" id="idCard" type="text" placeholder="请输入身份证号" class="form-input" />
|
||||
</div>
|
||||
<!-- 双人婚姻 -->
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('nameMan')">
|
||||
<label for="nameMan" class="form-label">男方姓名</label>
|
||||
<input v-model="nameMan" id="nameMan" type="text" placeholder="请输入男方姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCardMan')">
|
||||
<label for="idCardMan" class="form-label">男方身份证号</label>
|
||||
<input v-model="idCardMan" id="idCardMan" type="text" placeholder="请输入男方身份证号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('nameWoman')">
|
||||
<label for="nameWoman" class="form-label">女方姓名</label>
|
||||
<input v-model="nameWoman" id="nameWoman" type="text" placeholder="请输入女方姓名" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('idCardWoman')">
|
||||
<label for="idCardWoman" class="form-label">女方身份证号</label>
|
||||
<input v-model="idCardWoman" id="idCardWoman" type="text" placeholder="请输入女方身份证号" class="form-input" />
|
||||
</div>
|
||||
<!-- 双人婚姻 -->
|
||||
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('entName')">
|
||||
<label for="entName" class="form-label">企业名称</label>
|
||||
<input v-model="entName" id="entName" type="text" placeholder="请输入企业名称" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('entCode')">
|
||||
<label for="entCode" class="form-label">统一社会信用代码</label>
|
||||
<input v-model="entCode" id="entCode" type="text" placeholder="请输入统一社会信用代码" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('carType')">
|
||||
<label for="carType" class="form-label">汽车类型</label>
|
||||
<van-field id="carType" v-model="carType" is-link readonly placeholder="点击选择汽车类型"
|
||||
@click="showCarTypePicker = true" class="form-input" />
|
||||
<van-popup v-model:show="showCarTypePicker" destroy-on-close round position="bottom">
|
||||
<van-picker :model-value="carPickerVal" :columns="carTypeColumns"
|
||||
@cancel="showCarTypePicker = false" @confirm="onConfirmCarType" />
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('carLicense')">
|
||||
<!-- <label for="entCode" class="form-label">车牌号</label> -->
|
||||
<CarNumberInput class="form-input" @number-input-result="carLicenseChange" :default-str="carLicense">
|
||||
</CarNumberInput>
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('bankCard')">
|
||||
<label for="bankCard" class="form-label">银行卡号</label>
|
||||
<input v-model="bankCard" id="bankCard" type="tel" placeholder="请输入银行卡号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('mobile')">
|
||||
<label for="mobile" class="form-label">手机号</label>
|
||||
<input v-model="mobile" id="mobile" type="tel" placeholder="请输入手机号" class="form-input" />
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('date')">
|
||||
<label for="date" class="form-label">业务日期</label>
|
||||
<van-field id="date" v-model="dateVal" is-link readonly placeholder="点击选择日期"
|
||||
@click="showDatePicker = true" class="form-input" />
|
||||
<van-popup v-model:show="showDatePicker" destroy-on-close round position="bottom">
|
||||
<van-date-picker v-model="startDate" :formatter="formatterDate" :min-date="minDate"
|
||||
:max-date="maxDate" title="选择日期" @confirm="onConfirmDate" @cancel="showDatePicker = false" />
|
||||
</van-popup>
|
||||
</div>
|
||||
<div class="mb-4 flex items-center" v-if="isHasInput('verificationCode')">
|
||||
<label for="verificationCode" class="form-label">验证码</label>
|
||||
<div class="flex-1 flex items-center">
|
||||
<input v-model="verificationCode" id="verificationCode" type="text" placeholder="请输入验证码"
|
||||
class="form-input flex-1" />
|
||||
<button class="ml-2 px-4 py-2 text-sm text-blue-500 disabled:text-gray-400"
|
||||
:disabled="isCountingDown || !isPhoneNumberValid" @click="sendVerificationCode">
|
||||
{{
|
||||
isCountingDown
|
||||
? `${countdown}s重新获取`
|
||||
: "获取验证码"
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4 flex items-center">
|
||||
<input type="checkbox" v-model="agreeToTerms" />
|
||||
<span class="ml-2 text-xs text-gray-400">
|
||||
我已阅读并同意
|
||||
<span @click="toUserAgreement" class="text-blue-500 ">用户协议、</span>
|
||||
<span @click="toPrivacyPolicy" class="text-blue-500 ">隐私政策</span>
|
||||
<template v-if="!NeedAuthorization.includes(feature)">
|
||||
<span @click="toAuthorization" class="text-blue-500 ">、授权书</span>
|
||||
</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<button class="w-24 rounded-l-xl bg-blue-400 py-2 text-white" @click="toExample">
|
||||
示例报告
|
||||
</button>
|
||||
<button class="flex-1 rounded-r-xl bg-blue-500 py-2 text-white" @click="handleSubmit">
|
||||
立即查询
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card mt-4">
|
||||
<div class="mb-4 text-xl text-gray-800 font-bold">
|
||||
{{ featureData.product_name }}
|
||||
</div>
|
||||
<div class="mb-4 flex items-start justify-between">
|
||||
<div class="text-lg text-gray-500">价格:</div>
|
||||
<div>
|
||||
<div v-if="discountPrice" class="line-through text-gray-500" :class="{ 'text-lg': discountPrice }">
|
||||
¥ {{ featureData.sell_price }}
|
||||
</div>
|
||||
<div class="text-2xl text-red-600 font-semibold">
|
||||
¥{{ discountPrice ? (featureData.sell_price * 0.2).toFixed(2) : featureData.sell_price }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="discountPrice" class="text-sm text-right text-red-500 mb-4">
|
||||
限时活动价:2折优惠
|
||||
</div>
|
||||
<div class="mb-4 text-gray-600 leading-relaxed" v-html="featureData.description">
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<template v-if="featureData.features && featureData.features.length > 1">
|
||||
<div class="mb-4 text-lg text-gray-800 font-semibold">
|
||||
报告包含内容
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div v-for="(feature, index) in featureData.features" :key="feature.id"
|
||||
class="rounded-lg py-2 text-center text-sm text-gray-700 font-medium" :class="[
|
||||
(Math.floor(index / 2) + (index % 2)) % 2 === 0
|
||||
? 'bg-gradient-to-r from-blue-200 via-blue-200 to-blue-100'
|
||||
: 'bg-gradient-to-r from-sky-200 via-sky-200 to-sky-100',
|
||||
]">
|
||||
{{ feature.name }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部弹出 -->
|
||||
<van-popup v-model:show="showAuthorizationPopup" position="bottom" :style="{ height: '80%' }">
|
||||
<Authorization :style="{ height: '100%' }" :name="name" :id-card="idCard" :mobile="mobile" @agreed="agreed"
|
||||
@cancel="cancel" />
|
||||
</van-popup>
|
||||
<Payment v-model="showPayment" :data="featureData" :id="queryId" @close="showPayment = false" />
|
||||
<RecordFooter v-if="!webviewEnv" />
|
||||
<div
|
||||
class=" fixed right-2 top-3/4 px-4 py-2 text-sm bg-blue-400 rounded-xl text-white font-bold shadow active:bg-blue-500">
|
||||
class=" fixed right-2 top-3/4 px-4 py-2 text-sm bg-blue-400 rounded-xl cursor-pointer text-white font-bold shadow active:bg-blue-500">
|
||||
历史查询
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Inquire from './Inquire.vue';
|
||||
</script>
|
||||
<style scoped>
|
||||
.form-label {
|
||||
@apply w-20 text-sm font-medium text-gray-700 flex-shrink-0;
|
||||
}
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
.form-input::placeholder {
|
||||
color: var(--van-text-color-3);
|
||||
}
|
||||
|
||||
.form-input {
|
||||
@apply w-full border-b border-gray-200 px-2 py-2 focus:outline-none;
|
||||
}
|
||||
|
||||
.inquire-bg {
|
||||
background: url("@/assets/images/bg_2.png") no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
}
|
||||
</style>
|
||||
|
@ -231,7 +231,7 @@ const maskValue = computed(() => {
|
||||
v-if="reportParams?.name">
|
||||
<span class="text-gray-700 font-bold">姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name", reportParams?.name)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.id_card">
|
||||
@ -243,69 +243,69 @@ const maskValue = computed(() => {
|
||||
v-if="reportParams?.nameMan">
|
||||
<span class="text-gray-700 font-bold">男方姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name", reportParams?.nameMan)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.idCardMan">
|
||||
<span class="text-gray-700 font-bold">男方身份证号</span>
|
||||
<span class="text-gray-600">{{ maskValue("id_card",
|
||||
reportParams?.idCardMan)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.nameWoman">
|
||||
<span class="text-gray-700 font-bold">女方姓名</span>
|
||||
<span class="text-gray-600">{{ maskValue("name",
|
||||
reportParams?.nameWoman)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.idCardWoman">
|
||||
<span class="text-gray-700 font-bold">女方身份证号</span>
|
||||
<span class="text-gray-600">{{ maskValue("id_card",
|
||||
reportParams?.idCardWoman)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.bank_card">
|
||||
<span class="text-gray-700 font-bold">银行卡号</span>
|
||||
<span class="text-gray-600">{{ maskValue("bank_card",
|
||||
reportParams?.bank_card)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.mobile">
|
||||
<span class="text-gray-700 font-bold">手机号</span>
|
||||
<span class="text-gray-600">{{ maskValue("mobile", reportParams?.mobile)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.verification_code">
|
||||
<span class="text-gray-700 font-bold">验证码</span>
|
||||
<span class="text-gray-600">{{ maskValue("code",
|
||||
reportParams?.verification_code)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.car_license">
|
||||
<span class="text-gray-700 font-bold">车牌号</span>
|
||||
<span class="text-gray-600">{{ maskValue("car_license",
|
||||
reportParams?.car_license)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.ent_name">
|
||||
<span class="text-gray-700 font-bold">企业名称</span>
|
||||
<span class="text-gray-600">{{ maskValue("ent_name",
|
||||
reportParams?.ent_name)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex justify-between border-b pb-2 pl-2"
|
||||
v-if="reportParams?.ent_code">
|
||||
<span class="text-gray-700 font-bold">企业代码</span>
|
||||
<span class="text-gray-600">{{ maskValue("ent_code",
|
||||
reportParams?.ent_code)
|
||||
}}</span>
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex items-center bg-blue-100 rounded-xl px-4 py-2 flex-1">
|
||||
@ -378,7 +378,7 @@ const maskValue = computed(() => {
|
||||
</div> -->
|
||||
<div>
|
||||
<a class="text-blue-500" href="https://beian.miit.gov.cn">
|
||||
琼ICP备2024048057号-2
|
||||
琼ICP备2024048057号-1
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,65 +1,342 @@
|
||||
<template>
|
||||
<div class="p-6">
|
||||
<!-- 提现信息 -->
|
||||
<div class="bg-white bg-opacity-90 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl text-gray-900 font-semibold">提现到</span>
|
||||
<span class="text-lg text-blue-600">银行账户</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center mt-4">
|
||||
<span class="text-sm text-gray-600">银行卡号</span>
|
||||
<span class="text-sm text-blue-500">UPCash</span>
|
||||
<button class="text-sm text-blue-500">修改</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 bg-gradient-to-b from-blue-50/30 to-gray-50 min-h-screen">
|
||||
<div> <!-- 提现卡片 -->
|
||||
<div class="rounded-xl shadow-lg bg-gradient-to-r from-blue-50/70 to-blue-100/50 p-6 mb-4">
|
||||
<div class="flex items-center mb-6">
|
||||
<van-icon name="alipay" class="text-blue-500 text-xl mr-2" />
|
||||
<h1 class="text-xl font-bold text-gray-800">支付宝提现</h1>
|
||||
</div>
|
||||
|
||||
<!-- 提现金额 -->
|
||||
<div class="bg-white bg-opacity-90 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-xl text-gray-900 font-semibold">提现金额</span>
|
||||
<span class="text-3xl text-blue-600 font-bold">¥ 0.0</span>
|
||||
</div>
|
||||
<div class="mt-4 text-sm text-gray-600">
|
||||
<p>可提现金额 0.0 元</p>
|
||||
<p class="mt-2">每天最多提现2次,最低提现金额20元,提现金额超过1000元,会进行人工审核。</p>
|
||||
</div>
|
||||
<div class="mt-6 flex justify-end">
|
||||
<button class="bg-blue-600 hover:bg-blue-500 text-white py-2 px-6 rounded-lg shadow-md">
|
||||
全部提现
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 支付宝账号 -->
|
||||
<div class="mb-6">
|
||||
<label class="text-sm text-gray-600 mb-2 block">支付宝账号</label>
|
||||
<van-field v-model="alipayAccount" placeholder="请输入支付宝账号"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm"
|
||||
:rules="[{ required: true, message: ' ' }]">
|
||||
<template #left-icon>
|
||||
<van-icon name="phone-o" class="text-gray-500" />
|
||||
</template>
|
||||
</van-field>
|
||||
<small class="text-gray-400 text-xs mt-1 block">可填写支付宝账户绑定的手机号</small>
|
||||
|
||||
<!-- 短信验证码 -->
|
||||
<div class="bg-white bg-opacity-90 shadow-lg rounded-xl p-6 mb-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-lg text-gray-900">短信验证码</span>
|
||||
<button class="text-sm text-blue-500">手机为优先查看注册号码</button>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<input type="text" placeholder="请输入短信验证码"
|
||||
class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
||||
</div>
|
||||
<div class="mt-4 flex justify-between items-center">
|
||||
<button class="bg-blue-600 text-white py-2 px-6 rounded-lg shadow-md flex items-center space-x-2">
|
||||
<van-icon name="refresh" size="20" />
|
||||
<span>获取验证码</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 支付宝实名姓名 -->
|
||||
<div class="mb-6">
|
||||
<label class="text-sm text-gray-600 mb-2 block">实名姓名</label>
|
||||
<van-field v-model="realName" placeholder="请输入支付宝认证姓名"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm" :rules="[{
|
||||
required: true,
|
||||
message: ' ',
|
||||
validator: (val) => /^[\u4e00-\u9fa5]{2,4}$/.test(val)
|
||||
}]">
|
||||
<template #left-icon>
|
||||
<van-icon name="contact-o" class="text-gray-500" />
|
||||
</template>
|
||||
</van-field>
|
||||
<small class="text-gray-400 text-xs mt-1 block">请填写支付宝账户认证的真实姓名</small>
|
||||
|
||||
<!-- 确认提现 -->
|
||||
<div class="mt-6">
|
||||
<button class="bg-blue-600 hover:bg-blue-500 text-white py-3 px-6 rounded-lg w-full shadow-md">
|
||||
确认提现
|
||||
</button>
|
||||
</div>
|
||||
<!-- 提现金额 -->
|
||||
<div class="mb-4">
|
||||
<label class="text-sm text-gray-600 mb-2 block">提现金额</label>
|
||||
<van-field v-model.number="amount" type="number" placeholder="请输入提现金额"
|
||||
class="flex items-center rounded-lg bg-white/90 backdrop-blur-sm shadow-sm"
|
||||
:rules="[{ required: true, message: ' ' }, { validator: validateAmount, message: ' ' }]">
|
||||
<template #left-icon>
|
||||
<van-icon name="gold-coin-o" class="text-gray-500" />
|
||||
</template>
|
||||
<template #right-icon>
|
||||
元
|
||||
</template>
|
||||
<template #button>
|
||||
<van-button size="small" type="primary"
|
||||
class="bg-gradient-to-r from-blue-500/20 to-blue-400/20 text-blue-600 rounded-full px-3 shadow-sm"
|
||||
@click="fillMaxAmount">
|
||||
全部提现
|
||||
</van-button>
|
||||
</template>
|
||||
</van-field>
|
||||
</div>
|
||||
|
||||
<!-- 金额提示 -->
|
||||
<div class="text-sm text-gray-500 mb-6">
|
||||
可提现金额:<span class="text-blue-600 font-semibold">¥{{ availableAmount }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 提现规则 -->
|
||||
<div class="bg-blue-50/60 p-4 rounded-xl backdrop-blur-sm">
|
||||
<div class="flex items-center text-sm text-blue-500 mb-2">
|
||||
<van-icon name="warning" class="mr-1" />提现须知
|
||||
</div>
|
||||
<ul class="text-xs text-gray-600 space-y-1">
|
||||
<li>· 每日限提现1次,最低50元</li>
|
||||
<li>· 超过800元需人工审核(1-3个工作日)</li>
|
||||
<li>· 到账时间:24小时内</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提交按钮 -->
|
||||
<van-button type="primary" block :loading="isSubmitting"
|
||||
class="bg-gradient-to-r from-blue-500 to-blue-400 text-white rounded-xl shadow-lg h-12 font-bold text-base"
|
||||
@click="handleSubmit">
|
||||
立即提现
|
||||
</van-button>
|
||||
</div>
|
||||
<van-popup v-model:show="showStatusPopup" round position="center"
|
||||
:style="{ width: '85%', borderRadius: '20px' }" :overlay-style="{ backgroundColor: 'rgba(0,0,0,0.4)' }">
|
||||
<div class="p-8 bg-gradient-to-b from-white to-blue-50/30 relative">
|
||||
<!-- 状态内容 -->
|
||||
<div class="text-center space-y-5">
|
||||
<!-- 状态图标 -->
|
||||
<div class="relative inline-block">
|
||||
<div class="absolute inset-0 bg-gradient-to-r opacity-20 rounded-full animate-pulse blur-sm"
|
||||
:class="statusBg[status]"></div>
|
||||
<van-icon :name="statusIcon[status]" size="56" class="p-1 rounded-full border-[3px]"
|
||||
:class="statusIconClass[status]" />
|
||||
</div>
|
||||
|
||||
<!-- 状态文案 -->
|
||||
<div>
|
||||
<h2 class="text-xl font-semibold mb-1" :class="statusTextColors[status]">
|
||||
{{ statusMessages[status] }}
|
||||
</h2>
|
||||
<template v-if="status === 2">
|
||||
<p class="text-sm text-gray-500">
|
||||
已向 <span class="text-blue-500">{{ alipayAccount }}</span> 转账
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-green-600 mt-2">¥{{ amount }}</p>
|
||||
</template>
|
||||
<template v-if="status === 3">
|
||||
<p class="text-red-500 text-sm px-4">{{ failMsg }}</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 进度条(处理中状态) -->
|
||||
<van-progress v-if="status === 1" :percentage="60" stroke-width="8"
|
||||
color="linear-gradient(to right, #3b82f6, #60a5fa)" track-color="#e0f2fe"
|
||||
class="!rounded-full" />
|
||||
|
||||
<!-- 辅助文案 -->
|
||||
<div class="text-xs text-gray-400 space-y-1.5">
|
||||
<template v-if="status === 2">
|
||||
<p>预计24小时内到账</p>
|
||||
<p>可在支付宝账单中查看详情</p>
|
||||
</template>
|
||||
<template v-if="status === 1">
|
||||
<p>您的申请已进入处理队列</p>
|
||||
<p>5分钟后结果在提现记录种查看</p>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<van-button block round size="small" :color="statusButtonColor[status]"
|
||||
class="mt-4 h-11 font-medium shadow-sm" @click="handlePopupAction">
|
||||
{{ status === 1 ? '知道了' : status === 2 ? '完成' : '重新提现' }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
// 状态管理
|
||||
const status = ref(null);
|
||||
const failMsg = ref('');
|
||||
const isSubmitting = ref(false);
|
||||
const showStatusPopup = ref(false);
|
||||
|
||||
// 样式配置
|
||||
const statusIcon = {
|
||||
1: 'clock',
|
||||
2: 'checked',
|
||||
3: 'close'
|
||||
};
|
||||
|
||||
const statusIconClass = {
|
||||
1: 'text-blue-400 border-blue-100 bg-blue-50',
|
||||
2: 'text-green-500 border-green-100 bg-green-50',
|
||||
3: 'text-red-500 border-red-100 bg-red-50'
|
||||
};
|
||||
|
||||
const statusBg = {
|
||||
1: 'from-blue-100 to-blue-50',
|
||||
2: 'from-green-100 to-green-50',
|
||||
3: 'from-red-100 to-red-50'
|
||||
};
|
||||
|
||||
const statusTextColors = {
|
||||
1: 'text-blue-600',
|
||||
2: 'text-green-600',
|
||||
3: 'text-red-600'
|
||||
};
|
||||
|
||||
const statusButtonColor = {
|
||||
1: '#e0f2fe',
|
||||
2: '#4ade80',
|
||||
3: '#fca5a5'
|
||||
};
|
||||
const statusMessages = {
|
||||
1: '提现申请处理中,请稍后再查询结果',
|
||||
2: '提现成功',
|
||||
3: '提现失败'
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const alipayAccount = ref('');
|
||||
const amount = ref(null);
|
||||
const availableAmount = ref(null);
|
||||
const realName = ref('');
|
||||
|
||||
const getData = async () => {
|
||||
const { data: res, error } = await useApiFetch("/agent/revenue")
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
availableAmount.value = res.value.data.balance;
|
||||
}
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
getData();
|
||||
});
|
||||
|
||||
// 表单验证
|
||||
const validateAmount = (val) => {
|
||||
const num = Number(val);
|
||||
return num >= 50 && num <= availableAmount.value;
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
if (!realName.value.trim()) {
|
||||
showToast('请输入账户实名姓名');
|
||||
return false;
|
||||
}
|
||||
if (!/^[\u4e00-\u9fa5]{2,4}$/.test(realName.value)) {
|
||||
showToast('请输入2-4位中文姓名');
|
||||
return false;
|
||||
}
|
||||
if (!alipayAccount.value.trim()) {
|
||||
showToast('请输入支付宝账号');
|
||||
return false;
|
||||
}
|
||||
|
||||
const amountNum = Number(amount.value);
|
||||
if (!amount.value || isNaN(amountNum)) {
|
||||
showToast('请输入有效金额');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amountNum < 50) {
|
||||
showToast('提现金额不能低于50元');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (amountNum > availableAmount.value) {
|
||||
showToast('超过可提现金额');
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
// 先进行表单验证
|
||||
if (!validateForm()) return;
|
||||
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
const { data, error } = await useApiFetch("/agent/withdrawal")
|
||||
.post({ payee_account: alipayAccount.value, amount: amount.value, payee_name: realName.value })
|
||||
.json();
|
||||
if (data.value?.code === 200) {
|
||||
status.value = data.value.data.status;
|
||||
showStatusPopup.value = true;
|
||||
if (status.value === 3) {
|
||||
failMsg.value = data.value.data.fail_msg;
|
||||
}
|
||||
}
|
||||
|
||||
} catch {
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
// 弹窗操作
|
||||
const handlePopupAction = () => {
|
||||
if (status.value === 3) {
|
||||
showStatusPopup.value = false;
|
||||
resetForm();
|
||||
} else {
|
||||
showStatusPopup.value = false;
|
||||
if (status.value === 2) resetPage();
|
||||
}
|
||||
};
|
||||
// 填充最大金额
|
||||
const fillMaxAmount = () => {
|
||||
amount.value = availableAmount.value;
|
||||
};
|
||||
|
||||
|
||||
// 重置页面
|
||||
const resetForm = () => {
|
||||
status.value = null;
|
||||
alipayAccount.value = '';
|
||||
amount.value = '';
|
||||
realName.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 如果需要额外的样式,可以在这里添加 */
|
||||
<style>
|
||||
/* 自定义表单样式 */
|
||||
.van-field__control {
|
||||
@apply py-1 px-4 text-gray-800;
|
||||
}
|
||||
|
||||
.van-field__error-message {
|
||||
@apply mt-1;
|
||||
}
|
||||
|
||||
.van-button--disabled {
|
||||
@apply opacity-60 cursor-not-allowed;
|
||||
}
|
||||
|
||||
/* 弹窗入场动画 */
|
||||
.van-popup {
|
||||
transition: transform 0.4s cubic-bezier(0.22, 0.61, 0.36, 1), opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.van-popup-enter-active,
|
||||
.van-popup-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.van-popup-enter-from,
|
||||
.van-popup-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.van-popup-enter-active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.van-popup-enter-to {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* 状态图标动画 */
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
160
src/views/WithdrawDetails.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<!-- 提现记录列表 -->
|
||||
<van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" @load="onLoad">
|
||||
<div v-for="(item, index) in data.list" :key="index" class="mx-4 my-2 bg-white rounded-lg p-4 shadow-sm">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<span class="text-gray-500 text-sm">{{ item.create_time || '-' }}</span>
|
||||
<span class="font-bold" :class="getAmountColor(item.status)">{{ item.amount.toFixed(2) }}元</span>
|
||||
</div>
|
||||
<div class="flex items-center mb-2">
|
||||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium"
|
||||
:class="getStatusStyle(item.status)">
|
||||
<span class="w-2 h-2 rounded-full mr-1" :class="getDotColor(item.status)"></span>
|
||||
{{ statusToChinese(item.status) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<div v-if="item.payee_account">收款账户:{{ maskName(item.payee_account) }}</div>
|
||||
<div v-if="item.remark">备注:{{ item.remark }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 状态映射配置
|
||||
const statusConfig = {
|
||||
1: {
|
||||
chinese: '处理中',
|
||||
color: {
|
||||
bg: 'bg-yellow-100',
|
||||
text: 'text-yellow-800',
|
||||
dot: 'bg-yellow-500',
|
||||
amount: 'text-yellow-500'
|
||||
}
|
||||
},
|
||||
2: {
|
||||
chinese: '提现成功',
|
||||
color: {
|
||||
bg: 'bg-green-100',
|
||||
text: 'text-green-800',
|
||||
dot: 'bg-green-500',
|
||||
amount: 'text-green-500'
|
||||
}
|
||||
},
|
||||
3: {
|
||||
chinese: '提现失败',
|
||||
color: {
|
||||
bg: 'bg-red-100',
|
||||
text: 'text-red-800',
|
||||
dot: 'bg-red-500',
|
||||
amount: 'text-red-500'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const data = ref({
|
||||
total: 0,
|
||||
list: []
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finished = ref(false)
|
||||
|
||||
// 账户脱敏处理
|
||||
const maskName = (name) => {
|
||||
if (!name || typeof name !== 'string') return ''
|
||||
if (name.length <= 7) return name
|
||||
return name.substring(0, 3) + '****' + name.substring(7)
|
||||
}
|
||||
|
||||
// 状态转中文
|
||||
const statusToChinese = (status) => {
|
||||
return statusConfig[status]?.chinese || '未知状态'
|
||||
}
|
||||
|
||||
// 获取状态样式
|
||||
const getStatusStyle = (status) => {
|
||||
const config = statusConfig[status] || {}
|
||||
return `${config.color?.bg || 'bg-gray-100'} ${config.color?.text || 'text-gray-800'}`
|
||||
}
|
||||
|
||||
// 获取小圆点颜色
|
||||
const getDotColor = (status) => {
|
||||
return statusConfig[status]?.color.dot || 'bg-gray-500'
|
||||
}
|
||||
|
||||
// 获取金额颜色
|
||||
const getAmountColor = (status) => {
|
||||
return statusConfig[status]?.color.amount || 'text-gray-500'
|
||||
}
|
||||
|
||||
// 加载更多数据
|
||||
const onLoad = async () => {
|
||||
|
||||
if (!finished.value) {
|
||||
|
||||
await getData()
|
||||
}
|
||||
}
|
||||
|
||||
// 获取数据(修改分页逻辑)
|
||||
const getData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data: res, error } = await useApiFetch(
|
||||
`/agent/withdrawal?page=${page.value}&page_size=${pageSize.value}`
|
||||
).get().json()
|
||||
if (res.value?.code === 200 && !error.value) {
|
||||
// 保留首次加载数据
|
||||
if (page.value === 1) {
|
||||
data.value = res.value.data
|
||||
} else {
|
||||
data.value.list.push(...res.value.data.list)
|
||||
}
|
||||
|
||||
// 更新分页状态
|
||||
page.value++
|
||||
|
||||
// 判断是否加载完成
|
||||
if (data.value.list.length >= res.value.data.total ||
|
||||
res.value.data.list.length < pageSize.value) {
|
||||
finished.value = true
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化加载
|
||||
onMounted(async () => {
|
||||
// 重置分页状态
|
||||
page.value = 1
|
||||
finished.value = false
|
||||
await getData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 保持原有样式不变 */
|
||||
.list-enter-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
|
||||
:deep(.van-list__finished-text) {
|
||||
@apply py-4 text-gray-400 text-sm;
|
||||
}
|
||||
|
||||
:deep(.van-list__loading) {
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
@ -10,8 +10,15 @@ import indexIcon7 from '@/assets/images/index_icon_7.png'
|
||||
function toInquire(name) {
|
||||
router.push(`/inquire/${name}`)
|
||||
}
|
||||
function toInvitation() {
|
||||
router.push({ name: "invitation" })
|
||||
|
||||
}
|
||||
const toPromote = () => {
|
||||
router.push("/promote")
|
||||
router.push({ name: "promote" })
|
||||
}
|
||||
const toHelp = () => {
|
||||
router.push("/help")
|
||||
}
|
||||
const services = ref([
|
||||
{
|
||||
@ -86,7 +93,7 @@ function toHistory() {
|
||||
<img class="h-full w-full rounded-xl overflow-hidden" src="@/assets/images/banner.png" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex items-center justify-around gap-4 px-6 pb-1">
|
||||
<div class="flex items-center justify-around gap-3 px-6 pb-1">
|
||||
<div class="" @click="toPromote">
|
||||
<div
|
||||
class="h-16 w-16 p-2 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
@ -94,26 +101,26 @@ function toHistory() {
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold">直推报告</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="" @click="toInvitation">
|
||||
<div
|
||||
class="h-16 w-16 p-2 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/icon_xj.svg" alt="邀请下级" class="w-12 h-12" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold">邀请下级</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<!-- <div class="" @click="toHelp">
|
||||
<div
|
||||
class="h-16 w-16 p-2 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/icon_xj.svg" alt="邀请下级" class="w-12 h-12" />
|
||||
<img src="@/assets/images/icon_bz.svg" alt="帮助中心" class="w-12 h-12" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold">邀请下级</div>
|
||||
</div>
|
||||
<div class="">
|
||||
<div class="text-center mt-1 font-bold">帮助中心</div>
|
||||
</div> -->
|
||||
<div class="" @click="toHistory">
|
||||
<div
|
||||
class="h-16 w-16 p-2 bg-gradient-to-b from-white to-blue-100/10 rounded-full shadow-lg flex items-center justify-center">
|
||||
<img src="@/assets/images/icon_xj.svg" alt="邀请下级" class="w-12 h-12" />
|
||||
<img src="@/assets/images/icon_bg.svg" alt="我的报告" class="w-12 h-12" />
|
||||
</div>
|
||||
<div class="text-center mt-1 font-bold">邀请下级</div>
|
||||
<div class="text-center mt-1 font-bold">我的报告</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
486
src/views/temp.vue
Normal file
@ -0,0 +1,486 @@
|
||||
<template>
|
||||
<div class="p-4 max-w-3xl mx-auto min-h-screen">
|
||||
<!-- 标题部分 -->
|
||||
<div class="card mb-4 p-4 bg-gradient-to-r from-blue-500 to-blue-600 rounded-lg shadow-lg text-white">
|
||||
<h1 class="text-2xl font-extrabold mb-2">专业报告定价配置</h1>
|
||||
<p class=" opacity-90">请选择报告类型并设置定价策略,助您实现精准定价</p>
|
||||
</div>
|
||||
|
||||
<!-- 报告选择器 -->
|
||||
<!-- <div class="mb-4 overflow-x-auto">
|
||||
<div class="flex space-x-4">
|
||||
<label v-for="report in reportOptions" :key="report.id" class="cursor-pointer flex-shrink-0">
|
||||
<input type="radio" :value="report.id" v-model="selectedReportId" class="hidden" />
|
||||
<div :class="[
|
||||
'px-3 py-1 rounded border text-center',
|
||||
selectedReportId === report.id ? 'bg-blue-500 text-white border-blue-500' : 'bg-white text-gray-700'
|
||||
]">
|
||||
{{ report.text }}
|
||||
<span v-if="!isReportConfigured(report.id)" class="ml-1 text-xs text-yellow-500">(未配置)</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<van-field readonly clickable name="reportType" v-model="selectedReportText" label="报告类型"
|
||||
placeholder="点击选择报告" @click="showPicker = true" class="card">
|
||||
<template #label>
|
||||
<span class="text-blue-600 font-medium">📝 选择报告</span>
|
||||
</template>
|
||||
<template #right-icon>
|
||||
<van-icon name="arrow-down" class="text-gray-400" />
|
||||
</template>
|
||||
</van-field>
|
||||
<van-popup v-model:show="showPicker" position="bottom">
|
||||
<van-picker :columns="reportOptions" :default-index="0" @confirm="onConfirm"
|
||||
@cancel="showPicker = false" />
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedReportText" class="space-y-6">
|
||||
<!-- 配置卡片 -->
|
||||
<div class="card">
|
||||
<!-- 当前报告标题 -->
|
||||
<div class="flex items-center mb-6">
|
||||
<van-icon name="description" class="text-blue-500 text-xl mr-2" />
|
||||
<h2 class="text-xl font-semibold text-gray-800">
|
||||
{{ selectedReportText }}配置
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- 显示当前产品的基础成本信息 -->
|
||||
<div v-if="currentProductConf && currentProductConf.cost_price"
|
||||
class="px-4 py-2 mb-4 bg-gray-50 border border-gray-200 rounded-lg shadow-sm">
|
||||
<div class="text-lg font-semibold text-gray-700">产品基础信息</div>
|
||||
<div class="mt-1 text-sm text-gray-600">
|
||||
<div>基础成本价:<span class="font-medium">{{ currentProductConf.cost_price }}</span> 元</div>
|
||||
<!-- <div>区间起始价:<span class="font-medium">{{ currentProductConf.price_range_min }}</span> 元</div> -->
|
||||
<div>最高设定金额上限:<span class="font-medium">{{ currentProductConf.price_range_max }}</span> 元</div>
|
||||
<div>最高设定比例上限:<span class="font-medium">{{ priceRatioMax }}</span> %</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">成本策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 加价金额 -->
|
||||
<van-field v-model.number="currentConfig.price_increase_amount" label="加价金额" type="number"
|
||||
placeholder="0" @blur="validateDecimal('price_increase_amount')" class="custom-field"
|
||||
:class="{ 'van-field--error': increaseError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">🚀 加价金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大加价金额为{{ priceIncreaseAmountMax }}元<br>
|
||||
说明:加价金额是在基础成本价上增加的额外费用,决定下级报告的最低定价,您将获得所有输入的金额利润。
|
||||
</div>
|
||||
|
||||
<!-- 分隔线 -->
|
||||
<van-divider :style="{ borderColor: '#e5e7eb', padding: '0 16px' }" class="my-6">
|
||||
<van-icon name="exchange" class="text-gray-400 mx-2" />
|
||||
<span class="text-gray-400 text-sm">定价策略配置</span>
|
||||
</van-divider>
|
||||
|
||||
<!-- 定价区间最低 -->
|
||||
<van-field v-model.number="currentConfig.price_range_from" label="定价区间最低" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_from'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最低金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最低金额不能低于(基础最低 {{ currentProductConf?.price_range_min || 0 }}元 + 加价金额)<br>
|
||||
说明:设定的最低金额为定价区间的起始值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 定价区间最高 -->
|
||||
<van-field v-model.number="currentConfig.price_range_to" label="定价区间最高" type="number" placeholder="0"
|
||||
@blur="() => { validateDecimal('price_range_to'); validateRange(); }" class="custom-field"
|
||||
:class="{ 'van-field--error': rangeError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">💰 最高金额</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">元</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最高金额不能超过上限({{ currentProductConf?.price_range_max || 0 }}元)和大于最低金额({{ priceIncreaseMax
|
||||
}}元)<br>
|
||||
说明:设定的最高金额为定价区间的结束值,若下级设定的报告金额在区间内,则区间内部分将按比例获得收益。
|
||||
</div>
|
||||
|
||||
<!-- 收取比例 -->
|
||||
<van-field v-model.number="currentConfig.price_ratio" label="收取比例" type="digit" placeholder="0"
|
||||
@blur="() => { validateRatio(); }" class="custom-field" :class="{ 'van-field--error': ratioError }">
|
||||
<template #label>
|
||||
<span class="text-gray-600 font-medium">📈 收取比例</span>
|
||||
</template>
|
||||
<template #extra>
|
||||
<span class="text-blue-500 font-medium ml-2">%</span>
|
||||
</template>
|
||||
</van-field>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
提示:最大收取比例为{{ priceRatioMax }}%<br>
|
||||
说明:收取比例表示对定价区间内(即报告金额超过最低金额,小于最高金额的部分)的金额,按此比例进行利润分成。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 保存按钮 -->
|
||||
<van-button type="primary" block
|
||||
class="shadow-lg bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white rounded-xl h-12"
|
||||
@click="handleSubmit">
|
||||
<van-icon name="success" class="mr-2" />
|
||||
保存当前报告配置
|
||||
</van-button>
|
||||
</div>
|
||||
<!-- 未选择提示 -->
|
||||
<div v-else class="text-center py-12">
|
||||
<van-icon name="warning" class="text-gray-400 text-4xl mb-4" />
|
||||
<p class="text-gray-500">请先选择需要配置的报告类型</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { showToast } from 'vant';
|
||||
|
||||
// 报告类型选项
|
||||
const reportOptions = [
|
||||
{ text: '人事背调', value: 'backgroundcheck', id: 1 },
|
||||
{ text: '家政风险', value: 'homeservice', id: 3 },
|
||||
{ text: '婚恋风险', value: 'marriage', id: 4 },
|
||||
{ text: '贷前背调', value: 'preloanbackgroundcheck', id: 5 },
|
||||
{ text: '租赁风险', value: 'rentalrisk', id: 6 },
|
||||
{ text: '个人风险', value: 'riskassessment', id: 7 },
|
||||
{ text: '老板企业报告', value: 'companyinfo', id: 2 },
|
||||
|
||||
];
|
||||
|
||||
// 状态管理
|
||||
const showPicker = ref(false);
|
||||
const selectedReport = ref(reportOptions[0]);
|
||||
const selectedReportText = ref(reportOptions[0].text);
|
||||
const selectedReportId = ref(reportOptions[0].id);
|
||||
const originalConfigData = reactive([]);
|
||||
|
||||
const configData = reactive([]);
|
||||
const productConfigData = reactive([]);
|
||||
const priceIncreaseMax = ref(null);
|
||||
const priceIncreaseAmountMax = ref(null);
|
||||
const priceRatioMax = ref(null);
|
||||
const rangeError = ref(false);
|
||||
const ratioError = ref(false);
|
||||
const increaseError = ref(false);
|
||||
|
||||
// 监听selectedReportId变化,更新当前报告
|
||||
watch(selectedReportId, (newVal) => {
|
||||
const report = reportOptions.find(r => r.id === newVal);
|
||||
selectedReport.value = report;
|
||||
selectedReportText.value = report.text;
|
||||
});
|
||||
|
||||
const isReportConfigured = (reportId) => {
|
||||
return originalConfigData.some(item => item.product_id === reportId);
|
||||
};
|
||||
|
||||
// 辅助函数:判断报告配置是否已经修改
|
||||
const isReportModified = (reportId) => {
|
||||
// 查找对应的配置数据
|
||||
const conf = configData.find(item => item.id === reportId);
|
||||
// 如果没有配置数据,说明还未修改
|
||||
if (!conf) return false;
|
||||
// 如果所有关键字段仍为 null,则认为未修改
|
||||
return conf.price_range_from !== null || conf.price_range_to !== null || conf.price_ratio !== null || conf.price_increase_amount !== null;
|
||||
};
|
||||
// 当前配置计算属性
|
||||
const currentConfig = computed(() => {
|
||||
let existing = configData.find(item => item.id === selectedReport.value.id);
|
||||
if (!existing) {
|
||||
existing = {
|
||||
id: selectedReport.value.id,
|
||||
price_range_from: null,
|
||||
price_range_to: null,
|
||||
price_ratio: null,
|
||||
price_increase_amount: null,
|
||||
};
|
||||
configData.push(existing);
|
||||
}
|
||||
return existing;
|
||||
});
|
||||
// 当前产品配置计算属性,用于显示基础成本、区间等信息
|
||||
const currentProductConf = computed(() => {
|
||||
return productConfigData.find(item => item.product_id === selectedReport.value.id) || {};
|
||||
});// 辅助函数:获取当前产品配置
|
||||
const getCurrentProductConfig = () => {
|
||||
return productConfigData.find(item => item.product_id === selectedReport.value.id);
|
||||
};
|
||||
|
||||
// 金额输入格式验证:确保最多两位小数
|
||||
const validateDecimal = (field) => {
|
||||
const value = currentConfig.value[field];
|
||||
if (value === null || value === undefined) return;
|
||||
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
currentConfig.value[field] = null;
|
||||
return;
|
||||
}
|
||||
const fixedValue = parseFloat(numValue.toFixed(2));
|
||||
currentConfig.value[field] = fixedValue;
|
||||
|
||||
if (field === 'price_increase_amount') {
|
||||
if (fixedValue > priceIncreaseAmountMax.value) {
|
||||
currentConfig.value[field] = priceIncreaseAmountMax.value;
|
||||
showToast(`加价金额最大为${priceIncreaseAmountMax.value}元`);
|
||||
increaseError.value = true;
|
||||
setTimeout(() => {
|
||||
increaseError.value = false;
|
||||
}, 2000);
|
||||
} else {
|
||||
increaseError.value = false;
|
||||
}
|
||||
// 当加价金额改变后,重新验证价格区间
|
||||
validateRange();
|
||||
}
|
||||
};
|
||||
|
||||
// 价格区间验证(在 @blur 中调用)
|
||||
const validateRange = () => {
|
||||
if (currentConfig.value.price_range_from === null || currentConfig.value.price_range_to === null) {
|
||||
rangeError.value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNaN(currentConfig.value.price_range_from) || isNaN(currentConfig.value.price_range_to)) return;
|
||||
const productConf = getCurrentProductConfig();
|
||||
const additional = currentConfig.value.price_increase_amount || 0;
|
||||
const minAllowed = productConf.cost_price + additional // 使用成本价作为最小值
|
||||
const maxAllowed = productConf.price_range_max; // 使用产品配置中的最大价格作为最大值
|
||||
console.log("currentConfig.value.price_range_from", typeof currentConfig.value.price_range_from)
|
||||
if (currentConfig.value.price_range_from < minAllowed) {
|
||||
currentConfig.value.price_range_from = minAllowed;
|
||||
showToast(`最低金额不能低于成本价 ${minAllowed}元`);
|
||||
rangeError.value = true;
|
||||
closeRangeError()
|
||||
|
||||
currentConfig.value.price_range_to = currentConfig.value.price_range_from + priceIncreaseMax.value
|
||||
return
|
||||
}
|
||||
|
||||
if (currentConfig.value.price_range_to < currentConfig.value.price_range_from) {
|
||||
showToast('最高金额不能低于最低金额');
|
||||
if (currentConfig.value.price_range_from + priceIncreaseMax.value > maxAllowed) {
|
||||
currentConfig.value.price_range_to = maxAllowed
|
||||
} else {
|
||||
currentConfig.value.price_range_to = currentConfig.value.price_range_from + priceIncreaseMax.value
|
||||
}
|
||||
rangeError.value = true;
|
||||
closeRangeError()
|
||||
return;
|
||||
}
|
||||
const diff = currentConfig.value.price_range_to - currentConfig.value.price_range_from;
|
||||
if (diff > priceIncreaseMax.value) {
|
||||
showToast(`价格区间最大差值为${priceIncreaseMax.value}元`);
|
||||
currentConfig.value.price_range_to = currentConfig.value.price_range_from + priceIncreaseMax.value
|
||||
closeRangeError()
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentConfig.value.price_range_to > maxAllowed) {
|
||||
currentConfig.value.price_range_to = maxAllowed;
|
||||
showToast(`最高金额不能超过 ${maxAllowed}元`);
|
||||
closeRangeError()
|
||||
}
|
||||
|
||||
if (!rangeError.value) {
|
||||
rangeError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 收取比例验证(修改为保留两位小数,不再四舍五入取整)
|
||||
const validateRatio = () => {
|
||||
let value = currentConfig.value.price_ratio;
|
||||
if (value === null || value === undefined) return;
|
||||
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) {
|
||||
currentConfig.value.price_ratio = null;
|
||||
ratioError.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (numValue > priceRatioMax.value) {
|
||||
currentConfig.value.price_ratio = priceRatioMax.value;
|
||||
showToast(`收取比例最大为${priceRatioMax.value}%`);
|
||||
ratioError.value = true;
|
||||
setTimeout(() => {
|
||||
ratioError.value = false;
|
||||
}, 1000);
|
||||
} else if (numValue < 0) {
|
||||
currentConfig.value.price_ratio = 0;
|
||||
ratioError.value = true;
|
||||
} else {
|
||||
currentConfig.value.price_ratio = parseFloat(numValue.toFixed(2));
|
||||
ratioError.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取配置
|
||||
const getConfig = async () => {
|
||||
try {
|
||||
const { data, error } = await useApiFetch("/agent/membership/user_config")
|
||||
.get()
|
||||
.json();
|
||||
|
||||
if (data.value?.code === 200) {
|
||||
// 转换API数据格式(原有配置)
|
||||
configData.splice(
|
||||
0,
|
||||
configData.length,
|
||||
...data.value.data.agent_membership_user_config.map(item => ({
|
||||
id: item.product_id,
|
||||
price_range_from: item.price_range_from,
|
||||
price_range_to: item.price_range_to,
|
||||
price_ratio: item.price_ratio * 100, // 转换为百分比
|
||||
price_increase_amount: item.price_increase_amount,
|
||||
}))
|
||||
);
|
||||
|
||||
// 保存产品配置数据
|
||||
productConfigData.splice(
|
||||
0,
|
||||
productConfigData.length,
|
||||
...data.value.data.product_config
|
||||
);
|
||||
|
||||
// 设置动态限制值
|
||||
priceIncreaseMax.value = data.value.data.price_increase_max;
|
||||
priceIncreaseAmountMax.value = data.value.data.price_increase_amount;
|
||||
priceRatioMax.value = data.value.data.price_ratio * 100;
|
||||
}
|
||||
} catch (error) {
|
||||
showToast('配置加载失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 提交处理
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
console.log("configData", configData)
|
||||
// 前端数据转换
|
||||
const submitData = configData.map(item => ({
|
||||
product_id: item.id,
|
||||
price_range_from: item.price_range_from || 0,
|
||||
price_range_to: item.price_range_to || 0,
|
||||
price_ratio: (item.price_ratio || 0) / 100, // 转换为小数
|
||||
price_increase_amount: item.price_increase_amount || 0,
|
||||
}));
|
||||
console.log("submitData", submitData)
|
||||
// 最终验证
|
||||
const isValid = configData.every(item => {
|
||||
const from = item.price_range_from;
|
||||
const to = item.price_range_to;
|
||||
|
||||
if (from !== null && to !== null && to < from) {
|
||||
showToast(`${selectedReportText.value}的价格区间无效`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (!isValid) return;
|
||||
|
||||
// const { data } = await useApiFetch("/agent/membership/user_config")
|
||||
// .post({ configs: submitData })
|
||||
// .json();
|
||||
|
||||
// if (data.value?.code === 200) {
|
||||
// showToast({ message: '配置保存成功', position: 'top' });
|
||||
// await getConfig();
|
||||
// }
|
||||
} catch (error) {
|
||||
showToast('保存失败,请稍后重试');
|
||||
}
|
||||
};
|
||||
|
||||
// 选择器确认
|
||||
const onConfirm = ({ selectedOptions }) => {
|
||||
console.log("selectedOption", selectedOptions)
|
||||
selectedReport.value = selectedOptions[0];
|
||||
selectedReportText.value = selectedOptions[0].text;
|
||||
showPicker.value = false;
|
||||
// 重置错误状态
|
||||
rangeError.value = false;
|
||||
ratioError.value = false;
|
||||
increaseError.value = false;
|
||||
};
|
||||
const closeRangeError = () => {
|
||||
setTimeout(() => {
|
||||
rangeError.value = false;
|
||||
}, 2000)
|
||||
}
|
||||
onMounted(() => {
|
||||
getConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.custom-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.custom-field .van-field__body {
|
||||
@apply bg-gray-50 rounded-lg px-3 py-2;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.custom-field:focus-within .van-field__body {
|
||||
@apply ring-2 ring-blue-200;
|
||||
}
|
||||
|
||||
.van-picker__toolbar {
|
||||
@apply bg-gray-50 rounded-t-lg;
|
||||
}
|
||||
|
||||
.van-picker__confirm {
|
||||
@apply text-blue-500 font-medium;
|
||||
}
|
||||
|
||||
.van-divider {
|
||||
@apply before:bg-gray-100 after:bg-gray-100;
|
||||
}
|
||||
|
||||
/* 错误状态样式 */
|
||||
.van-field--error .van-field__control {
|
||||
color: #ee0a24;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__label {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.van-field--error .van-field__body {
|
||||
@apply ring-2 ring-red-200 bg-red-50;
|
||||
}
|
||||
</style>
|
@ -14,19 +14,19 @@ export default defineConfig({
|
||||
port: 5678, // 自定义端口号,可选
|
||||
strictPort: true, // 如果端口被占用则抛出错误而不是使用下一个可用端口
|
||||
proxy: {
|
||||
"/api/v1": {
|
||||
target: "https://www.tianyuandb.com", // 本地接口地址
|
||||
changeOrigin: true,
|
||||
},
|
||||
// "/api/v1": {
|
||||
// target: "https://www.tianyuandb.com", // 本地接口地址
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
"/api/v1/chat": {
|
||||
target: "https://www.tianyuandb.com", // 本地接口地址
|
||||
changeOrigin: true,
|
||||
// rewrite: (path) => path.replace(/^\/api\/v1\/chat/, '/chat')
|
||||
},
|
||||
// "/api/v1": {
|
||||
// target: "https://6m4685017o.goho.co", // 本地接口地址
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
"/api/v1": {
|
||||
target: "https://6m4685017o.goho.co", // 本地接口地址
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
@ -38,7 +38,12 @@ export default defineConfig({
|
||||
"@vueuse/core", // 自动引入 VueUse 中的工具函数(可选)
|
||||
],
|
||||
dts: "src/auto-imports.d.ts", // 生成类型定义文件(可选)
|
||||
dirs: ["src/composables", "src/stores", "src/components"],
|
||||
dirs: [
|
||||
"src/composables",
|
||||
"src/stores",
|
||||
"src/components",
|
||||
"src/stores",
|
||||
],
|
||||
resolvers: [VantResolver()],
|
||||
}),
|
||||
Components({
|
||||
|