mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
synced 2026-11-06 17:09:01 +08:00
Compare commits
519 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1411f15679 | ||
|
|
af7bd78614 | ||
|
|
0170c92f9a | ||
|
|
5e59f2cd3c | ||
|
|
6b59ae3d6e | ||
|
|
b589c551bf | ||
|
|
b8a60a9369 | ||
|
|
1032b6e34d | ||
|
|
0a47f84c32 | ||
|
|
2d4a316129 | ||
|
|
b1d3f56f10 | ||
|
|
87477bc32b | ||
|
|
6c13dc1b65 | ||
|
|
215754a6de | ||
|
|
c15e954549 | ||
|
|
755f574e8e | ||
|
|
986625c516 | ||
|
|
a41936dc00 | ||
|
|
eba79a78e8 | ||
|
|
a700a3d211 | ||
|
|
b603f8c451 | ||
|
|
b773ada7a8 | ||
|
|
40d42f001d | ||
|
|
a108bb4d13 | ||
|
|
79433dd766 | ||
|
|
fb10038547 | ||
|
|
c6d58f28b7 | ||
|
|
a32f6d2313 | ||
|
|
9cf9957e5b | ||
|
|
cc9d5535cf | ||
|
|
74d186ff36 | ||
|
|
a3193c3b68 | ||
|
|
e858741bb8 | ||
|
|
0bffe81c6b | ||
|
|
2107e3f03a | ||
|
|
27034902fe | ||
|
|
42e9b7c15f | ||
|
|
30ce901137 | ||
|
|
7cc605bed1 | ||
|
|
2ad8a7f5a6 | ||
|
|
e30ff03b10 | ||
|
|
ba4e9126e8 | ||
|
|
43bf0773bc | ||
|
|
f6d4fac35e | ||
|
|
ef3401a9e4 | ||
|
|
8f540224bb | ||
|
|
c12c76f6f5 | ||
|
|
4fdd266dec | ||
|
|
9d18d09cab | ||
|
|
4a194ee0a1 | ||
|
|
69b8395686 | ||
|
|
907ad2d02c | ||
|
|
651d1106da | ||
|
|
4b1a09cbc3 | ||
|
|
ee6f66cac3 | ||
|
|
f51fcad9c2 | ||
|
|
359d1ad24c | ||
|
|
7d3daf2f02 | ||
|
|
eb40916cf7 | ||
|
|
370c63afcd | ||
|
|
94e846af6c | ||
|
|
a6ba2b3dbe | ||
|
|
6754a54f1b | ||
|
|
7626f944ad | ||
|
|
27134526a0 | ||
|
|
4bb4bbdc6b | ||
|
|
239855e53b | ||
|
|
8ed2997fcf | ||
|
|
3ec61f41bc | ||
|
|
38defd1201 | ||
|
|
7566076fa4 | ||
|
|
41e8cff749 | ||
|
|
f727e01ef0 | ||
|
|
02b5f246a6 | ||
|
|
349ffa4f34 | ||
|
|
4d0b0fb5e4 | ||
|
|
fc3cad18e6 | ||
|
|
23e3825366 | ||
|
|
686aed848d | ||
|
|
4e2fcbc1fe | ||
|
|
e14481ce6b | ||
|
|
6198a6d244 | ||
|
|
90ee7d5b1d | ||
|
|
b1743cefb0 | ||
|
|
2965800d87 | ||
|
|
3a44b7243d | ||
|
|
5f293d929d | ||
|
|
6654c1de51 | ||
|
|
dc2bef455a | ||
|
|
c290c7a329 | ||
|
|
d5663cfb4d | ||
|
|
b7cfe13ffd | ||
|
|
134a8b19e4 | ||
|
|
137232290b | ||
|
|
87743cb77d | ||
|
|
7939998a30 | ||
|
|
1fcc016ccd | ||
|
|
3219f6d576 | ||
|
|
83da8f3003 | ||
|
|
c858c82950 | ||
|
|
f9c7610976 | ||
|
|
919af44fda | ||
|
|
db7cfcea85 | ||
|
|
e95aed8e38 | ||
|
|
24454e2546 | ||
|
|
0ee03d9fcf | ||
|
|
c93862066b | ||
|
|
46dbeb77c8 | ||
|
|
8bcc9de352 | ||
|
|
bac8e3a327 | ||
|
|
29a20afe93 | ||
|
|
0ef3eb9bf7 | ||
|
|
ef6f572311 | ||
|
|
c1fce859ce | ||
|
|
2cd7b7b63f | ||
|
|
75991db6f6 | ||
|
|
f894b5b1fc | ||
|
|
7ae6967b1e | ||
|
|
de94f212c3 | ||
|
|
d166d5943c | ||
|
|
c0e5d75cc6 | ||
|
|
9195bcdeb5 | ||
|
|
7778a62d6a | ||
|
|
31c341148f | ||
|
|
0b601e4f3e | ||
|
|
1d93d9103d | ||
|
|
5bc5152e49 | ||
|
|
e9d1a81612 | ||
|
|
7d3d06e8af | ||
|
|
55bb6ab51a | ||
|
|
70c2e68bff | ||
|
|
17287df293 | ||
|
|
a8d321c66d | ||
|
|
fad004aed5 | ||
|
|
fe9060073f | ||
|
|
393c60200f | ||
|
|
e61222928e | ||
|
|
b51caa9135 | ||
|
|
992bb76072 | ||
|
|
863315eaed | ||
|
|
52bf47e358 | ||
|
|
8f49dce8f0 | ||
|
|
4490a4119e | ||
|
|
2d0b78af52 | ||
|
|
7148b32c43 | ||
|
|
20b2ca04f0 | ||
|
|
25677ced97 | ||
|
|
4517c9562a | ||
|
|
2634017fb3 | ||
|
|
b69c7d5a05 | ||
|
|
098bee9bb4 | ||
|
|
2d369ad729 | ||
|
|
3bf0be98cb | ||
|
|
1046a1052e | ||
|
|
1cc6cc4ac7 | ||
|
|
dae3e74a60 | ||
|
|
fddceb5f36 | ||
|
|
2aed3b9305 | ||
|
|
101aa279d8 | ||
|
|
abbadfbab1 | ||
|
|
8ad76a5278 | ||
|
|
726a85e76c | ||
|
|
2d69967f51 | ||
|
|
4aeaa115dc | ||
|
|
2d251faff1 | ||
|
|
231936c91d | ||
|
|
37b8e17cba | ||
|
|
7d928e3df9 | ||
|
|
bb4435fab4 | ||
|
|
1e0580ba15 | ||
|
|
f5716c901d | ||
|
|
2c4c0ea4f0 | ||
|
|
63fee6f272 | ||
|
|
9ab1cec1bb | ||
|
|
7c2a50eec0 | ||
|
|
82810eb179 | ||
|
|
53917d32c0 | ||
|
|
f324fbe71d | ||
|
|
c6afd889ef | ||
|
|
116ba65bda | ||
|
|
02812a9128 | ||
|
|
fcc99b61e8 | ||
|
|
c9a3c72c10 | ||
|
|
abc9c59e29 | ||
|
|
9ed897aa85 | ||
|
|
5a84f2192f | ||
|
|
9ac9cd480b | ||
|
|
b635f91e9f | ||
|
|
99079f0b3d | ||
|
|
317c2171e4 | ||
|
|
cba7ae98fa | ||
|
|
e6dcb5cd3a | ||
|
|
94f1da349a | ||
|
|
06b5a9d7f1 | ||
|
|
48bebc0503 | ||
|
|
af1897f4ea | ||
|
|
8238211f33 | ||
|
|
907f51b13a | ||
|
|
3b036a7cb7 | ||
|
|
9a0cb35637 | ||
|
|
1c938e0007 | ||
|
|
f31ff79a39 | ||
|
|
d5ea5a4fa9 | ||
|
|
c7237e561c | ||
|
|
6717721725 | ||
|
|
5e5d5b904b | ||
|
|
a353022d36 | ||
|
|
ca4e458db1 | ||
|
|
7b8e954555 | ||
|
|
b4f46ded2b | ||
|
|
274a03fe73 | ||
|
|
0a07ebfe8b | ||
|
|
39c82361b6 | ||
|
|
89f4e051ca | ||
|
|
c04ae5eb39 | ||
|
|
ec692e1134 | ||
|
|
02a1f7ee83 | ||
|
|
97aa989e0b | ||
|
|
ac67c16378 | ||
|
|
2bdb8682e1 | ||
|
|
c070ed01f7 | ||
|
|
af4cfe7fba | ||
|
|
6a8e666fb8 | ||
|
|
c097a423f9 | ||
|
|
f0dcf473e8 | ||
|
|
bdff6a3c93 | ||
|
|
74829c966d | ||
|
|
77db8df123 | ||
|
|
a403a7d432 | ||
|
|
951d2d7f30 | ||
|
|
e8ddc53188 | ||
|
|
0708226890 | ||
|
|
b0447eec3a | ||
|
|
e37a8ec41d | ||
|
|
467b7a0cba | ||
|
|
9a583e75f3 | ||
|
|
f9ae51606f | ||
|
|
e768a4c1db | ||
|
|
a77390bf5c | ||
|
|
1e41801612 | ||
|
|
d870ee409a | ||
|
|
b2a448447c | ||
|
|
38a00e6177 | ||
|
|
767ad94565 | ||
|
|
7499682883 | ||
|
|
9192f32bbb | ||
|
|
d256fd3559 | ||
|
|
5844e74d32 | ||
|
|
989a567ecb | ||
|
|
d5d47ffae5 | ||
|
|
8001622be2 | ||
|
|
fc5a6a6f7f | ||
|
|
dc15439861 | ||
|
|
c9c0d2d171 | ||
|
|
44cf46fed3 | ||
|
|
63ebf1a7c5 | ||
|
|
bc10d774a7 | ||
|
|
90f656beab | ||
|
|
66c729b539 | ||
|
|
db4dfd3edc | ||
|
|
e7879aac65 | ||
|
|
723570b392 | ||
|
|
5dc562396c | ||
|
|
b266aad634 | ||
|
|
a25c75bf7a | ||
|
|
43fc159e27 | ||
|
|
2e37cb8158 | ||
|
|
529175c0ab | ||
|
|
071599074c | ||
|
|
d420c128eb | ||
|
|
b33fd7e2b3 | ||
|
|
815118d496 | ||
|
|
83031507b9 | ||
|
|
7565086291 | ||
|
|
7c71375b6c | ||
|
|
c919b1463c | ||
|
|
b72d831df9 | ||
|
|
2c494ac0d1 | ||
|
|
83b2295818 | ||
|
|
11a831299a | ||
|
|
bbe315fb45 | ||
|
|
9c03205c9f | ||
|
|
12a4e15c13 | ||
|
|
0843e457b0 | ||
|
|
b1bde2f105 | ||
|
|
d2ff0b1ea1 | ||
|
|
7ff13babaa | ||
|
|
0da7dc6796 | ||
|
|
f84f9ab696 | ||
|
|
8b5f7def93 | ||
|
|
8c33b899bf | ||
|
|
8767ad6d3f | ||
|
|
f797538935 | ||
|
|
cfb4310d6f | ||
|
|
d59cd74bf0 | ||
|
|
c378ce7d71 | ||
|
|
9cc6f519a9 | ||
|
|
18251dde89 | ||
|
|
04bf707836 | ||
|
|
5e38247ce8 | ||
|
|
979a1e6b14 | ||
|
|
9127d61a59 | ||
|
|
fed76499a0 | ||
|
|
6843feebfa | ||
|
|
e650ee66ff | ||
|
|
fa6f571f61 | ||
|
|
09b7ece510 | ||
|
|
b30caf051c | ||
|
|
861bf1eb07 | ||
|
|
a0f1417f42 | ||
|
|
2b5f57dfab | ||
|
|
22a5f15201 | ||
|
|
6baf1534c1 | ||
|
|
df3ce9eda7 | ||
|
|
7f71614781 | ||
|
|
8c5b703c47 | ||
|
|
394cda3efd | ||
|
|
7b9b5c2819 | ||
|
|
4625c087d5 | ||
|
|
f7c3c56b53 | ||
|
|
0aa9831a62 | ||
|
|
ea32948b84 | ||
|
|
0feaf55381 | ||
|
|
eaa04e395b | ||
|
|
0f8d53b09a | ||
|
|
031aa0f173 | ||
|
|
5f5b571899 | ||
|
|
b6f041f68c | ||
|
|
558a3228be | ||
|
|
2e4bedae31 | ||
|
|
e2905e8fdd | ||
|
|
ddee1fca8e | ||
|
|
d2b13e146e | ||
|
|
9b3704a141 | ||
|
|
9b5e2ec7f0 | ||
|
|
adf4e106d8 | ||
|
|
d01edbdfe2 | ||
|
|
f08d166c86 | ||
|
|
b8c61fdabd | ||
|
|
6b3b61acb5 | ||
|
|
1a6951b717 | ||
|
|
165df678b3 | ||
|
|
2abbee0a49 | ||
|
|
a6883fe607 | ||
|
|
6ee0abd150 | ||
|
|
68e927c6f4 | ||
|
|
1776f43425 | ||
|
|
fc8123c194 | ||
|
|
34448b8a23 | ||
|
|
7179ca41de | ||
|
|
3c7d940ba8 | ||
|
|
1225a41de4 | ||
|
|
d101cd1b6a | ||
|
|
4851487ecd | ||
|
|
eb0faad7ec | ||
|
|
524666f6e4 | ||
|
|
e9da67dcce | ||
|
|
aa8fc523df | ||
|
|
63570a923b | ||
|
|
638e82dd28 | ||
|
|
68dc960232 | ||
|
|
bbbe25e339 | ||
|
|
d8cca8b372 | ||
|
|
1e9df74034 | ||
|
|
8c8522c68e | ||
|
|
5afc5477a9 | ||
|
|
a6e48813cd | ||
|
|
a1a286dfb6 | ||
|
|
132c64f438 | ||
|
|
8e0bde3428 | ||
|
|
d9ac9eb2eb | ||
|
|
6ccdeba85e | ||
|
|
bbf0f39d69 | ||
|
|
109af44c6b | ||
|
|
bc9c012bb2 | ||
|
|
ba02c6ca7d | ||
|
|
b840a0adf1 | ||
|
|
5cb2f6ad91 | ||
|
|
36a7a80814 | ||
|
|
17299d00e1 | ||
|
|
a56ea2d456 | ||
|
|
3a705cce8e | ||
|
|
5550285531 | ||
|
|
0ba7644862 | ||
|
|
0e169bd116 | ||
|
|
4b9515b21a | ||
|
|
3f0e34b715 | ||
|
|
a393910c0a | ||
|
|
213f4b8408 | ||
|
|
8cb01d0ba8 | ||
|
|
d8c46b49d9 | ||
|
|
73e4ba9994 | ||
|
|
3aa5b21d49 | ||
|
|
f90807ebc6 | ||
|
|
a555009ac9 | ||
|
|
af21d1abba | ||
|
|
dcf77ba862 | ||
|
|
f281011a47 | ||
|
|
6bb8ca73d3 | ||
|
|
a070edc1c2 | ||
|
|
09485747a6 | ||
|
|
cbd73ae638 | ||
|
|
1aeec348eb | ||
|
|
ea8e783909 | ||
|
|
e06338dd2e | ||
|
|
d6fffc43e4 | ||
|
|
7633f84385 | ||
|
|
93fc20c825 | ||
|
|
ca32c11296 | ||
|
|
d2b8d2238a | ||
|
|
04ea9c39e1 | ||
|
|
905e965917 | ||
|
|
1ada23584d | ||
|
|
1456fd9076 | ||
|
|
941dd6015d | ||
|
|
b1f320f361 | ||
|
|
63e3718178 | ||
|
|
9bfa30f849 | ||
|
|
bb24b27963 | ||
|
|
403d93831f | ||
|
|
9a149eb673 | ||
|
|
470995c13d | ||
|
|
ddcd03f096 | ||
|
|
b8fe2aacf1 | ||
|
|
3c9f0d3c5c | ||
|
|
ded0ab5381 | ||
|
|
42d20f0fd9 | ||
|
|
0e8ace560b | ||
|
|
6e04a5d3da | ||
|
|
a3d8a8037f | ||
|
|
1dc42e1333 | ||
|
|
68cdcd8587 | ||
|
|
ebf0d585fc | ||
|
|
75365c428d | ||
|
|
3e30f5e18f | ||
|
|
e8d3b3decb | ||
|
|
5dcb364469 | ||
|
|
df9530e48f | ||
|
|
240a27a61a | ||
|
|
a6a8a5d583 | ||
|
|
a9046aded4 | ||
|
|
6f60cc6afb | ||
|
|
6e5470e20c | ||
|
|
dc04b4220f | ||
|
|
b1cfdef41c | ||
|
|
06e609613e | ||
|
|
8e66c24ff9 | ||
|
|
b4e2761b1f | ||
|
|
884d3e8c34 | ||
|
|
53739d24df | ||
|
|
f59a76d9a0 | ||
|
|
6cd36b19b2 | ||
|
|
b22ad88f37 | ||
|
|
f87ec18669 | ||
|
|
64686e165f | ||
|
|
055bad9ca9 | ||
|
|
c6e04bc4b9 | ||
|
|
b6782df565 | ||
|
|
2b4cb2cc88 | ||
|
|
14d12d90e3 | ||
|
|
4ed3832a5f | ||
|
|
b0714ab27e | ||
|
|
201ee840d5 | ||
|
|
79f737404e | ||
|
|
0b649e3f5b | ||
|
|
c756223363 | ||
|
|
ea60742d82 | ||
|
|
2ce90ba008 | ||
|
|
63211457e4 | ||
|
|
67f014d74a | ||
|
|
08fffade74 | ||
|
|
c8adb52660 | ||
|
|
4f74502951 | ||
|
|
4a5a4c19a5 | ||
|
|
e73d76eb57 | ||
|
|
b9a89d7c40 | ||
|
|
1a1bf814a0 | ||
|
|
f52e929d04 | ||
|
|
38d8d2e1ca | ||
|
|
ab4015d0e0 | ||
|
|
15027e51ee | ||
|
|
557d7ef1d9 | ||
|
|
4dea906e23 | ||
|
|
68e25cb0b3 | ||
|
|
4d033e59f1 | ||
|
|
a80c579d8a | ||
|
|
5cc48ed462 | ||
|
|
2abc8ff9d8 | ||
|
|
198bd6c322 | ||
|
|
7fb96f1eac | ||
|
|
2020f55826 | ||
|
|
c58f8d7831 | ||
|
|
32a6bbacd0 | ||
|
|
b08c77b6c0 | ||
|
|
a8836744f6 | ||
|
|
3a2202a2e8 | ||
|
|
896749d812 | ||
|
|
09ec1e8c06 | ||
|
|
07627c41e3 | ||
|
|
e4e278c2fa | ||
|
|
1b1b239ba0 | ||
|
|
c1d4d39265 | ||
|
|
dfd3885486 | ||
|
|
892f49c83a | ||
|
|
85d0671443 | ||
|
|
f22e7b9fcb | ||
|
|
d282ef1b24 | ||
|
|
9c2529ae3d | ||
|
|
108b7f5b0b | ||
|
|
b4c8be3e63 | ||
|
|
4064c56b21 | ||
|
|
1416762afe | ||
|
|
17f23fe920 | ||
|
|
c412c92b3e | ||
|
|
deed875c1b | ||
|
|
f600b628ce | ||
|
|
0c887a25e3 | ||
|
|
89dbd32351 | ||
|
|
98c5863973 |
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,10 +1,4 @@
|
||||
# OSX
|
||||
#
|
||||
node_modules/
|
||||
unpackage/
|
||||
.DS_Store
|
||||
|
||||
#Intellij idea
|
||||
.idea/
|
||||
/idea/
|
||||
.vscode/
|
||||
/unpackage/
|
||||
.hbuilderx/launch.json
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
|
||||
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
|
||||
"version": "0.0",
|
||||
"configurations": [{
|
||||
"app-plus" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"default" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"h5" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"mp-weixin" :
|
||||
{
|
||||
"launchtype" : "remote"
|
||||
},
|
||||
"type" : "uniCloud"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -1,15 +0,0 @@
|
||||
{
|
||||
// 使用 IntelliSense 了解相关属性。
|
||||
// 悬停以查看现有属性的描述。
|
||||
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
571
App.vue
571
App.vue
@@ -1,221 +1,350 @@
|
||||
|
||||
|
||||
<script>
|
||||
/**
|
||||
* vuex管理登录状态,具体可以参考官方登录模板示例
|
||||
*/
|
||||
import { mapMutations } from "vuex";
|
||||
import APPUpdate from "@/plugins/APPUpdate";
|
||||
import { getClipboardData } from "@/js_sdk/h5-copy/h5-copy.js";
|
||||
import config from "@/config/config";
|
||||
// 悬浮球
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 监听返回
|
||||
*/
|
||||
onBackPress(e) {
|
||||
if (e.from == "backbutton") {
|
||||
let routes = getCurrentPages();
|
||||
let curRoute = routes[routes.length - 1].options;
|
||||
routes.forEach((item) => {
|
||||
if (
|
||||
item.route == "pages/tabbar/cart/cartList" ||
|
||||
item.route.indexOf("pages/product/goods") != -1
|
||||
) {
|
||||
uni.redirectTo({
|
||||
url: item.route,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (curRoute.addId) {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/cart/cartList",
|
||||
});
|
||||
} else {
|
||||
uni.navigateBack();
|
||||
}
|
||||
return true; //阻止默认返回行为
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
...mapMutations(["login"]),
|
||||
},
|
||||
onLaunch: function () {
|
||||
// #ifdef APP-PLUS
|
||||
this.checkArguments(); // 检测启动参数
|
||||
APPUpdate();
|
||||
|
||||
// 重点是以下: 一定要监听后台恢复 !一定要
|
||||
plus.globalEvent.addEventListener("newintent", (e) => {
|
||||
this.checkArguments(); // 检测启动参数
|
||||
});
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
this.applyUpdateWeChat();
|
||||
// #endif
|
||||
},
|
||||
|
||||
onShow() {
|
||||
// #ifndef H5
|
||||
this.getClipboard();
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 微信小程序版本提交更新版本 解决缓存问题
|
||||
*/
|
||||
applyUpdateWeChat() {
|
||||
const updateManager = uni.getUpdateManager();
|
||||
|
||||
updateManager.onCheckForUpdate(function (res) {
|
||||
// 请求完新版本信息的回调
|
||||
});
|
||||
|
||||
updateManager.onUpdateReady(function (res) {
|
||||
uni.showModal({
|
||||
title: "更新提示",
|
||||
content: "发现新版本,是否重启应用?",
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
|
||||
updateManager.applyUpdate();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
updateManager.onUpdateFailed(function (res) {
|
||||
// 新的版本下载失败
|
||||
});
|
||||
},
|
||||
|
||||
// TODO 开屏广告 后续优化添加
|
||||
launch() {
|
||||
try {
|
||||
// 获取本地存储中launchFlag标识 开屏广告
|
||||
const value = uni.getStorageSync("launchFlag");
|
||||
if (!value) {
|
||||
// this.$u.route("/pages/index/agreement");
|
||||
} else {
|
||||
//app启动时打开启动广告页
|
||||
var w = plus.webview.open(
|
||||
"/hybrid/html/advertise/advertise.html",
|
||||
"本地地址",
|
||||
{
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zindex: 999,
|
||||
},
|
||||
"fade-in",
|
||||
500
|
||||
);
|
||||
//设置定时器,4s后关闭启动广告页
|
||||
setTimeout(function () {
|
||||
plus.webview.close(w);
|
||||
APPUpdate();
|
||||
}, 3000);
|
||||
}
|
||||
} catch (e) {
|
||||
// error
|
||||
uni.setStorage({
|
||||
key: "launchFlag",
|
||||
data: true,
|
||||
success: function () {
|
||||
console.log("error时存储launchFlag");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取粘贴板数据
|
||||
*/
|
||||
async getClipboard() {
|
||||
let res = await getClipboardData();
|
||||
/**
|
||||
* 解析粘贴板数据
|
||||
*/
|
||||
if (res.indexOf(config.shareLink) != -1) {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "检测到一个分享链接是否跳转?",
|
||||
confirmText: "跳转",
|
||||
success: function (callback) {
|
||||
if (callback.confirm) {
|
||||
const path = res.split(config.shareLink)[1];
|
||||
if (path.indexOf("tabbar") != -1) {
|
||||
uni.switchTab({
|
||||
url: path,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: path,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* h5中打开app获取跳转app的链接并跳转
|
||||
*/
|
||||
checkArguments() {
|
||||
// #ifdef APP-PLUS
|
||||
setTimeout(() => {
|
||||
const args = plus.runtime.arguments;
|
||||
if (args) {
|
||||
const argsStr = decodeURIComponent(args);
|
||||
const path = argsStr.split("//")[1];
|
||||
if (path.indexOf("tabbar") != -1) {
|
||||
uni.switchTab({
|
||||
url: `/${path}`,
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/${path}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "uview-ui/index.scss";
|
||||
|
||||
// -------适配底部安全区 苹果x系列刘海屏
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
.mp-iphonex-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: content-box;
|
||||
height: auto !important;
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
// #endif
|
||||
|
||||
body {
|
||||
background-color: $bg-color;
|
||||
}
|
||||
/************************ */
|
||||
.w200 {
|
||||
width: 200rpx !important;
|
||||
}
|
||||
.flex1 {
|
||||
flex: 1; //必须父级设置flex
|
||||
}
|
||||
</style>
|
||||
<script setup lang="ts">
|
||||
import config from '@/config/config'
|
||||
import { getClipboardData } from '@/js_sdk/h5-copy/h5-copy.js'
|
||||
import APPUpdate from '@/plugins/APPUpdate'
|
||||
import storage from '@/utils/storage'
|
||||
import { getThemeSetting, getCategoryPageSetting } from '@/api/common.js'
|
||||
import {
|
||||
applyTabBarStyle,
|
||||
cacheTheme,
|
||||
loadCachedTheme,
|
||||
normalizeTheme,
|
||||
} from '@/utils/theme'
|
||||
import {
|
||||
cacheCategoryPageSetting,
|
||||
loadCachedCategoryPageSetting,
|
||||
normalizeCategoryPageSetting,
|
||||
} from '@/utils/categoryPageSetting'
|
||||
import { onLaunch, onShow } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { handleAppLaunchScene } from '@/utils/distributionBind.js'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.onAppRoute((res) => {
|
||||
applyTabBarStyle(store.state.theme && store.state.theme.mainColor, res && res.path)
|
||||
})
|
||||
// #endif
|
||||
|
||||
onLaunch((val) => {
|
||||
initTheme()
|
||||
initCategoryPageSetting()
|
||||
handleAppLaunchScene(val)
|
||||
if (val?.query?.inviter) {
|
||||
storage.setInviter(val.query.inviter)
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.globalEvent.addEventListener('newintent', () => {
|
||||
checkArguments()
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
applyUpdateWeChat()
|
||||
// #endif
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
applyTabBarStyle(store.state.theme && store.state.theme.mainColor)
|
||||
|
||||
// #ifndef H5
|
||||
if (config.enableGetClipboard) {
|
||||
getClipboard()
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
if (storage.getShow()) {
|
||||
if (uni.getSystemInfoSync().platform == 'ios') {
|
||||
uni.navigateTo({ url: '/pages/tabbar/screen/fullScreen' })
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
|
||||
function initTheme() {
|
||||
const cached = loadCachedTheme()
|
||||
if (cached) {
|
||||
store.commit('SET_THEME', cached)
|
||||
applyTabBarStyle(cached.mainColor)
|
||||
}
|
||||
getThemeSetting()
|
||||
.then((res) => {
|
||||
if (res.data?.success && res.data.result?.settingValue) {
|
||||
try {
|
||||
const raw = JSON.parse(res.data.result.settingValue)
|
||||
const theme = normalizeTheme(raw)
|
||||
store.commit('SET_THEME', theme)
|
||||
cacheTheme(theme)
|
||||
applyTabBarStyle(theme.mainColor)
|
||||
} catch (e) {
|
||||
// 主题配置解析失败时使用缓存
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function initCategoryPageSetting() {
|
||||
const cached = loadCachedCategoryPageSetting()
|
||||
if (cached) {
|
||||
store.commit('SET_CATEGORY_PAGE_SETTING', cached)
|
||||
}
|
||||
getCategoryPageSetting()
|
||||
.then((res) => {
|
||||
if (res.data?.success) {
|
||||
const setting = normalizeCategoryPageSetting(res.data.result)
|
||||
store.commit('SET_CATEGORY_PAGE_SETTING', setting)
|
||||
cacheCategoryPageSetting(setting)
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function applyUpdateWeChat() {
|
||||
const updateManager = uni.getUpdateManager()
|
||||
|
||||
updateManager.onCheckForUpdate(() => {})
|
||||
|
||||
updateManager.onUpdateReady(() => {
|
||||
uni.showModal({
|
||||
title: '更新提示',
|
||||
content: '发现新版本,是否重启应用?',
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
updateManager.applyUpdate()
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
updateManager.onUpdateFailed(() => {})
|
||||
}
|
||||
|
||||
function launch() {
|
||||
try {
|
||||
const value = uni.getStorageSync('launchFlag')
|
||||
if (!value) {
|
||||
// uni.navigateTo({ url: '/pages/index/agreement' })
|
||||
} else {
|
||||
const w = plus.webview.open(
|
||||
'/hybrid/html/advertise/advertise.html',
|
||||
'本地地址',
|
||||
{
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zindex: 999,
|
||||
},
|
||||
'fade-in',
|
||||
500,
|
||||
)
|
||||
setTimeout(() => {
|
||||
plus.webview.close(w)
|
||||
APPUpdate()
|
||||
}, 3000)
|
||||
}
|
||||
} catch (e) {
|
||||
uni.setStorage({
|
||||
key: 'launchFlag',
|
||||
data: true,
|
||||
success() {
|
||||
console.log('error时存储launchFlag')
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async function getClipboard() {
|
||||
const res = await getClipboardData()
|
||||
if (res.indexOf(config.shareLink) != -1 && res != store.state.shareLink) {
|
||||
store.state.shareLink = res
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '检测到一个分享链接是否跳转?',
|
||||
confirmText: '跳转',
|
||||
success(callback) {
|
||||
if (callback.confirm) {
|
||||
const path = res.split(config.shareLink)[1]
|
||||
if (path.indexOf('tabbar') != -1) {
|
||||
uni.switchTab({ url: path })
|
||||
} else {
|
||||
uni.navigateTo({ url: path })
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function checkArguments() {
|
||||
// #ifdef APP-PLUS
|
||||
setTimeout(() => {
|
||||
const args = plus.runtime.arguments
|
||||
if (args) {
|
||||
const argsStr = decodeURIComponent(args)
|
||||
const path = argsStr.split('//')[1]
|
||||
if (path.indexOf('tabbar') != -1) {
|
||||
uni.switchTab({ url: `/${path}` })
|
||||
} else {
|
||||
uni.navigateTo({ url: `/${path}` })
|
||||
}
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "uview-plus/index.scss";
|
||||
@import "@/styles/uview-form-fix.scss";
|
||||
|
||||
// -------适配底部安全区 苹果x系列刘海屏
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
.mp-iphonex-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: content-box;
|
||||
height: auto !important;
|
||||
padding-top: 10rpx;
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
body {
|
||||
background-color: $bg-color;
|
||||
}
|
||||
|
||||
/************************ */
|
||||
.w200 {
|
||||
width: 200rpx !important;
|
||||
}
|
||||
|
||||
.flex1 {
|
||||
flex: 1; //必须父级设置flex
|
||||
}
|
||||
|
||||
.light-color {
|
||||
color: $light-color;
|
||||
}
|
||||
.main-color {
|
||||
color: $main-color !important;
|
||||
}
|
||||
.bg-light-color {
|
||||
background-color: $light-color !important;
|
||||
}
|
||||
.bg-main-color {
|
||||
background-color: $main-color !important;
|
||||
}
|
||||
|
||||
// 渐变主题颜色
|
||||
.bg-linear-gradient {
|
||||
background-image: linear-gradient(25deg, var(--theme-primary, #fa123b), var(--theme-light, #ff6b35), var(--theme-aider, #ff9f28), #ffcc03);
|
||||
}
|
||||
.uni-tabbar .uni-tabbar__icon {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: $light-color;
|
||||
color: #ffffff;
|
||||
border-width: 0px;
|
||||
border: none;
|
||||
border-radius: 44px;
|
||||
height: 44px;
|
||||
box-shadow: none;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 44px;
|
||||
text-align: center;
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
.button-hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: rgba(242, 242, 242, 1);
|
||||
color: #333333;
|
||||
height: 28px;
|
||||
border-radius: 28px;
|
||||
box-shadow: none;
|
||||
font-family: "PingFangSC-Regular", "PingFang SC", sans-serif;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
line-height: 28px;
|
||||
text-align: center;
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.c-content {
|
||||
background-color: #ffffff;
|
||||
// margin-top: 30rpx;
|
||||
border-top: 1px solid $border-color-light;
|
||||
border-bottom: 1px solid $border-color-light;
|
||||
}
|
||||
.mt-30 {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.border-top {
|
||||
border-top: 1px solid $border-color-light;
|
||||
}
|
||||
.describe {
|
||||
color: $font-color-light;
|
||||
font-size: $font-sm;
|
||||
padding: 30rpx;
|
||||
}
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-a-c{
|
||||
align-items: center;
|
||||
}
|
||||
.flex-j-sb{
|
||||
justify-content: space-between;
|
||||
}
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.wes {
|
||||
/* 多出部分用省略号表示 , 用于一行 */
|
||||
overflow: hidden;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.wes-2 {
|
||||
/* 适用于webkit内核和移动端 */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.wes-3 {
|
||||
/* 适用于webkit内核和移动端 */
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 3;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
682
LICENSE
682
LICENSE
@@ -1,661 +1,21 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2026 Beijing Hongye Huicheng Technology Co., Ltd. (北京宏业汇成科技有限公司)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
qSyvBPhDsPdxvOhC
|
||||
140
README.en.md
Normal file
140
README.en.md
Normal file
@@ -0,0 +1,140 @@
|
||||
<div align="center">
|
||||
<img src="./icon.png" width="80" alt="Lilishop Logo" />
|
||||
<h1>Lilishop(PickMall) - Mobile Client (uni-app)</h1>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/lilishop/lilishop"><img src="https://img.shields.io/github/stars/lilishop/lilishop.svg?style=social&logo=github" alt="GitHub Stars" /></a>
|
||||
<a href="https://gitee.com/beijing_hongye_huicheng/lilishop"><img src="https://gitee.com/beijing_hongye_huicheng/lilishop/badge/star.svg?theme=dark" alt="Gitee Stars" /></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License" /></a>
|
||||
<a href="https://vuejs.org/"><img src="https://img.shields.io/badge/Vue-3-brightgreen.svg" alt="Vue 3" /></a>
|
||||
<a href="https://uniapp.dcloud.net.cn/"><img src="https://img.shields.io/badge/uni--app-Vue3-blue.svg" alt="uni-app" /></a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://pickmall.cn">🏠 Official Website</a> ·
|
||||
<a href="https://bdwx2tfwwt.feishu.cn/wiki/space/7675179368632617952?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home">📖 Product Documentation</a> ·
|
||||
<a href="#2-online-demo">📱 Online Demo</a> ·
|
||||
<a href="https://github.com/lilishop/lilishop">💻 GitHub</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="./README.md">简体中文</a> | <b>English</b>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
> ⭐ **If you like this project, please click `Star` in the top-right corner to support us! Your Star is our greatest motivation for continuous open-source iteration!**
|
||||
|
||||
**LILISHOP** is an open-source e-commerce system built with Spring Boot, Vue 3, and uni-app. It supports B2B2C multi-vendor marketplaces, WeChat Mini Programs, H5, Android/iOS Apps, live-streaming e-commerce, affiliate distribution, and flash sales.
|
||||
|
||||
### ✨ Why Choose Lilishop
|
||||
- 🟢 **True MIT License**: Free for commercial use and private secondary development without licensing concerns.
|
||||
- 🚀 **Modern Vue 3 Stack**: Built on `uni-app + Vue 3 + uview-plus 3.x`, leaving legacy Vue 2 templates behind.
|
||||
- 🧩 **Production-Ready Completeness**: Not a static UI template—fully integrated with B2B2C multi-vendor workflows, visual storefront decoration, IM customer service, flash sales, group buying, affiliate distribution, and live streaming.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
This repository (`lilishop-uniapp`) is the **Mobile Buyer Client** of Lilishop, supporting **H5, WeChat Mini Program, Android, and iOS Apps** from a single codebase powered by **Vue 3** and **uni-app**.
|
||||
|
||||
- **Official Website**: <https://pickmall.cn>
|
||||
- **Documentation**: [Product Documentation](https://bdwx2tfwwt.feishu.cn/wiki/space/7675179368632617952?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home)
|
||||
- **Gitee Repository**: <https://gitee.com/beijing_hongye_huicheng>
|
||||
- **GitHub Main Repository**: <https://github.com/lilishop/lilishop>
|
||||
|
||||
---
|
||||
|
||||
## 2. Online Demo
|
||||
|
||||
> **Note**: The verification code for the demo environment is `111111`.
|
||||
|
||||
| WeChat Mini Program | Mobile H5 ([Visit Directly](https://m-b2b2c.pickmall.cn)) |
|
||||
| :---: | :---: |
|
||||
| <img src="https://static.pickmall.cn/images/h5-qrcode.png" width="180" alt="WeChat Mini Program QR Code" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/h5-qrcode.png" width="180" alt="Mobile H5 QR Code" /> |
|
||||
| Scan with WeChat | Scan or visit <https://m-b2b2c.pickmall.cn> |
|
||||
|
||||
- **PC Mall**: <https://pc-b2b2c.pickmall.cn>
|
||||
- **Admin Panel**: <https://admin-b2b2c.pickmall.cn> (`admin` / `123456`)
|
||||
- **Store Panel**: <https://store-b2b2c.pickmall.cn> (`13011111111` / `111111`)
|
||||
|
||||
### Mobile UI Preview
|
||||
|
||||
| Home | Product List | Product Detail | Checkout | User Center |
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
| <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image.png" width="160" alt="Home" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image3.png" width="160" alt="Product List" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image4.png" width="160" alt="Product Detail" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image5.png" width="160" alt="Checkout" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image2.png" width="160" alt="User Center" /> |
|
||||
|
||||
---
|
||||
|
||||
## 3. Core Features & Tech Stack
|
||||
|
||||
### Core Features
|
||||
- **Cross-Platform**: One codebase runs on H5, WeChat Mini Program, Android, and iOS.
|
||||
- **Multi-Vendor B2B2C**: Supports merchant onboarding and store-level operations.
|
||||
- **Visual Storefront Decoration**: Dynamic home page & floor layouts (`pages/floor`) and customizable theme colors.
|
||||
- **Live Streaming & IM**: Integrated with Tencent Cloud IM / MQTT and HLS video streaming.
|
||||
- **Full E-Commerce Workflow**: Product browsing, cart, promotions (flash sale, group buy, coupons, points, bargain, affiliate), order fulfillment, after-sales, and member center.
|
||||
|
||||
### Mobile Tech Stack
|
||||
| Category | Technology | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| Core Framework | `uni-app` + `Vue 3` | Cross-platform framework with Vue 3 support |
|
||||
| State Management | `Vuex 4` | Global auth, cart, and user state management |
|
||||
| UI Library | `uview-plus 3.x` | Vue 3 compatible uni-app multi-platform UI library |
|
||||
| Messaging & IM | `mqtt 5.x` / `@tencentcloud/chat` | Real-time customer service and notification push |
|
||||
| Video Streaming | `hls.js` | Live streaming playback support |
|
||||
| CSS Preprocessor | `Sass (SCSS)` | Global theme variables (`uni.scss`) and styling |
|
||||
|
||||
---
|
||||
|
||||
## 4. Directory Structure
|
||||
|
||||
```text
|
||||
lilishop-uniapp
|
||||
├── api/ # API module definitions (member, goods, order, promotion, IM, etc.)
|
||||
├── components/ # Shared business and base components
|
||||
├── config/ # Global configuration
|
||||
│ ├── api.js # API base URLs (buyer / im / mqtt)
|
||||
│ └── config.js # Mall name, Mini Program AppID, AMap Key, theme colors, etc.
|
||||
├── hybrid/ # Local HTML assets for App WebView
|
||||
├── js_sdk/ # Third-party and uni-app SDKs
|
||||
├── lib/ # Base utility libraries
|
||||
├── pages/ # Pages and sub-packages
|
||||
│ ├── tabbar/ # Main tabbar pages (Home, Category, Cart, Mine)
|
||||
│ ├── passport/ # Auth sub-package (Login, Register, Reset Password)
|
||||
│ ├── product/ # Product detail, list, reviews, store homepage
|
||||
│ ├── cart/ # Shopping cart and checkout
|
||||
│ ├── order/ # Order list, detail, after-sales, logistics, invoice
|
||||
│ ├── promotion/ # Flash sale, group buy, bargain, points mall, distribution, live
|
||||
│ ├── mine/ # User center, wallet, points, favorites, addresses
|
||||
│ ├── floor/ # Visual decoration floor components and topic pages
|
||||
│ └── navigation/ # Navigation and search pages
|
||||
├── patches/ # patch-package patches for dependencies
|
||||
├── scripts/ # Build and postinstall runtime patch scripts
|
||||
├── static/ # Static images and fonts
|
||||
├── store/ # Vuex store modules
|
||||
├── styles/ # Global stylesheets
|
||||
├── uni_modules/ # uni_modules plugins (including uview-plus)
|
||||
├── utils/ # Request interceptors, encryption, validators, storage helpers
|
||||
├── App.vue # Root application component
|
||||
├── main.js # Application entry point
|
||||
├── manifest.json # uni-app multi-platform configuration
|
||||
├── pages.json # Page routes, sub-packages, and navigation bar settings
|
||||
└── uni.scss # Global SCSS variables and theme color definitions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Related Repositories
|
||||
|
||||
| Repository | Description | Links |
|
||||
| :--- | :--- | :--- |
|
||||
| **`lilishop`** | Backend Services (Spring Boot 4 / Java 21) | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop) · [GitHub](https://github.com/lilishop/lilishop) |
|
||||
| **`lilishop-ui`** | PC Mall / Seller / Admin / IM Web Frontend | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop-ui) |
|
||||
| **`lilishop-uniapp`** | Mobile Buyer Client (H5 / Mini Program / App) | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp) |
|
||||
| **`docker`** | Docker Compose, Images & SQL Init Scripts | [Gitee](https://gitee.com/beijing_hongye_huicheng/docker) · [MySQL Scripts](https://gitee.com/beijing_hongye_huicheng/docker/tree/master/init/mysql) |
|
||||
|
||||
---
|
||||
|
||||
## 6. License
|
||||
|
||||
Lilishop is licensed under the [MIT](./LICENSE) License. Copyright © 2020-2026 **Beijing Hongye Huicheng Technology Co., Ltd.**
|
||||
377
README.md
377
README.md
@@ -1,218 +1,179 @@
|
||||
## 🔥 Lilishop B2B2C商城系统
|
||||
|
||||
##### 🌹 开源不易,如有帮助请点Star
|
||||
|
||||
|
||||
|
||||
#### 欢迎交流需求,交流业务,交流技术(基础问题自行解决,进群先看文档后提问)
|
||||
|
||||
##### 交流 qq 1群 961316482(已满)
|
||||
|
||||
##### 交流 qq 2群 875294241
|
||||
|
||||
|
||||
|
||||
##### 商城 公众号/小程序体验,扫描二维码
|
||||
|
||||

|
||||
|
||||
[](https://gitee.com/beijing_hongye_huicheng/lilishop/stargazers)
|
||||

|
||||
|
||||
|
||||
### 🔥 商城介绍
|
||||
**官网**:https://pickmall.cn
|
||||
|
||||
Lilishop 商城系统 基于SpringBoot 研发,B2B2C多用户商城系统,前端使用 Vue、uniapp开发 **系统全端全部代码开源**
|
||||
|
||||
业务兼容O2O商城/B2B商城/B2B2C商城/F2B2C商城/S2B2C商城。支持小程序商城、H5商城、APP商城、 PC商城。
|
||||
|
||||
|
||||
商城前后端分离、支持分布式部署。
|
||||
|
||||
商城包含 会员模块、**第三方登录模块**、**第三方支付模块**、**楼层装修模块**、订单模块、分销模块、文章模块、系统设置模块、流量分析模块
|
||||
|
||||
商城包含各种中间件、搜索引擎、多级缓存、分布式事务、分布式任务调度等,支持Docker,支持k8s。是一款高性能,支持高并发的商城系统。
|
||||
|
||||
##### 商城 API/消费者 聚合版
|
||||
api不需要单独部署,只需启动一个jar包就可以正常运转 如有需要,可以点击跳转https://gitee.com/beijing_hongye_huicheng/lilishop-simplify
|
||||
|
||||
### ☃️ 商城 开发/使用/常见问题 帮助文档
|
||||
|
||||
https://docs.pickmall.cn
|
||||
|
||||
### 💧 开源商城项目地址(gitee)
|
||||
|
||||
**API(商城所有API)**:https://gitee.com/beijing_hongye_huicheng/lilishop.git
|
||||
|
||||
**UI(商城管理端/商家端/买家PC端)**: https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
|
||||
|
||||
**uniapp(商城移动端,支持小程序/APP/H5)**:https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp.git
|
||||
|
||||
**docker一键部署(商城部署脚本)**:https://gitee.com/beijing_hongye_huicheng/docker.git
|
||||
|
||||
### 💧 开源商城项目地址(github)
|
||||
|
||||
**API(商城所有API)**:https://github.com/hongyehuicheng/lilishop.git
|
||||
|
||||
**UI(商城管理端/商家端/买家PC端)**: https://github.com/hongyehuicheng/lilishop-ui.git
|
||||
|
||||
**uniapp(商城移动端,支持小程序/APP/H5)**:https://github.com/hongyehuicheng/lilishop-uniapp.git
|
||||
|
||||
**docker一键部署(商城部署脚本)**:https://github.com/hongyehuicheng/docker.git
|
||||
|
||||
|
||||
☃️ UI 项目下3个文件夹 buyer:买家PC端,seller:商家端,manager:后台管理端
|
||||
|
||||
|
||||
### 💧 演示地址(手机验证码为 ‘111111’)
|
||||
|
||||
**商城管理端**:https://admin-b2b2c.pickmall.cn 账号:admin/123456
|
||||
|
||||
**商城店铺后台**:https://store-b2b2c.pickmall.cn 账号:13011111111/111111
|
||||
|
||||
**商城PC页面**:https://pc-b2b2c.pickmall.cn
|
||||
|
||||
**商城移动端(请使浏览器手机模式,或者用手机浏览器打开)**:https://m-b2b2c.pickmall.cn
|
||||
|
||||
**小程序/公众号**:扫描二维码
|
||||
|
||||

|
||||
|
||||
### 🚙 3行命令搭建本地商城(注:只能本机访问,如需调整,请自行操作镜像)
|
||||
|
||||
温馨提示:由于服务中间件较多,如果笔记本环境启动内存没有32g可能无法启动成功(macbookpro 2020 16g内存启动无法成功),台式机在16g内存、AMD 3700x 的ubuntu系统成功运行。
|
||||
|
||||
|
||||
|
||||
##### docker环境安装 [点击跳转](https://docs.pickmall.cn/deploy/%E8%BF%90%E8%A1%8C%E7%8E%AF%E5%A2%83%E5%87%86%E5%A4%87.html)
|
||||
|
||||
|
||||
|
||||
##### 下载docker-compose脚本
|
||||
`git clone https://gitee.com/beijing_hongye_huicheng/docker.git `
|
||||
|
||||
##### 部署商城所需中间件
|
||||
`docker-compose up -d`
|
||||
|
||||
##### 部署商城应用
|
||||
`docker-compose -f docker-compose-application.yml up -d`
|
||||
|
||||
|
||||
|
||||
PS:商城数据库单独部署 https://gitee.com/beijing_hongye_huicheng/docker/tree/master/init/mysql 这里有与tag版本一致的sql,根据tag获取sql,如果使用master代码,则需要在lilishop项目根目录的DB目录中,获取对应的升级sql。
|
||||
|
||||
##### 商城 API/UI 地址
|
||||
|
||||
| API | 地址 |
|
||||
| -------------- | --------------- |
|
||||
| 商城买家API | http://127.0.0.1:8888 |
|
||||
| 商城商家API | http://127.0.0.1:8889 |
|
||||
| 商城管理端API | http://127.0.0.1:8887 |
|
||||
| 商城基础API | http://127.0.0.1:8890 |
|
||||
|
||||
| 前端演示 | 地址 |
|
||||
| -------------- | --------------- |
|
||||
| 商城PC端 | http://127.0.0.1:10000 |
|
||||
| 商城WAP端 | http://127.0.0.1:10001 |
|
||||
| 商城卖家端 | http://127.0.0.1:10002 |
|
||||
| 商城管理端 | http://127.0.0.1:10003 |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### ⚾️ 功能列表
|
||||
|
||||
|
||||
|
||||
#### 🥎 商城平台功能
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
#### 🥎 商城卖家功能
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### 🧩 商城前端功能展示
|
||||
|
||||
|
||||
|
||||
#### ⚽️ 商城移动端
|
||||
|
||||
<img src="https://pickmall.cn/assets/imgs/other/app.gif" alt="移动端功能展示" style="zoom:50%;" />
|
||||
|
||||
|
||||
|
||||
#### ⚽️ 商城管理端
|
||||
|
||||

|
||||
|
||||
|
||||
### 商城技术选型
|
||||
|
||||
#### 🥅 架构图
|
||||
|
||||

|
||||
|
||||
##### 🕹 后台技术选型
|
||||
|
||||
| 说明 | 框架 | 说明 | |
|
||||
| -------------- | --------------- | -------------- | ------------- |
|
||||
| 基础框架 | Spring Boot | MVC框架 | Spring MVC |
|
||||
| 持久框架 | Mybatis-Plus | 程序构建 | Maven |
|
||||
| 关系型数据库 | MySQL | 消息中间件AMQP | RocketMQ |
|
||||
| 缓存 | Redis +MongoDB | 搜索引擎 | Elasticsearch |
|
||||
| 安全框架 | Spring Security | 数据库连接池 | Druid |
|
||||
| 数据库分库分表 | sharding | 定时任务 | xxl-job |
|
||||
| 负载均衡 | Nginx | 静态资源 | 阿里云OSS |
|
||||
| 短信 | 阿里云短信 | 认证 | JWT |
|
||||
| 日志处理 | Log4j | 接口规范 | RESTful |
|
||||
|
||||
##### 🖥 前端-运营后台、店铺后台
|
||||
|
||||
| 说明 | 框架 | 说明 | 框架 |
|
||||
| ---------- | ---------- | ---------- | ------- |
|
||||
| 构建工具 | webpack | JS版本 | ES6 |
|
||||
| 基础JS框架 | Vue.js | 视频播放器 | Dplayer |
|
||||
| 路由管理 | Vue Router | 状态管理 | Vuex |
|
||||
| 基础UI库 | iView | UI界面基于 | iView |
|
||||
| 网络请求 | axios | | |
|
||||
|
||||
##### 📱前端-移动端
|
||||
|
||||
| 说明 | 架构 | 说明 | 架构 |
|
||||
| --------- | ------- | -------- | ------- |
|
||||
| 基础UI库 | uViewui | 基础框架 | uni-app |
|
||||
| CSS预处理 | scss | 地图引擎 | amap |
|
||||
|
||||
### 🌟 版本升级
|
||||
|
||||
```
|
||||
商城后续会持续版本升级,修复bug,完善功能,覆盖更多业务场景 o2o/b2b/s2b2b2c/跨境电商
|
||||
|
||||
后续会考虑推出微服务商城系统/商城中台等
|
||||
<div align="center">
|
||||
<img src="./icon.png" width="80" alt="Lilishop Logo" />
|
||||
<h1>Lilishop(PickMall) 开源商城系统 - 移动端 (uni-app)</h1>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/lilishop/lilishop"><img src="https://img.shields.io/github/stars/lilishop/lilishop.svg?style=social&logo=github" alt="GitHub Stars" /></a>
|
||||
<a href="https://gitee.com/beijing_hongye_huicheng/lilishop"><img src="https://gitee.com/beijing_hongye_huicheng/lilishop/badge/star.svg?theme=dark" alt="Gitee Stars" /></a>
|
||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License" /></a>
|
||||
<a href="https://vuejs.org/"><img src="https://img.shields.io/badge/Vue-3-brightgreen.svg" alt="Vue 3" /></a>
|
||||
<a href="https://uniapp.dcloud.net.cn/"><img src="https://img.shields.io/badge/uni--app-Vue3-blue.svg" alt="uni-app" /></a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://pickmall.cn">🏠 官方网站</a> ·
|
||||
<a href="https://bdwx2tfwwt.feishu.cn/wiki/space/7675179368632617952?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home">📖 产品文档</a> ·
|
||||
<a href="#2-在线演示">📱 在线演示</a> ·
|
||||
<a href="#7-社区与支持">💬 微信交流群</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>简体中文</b> | <a href="./README.en.md">English</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
> ⭐ **如果觉得项目不错,请点击右上角 `Star` 收藏支持!您的 Star 是我们持续开源迭代的最大动力!**
|
||||
|
||||
**LILISHOP** 是基于 Spring Boot / Vue 3 / uni-app 开发的开源商城系统,支持 B2B2C 多商户商城、微信小程序商城、H5 商城、APP 商城、直播电商、分销返佣、秒杀活动。
|
||||
|
||||
### ✨ 为什么选择 Lilishop
|
||||
- 🟢 **真 MIT 协议**:支持免费商用与私有化闭源二开,无版权后顾之忧。
|
||||
- 🚀 **Vue 3 全新栈**:基于 `uni-app + Vue 3 + uview-plus 3.x`,告别老旧 Vue 2 模板。
|
||||
- 🧩 **生产级完整度**:非静态切图模板,完整覆盖 B2B2C 多商户、可视化装修、IM 客服、秒杀拼团、分销直播全闭环。
|
||||
|
||||
---
|
||||
|
||||
## 1. 项目简介
|
||||
|
||||
**Lilishop** 是一款功能完善的 B2B2C 多商户商城系统,采用前后端分离架构,全端代码开源。
|
||||
后端基于 **Spring Boot 4**(Java 21)构建,具备高内聚、低耦合的特性,支持分布式部署;前端覆盖 PC、H5、小程序和 APP,基于 **Vue 3** 和 **uni-app** 开发。
|
||||
|
||||
本仓库(`lilishop-uniapp`)为 **买家端移动端源码**,一套代码同时支持 **H5、微信小程序、Android / iOS APP**。
|
||||
|
||||
- **官方网站**: <https://pickmall.cn>
|
||||
- **官方文档**: [产品文档](https://bdwx2tfwwt.feishu.cn/wiki/space/7675179368632617952?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home)
|
||||
- **Gitee 仓库**: <https://gitee.com/beijing_hongye_huicheng>
|
||||
- **GitHub 主仓库**: <https://github.com/lilishop/lilishop>
|
||||
|
||||
---
|
||||
|
||||
## 2. 在线演示
|
||||
|
||||
> **注意**: 演示站手机验证码统一为 `111111`。演示环境部署于 `master` 分支。
|
||||
|
||||
| 微信小程序 | 移动端 H5([点击直接访问](https://m-b2b2c.pickmall.cn)) |
|
||||
| :---: | :---: |
|
||||
| <img src="https://static.pickmall.cn/images/h5-qrcode.png" width="180" alt="微信小程序体验码" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/h5-qrcode.png" width="180" alt="移动端 H5 体验二维码" /> |
|
||||
| 微信扫码体验小程序 | 手机扫码或访问 <https://m-b2b2c.pickmall.cn> |
|
||||
|
||||
- **商城 PC 端**: <https://pc-b2b2c.pickmall.cn>
|
||||
- **平台管理端**: <https://admin-b2b2c.pickmall.cn>
|
||||
- 账号: `admin` / 密码: `123456`
|
||||
- **店铺管理端**: <https://store-b2b2c.pickmall.cn>
|
||||
- 账号: `13011111111` / 密码: `111111`
|
||||
|
||||
### 移动端界面预览
|
||||
|
||||
| 商城首页 | 商品列表 | 商品详情 | 填写订单 | 个人中心 |
|
||||
| :---: | :---: | :---: | :---: | :---: |
|
||||
| <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image.png" width="160" alt="商城首页" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image3.png" width="160" alt="商品列表" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image4.png" width="160" alt="商品详情" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image5.png" width="160" alt="填写订单" /> | <img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/image2.png" width="160" alt="个人中心" /> |
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心特性与技术栈
|
||||
|
||||
### 核心特性
|
||||
- **全端覆盖**: 一套 `uni-app` 代码库支持 H5、微信小程序、Android / iOS APP,大幅降低多端开发与维护成本。
|
||||
- **商家入驻**: 支持多商家入驻,构建平台化 B2B2C 电商生态。
|
||||
- **可视化装修**: 支持首页及专题楼层(`pages/floor`)动态可视化配置与主题色切换。
|
||||
- **即时通讯与直播**: 集成腾讯云 IM / MQTT 消息推送及 HLS 视频流,支持在线客服与直播带货。
|
||||
- **完整电商闭环**: 涵盖商品浏览、购物车、多种促销(秒杀/拼团/优惠券/积分/砍价/分销)、订单履约、售后维权、会员中心等全流程。
|
||||
|
||||
### 移动端技术栈
|
||||
| 类别 | 技术选型 | 说明 |
|
||||
| :--- | :--- | :--- |
|
||||
| 核心框架 | `uni-app` + `Vue 3` | 跨端开发框架(支持 Vue 3 语法与渲染) |
|
||||
| 状态管理 | `Vuex 4` | 全局登录态、购物车与用户状态管理 |
|
||||
| UI 组件库 | `uview-plus 3.x` | 适配 Vue 3 的 uni-app 多端 UI 组件库 |
|
||||
| 即时通讯 | `mqtt 5.x` / `@tencentcloud/chat` | 客服消息与实时通知推送 |
|
||||
| 音视频流 | `hls.js` | 直播流播放支持 |
|
||||
| 样式预处理 | `Sass (SCSS)` | 全局主题变量(`uni.scss`)与组件样式定制 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 目录结构
|
||||
|
||||
```text
|
||||
lilishop-uniapp
|
||||
├── api/ # 各业务模块接口定义(会员、商品、订单、促销、IM 等)
|
||||
├── components/ # 公共业务与基础组件
|
||||
├── config/ # 全局配置目录
|
||||
│ ├── api.js # 接口域名配置(buyer / im / mqtt)
|
||||
│ └── config.js # 商城名称、小程序 AppID、高德 Key、主题色等全局参数
|
||||
├── hybrid/ # App 端本地 HTML 资源(如异常页、WebView 辅助页)
|
||||
├── js_sdk/ # 第三方及 uni-app 插件 SDK
|
||||
├── lib/ # 基础工具库封装
|
||||
├── pages/ # 页面与分包目录
|
||||
│ ├── tabbar/ # 底部导航主包页面(首页、分类、购物车、我的等)
|
||||
│ ├── passport/ # 登录、注册、找回密码分包
|
||||
│ ├── product/ # 商品详情、列表、评价、店铺主页分包
|
||||
│ ├── cart/ # 购物车与结算页分包
|
||||
│ ├── order/ # 订单列表、详情、售后、物流、发票分包
|
||||
│ ├── promotion/ # 秒杀、拼团、砍价、积分商城、分销、直播等营销分包
|
||||
│ ├── mine/ # 个人中心、钱包、积分、收藏、地址管理分包
|
||||
│ ├── floor/ # 可视化装修楼层组件与专题页
|
||||
│ └── navigation/ # 导航与搜索相关页面
|
||||
├── patches/ # patch-package 第三方依赖补丁
|
||||
├── scripts/ # 构建与依赖修补脚本(如 postinstall 补丁)
|
||||
├── static/ # 静态图片与字体资源
|
||||
├── store/ # Vuex 状态管理
|
||||
├── styles/ # 公共样式文件
|
||||
├── uni_modules/ # uni_modules 模块(含 uview-plus 等)
|
||||
├── utils/ # 请求拦截器、加密、校验、存储等工具函数
|
||||
├── App.vue # 应用入口组件
|
||||
├── main.js # 应用初始化入口
|
||||
├── manifest.json # uni-app 多端应用配置(AppID、权限、SDK 配置等)
|
||||
├── pages.json # 页面路由、分包加载与导航栏配置
|
||||
└── uni.scss # 全局 SCSS 变量与主题色定义
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 开源须知
|
||||
1.仅允许用于个人学习研究使用.
|
||||
## 5. 配套源码仓库
|
||||
|
||||
2.禁止将本开源的代码和资源进行任何形式任何名义的出售.
|
||||
本仓库专注于买家端 **H5 / 小程序 / APP**(`lilishop-uniapp`)。完整系统由以下四个核心仓库组成:
|
||||
|
||||
3.软件受国家计算机软件著作权保护(登记号:2021SR0805085)。
|
||||
| 仓库名称 | 职责说明 | 仓库地址 |
|
||||
| :--- | :--- | :--- |
|
||||
| **`lilishop`** | 后端服务源码(Spring Boot 4 / Java 21) | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop) · [GitHub](https://github.com/lilishop/lilishop) |
|
||||
| **`lilishop-ui`** | PC 商城 / 商家端 / 运营管理端 / IM Web 前端 | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop-ui) |
|
||||
| **`lilishop-uniapp`** | 买家端 H5 / 微信小程序 / Android & iOS APP | [Gitee](https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp) |
|
||||
| **`docker`** | 容器化部署编排、镜像与数据库脚本 | [Gitee](https://gitee.com/beijing_hongye_huicheng/docker) · [MySQL 脚本](https://gitee.com/beijing_hongye_huicheng/docker/tree/master/init/mysql) |
|
||||
|
||||
4.限制商用,如果需要商业使用请联系我们。QQ3409056806.
|
||||
---
|
||||
|
||||
## 6. 开源与授权
|
||||
|
||||
### 🐧 交流群
|
||||
本项目遵循 [MIT](./LICENSE) 开源协议,版权归 **北京宏业汇成科技有限公司** 所有。允许个人及企业免费用于学习、研究、修改和商业用途(需保留原始版权和许可声明)。
|
||||
|
||||
##### 官方qq 1群 961316482(已满)
|
||||
##### 官方qq 2群 875294241
|
||||
---
|
||||
|
||||
## 7. 社区与支持
|
||||
|
||||
我们欢迎任何形式的交流与贡献。在提问前,请先查阅 [产品文档](https://bdwx2tfwwt.feishu.cn/wiki/space/7675179368632617952?ccm_open_type=lark_wiki_spaceLink&open_tab_from=wiki_home),并参考 [《提问的智慧》](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/master/README-zh_CN.md) 以便高效沟通。
|
||||
|
||||
- **[在线客服](https://work.weixin.qq.com/kfid/kfc4d8dc24a73c15f44)**
|
||||
- **微信交流群**:
|
||||
<img src="https://lilishop-wechat.oss-cn-beijing.aliyuncs.com/wechat.jpg" width="200" alt="微信群" />
|
||||
|
||||
---
|
||||
|
||||
## 8. 升级计划
|
||||
|
||||
### ✅ 已完成特性
|
||||
| 功能模块 | 功能模块 |
|
||||
| :--- | :--- |
|
||||
| ✅ 商品定时上下架 | ✅ 商品分组 / 会员分组 |
|
||||
| ✅ 商品虚拟销量 | ✅ 专用发票(发票上传) |
|
||||
| ✅ 置顶评论 | ✅ 客户等级体系 |
|
||||
| ✅ 购物卡 | ✅ 腾讯云-云直播 |
|
||||
| ✅ 卡密商品 | ✅ 二级分销 |
|
||||
| ✅ 定时任务管理 | ✅ 预约服务商品 |
|
||||
| ✅ 主题色管理 | ✅ 商家发货改造(整单/拆单/无需配送) |
|
||||
| ✅ 多维统计(营业/商品/会员/积分/储值/营销/分销) | ✅ 经营报表(店铺业绩/商品同比环比/订单明细/销售汇总) |
|
||||
|
||||
### 🔜 近期规划中
|
||||
- 🔜 店铺结算汇总 & 结算台账
|
||||
- 🔜 支付方式汇总 & 钱包流水 & 财务汇总
|
||||
- 🔜 微信小店对接
|
||||
- 🔜 抖店对接
|
||||
|
||||
17
androidPrivacy.json
Normal file
17
androidPrivacy.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"version" : "1",
|
||||
"prompt" : "template",
|
||||
"title" : "服务协议和隐私政策",
|
||||
"message" : " 请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/> 你可阅读<a href=\"https://pc-b2b2c.pickmall.cn/article/detail?id=1371992704333905920\">《服务协议》</a>和<a href=\"https://pc-b2b2c.pickmall.cn/article/detail?id=1371779927900160000\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
|
||||
"buttonAccept" : "同意并接受",
|
||||
"buttonRefuse" : "暂不同意",
|
||||
"second" : {
|
||||
"title" : "确认提示",
|
||||
"message" : " 进入应用前,你需先同意<a href=\"https://pc-b2b2c.pickmall.cn/article/detail?id=1371992704333905920\">《服务协议》</a>和<a href=\"https://pc-b2b2c.pickmall.cn/article/detail?id=1371779927900160000\">《隐私政策》</a>,否则将退出应用。",
|
||||
"buttonAccept" : "同意并继续",
|
||||
"buttonRefuse" : "退出应用"
|
||||
},
|
||||
"styles" : {
|
||||
"borderRadius" : "5px"
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import api from "@/config/api.js";
|
||||
*/
|
||||
export function getAddressList(pageNumber, pageSize) {
|
||||
return http.request({
|
||||
url: "/memberAddress",
|
||||
url: "/member/address",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { pageNumber, pageSize },
|
||||
@@ -25,7 +25,7 @@ export function getAddressList(pageNumber, pageSize) {
|
||||
*/
|
||||
export function getLogistics() {
|
||||
return http.request({
|
||||
url: "/logistics",
|
||||
url: "/other/logistics",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { pageNumber: 1, pageSize: 200, disabled: "OPEN" },
|
||||
@@ -37,7 +37,7 @@ export function getLogistics() {
|
||||
*/
|
||||
export function getAddressCode(cityCode, townName) {
|
||||
return http.request({
|
||||
url: api.common + "/region/region",
|
||||
url: `${api.buyer}/common/region/region`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { cityCode, townName },
|
||||
@@ -51,7 +51,7 @@ export function getAddressCode(cityCode, townName) {
|
||||
*/
|
||||
export function addAddress(data) {
|
||||
return http.request({
|
||||
url: "/memberAddress",
|
||||
url: "/member/address",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
@@ -67,7 +67,7 @@ export function addAddress(data) {
|
||||
*/
|
||||
export function editAddress(params) {
|
||||
return http.request({
|
||||
url: `/memberAddress`,
|
||||
url: `/member/address`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
@@ -81,7 +81,7 @@ export function editAddress(params) {
|
||||
*/
|
||||
export function deleteAddress(id) {
|
||||
return http.request({
|
||||
url: `/memberAddress/delById/${id}`,
|
||||
url: `/member/address/delById/${id}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -95,7 +95,7 @@ export function deleteAddress(id) {
|
||||
*/
|
||||
export function getAddressDetail(id) {
|
||||
return http.request({
|
||||
url: `/memberAddress/get/${id}`,
|
||||
url: `/member/address/get/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
@@ -107,7 +107,7 @@ export function getAddressDetail(id) {
|
||||
*/
|
||||
export function getAddressDefault() {
|
||||
return http.request({
|
||||
url: `/memberAddress/get/default`,
|
||||
url: `/member/address/get/default`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
needToken: true,
|
||||
|
||||
@@ -34,16 +34,29 @@ export function applyCancelOrder(params) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* 获取商家售后收件地址
|
||||
*/
|
||||
export function getstoreAfterSaleAddress(sn) {
|
||||
export function getStoreAfterSaleAddress(sn) {
|
||||
return http.request({
|
||||
url: `/afterSale/getStoreAfterSaleAddress/${sn}`,
|
||||
url: `/order/afterSale/getStoreAfterSaleAddress/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 取消售后
|
||||
*/
|
||||
export function cancelAfterSale(afterSaleSn) {
|
||||
return http.request({
|
||||
url: `/order/afterSale/cancel/${afterSaleSn}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取售后服务记录相关数据
|
||||
@@ -51,7 +64,7 @@ export function getstoreAfterSaleAddress(sn) {
|
||||
*/
|
||||
export function getAfterSaleList(params) {
|
||||
return http.request({
|
||||
url: `/afterSale/page`,
|
||||
url: `/order/afterSale/page`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -64,7 +77,7 @@ export function getAfterSaleList(params) {
|
||||
*/
|
||||
export function getServiceDetail(sn) {
|
||||
return http.request({
|
||||
url: `/afterSale/get/${sn}`,
|
||||
url: `/order/afterSale/get/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -76,7 +89,7 @@ export function getServiceDetail(sn) {
|
||||
*/
|
||||
export function addComplain(params) {
|
||||
return http.request({
|
||||
url: `/complain`,
|
||||
url: `/order/complain`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: {
|
||||
@@ -91,7 +104,7 @@ export function addComplain(params) {
|
||||
*/
|
||||
export function clearComplain(id, params) {
|
||||
return http.request({
|
||||
url: `/complain/status/${id}`,
|
||||
url: `/order/complain/status/${id}`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -103,7 +116,7 @@ export function clearComplain(id, params) {
|
||||
*/
|
||||
export function getAfterSaleLog(sn) {
|
||||
return http.request({
|
||||
url: `/afterSale/get/getAfterSaleLog/${sn}`,
|
||||
url: `/order/afterSale/get/getAfterSaleLog/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -114,7 +127,7 @@ export function getAfterSaleLog(sn) {
|
||||
*/
|
||||
export function getComplain(params) {
|
||||
return http.request({
|
||||
url: `/complain`,
|
||||
url: `/order/complain`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -126,7 +139,7 @@ export function getComplain(params) {
|
||||
*/
|
||||
export function getAfterSaleReason(serviceType) {
|
||||
return http.request({
|
||||
url: `/afterSale/get/afterSaleReason/${serviceType}`,
|
||||
url: `/order/afterSale/get/afterSaleReason/${serviceType}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -137,7 +150,7 @@ export function getAfterSaleReason(serviceType) {
|
||||
*/
|
||||
export function getClearReason() {
|
||||
return http.request({
|
||||
url: `/afterSale/get/afterSaleReason/CANCEL`,
|
||||
url: `/order/afterSale/get/afterSaleReason/CANCEL`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -148,7 +161,7 @@ export function getClearReason() {
|
||||
*/
|
||||
export function getComplainReason() {
|
||||
return http.request({
|
||||
url: `/afterSale/get/afterSaleReason/COMPLAIN`,
|
||||
url: `/order/afterSale/get/afterSaleReason/COMPLAIN`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -158,7 +171,7 @@ export function getComplainReason() {
|
||||
*/
|
||||
export function getComplainDetail(id) {
|
||||
return http.request({
|
||||
url: `/complain/${id}`,
|
||||
url: `/order/complain/${id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -169,7 +182,7 @@ export function getComplainDetail(id) {
|
||||
*/
|
||||
export function getAfterSaleInfo(sn) {
|
||||
return http.request({
|
||||
url: `/afterSale/applyAfterSaleInfo/${sn}`,
|
||||
url: `/order/afterSale/applyAfterSaleInfo/${sn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -181,7 +194,7 @@ export function getAfterSaleInfo(sn) {
|
||||
*/
|
||||
export function applyReturn(orderItemSn, params) {
|
||||
return http.request({
|
||||
url: `/afterSale/save/${orderItemSn}`,
|
||||
url: `/order/afterSale/save/${orderItemSn}`,
|
||||
method: Method.POST,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
@@ -197,7 +210,7 @@ export function applyReturn(orderItemSn, params) {
|
||||
*/
|
||||
export function fillShipInfo(afterSaleSn, params) {
|
||||
return http.request({
|
||||
url: `/afterSale/delivery/${afterSaleSn}`,
|
||||
url: `/order/afterSale/delivery/${afterSaleSn}`,
|
||||
method: Method.POST,
|
||||
header: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
@@ -205,3 +218,13 @@ export function fillShipInfo(afterSaleSn, params) {
|
||||
data: params,
|
||||
});
|
||||
}
|
||||
|
||||
// 添加交易投诉对话
|
||||
export function communication(params) {
|
||||
return http.request({
|
||||
url: `/order/complain/communication`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params
|
||||
});
|
||||
}
|
||||
43
api/appointment.js
Normal file
43
api/appointment.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* 预约服务买家端 API
|
||||
*/
|
||||
import { http, Method } from '@/utils/request.js'
|
||||
|
||||
/** 查询可预约时段 */
|
||||
export function listSlots(skuId, slotDate) {
|
||||
return http.request({
|
||||
url: '/appointment/slots',
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: { skuId, slotDate },
|
||||
})
|
||||
}
|
||||
|
||||
/** 绑定预约结算上下文(addToCart 后、createTrade 前) */
|
||||
export function bindCheckout(data) {
|
||||
return http.request({
|
||||
url: '/appointment/checkout/bind',
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 预约商品扩展配置 */
|
||||
export function getGoodsExt(goodsId) {
|
||||
return http.request({
|
||||
url: `/appointment/goods/${goodsId}/ext`,
|
||||
method: Method.GET,
|
||||
needToken: false,
|
||||
})
|
||||
}
|
||||
|
||||
/** 买家预约订单详情(时段、核销码) */
|
||||
export function getAppointmentOrderDetail(orderSn) {
|
||||
return http.request({
|
||||
url: `/appointment/order/${orderSn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,8 +23,33 @@ export function getArticleCategory(category_type) {
|
||||
*/
|
||||
export function getArticleDetail(type) {
|
||||
return http.request({
|
||||
url: `/article/type/${type}`,
|
||||
url: `/other/article/get/${type}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文章详情
|
||||
* @param type
|
||||
*/
|
||||
export function getArticleDetailByType(type) {
|
||||
return http.request({
|
||||
url: `/other/article/type/${type}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取文章列表(楼层装修用)
|
||||
* @param params { pageNumber, pageSize, categoryId? }
|
||||
*/
|
||||
export function getArticleList(params) {
|
||||
return http.request({
|
||||
url: `/other/article`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +1,54 @@
|
||||
/**
|
||||
* 公共API
|
||||
*/
|
||||
import {http, Method} from '@/utils/request.js';
|
||||
import api from '@/config/api.js';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取地区数据
|
||||
* @param id
|
||||
*/
|
||||
export function getRegionsById(id = 0) {
|
||||
return http.request({
|
||||
url: `${api.common}/region/item/${id}`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传地址
|
||||
* @type {string}
|
||||
*/
|
||||
export const upload =api.common+'/upload/file';
|
||||
|
||||
/**
|
||||
* 公共 API(由 buyer-api 提供)
|
||||
*/
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
import api from "@/config/api.js";
|
||||
|
||||
/**
|
||||
* 获取地区数据
|
||||
* @param id
|
||||
*/
|
||||
export function getRegionsById(id = 0) {
|
||||
return http.request({
|
||||
url: `${api.buyer}/common/region/item/${id}`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
// 获取 IM 接口前缀
|
||||
export function getIMDetail() {
|
||||
return http.request({
|
||||
url: `${api.buyer}/common/IM`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传地址
|
||||
* @type {string}
|
||||
*/
|
||||
export const upload = `${api.buyer}/common/upload/file`;
|
||||
|
||||
/**
|
||||
* 获取主题色配置
|
||||
*/
|
||||
export function getThemeSetting() {
|
||||
return http.request({
|
||||
url: `${api.buyer}/common/site/theme`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类页配置
|
||||
*/
|
||||
export function getCategoryPageSetting() {
|
||||
return http.request({
|
||||
url: `${api.buyer}/common/site/category-page`,
|
||||
method: Method.GET,
|
||||
message: false,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const request = http.request;
|
||||
*/
|
||||
export function webConnect(code) {
|
||||
return http.request({
|
||||
url: `connect/login/web/${code}`,
|
||||
url: `passport/connect/connect/login/web/${code}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
header: {
|
||||
@@ -25,8 +25,8 @@ export function webConnect(code) {
|
||||
}
|
||||
export function openIdLogin(params, clientType) {
|
||||
return http.request({
|
||||
url: `connect/app/login`,
|
||||
method: Method.GET,
|
||||
url: `passport/connect/connect/app/login`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
data: params,
|
||||
header: {
|
||||
@@ -40,7 +40,7 @@ export function openIdLogin(params, clientType) {
|
||||
*/
|
||||
export function loginCallback(state) {
|
||||
return http.request({
|
||||
url: `connect/result?state=${state}`,
|
||||
url: `passport/connect/connect/result?state=${state}`,
|
||||
method: Method.GET,
|
||||
needToken: false
|
||||
});
|
||||
@@ -54,7 +54,7 @@ export function loginCallback(state) {
|
||||
*/
|
||||
export function mpAutoLogin(params) {
|
||||
return http.request({
|
||||
url: 'mini-program/auto-login',
|
||||
url: 'passport/connect/miniProgram/auto-login',
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
|
||||
233
api/distribution.js
Normal file
233
api/distribution.js
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* 分销员相关 API
|
||||
*/
|
||||
|
||||
import { http, Method } from '@/utils/request.js'
|
||||
|
||||
/**
|
||||
* 绑定分销员
|
||||
* @param distributionId 分销员ID
|
||||
*/
|
||||
export function getGoodsDistribution(distributionId) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/bindingDistribution/${distributionId}`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会员的分销员信息(含待提现、冻结金额等)
|
||||
*/
|
||||
export function distribution() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 推广海报上下文(邀请好友锁客) */
|
||||
export function getPromotionPoster() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/promotion-poster`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 邀请卡上下文(招募下级分销员) */
|
||||
export function getInviteCard() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/invite-card`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 我邀请的下级分销员列表 */
|
||||
export function getDistributionInvitees(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/invite/invitees`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 分销员业绩统计 */
|
||||
export function getDistributionPerformance(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/performance`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 我的客户列表 */
|
||||
export function getDistributionCustomers(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/customers`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 客户详情 */
|
||||
export function getDistributionCustomerDetail(id) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/customers/${id}`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 设置/取消客户星标 */
|
||||
export function updateDistributionCustomerStarred(id, starred) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/customers/${id}/starred`,
|
||||
method: Method.PUT,
|
||||
params: { starred },
|
||||
})
|
||||
}
|
||||
|
||||
/** 分销等级列表(包装 VO:upgradeConditionVisible + grades) */
|
||||
export function getDistributionGrades() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/grades`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 分销员提现 */
|
||||
export function cash(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 分销员提现历史 */
|
||||
export function cashLog(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 推广/邀请订单分页列表 */
|
||||
export function getDistributionOrders(params) {
|
||||
return http.request({
|
||||
url: `/distribution/order/orders`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 推广/邀请订单详情 */
|
||||
export function getDistributionOrderDetail(params) {
|
||||
return http.request({
|
||||
url: `/distribution/order/orders/detail`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分销商品设置 */
|
||||
export function getDistributionGoodsSetting() {
|
||||
return http.request({
|
||||
url: `/distribution/goods/setting`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分销员商品列表 */
|
||||
export function distributionGoods(params) {
|
||||
return http.request({
|
||||
url: `/distribution/goods`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分销员中心推荐商品 */
|
||||
export function getDistributionFeaturedGoods() {
|
||||
return http.request({
|
||||
url: `/distribution/goods/featured`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分销商品分享上下文(推广图文) */
|
||||
export function getDistributionGoodsShareContext(params) {
|
||||
return http.request({
|
||||
url: `/distribution/goods/share-context`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 获取分销商品海报上下文(生成葵花码,服务端记录分享) */
|
||||
export function getDistributionGoodsPosterContext(params) {
|
||||
return http.request({
|
||||
url: `/distribution/goods/poster-context`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 分享商品访问(记录访问并绑定客户关系) */
|
||||
export function recordDistributionGoodsVisit(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/share-visit`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/** 招募页信息 */
|
||||
export function getRecruitPage() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/recruit/page`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 招募页邀请人信息(无需登录) */
|
||||
export function getRecruitInviter(distributionId) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/recruit/inviter`,
|
||||
method: Method.GET,
|
||||
params: { distributionId },
|
||||
})
|
||||
}
|
||||
|
||||
/** 提交招募申请 */
|
||||
export function submitRecruitApplication(data) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/recruit/apply`,
|
||||
method: Method.POST,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/** 招募条件进度 */
|
||||
export function getRecruitProgress() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/recruit/progress`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
|
||||
/** 上报招募邀请 token(登录后供自动申请使用) */
|
||||
export function savePendingRecruitToken(recruitToken) {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/recruit/pending-token`,
|
||||
method: Method.PUT,
|
||||
params: { recruitToken },
|
||||
})
|
||||
}
|
||||
|
||||
/** 上级分销员信息 */
|
||||
export function getDistributionParent() {
|
||||
return http.request({
|
||||
url: `/distribution/distribution/parent`,
|
||||
method: Method.GET,
|
||||
})
|
||||
}
|
||||
41
api/entry.js
Normal file
41
api/entry.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 入驻类相关api
|
||||
*/
|
||||
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
// 获取当前用户的代理入驻详情
|
||||
export function getCompanyDetail(params) {
|
||||
return http.request({
|
||||
url: "/store/store/apply",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 申请店铺第一步-填写企业信息
|
||||
export function applyFirst(params) {
|
||||
return http.request({
|
||||
url: "/store/store/apply/first",
|
||||
method: Method.PUT,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 申请店铺第二步-填写银行
|
||||
export function applySecond(params) {
|
||||
return http.request({
|
||||
url: "/store/store/apply/second",
|
||||
method: Method.PUT,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 申请店铺第三步-填写银行
|
||||
export function applyThird(params) {
|
||||
return http.request({
|
||||
url: "/store/store/apply/third",
|
||||
method: Method.PUT,
|
||||
params,
|
||||
});
|
||||
}
|
||||
107
api/goods.js
107
api/goods.js
@@ -9,17 +9,17 @@ import { http, Method } from "@/utils/request.js";
|
||||
*/
|
||||
export function getGoodsMessage(goodsId) {
|
||||
return http.request({
|
||||
url: `/goods/get/${goodsId}`,
|
||||
url: `/goods/goods/get/${goodsId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 从ES中获取相关商品品牌名称,分类名称及属性
|
||||
* 获取商品搜索筛选条件
|
||||
*/
|
||||
export function getGoodsRelated(params) {
|
||||
return http.request({
|
||||
url: `/goods/es/related`,
|
||||
url: `/goods/goods/search/related`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
@@ -32,23 +32,11 @@ export function getGoodsRelated(params) {
|
||||
*/
|
||||
export function getGoods(skuId, goodsId) {
|
||||
return http.request({
|
||||
url: `/goods/sku/${goodsId}/${skuId}`,
|
||||
url: `/goods/goods/sku/${goodsId}/${skuId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品分销
|
||||
* @param distributionId 商品分销ID
|
||||
*/
|
||||
export function getGoodsDistribution(distributionId) {
|
||||
return http.request({
|
||||
url: `/distribution/bindingDistribution/${distributionId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取商品列表
|
||||
* @param params
|
||||
@@ -56,7 +44,7 @@ export function getGoodsRelated(params) {
|
||||
*/
|
||||
export function getGoodsList(params) {
|
||||
return http.request({
|
||||
url: "/goods/es",
|
||||
url: "/goods/goods/search",
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
@@ -112,97 +100,18 @@ export function getPlateformTagGoods(tag_id) {
|
||||
*/
|
||||
export function getCategoryList(id) {
|
||||
return http.request({
|
||||
url: `/category/get/${id}`,
|
||||
url: `/goods/category/get/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前会员的分销商信息 可根据分销商信息查询待提现金额以及冻结金额等信息
|
||||
*/
|
||||
export function distribution() {
|
||||
return http.request({
|
||||
url: `/distribution`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请分销商
|
||||
*/
|
||||
export function applyDistribution(params) {
|
||||
return http.request({
|
||||
url: `/distribution`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现
|
||||
*/
|
||||
export function cash(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.POST,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分销商提现历史
|
||||
*/
|
||||
export function cashLog(params) {
|
||||
return http.request({
|
||||
url: `/distribution/cash`,
|
||||
method: Method.GET,
|
||||
params
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销商分页订单列表
|
||||
*/
|
||||
export function distributionOrderList(params) {
|
||||
return http.request({
|
||||
url: `/distribution/distributionOrder`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分销商商品列表
|
||||
*/
|
||||
export function distributionGoods(params) {
|
||||
return http.request({
|
||||
url: `/distributionGoods`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 选择分销商品 分销商品id
|
||||
*/
|
||||
export function checkedDistributionGoods(params) {
|
||||
return http.request({
|
||||
url: `/distributionGoods/checked/${params.id}`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 小程序码
|
||||
*/
|
||||
export function getMpCode(params){
|
||||
return http.request({
|
||||
url:`/mini-program/mp/unlimited`,
|
||||
url:`/passport/connect/miniProgram/mp/unlimited`,
|
||||
method:Method.GET,
|
||||
params
|
||||
})
|
||||
@@ -213,7 +122,7 @@ export function checkedDistributionGoods(params) {
|
||||
*/
|
||||
export function getMpScene(id){
|
||||
return http.request({
|
||||
url:`/mini-program/mp/unlimited/scene?id=${id}`,
|
||||
url:`/passport/connect/miniProgram/mp/unlimited/scene?id=${id}`,
|
||||
method:Method.GET,
|
||||
|
||||
})
|
||||
|
||||
37
api/home.js
37
api/home.js
@@ -1,6 +1,21 @@
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
|
||||
export function toSpecial(data) {
|
||||
return http.request({
|
||||
url: `/other/pageData/getSpecial`,
|
||||
method: Method.GET,
|
||||
data
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 专题内容
|
||||
*/
|
||||
export function getSpecial(id) {
|
||||
return http.request({
|
||||
url: `/other/pageData/get/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取广告图
|
||||
@@ -32,7 +47,7 @@ export function getCategory(parent_id = 0) {
|
||||
*/
|
||||
export function getHotKeywords(count) {
|
||||
return http.request({
|
||||
url: "/goods/hot-words",
|
||||
url: "/goods/goods/hot-words",
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params: { count },
|
||||
@@ -44,10 +59,22 @@ export function getHotKeywords(count) {
|
||||
* @param client_type
|
||||
* @param page_type
|
||||
*/
|
||||
export function getFloorData() {
|
||||
export function getFloorData(params) {
|
||||
return http.request({
|
||||
url: `/pageData/getIndex?clientType=H5`,
|
||||
url: `/other/pageData/getIndex?clientType=H5`,
|
||||
method: "get",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取店铺楼层数据
|
||||
*/
|
||||
export function getFloorStoreData(params) {
|
||||
return http.request({
|
||||
url: `/other/pageData?pageClientType=H5`,
|
||||
method: "get",
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,7 +83,7 @@ export function getFloorData() {
|
||||
*/
|
||||
export function getCategoryIndexData(parentId = 0) {
|
||||
return http.request({
|
||||
url: `/category/get/${parentId}`,
|
||||
url: `/goods/category/get/${parentId}`,
|
||||
method: "get",
|
||||
});
|
||||
}
|
||||
|
||||
82
api/im.js
Normal file
82
api/im.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { http,Method } from "@/utils/request.js";
|
||||
|
||||
import api from "@/config/api.js";
|
||||
|
||||
/**
|
||||
* 获取聊天详情接口
|
||||
* @param {*} talkId
|
||||
* @returns
|
||||
*/
|
||||
export function getTalk(talkId) {
|
||||
return http.request({
|
||||
url: `${api.im}/talk/${talkId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取与用户的聊天详情
|
||||
* @param {*} talkId
|
||||
* @returns
|
||||
*/
|
||||
export function getTalkByUser(userId) {
|
||||
return http.request({
|
||||
url: `${api.im}/talk/by/user/${userId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天列表
|
||||
* @param {*} talkId
|
||||
* @returns
|
||||
*/
|
||||
export function getTalkList(params) {
|
||||
return http.request({
|
||||
url: `${api.im}/talk/list`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天信息接口
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function getTalkMessage(params) {
|
||||
return http.request({
|
||||
url: `${api.im}/message`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取聊天信息接口
|
||||
* @param {*} params
|
||||
* @returns
|
||||
*/
|
||||
export function cleanUnreadMessage(params) {
|
||||
return http.request({
|
||||
url: `${api.im}/message/clean/unred`,
|
||||
method: Method.PUT,
|
||||
params
|
||||
});
|
||||
}
|
||||
// 从商品页点击 客服 跳转 获取商品详情
|
||||
export function jumpObtain(skuId, goodsId) {
|
||||
return http.request({
|
||||
url: `/goods/goods/sku/${goodsId}/${skuId}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
// 清除未读
|
||||
// /im/message/clean/unred
|
||||
export function clearmeaager() {
|
||||
return http.request({
|
||||
url: `${api.im}/message/clean/unred`,
|
||||
method: Method.PUT,
|
||||
});
|
||||
}
|
||||
|
||||
65
api/live.js
Normal file
65
api/live.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 直播流相关 API
|
||||
*/
|
||||
import { http, Method } from "@/utils/request.js";
|
||||
|
||||
/**
|
||||
* 通过 id 获取直播间详情
|
||||
* @param {string} id 直播流 ID
|
||||
*/
|
||||
export function getLiveRoomById(id) {
|
||||
return http.request({
|
||||
url: `live/room/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 观看的同时注册直播用户
|
||||
* @param {string} liveRoomId 直播流 ID
|
||||
*/
|
||||
export function registerLiveViewUser(liveRoomId) {
|
||||
return http.request({
|
||||
url: `live/user/view`,
|
||||
method: Method.POST,
|
||||
params: { liveRoomId },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送直播消息
|
||||
* @param {object} data LiveMessage
|
||||
*/
|
||||
export function sendLiveMessage(data) {
|
||||
return http.request({
|
||||
url: `live/message`,
|
||||
method: Method.POST,
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直播轮询聚合数据
|
||||
* @param {string} id 直播流 ID
|
||||
*/
|
||||
export function getLivePollingData(id) {
|
||||
return http.request({
|
||||
url: `live/room/polling/${id}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取直播间优惠券
|
||||
* @param {string} liveRoomId 直播流 ID
|
||||
* @param {string} couponId 优惠券 ID
|
||||
*/
|
||||
export function receiveLiveCoupon(liveRoomId, couponId) {
|
||||
return http.request({
|
||||
url: `promotion/coupon/receive/live/${liveRoomId}/${couponId}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
90
api/login.js
90
api/login.js
@@ -8,19 +8,58 @@ import api from "@/config/api.js";
|
||||
*/
|
||||
export function resetByMobile(params) {
|
||||
return http.request({
|
||||
url: `/members/resetByMobile`,
|
||||
url: `/passport/member/resetByMobile`,
|
||||
method: "POST",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号码
|
||||
* @param mobile
|
||||
*/
|
||||
export function bindMobile(params) {
|
||||
return http.request({
|
||||
url: `/passport/member/bindMobile`,
|
||||
method: "POST",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
//获取自动发券
|
||||
export function getAutoCoup(){
|
||||
return http.request({
|
||||
url:`/promotion/coupon/activity`,
|
||||
method:"GET",
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号密码登陆
|
||||
* @params password
|
||||
* @params username
|
||||
*/
|
||||
export function userLogin(params, clientType){
|
||||
return http.request({
|
||||
method: "POST",
|
||||
url:`/passport/member/userLogin`,
|
||||
data: params,
|
||||
header: {
|
||||
"content-type": "application/x-www-form-urlencoded",
|
||||
clientType: clientType,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 发送验证码
|
||||
* @param mobile
|
||||
*/
|
||||
export function sendMobile(mobile,type='LOGIN') {
|
||||
return http.request({
|
||||
url: `${api.common}/sms/${type}/${mobile}`,
|
||||
url: `${api.buyer}/common/sms/${type}/${mobile}`,
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
@@ -32,7 +71,7 @@ export function sendMobile(mobile,type='LOGIN') {
|
||||
*/
|
||||
export function smsLogin(params, clientType) {
|
||||
return http.request({
|
||||
url: `/members/smsLogin`,
|
||||
url: `/passport/member/smsLogin`,
|
||||
method: "POST",
|
||||
data: params,
|
||||
header: {
|
||||
@@ -50,18 +89,31 @@ export function smsLogin(params, clientType) {
|
||||
|
||||
export function modifyPass(params) {
|
||||
return http.request({
|
||||
url: `/members/modifyPass`,
|
||||
url: `/passport/member/modifyPass`,
|
||||
method: "PUT",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param newPassword
|
||||
* @param password
|
||||
*/
|
||||
export function resetPassword(params) {
|
||||
return http.request({
|
||||
url: `/passport/member/resetPassword`,
|
||||
method: "POST",
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token
|
||||
*/
|
||||
export function refreshTokenFn(refresh_token) {
|
||||
return http.request({
|
||||
url: `/members/refresh/${refresh_token}`,
|
||||
url: `/passport/member/refresh/${refresh_token}`,
|
||||
method: "GET",
|
||||
});
|
||||
}
|
||||
@@ -69,8 +121,34 @@ export function refreshTokenFn(refresh_token) {
|
||||
// 获取密码状态
|
||||
export function logout () {
|
||||
return http.request({
|
||||
url: '/members/logout',
|
||||
url: '/passport/member/logout',
|
||||
method: "POST",
|
||||
needToken: true,
|
||||
})
|
||||
}
|
||||
|
||||
export function scannerCodeLogin(params){
|
||||
return http.request({
|
||||
url: '/passport/member/app_scanner',
|
||||
method: "POST",
|
||||
params,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
export function scannerCodeLoginConfirm(params){
|
||||
return http.request({
|
||||
url: '/passport/member/app_confirm',
|
||||
method: "POST",
|
||||
params,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
// 注销用户
|
||||
export function logoffConfirm() {
|
||||
return http.request({
|
||||
url: '/passport/member/cancellation',
|
||||
method: "PUT",
|
||||
needToken: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { http, Method } from "@/utils/request.js";
|
||||
*/
|
||||
export function feedBack(params) {
|
||||
return http.request({
|
||||
url: "/feedback",
|
||||
url: "/other/feedback",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -16,13 +16,30 @@ export function feedBack(params) {
|
||||
// 提现
|
||||
export function withdrawalApply(params) {
|
||||
return http.request({
|
||||
url: "/members/wallet/withdrawal",
|
||||
url: "/wallet/wallet/withdrawal",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function getWithdrawApplyPage(params) {
|
||||
return http.request({
|
||||
url: "/member/withdrawApply",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function getWithdrawApplyWechatTransferInfo(id) {
|
||||
return http.request({
|
||||
url: `/member/withdrawApply/wechat/transfer/${id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付结果查询
|
||||
* @param orderType 交易类型,可用值:TRADE,ORDER,RECHARGE
|
||||
@@ -30,7 +47,7 @@ export function withdrawalApply(params) {
|
||||
*/
|
||||
export function payCallback(params) {
|
||||
return http.request({
|
||||
url: `/cashier/result`,
|
||||
url: `/payment/cashier/result`,
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
@@ -117,7 +134,7 @@ export function getPointsData(params) {
|
||||
*/
|
||||
export function getComments(params) {
|
||||
return http.request({
|
||||
url: "/memberEvaluation",
|
||||
url: "/member/evaluation",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -131,7 +148,7 @@ export function getComments(params) {
|
||||
*/
|
||||
export function getFootprintNum(params) {
|
||||
return http.request({
|
||||
url: "/footprint/getFootprintNum",
|
||||
url: "/member/footprint/getFootprintNum",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -144,7 +161,7 @@ export function getFootprintNum(params) {
|
||||
*/
|
||||
export function commentsMemberOrder(params) {
|
||||
return http.request({
|
||||
url: "/memberEvaluation",
|
||||
url: "/member/evaluation",
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
@@ -202,6 +219,22 @@ export function getGoodsCollection(params, type) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品收藏
|
||||
* @param params
|
||||
* @returns {AxiosPromise}
|
||||
*/
|
||||
export function getStoreCollection(params, type) {
|
||||
return http.request({
|
||||
url: `/member/storeCollection/${type}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
message: false,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏商品
|
||||
* @returns {AxiosPromise}
|
||||
@@ -236,7 +269,7 @@ export function deleteGoodsCollection(ids) {
|
||||
*/
|
||||
export function deleteStoreCollection(store_id) {
|
||||
return http.request({
|
||||
url: `/member/collection/delete/STORE/${store_id}`,
|
||||
url: `/member/storeCollection/delete/STORE/${store_id}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -255,6 +288,19 @@ export function getGoodsIsCollect(type, good_id) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取商品是否被收藏
|
||||
* @param good_id
|
||||
*/
|
||||
export function getStoreIsCollect(type, store_id) {
|
||||
return http.request({
|
||||
url: `/member/storeCollection/isCollection/${type}/${store_id}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
loading: false,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏店铺
|
||||
* @param store_id 店铺ID
|
||||
@@ -262,10 +308,9 @@ export function getGoodsIsCollect(type, good_id) {
|
||||
*/
|
||||
export function collectionStore(store_id) {
|
||||
return http.request({
|
||||
url: "members/collection/store",
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
url: `/member/storeCollection/add/STORE/${store_id}`,
|
||||
method: Method.POST,
|
||||
data: { store_id },
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -275,7 +320,7 @@ export function collectionStore(store_id) {
|
||||
*/
|
||||
export function getUserInfo() {
|
||||
return http.request({
|
||||
url: "/members",
|
||||
url: "/passport/member",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -287,7 +332,7 @@ export function getUserInfo() {
|
||||
*/
|
||||
export function getUserWallet() {
|
||||
return http.request({
|
||||
url: "/members/wallet",
|
||||
url: "/wallet/wallet",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -300,7 +345,7 @@ export function getUserWallet() {
|
||||
*/
|
||||
export function saveUserInfo(params) {
|
||||
return http.request({
|
||||
url: "/members/editOwn",
|
||||
url: "/passport/member/editOwn",
|
||||
method: Method.PUT,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
needToken: true,
|
||||
@@ -328,7 +373,7 @@ export function addReceipt(params) {
|
||||
*/
|
||||
export function getGoodsComments(goodsId, params) {
|
||||
return http.request({
|
||||
url: `/memberEvaluation/${goodsId}/goodsEvaluation`,
|
||||
url: `/member/evaluation/${goodsId}/goodsEvaluation`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
params,
|
||||
@@ -341,7 +386,7 @@ export function getGoodsComments(goodsId, params) {
|
||||
*/
|
||||
export function getGoodsCommentsCount(goodsId) {
|
||||
return http.request({
|
||||
url: `/memberEvaluation/${goodsId}/evaluationNumber`,
|
||||
url: `/member/evaluation/${goodsId}/evaluationNumber`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
@@ -365,7 +410,7 @@ export function getNoReadMessageNum() {
|
||||
*/
|
||||
export function myTrackList(params) {
|
||||
return http.request({
|
||||
url: `/footprint`,
|
||||
url: `/member/footprint`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -378,7 +423,7 @@ export function myTrackList(params) {
|
||||
*/
|
||||
export function deleteHistoryListId(ids) {
|
||||
return http.request({
|
||||
url: `/footprint/delByIds/${ids}`,
|
||||
url: `/member/footprint/delByIds/${ids}`,
|
||||
method: Method.DELETE,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -419,3 +464,16 @@ export function getMemberPointSum() {
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员积分VO
|
||||
* @param
|
||||
*/
|
||||
export function withdrawalSettingVO() {
|
||||
return http.request({
|
||||
url: `/wallet/wallet/withdrawalSettingVO`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import api from '@/config/api.js';
|
||||
*/
|
||||
export function getWeChatMpMessage() {
|
||||
return http.request({
|
||||
url: 'mini-program/subscribeMessage',
|
||||
url: 'passport/connect/miniProgram/subscribeMessage',
|
||||
method: Method.GET
|
||||
});
|
||||
}
|
||||
@@ -42,12 +42,30 @@ export function getMessages(params) {
|
||||
*/
|
||||
export function messageMarkAsRead(ids) {
|
||||
return http.request({
|
||||
url: `members/member-nocice-logs/${ids}/read`,
|
||||
url: `/message/member/${ids}`,
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
//读取站内消息
|
||||
export function editMessages(message_id,params){
|
||||
return http.request({
|
||||
url:`/message/member/${message_id}`,
|
||||
method:Method.PUT,
|
||||
needToken:true,
|
||||
params
|
||||
})
|
||||
}
|
||||
//获取站内消息
|
||||
export function messages(params) {
|
||||
return http.request({
|
||||
url: "/message/member",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取物流消息列表
|
||||
@@ -73,7 +91,7 @@ export function getLogisticsMessages(params) {
|
||||
*/
|
||||
export function getAppVersion(appType) {
|
||||
return http.request({
|
||||
url: `/appVersion/${appType}`,
|
||||
url: `/other/appVersion/${appType}`,
|
||||
method: Method.GET,
|
||||
type:"manager"
|
||||
});
|
||||
@@ -86,7 +104,7 @@ export function getLogisticsMessages(params) {
|
||||
*/
|
||||
export function getAppVersionList(type,data) {
|
||||
return http.request({
|
||||
url: `/appVersion/appVersion/${type}`,
|
||||
url: `/other/appVersion/appVersion/${type}`,
|
||||
method: Method.GET,
|
||||
type:"manager",
|
||||
data
|
||||
|
||||
@@ -51,7 +51,7 @@ export function selectedShipMethod(params) {
|
||||
*/
|
||||
export function getOrderList(params) {
|
||||
return http.request({
|
||||
url: "/orders",
|
||||
url: "/order/order",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -64,7 +64,7 @@ export function getOrderList(params) {
|
||||
*/
|
||||
export function getOrderDetail(orderSn) {
|
||||
return http.request({
|
||||
url: `/orders/${orderSn}`,
|
||||
url: `/order/order/${orderSn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
@@ -77,7 +77,7 @@ export function getOrderDetail(orderSn) {
|
||||
*/
|
||||
export function cancelOrder(orderSn, reason) {
|
||||
return http.request({
|
||||
url: `/orders/${orderSn}/cancel`,
|
||||
url: `/order/order/${orderSn}/cancel`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { "content-type": "application/x-www-form-urlencoded" },
|
||||
@@ -91,7 +91,7 @@ export function cancelOrder(orderSn, reason) {
|
||||
*/
|
||||
export function confirmReceipt(orderSn) {
|
||||
return http.request({
|
||||
url: `/orders/${orderSn}/receiving`,
|
||||
url: `/order/order/${orderSn}/receiving`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,23 @@ export function getLiveList(params) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取直播间详情
|
||||
* @deprecated 请使用 api/live.js 中的 getLiveRoomById
|
||||
* @param {number} roomId 直播间房间号
|
||||
*/
|
||||
export function getLiveDetail(roomId) {
|
||||
return http.request({
|
||||
url: `broadcast/studio`,
|
||||
method: Method.GET,
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 1,
|
||||
roomId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前拼团活动的未成团的会员
|
||||
*/
|
||||
@@ -185,4 +202,40 @@ export function getMineBargainLog(params) {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询当前客户礼品卡
|
||||
*/
|
||||
export function getGiftCardCashMemberCardPage(params) {
|
||||
return http.request({
|
||||
url: '/promotion/giftCardCash/memberCard',
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定礼品卡(卡号 + 兑换码/卡密)
|
||||
*/
|
||||
export function bindGiftCardCashMemberCard(data) {
|
||||
return http.request({
|
||||
url: '/promotion/giftCardCash/memberCard/bind',
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
header: { 'content-type': 'application/json' },
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 激活礼品卡
|
||||
*/
|
||||
export function activateGiftCardCashMemberCard(memberCardId) {
|
||||
return http.request({
|
||||
url: `/promotion/giftCardCash/memberCard/${memberCardId}/activate`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
25
api/store.js
25
api/store.js
@@ -4,13 +4,16 @@
|
||||
|
||||
import {http, Method} from '@/utils/request.js';
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取店铺列表
|
||||
* @param params
|
||||
*/
|
||||
export function getstoreList(params) {
|
||||
export function getStoreList(params) {
|
||||
return http.request({
|
||||
url: '/store',
|
||||
url: '/store/store',
|
||||
method: Method.GET,
|
||||
params,
|
||||
});
|
||||
@@ -22,7 +25,7 @@ export function getstoreList(params) {
|
||||
*/
|
||||
export function getStoreBaseInfo(storeId) {
|
||||
return http.request({
|
||||
url: `/store/get/detail/${storeId}`,
|
||||
url: `/store/store/get/detail/${storeId}`,
|
||||
method: Method.GET,
|
||||
loading: false,
|
||||
});
|
||||
@@ -34,7 +37,7 @@ export function getstoreList(params) {
|
||||
*/
|
||||
export function getStoreCategory(id) {
|
||||
return http.request({
|
||||
url: `/store/label/get/${id}`,
|
||||
url: `/store/store/label/get/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
@@ -46,11 +49,23 @@ export function getstoreList(params) {
|
||||
*/
|
||||
export function getLicencePhoto(id) {
|
||||
return http.request({
|
||||
url: `/store/get/licencePhoto/${id}`,
|
||||
url: `/store/store/get/licencePhoto/${id}`,
|
||||
method: Method.GET,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取自提点信息
|
||||
* @param id
|
||||
*/
|
||||
export function getStoreAddress(storeId,params) {
|
||||
return http.request({
|
||||
url: `/store/address/page/${storeId}`,
|
||||
method: Method.GET,
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
67
api/trade.js
67
api/trade.js
@@ -155,8 +155,34 @@ export function setAddressId(addressId,way) {
|
||||
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 设置收货地址ID
|
||||
* @param addressId
|
||||
*/
|
||||
export function setStoreAddressId(storeAddressId,way) {
|
||||
return http.request({
|
||||
url: `/trade/carts/storeAddress?storeAddressId=${storeAddressId}&way=${way}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 选择礼品卡
|
||||
* @param way 购物车购买:CART/立即购买:BUY_NOW/拼团:PINTUAN / 积分:POINTS / 砍价:KANJIA
|
||||
* @param credentialId 礼品卡凭证 id;used=false 且不传则清空全部
|
||||
*/
|
||||
export function selectGiftCard(params) {
|
||||
return http.request({
|
||||
url: `/trade/carts/select/giftCard`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建交易
|
||||
*/
|
||||
@@ -177,7 +203,7 @@ export function createTrade(params) {
|
||||
*/
|
||||
export function getCashierData(params) {
|
||||
return http.request({
|
||||
url: "cashier/tradeDetail",
|
||||
url: "payment/cashier/tradeDetail",
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -194,7 +220,7 @@ export function getCashierData(params) {
|
||||
*/
|
||||
export function initiatePay(paymentMethod, paymentClient, params) {
|
||||
return http.request({
|
||||
url: `cashier/pay/${paymentMethod}/${paymentClient}`,
|
||||
url: `payment/cashier/pay/${paymentMethod}/${paymentClient}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params,
|
||||
@@ -209,7 +235,7 @@ export function initiatePay(paymentMethod, paymentClient, params) {
|
||||
*/
|
||||
export function getExpress(orderSn) {
|
||||
return http.request({
|
||||
url: `/orders/getTraces/${orderSn}`,
|
||||
url: `/order/order/getTraces/${orderSn}`,
|
||||
method: Method.POST,
|
||||
needToken: true,
|
||||
|
||||
@@ -281,3 +307,38 @@ export function reBuy(sn) {
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取全部配送方式
|
||||
*/
|
||||
export function shippingMethodList(params) {
|
||||
return http.request({
|
||||
url: `/trade/carts/shippingMethodList`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
params: params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交配送方式
|
||||
* @param params
|
||||
*/
|
||||
export function setShipMethod(params) {
|
||||
return http.request({
|
||||
url: "/trade/carts/shippingMethod",
|
||||
method: Method.PUT,
|
||||
needToken: true,
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
// 查看包裹列表
|
||||
export function getPackage(orderSn) {
|
||||
return http.request({
|
||||
url: `/order/order/getPackage/${orderSn}`,
|
||||
method: Method.GET,
|
||||
needToken: true,
|
||||
});
|
||||
}
|
||||
133
components/category/category-scroller.vue
Normal file
133
components/category/category-scroller.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<template>
|
||||
<scroll-view scroll-x scroll-with-animation class="cate-scroller" :scroll-into-view="intoView">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:id="'cs-' + item.id"
|
||||
class="cs-item"
|
||||
:class="{ active: item.id === currentId, 'cs-item--text': mode === 'text' }"
|
||||
@click="$emit('change', item)"
|
||||
>
|
||||
<template v-if="mode === 'icon'">
|
||||
<view class="cs-icon-box">
|
||||
<view class="cs-icon">
|
||||
<u-image width="80rpx" height="80rpx" shape="circle" :src="item.image" mode="aspectFill">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
</view>
|
||||
<text class="cs-name">{{ item.name }}</text>
|
||||
</template>
|
||||
<template v-else>
|
||||
<text class="cs-text-name">{{ item.name }}</text>
|
||||
<view class="cs-text-line" v-if="item.id === currentId"></view>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
list?: any[]
|
||||
currentId?: string | number | null
|
||||
mode?: 'icon' | 'text'
|
||||
}>(), {
|
||||
list: () => [],
|
||||
currentId: null,
|
||||
mode: 'icon'
|
||||
})
|
||||
|
||||
defineEmits(['change'])
|
||||
|
||||
const intoView = computed(() =>
|
||||
props.currentId != null ? 'cs-' + props.currentId : ''
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cate-scroller {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
border-bottom: 1rpx solid #f2f3f5;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cs-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 130rpx;
|
||||
padding: 16rpx 6rpx 14rpx;
|
||||
vertical-align: top;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.cs-icon-box {
|
||||
width: 92rpx;
|
||||
height: 92rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 3rpx solid transparent;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
.cs-item.active .cs-icon-box {
|
||||
border-color: $light-color;
|
||||
box-shadow: 0 4rpx 12rpx rgba(255, 102, 0, 0.15);
|
||||
}
|
||||
.cs-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background: #f6f7f9;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cs-name {
|
||||
margin-top: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: #555;
|
||||
max-width: 120rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.cs-item.active .cs-name {
|
||||
color: $light-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cs-item--text {
|
||||
width: auto;
|
||||
padding: 18rpx 28rpx;
|
||||
position: relative;
|
||||
}
|
||||
.cs-text-name {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
.cs-item--text.active .cs-text-name {
|
||||
color: $light-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cs-text-line {
|
||||
position: absolute;
|
||||
bottom: 6rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 32rpx;
|
||||
height: 4rpx;
|
||||
border-radius: 2rpx;
|
||||
background: $light-color;
|
||||
}
|
||||
</style>
|
||||
75
components/category/category-side-list.vue
Normal file
75
components/category/category-side-list.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<scroll-view scroll-y scroll-with-animation class="side-list">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="side-item"
|
||||
:class="{ active: item.id === currentId }"
|
||||
@click="$emit('change', item)"
|
||||
>
|
||||
<view class="active-indicator" v-if="item.id === currentId"></view>
|
||||
<text class="side-item-text">{{ item.name }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
list?: any[]
|
||||
currentId?: string | number | null
|
||||
}>(), {
|
||||
list: () => [],
|
||||
currentId: null
|
||||
})
|
||||
|
||||
defineEmits(['change'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.side-list {
|
||||
width: 176rpx;
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
background-color: #f6f7f9;
|
||||
border-right: 1rpx solid #ebebeb;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.side-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 96rpx;
|
||||
padding: 16rpx 14rpx;
|
||||
box-sizing: border-box;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
font-weight: 600;
|
||||
color: $light-color;
|
||||
background: #ffffff;
|
||||
}
|
||||
}
|
||||
.side-item-text {
|
||||
line-height: 1.35;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.active-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 6rpx;
|
||||
height: 36rpx;
|
||||
border-radius: 0 4rpx 4rpx 0;
|
||||
background: $light-color;
|
||||
}
|
||||
</style>
|
||||
94
components/category/sort-bar.vue
Normal file
94
components/category/sort-bar.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<view class="sort-bar">
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: sort === 'releaseTime' }"
|
||||
@click="onClick('releaseTime')"
|
||||
>
|
||||
<text class="sort-label">综合</text>
|
||||
</view>
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: sort === 'buyCount' }"
|
||||
@click="onClick('buyCount')"
|
||||
>
|
||||
<text class="sort-label">销量</text>
|
||||
</view>
|
||||
<view
|
||||
class="sort-item"
|
||||
:class="{ active: sort === 'price' }"
|
||||
@click="onClick('price')"
|
||||
>
|
||||
<text class="sort-label">价格</text>
|
||||
<view class="arrows">
|
||||
<u-icon
|
||||
name="arrow-up-fill"
|
||||
size="7"
|
||||
:color="sort === 'price' && order === 'asc' ? '#ff6700' : '#bbbbbb'"
|
||||
></u-icon>
|
||||
<u-icon
|
||||
name="arrow-down-fill"
|
||||
size="7"
|
||||
:color="sort === 'price' && order === 'desc' ? '#ff6700' : '#bbbbbb'"
|
||||
></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = withDefaults(defineProps<{
|
||||
sort?: string
|
||||
order?: string
|
||||
}>(), {
|
||||
sort: 'releaseTime',
|
||||
order: 'desc'
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
function onClick(type: string) {
|
||||
let nextOrder = 'desc'
|
||||
if (type === 'price') {
|
||||
nextOrder = props.sort === 'price' && props.order === 'desc' ? 'asc' : 'desc'
|
||||
}
|
||||
emit('change', { sort: type, order: nextOrder })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sort-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 68rpx;
|
||||
background: #ffffff;
|
||||
padding: 0 20rpx;
|
||||
border-bottom: 1rpx solid #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.sort-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 44rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: $light-color;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
.sort-label {
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.arrows {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 8rpx;
|
||||
gap: 2rpx;
|
||||
}
|
||||
</style>
|
||||
61
components/category/sub-tag-bar.vue
Normal file
61
components/category/sub-tag-bar.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<scroll-view scroll-x scroll-with-animation class="tag-bar" :scroll-into-view="intoView">
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:id="'tb-' + item.id"
|
||||
class="tag-item"
|
||||
:class="{ active: item.id === currentId }"
|
||||
@click="$emit('change', item)"
|
||||
>
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</scroll-view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
list?: any[]
|
||||
currentId?: string | number | null
|
||||
}>(), {
|
||||
list: () => [],
|
||||
currentId: null
|
||||
})
|
||||
|
||||
defineEmits(['change'])
|
||||
|
||||
const intoView = computed(() =>
|
||||
props.currentId != null ? 'tb-' + props.currentId : ''
|
||||
)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tag-bar {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
background: #ffffff;
|
||||
padding: 12rpx 16rpx 10rpx;
|
||||
border-bottom: 1rpx solid #f6f6f6;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tag-item {
|
||||
display: inline-block;
|
||||
padding: 6rpx 22rpx;
|
||||
margin-right: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
background: #f5f6f8;
|
||||
border-radius: 24rpx;
|
||||
line-height: 1.35;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.active {
|
||||
color: #ffffff;
|
||||
background: $light-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
297
components/category/tpl-category-goods.vue
Normal file
297
components/category/tpl-category-goods.vue
Normal file
@@ -0,0 +1,297 @@
|
||||
<template>
|
||||
<view class="tpl-goods">
|
||||
<!-- 模板2/3:顶部一级分类图标横滑 -->
|
||||
<category-scroller
|
||||
v-if="template !== 4"
|
||||
:list="categoryList"
|
||||
:current-id="currentL1 && currentL1.id"
|
||||
mode="icon"
|
||||
@change="onL1Change"
|
||||
/>
|
||||
<view class="tpl-main">
|
||||
<!-- 左侧:模板2/3 为二级分类,模板4 为一级分类 -->
|
||||
<category-side-list
|
||||
:list="sideList"
|
||||
:current-id="sideCurrentId"
|
||||
@change="onSideChange"
|
||||
/>
|
||||
<view class="tpl-right">
|
||||
<!-- 模板2:三级分类标签横滑 (置于右栏顶部) -->
|
||||
<sub-tag-bar
|
||||
v-if="template === 2 && l3List.length"
|
||||
:list="l3List"
|
||||
:current-id="currentL3 && currentL3.id"
|
||||
@change="onL3Change"
|
||||
/>
|
||||
<!-- 模板4:右上二级分类文字横滑(起点为当前一级名称,作为"全部"入口) -->
|
||||
<category-scroller
|
||||
v-if="template === 4"
|
||||
:list="l2Items"
|
||||
:current-id="topCurrentId"
|
||||
mode="text"
|
||||
@change="onL2Change"
|
||||
/>
|
||||
<sort-bar :sort="sort" :order="order" @change="onSortChange" />
|
||||
<scroll-view scroll-y class="goods-scroll" @scrolltolower="loadMore">
|
||||
<m-goods-list
|
||||
v-if="goodsList.length"
|
||||
:res="goodsList"
|
||||
type="oneColumns"
|
||||
:show-fields="setting.showFields"
|
||||
:tab-bar-gap="false"
|
||||
:store-name="false"
|
||||
@add-cart="onAddCart"
|
||||
/>
|
||||
<view v-else-if="!loading" class="empty-state">
|
||||
<u-empty
|
||||
:text="failed ? '加载失败,请重试' : '暂无相关商品'"
|
||||
mode="data"
|
||||
icon="http://cdn.uviewui.com/uview/empty/data.png"
|
||||
margin-top="100"
|
||||
>
|
||||
<view v-if="failed" class="retry" @click="loadGoods(true)">重试</view>
|
||||
</u-empty>
|
||||
</view>
|
||||
<uni-load-more
|
||||
v-if="showLoadMore"
|
||||
:status="loading ? 'loading' : hasMore ? 'more' : 'noMore'"
|
||||
:color="'#bfbfbf'"
|
||||
:content-text="loadMoreText"
|
||||
></uni-load-more>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { getGoodsList } from '@/api/goods.js'
|
||||
import CategoryScroller from './category-scroller.vue'
|
||||
import CategorySideList from './category-side-list.vue'
|
||||
import SubTagBar from './sub-tag-bar.vue'
|
||||
import SortBar from './sort-bar.vue'
|
||||
import MGoodsList from '@/components/m-goods-list/list.vue'
|
||||
|
||||
defineOptions({
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
},
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
categoryList?: any[]
|
||||
setting?: any
|
||||
template?: number
|
||||
}>(), {
|
||||
categoryList: () => [],
|
||||
setting: () => ({ template: 1, showFields: {} }),
|
||||
template: 2
|
||||
})
|
||||
|
||||
const currentL1 = ref<any>(null)
|
||||
const currentL2 = ref<any>(null)
|
||||
const currentL3 = ref<any>(null)
|
||||
const sort = ref('releaseTime')
|
||||
const order = ref('desc')
|
||||
|
||||
const goodsList = ref<any[]>([])
|
||||
const pageNumber = ref(1)
|
||||
const hasMore = ref(true)
|
||||
const loading = ref(false)
|
||||
const failed = ref(false)
|
||||
|
||||
const loadMoreText = {
|
||||
contentdown: '上拉加载更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '—— 到底啦 ——',
|
||||
}
|
||||
|
||||
// 仅在正在加载、或者有下一页、或者商品总数达到 6 件以上触底时才展示底线,避免少量商品(如1~2件)时在空白区域突兀展示
|
||||
const showLoadMore = computed(() => {
|
||||
if (!goodsList.value.length) return false
|
||||
if (loading.value) return true
|
||||
if (hasMore.value) return true
|
||||
return goodsList.value.length >= 6
|
||||
})
|
||||
|
||||
// 模板2/3:左侧为当前一级的二级分类;模板4:左侧为一级分类
|
||||
const sideList = computed(() =>
|
||||
props.template === 4
|
||||
? props.categoryList
|
||||
: (currentL1.value && currentL1.value.children) || []
|
||||
)
|
||||
const sideCurrentId = computed(() =>
|
||||
props.template === 4
|
||||
? currentL1.value && currentL1.value.id
|
||||
: currentL2.value && currentL2.value.id
|
||||
)
|
||||
// 模板2 的三级标签
|
||||
const l3List = computed(() =>
|
||||
(currentL2.value && currentL2.value.children) || []
|
||||
)
|
||||
// 模板4 顶部:当前一级名称(作为"全部") + 其二级分类
|
||||
const l2Items = computed(() => {
|
||||
if (!currentL1.value) return []
|
||||
return [currentL1.value, ...((currentL1.value.children) || [])]
|
||||
})
|
||||
const topCurrentId = computed(() =>
|
||||
(currentL2.value && currentL2.value.id) || (currentL1.value && currentL1.value.id)
|
||||
)
|
||||
|
||||
const queryCategoryId = computed(() =>
|
||||
(currentL3.value && currentL3.value.id) ||
|
||||
(currentL2.value && currentL2.value.id) ||
|
||||
(currentL1.value && currentL1.value.id)
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.categoryList,
|
||||
(list) => {
|
||||
if (list && list.length && !currentL1.value) {
|
||||
currentL1.value = list[0]
|
||||
currentL2.value = props.template === 4 ? null : (list[0].children && list[0].children[0]) || null
|
||||
currentL3.value = (currentL2.value && currentL2.value.children && currentL2.value.children[0]) || null
|
||||
loadGoods(true)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function onL1Change(item: any) {
|
||||
if (currentL1.value && item.id === currentL1.value.id) return
|
||||
currentL1.value = item
|
||||
currentL2.value = props.template === 4 ? null : (item.children && item.children[0]) || null
|
||||
currentL3.value = (currentL2.value && currentL2.value.children && currentL2.value.children[0]) || null
|
||||
resetSort()
|
||||
loadGoods(true)
|
||||
}
|
||||
|
||||
function onSideChange(item: any) {
|
||||
if (props.template === 4) {
|
||||
onL1Change(item)
|
||||
return
|
||||
}
|
||||
if (currentL2.value && item.id === currentL2.value.id) return
|
||||
currentL2.value = item
|
||||
currentL3.value = (item.children && item.children[0]) || null
|
||||
loadGoods(true)
|
||||
}
|
||||
|
||||
function onL3Change(item: any) {
|
||||
if (currentL3.value && item.id === currentL3.value.id) return
|
||||
currentL3.value = item
|
||||
loadGoods(true)
|
||||
}
|
||||
|
||||
function onL2Change(item: any) {
|
||||
// 选中一级名称项 = 全部(L2 置空)
|
||||
if (currentL1.value && item.id === currentL1.value.id) {
|
||||
currentL2.value = null
|
||||
} else {
|
||||
currentL2.value = item
|
||||
}
|
||||
loadGoods(true)
|
||||
}
|
||||
|
||||
function onSortChange(payload: { sort: string; order: string }) {
|
||||
sort.value = payload.sort
|
||||
order.value = payload.order
|
||||
loadGoods(true)
|
||||
}
|
||||
|
||||
function resetSort() {
|
||||
sort.value = 'releaseTime'
|
||||
order.value = 'desc'
|
||||
}
|
||||
|
||||
async function loadGoods(reset = false) {
|
||||
if (loading.value || !queryCategoryId.value) return
|
||||
if (reset) {
|
||||
pageNumber.value = 1
|
||||
goodsList.value = []
|
||||
hasMore.value = true
|
||||
}
|
||||
loading.value = true
|
||||
failed.value = false
|
||||
try {
|
||||
const res = await getGoodsList({
|
||||
categoryId: queryCategoryId.value,
|
||||
sort: sort.value,
|
||||
order: order.value,
|
||||
pageNumber: pageNumber.value,
|
||||
pageSize: 10,
|
||||
})
|
||||
const records = (res.data && res.data.result && res.data.result.records) || []
|
||||
goodsList.value.push(...records)
|
||||
hasMore.value = records.length >= 10
|
||||
} catch (e) {
|
||||
failed.value = true
|
||||
uni.showToast({ title: '加载失败,请重试', icon: 'none' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (!hasMore.value || loading.value) return
|
||||
pageNumber.value += 1
|
||||
loadGoods(false)
|
||||
}
|
||||
|
||||
function onAddCart(item: any) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/product/goods?id=' + item.id + '&goodsId=' + item.goodsId,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tpl-goods {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tpl-main {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tpl-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
}
|
||||
.goods-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 0;
|
||||
}
|
||||
.empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 60rpx 0 100rpx;
|
||||
}
|
||||
.retry {
|
||||
margin-top: 24rpx;
|
||||
padding: 12rpx 50rpx;
|
||||
border: 1rpx solid $light-color;
|
||||
color: $light-color;
|
||||
border-radius: 36rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -10,40 +10,30 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// 购物车 cart 消息 msg 订单 order 查询 search
|
||||
type: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
},
|
||||
isBtn:{
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
title:{
|
||||
type: String,
|
||||
default: '没有相关内容'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
src:''
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.src ='/static/default/default_'+ this.type + '.png';
|
||||
},
|
||||
methods: {
|
||||
toHome() {
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: string
|
||||
isBtn?: boolean
|
||||
title?: string
|
||||
}>(), {
|
||||
type: 'search',
|
||||
isBtn: false,
|
||||
title: '没有相关内容'
|
||||
})
|
||||
|
||||
const src = ref('')
|
||||
|
||||
onMounted(() => {
|
||||
src.value = `/static/default/default_${props.type}.png`
|
||||
})
|
||||
|
||||
const toHome = () => {
|
||||
uni.switchTab({
|
||||
url: '/pages/home/home'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
459
components/distribution-goods-share/index.vue
Normal file
459
components/distribution-goods-share/index.vue
Normal file
@@ -0,0 +1,459 @@
|
||||
<template>
|
||||
<view class="share-panel" :style="themeStyle">
|
||||
<view class="tabs">
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'text' }"
|
||||
@click="activeTab = 'text'"
|
||||
>
|
||||
<text>推广图文</text>
|
||||
<view v-if="activeTab === 'text'" class="tab-line"></view>
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'poster' }"
|
||||
@click="activeTab = 'poster'"
|
||||
>
|
||||
<text>生成海报</text>
|
||||
<view v-if="activeTab === 'poster'" class="tab-line"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-wrap">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="activeTab === 'text'" class="content">
|
||||
<view class="text-card">
|
||||
<text v-if="shareContext.defaultShareText" class="share-text" :user-select="true">
|
||||
{{ shareContext.defaultShareText }}
|
||||
</text>
|
||||
<view class="share-divider"></view>
|
||||
<text class="share-text" :user-select="true">{{ shareBodyText }}</text>
|
||||
<view class="share-divider"></view>
|
||||
</view>
|
||||
|
||||
<view class="media-section">
|
||||
<scroll-view scroll-x class="image-scroll" v-if="images.length">
|
||||
<view class="image-list">
|
||||
<image
|
||||
v-for="(img, index) in images"
|
||||
:key="index"
|
||||
class="goods-image"
|
||||
:src="img"
|
||||
mode="aspectFill"
|
||||
@click="previewImage(index)"
|
||||
/>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="save-btn-wrap">
|
||||
<view class="save-btn" @click="saveShareContent">保存图文</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-else class="content">
|
||||
<poster-panel
|
||||
:images="images"
|
||||
:goods-name="shareContext.goodsName"
|
||||
:goods-desc="posterGoodsDesc"
|
||||
:price="shareContext.price"
|
||||
:sku-id="skuId"
|
||||
:goods-id="goodsId"
|
||||
:distribution-id="distributionId"
|
||||
:active="activeTab === 'poster'"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import config from '@/config/config'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import { getDistributionGoodsShareContext } from '@/api/distribution'
|
||||
import { unitPrice, parseGoodsImageUrl } from '@/utils/filters.js'
|
||||
import PosterPanel from './poster-panel.vue'
|
||||
// #ifdef H5
|
||||
import { h5Copy } from '@/js_sdk/h5-copy/h5-copy.js'
|
||||
// #endif
|
||||
|
||||
const props = defineProps<{
|
||||
skuId?: string
|
||||
goodsId?: string
|
||||
distributionId?: string
|
||||
}>()
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const activeTab = ref<'text' | 'poster'>('text')
|
||||
const loading = ref(true)
|
||||
const shareContext = ref<Record<string, any>>({})
|
||||
|
||||
const posterGoodsDesc = computed(() => {
|
||||
const ctx = shareContext.value
|
||||
return ctx.goodsSellingPoint || ctx.defaultShareText || ''
|
||||
})
|
||||
|
||||
const images = computed(() => {
|
||||
const raw: string[] = []
|
||||
const list = shareContext.value.images
|
||||
if (Array.isArray(list) && list.length) {
|
||||
raw.push(...list)
|
||||
} else if (shareContext.value.thumbnail) {
|
||||
raw.push(shareContext.value.thumbnail)
|
||||
}
|
||||
const parsed = raw
|
||||
.map((item) => parseGoodsImageUrl(item))
|
||||
.filter((url) => !!url)
|
||||
return [...new Set(parsed)]
|
||||
})
|
||||
|
||||
const shareLink = computed(() => {
|
||||
const link = shareContext.value.miniProgramLink || shareContext.value.sharePath || ''
|
||||
if (!link) return ''
|
||||
if (link.startsWith('http')) return link
|
||||
const base = (config.shareLink || '').replace(/\/$/, '')
|
||||
return base + link
|
||||
})
|
||||
|
||||
const SHARE_DIVIDER = '- '.repeat(36).trim()
|
||||
|
||||
const shareBodyText = computed(() => {
|
||||
const ctx = shareContext.value
|
||||
const lines: string[] = []
|
||||
if (ctx.goodsName) {
|
||||
lines.push(`🔴 ${ctx.goodsName}`)
|
||||
}
|
||||
if (ctx.price !== null && ctx.price !== undefined) {
|
||||
lines.push(`【到手价】💰 ${unitPrice(ctx.price, '¥')}元`)
|
||||
}
|
||||
if (shareLink.value) {
|
||||
lines.push(`点击立即参与:${shareLink.value}`)
|
||||
}
|
||||
return lines.join('\n')
|
||||
})
|
||||
|
||||
const shareText = computed(() => {
|
||||
const ctx = shareContext.value
|
||||
const lines: string[] = []
|
||||
if (ctx.defaultShareText) {
|
||||
lines.push(ctx.defaultShareText)
|
||||
lines.push('')
|
||||
}
|
||||
lines.push(SHARE_DIVIDER)
|
||||
if (shareBodyText.value) {
|
||||
lines.push(shareBodyText.value)
|
||||
}
|
||||
lines.push(SHARE_DIVIDER)
|
||||
return lines.join('\n')
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [props.skuId, props.goodsId, props.distributionId],
|
||||
() => {
|
||||
loadShareContext()
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function loadShareContext() {
|
||||
const { skuId, goodsId, distributionId } = props
|
||||
if (!skuId || !goodsId || !distributionId) {
|
||||
loading.value = false
|
||||
shareContext.value = {}
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
getDistributionGoodsShareContext({ skuId, goodsId, distributionId })
|
||||
.then((res) => {
|
||||
if (res.data?.success) {
|
||||
shareContext.value = res.data.result || {}
|
||||
} else {
|
||||
uni.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uni.showToast({ title: '加载分享内容失败', icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function previewImage(index: number) {
|
||||
if (!images.value.length) return
|
||||
uni.previewImage({
|
||||
current: images.value[index],
|
||||
urls: images.value,
|
||||
})
|
||||
}
|
||||
|
||||
function isClipboardBlocked(errMsg = '') {
|
||||
return (
|
||||
errMsg.includes('privacy agreement') ||
|
||||
errMsg.includes('no permission') ||
|
||||
errMsg.includes('auth deny')
|
||||
)
|
||||
}
|
||||
|
||||
function saveShareContent() {
|
||||
const text = shareText.value
|
||||
if (!text.trim()) {
|
||||
uni.showToast({ title: '暂无可保存内容', icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
// #ifdef H5
|
||||
if (h5Copy(text) === false) {
|
||||
uni.showToast({ title: '复制失败,请重试', icon: 'none' })
|
||||
return
|
||||
}
|
||||
continueAfterCopy()
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
const runCopy = () => {
|
||||
uni.setClipboardData({
|
||||
data: text,
|
||||
showToast: false,
|
||||
success: () => continueAfterCopy(),
|
||||
fail: (err: any) => {
|
||||
console.error('setClipboardData fail', err)
|
||||
const errMsg = err?.errMsg || ''
|
||||
if (isClipboardBlocked(errMsg)) {
|
||||
continueAfterCopy()
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '复制失败,请重试', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const wxApi = (globalThis as any).wx
|
||||
if (wxApi?.requirePrivacyAuthorize) {
|
||||
wxApi.requirePrivacyAuthorize({
|
||||
success: () => runCopy(),
|
||||
fail: () => continueAfterCopy(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
runCopy()
|
||||
// #endif
|
||||
}
|
||||
|
||||
function continueAfterCopy() {
|
||||
const imageUrls = images.value
|
||||
if (!imageUrls.length) {
|
||||
uni.showToast({ title: '内容已复制', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showLoading({ title: '保存中', mask: true })
|
||||
saveImages(imageUrls)
|
||||
.then(() => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '内容已复制', icon: 'none' })
|
||||
})
|
||||
.catch((error) => {
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '图片保存失败', icon: 'none' })
|
||||
console.error('save images failed', error)
|
||||
})
|
||||
}
|
||||
|
||||
function saveToAlbum(filePath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: () => resolve(),
|
||||
fail: (err: any) => {
|
||||
const errMsg = err?.errMsg || ''
|
||||
const needAuth =
|
||||
errMsg.includes('auth deny') ||
|
||||
errMsg.includes('authorize') ||
|
||||
errMsg.includes('permission')
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!needAuth) {
|
||||
reject(new Error('save failed'))
|
||||
return
|
||||
}
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要您授权保存图片到相册',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (!modalRes.confirm) {
|
||||
reject(new Error('auth denied'))
|
||||
return
|
||||
}
|
||||
uni.openSetting({
|
||||
success: (settingRes) => {
|
||||
if (settingRes.authSetting['scope.writePhotosAlbum']) {
|
||||
saveToAlbum(filePath).then(resolve).catch(reject)
|
||||
} else {
|
||||
reject(new Error('auth denied'))
|
||||
}
|
||||
},
|
||||
fail: () => reject(new Error('auth denied')),
|
||||
})
|
||||
},
|
||||
fail: () => reject(new Error('auth denied')),
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
reject(new Error('save failed'))
|
||||
// #endif
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function saveImages(urls: string[]) {
|
||||
return urls.reduce<Promise<void>>((chain, url) => {
|
||||
return chain.then(() => downloadAndSaveImage(url))
|
||||
}, Promise.resolve())
|
||||
}
|
||||
|
||||
function downloadAndSaveImage(url: string) {
|
||||
if (!url || !/^https?:\/\//.test(url)) {
|
||||
return Promise.reject(new Error('invalid url'))
|
||||
}
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success: (res) => {
|
||||
if (res.statusCode !== 200 || !res.tempFilePath) {
|
||||
reject(new Error('download failed'))
|
||||
return
|
||||
}
|
||||
saveToAlbum(res.tempFilePath).then(resolve).catch(reject)
|
||||
},
|
||||
fail: () => reject(new Error('download failed')),
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.share-panel {
|
||||
min-height: 100%;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24rpx 32rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding-bottom: 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #999;
|
||||
|
||||
&.active {
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-line {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translateX(-50%);
|
||||
width: 56rpx;
|
||||
height: 6rpx;
|
||||
border-radius: 6rpx;
|
||||
background: #ff8f3f;
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24rpx 24rpx 48rpx;
|
||||
}
|
||||
|
||||
.text-card {
|
||||
background: #f7f7f7;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
}
|
||||
|
||||
.share-divider {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
margin: 20rpx 0;
|
||||
border-top: 2rpx dashed #c8c8c8;
|
||||
}
|
||||
|
||||
.share-text {
|
||||
font-size: 28rpx;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
.media-section {
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.image-scroll {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.goods-image {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.save-btn-wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
min-width: 140rpx;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 30rpx;
|
||||
background: linear-gradient(90deg, #ff9f3f, #ff6b35);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
font-weight: 400;
|
||||
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.2);
|
||||
}
|
||||
</style>
|
||||
556
components/distribution-goods-share/poster-panel.vue
Normal file
556
components/distribution-goods-share/poster-panel.vue
Normal file
@@ -0,0 +1,556 @@
|
||||
<template>
|
||||
<view class="poster-panel">
|
||||
<view v-if="posterList.length" class="poster-hint">点击海报预览后可保存到相册</view>
|
||||
|
||||
<view v-if="loading" class="loading-wrap">
|
||||
<text class="loading-text">海报生成中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else-if="loadError" class="loading-wrap">
|
||||
<text class="loading-text">{{ loadError }}</text>
|
||||
<view class="retry-btn" @click="generatePosters">重新生成</view>
|
||||
</view>
|
||||
|
||||
<view v-else-if="!images.length" class="loading-wrap">
|
||||
<text class="loading-text">暂无商品图片</text>
|
||||
</view>
|
||||
|
||||
<template v-else-if="posterList.length">
|
||||
<view class="poster-content">
|
||||
<swiper
|
||||
class="poster-swiper"
|
||||
:current="current"
|
||||
circular
|
||||
@change="onSwiperChange"
|
||||
>
|
||||
<swiper-item v-for="(item, index) in posterList" :key="index">
|
||||
<view class="poster-card-wrap" @click="previewPoster(index)">
|
||||
<image class="poster-image" :src="item" mode="widthFix" />
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
<view v-if="posterList.length > 1" class="poster-dots">
|
||||
<view
|
||||
v-for="(_, index) in posterList"
|
||||
:key="index"
|
||||
:class="['dot', current === index && 'active']"
|
||||
/>
|
||||
</view>
|
||||
<view class="save-btn-wrap">
|
||||
<view class="save-btn" @click="saveCurrentPoster">保存海报</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="canvas-hide">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<canvas id="distributionPosterCanvas" type="2d" style="width: 600px; height: 840px" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<canvas
|
||||
canvas-id="distributionPosterCanvas"
|
||||
id="distributionPosterCanvas"
|
||||
style="width: 600px; height: 840px"
|
||||
/>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, getCurrentInstance, nextTick } from 'vue'
|
||||
import DrawPoster from '@/js_sdk/u-draw-poster'
|
||||
import { getDistributionGoodsPosterContext } from '@/api/distribution'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
|
||||
const CANVAS_SELECTOR = 'distributionPosterCanvas'
|
||||
const POSTER_WIDTH = 600
|
||||
const IMAGE_HEIGHT = POSTER_WIDTH
|
||||
const BOTTOM_HEIGHT = 240
|
||||
const POSTER_HEIGHT = IMAGE_HEIGHT + BOTTOM_HEIGHT
|
||||
const QR_SIZE = 120
|
||||
const QR_MARGIN_RIGHT = 28
|
||||
const QR_MARGIN_BOTTOM = 28
|
||||
const GENERATE_TIMEOUT = 45000
|
||||
|
||||
const props = defineProps<{
|
||||
images?: string[]
|
||||
goodsName?: string
|
||||
goodsDesc?: string
|
||||
price?: number | null
|
||||
skuId?: string
|
||||
goodsId?: string
|
||||
distributionId?: string
|
||||
active?: boolean
|
||||
}>()
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
const posterList = ref<string[]>([])
|
||||
const current = ref(0)
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
const hasGenerated = ref(false)
|
||||
let dp: any = null
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
const st2 = (size: number) => size * 2
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
const st2 = (size: number) => size
|
||||
// #endif
|
||||
|
||||
watch(
|
||||
() => [props.images?.length, props.skuId, props.goodsId, props.distributionId],
|
||||
() => {
|
||||
resetPosters()
|
||||
if (props.active && props.images?.length) {
|
||||
generatePosters()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(active) => {
|
||||
if (active && !hasGenerated.value && props.images?.length && !loading.value) {
|
||||
generatePosters()
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
function resetPosters() {
|
||||
hasGenerated.value = false
|
||||
posterList.value = []
|
||||
loadError.value = ''
|
||||
clearDrawPosterCache()
|
||||
}
|
||||
|
||||
function clearDrawPosterCache() {
|
||||
const pages = getCurrentPages()
|
||||
const page = pages[pages.length - 1] as any
|
||||
if (!page) return
|
||||
delete page[`#${CANVAS_SELECTOR}__dp`]
|
||||
delete page[`${CANVAS_SELECTOR}__dp`]
|
||||
}
|
||||
|
||||
function onSwiperChange(e: any) {
|
||||
current.value = e.detail?.current ?? 0
|
||||
}
|
||||
|
||||
function previewPoster(index: number) {
|
||||
if (!posterList.value.length) return
|
||||
uni.previewImage({
|
||||
current: posterList.value[index],
|
||||
urls: posterList.value,
|
||||
})
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
function withTimeout<T>(promise: Promise<T>, ms: number, message: string) {
|
||||
return Promise.race([
|
||||
promise,
|
||||
new Promise<T>((_, reject) => {
|
||||
setTimeout(() => reject(new Error(message)), ms)
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
function getComponentThis() {
|
||||
return instance?.proxy || instance
|
||||
}
|
||||
|
||||
async function waitCanvasReady() {
|
||||
await nextTick()
|
||||
await sleep(150)
|
||||
}
|
||||
|
||||
async function resolveImagePath(url: string) {
|
||||
if (!url) {
|
||||
throw new Error('商品图片地址无效')
|
||||
}
|
||||
if (
|
||||
url.startsWith('data:') ||
|
||||
url.startsWith('wxfile://') ||
|
||||
url.startsWith('http://tmp/') ||
|
||||
url.startsWith('https://tmp/')
|
||||
) {
|
||||
return url
|
||||
}
|
||||
const res = await uni.downloadFile({ url })
|
||||
if (res.statusCode === 200 && res.tempFilePath) {
|
||||
return res.tempFilePath
|
||||
}
|
||||
throw new Error('商品图片下载失败,请检查图片域名配置')
|
||||
}
|
||||
|
||||
function getImageInfo(src: string): Promise<{ width: number; height: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.getImageInfo({
|
||||
src,
|
||||
success: (res) => resolve({ width: res.width, height: res.height }),
|
||||
fail: reject,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function calcContainBox(
|
||||
imgW: number,
|
||||
imgH: number,
|
||||
boxX: number,
|
||||
boxY: number,
|
||||
boxW: number,
|
||||
boxH: number
|
||||
) {
|
||||
const scale = Math.min(boxW / imgW, boxH / imgH)
|
||||
const width = imgW * scale
|
||||
const height = imgH * scale
|
||||
return {
|
||||
x: boxX + (boxW - width) / 2,
|
||||
y: boxY + (boxH - height) / 2,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
}
|
||||
|
||||
async function generatePosters() {
|
||||
const { skuId, goodsId, distributionId, images } = props
|
||||
if (!skuId || !goodsId || !distributionId || !images?.length) {
|
||||
return
|
||||
}
|
||||
if (loading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
{
|
||||
loadError.value = '请在微信小程序中生成海报'
|
||||
return
|
||||
}
|
||||
// #endif
|
||||
|
||||
loading.value = true
|
||||
loadError.value = ''
|
||||
posterList.value = []
|
||||
clearDrawPosterCache()
|
||||
|
||||
try {
|
||||
await withTimeout(runGeneratePosters(images, skuId, goodsId, distributionId), GENERATE_TIMEOUT, '海报生成超时,请重试')
|
||||
hasGenerated.value = true
|
||||
} catch (error: any) {
|
||||
console.error('generate posters failed', error)
|
||||
loadError.value = error?.message || '海报生成失败,请稍后重试'
|
||||
clearDrawPosterCache()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function runGeneratePosters(
|
||||
images: string[],
|
||||
skuId: string,
|
||||
goodsId: string,
|
||||
distributionId: string
|
||||
) {
|
||||
const posterRes = await getDistributionGoodsPosterContext({ skuId, goodsId, distributionId })
|
||||
if (!posterRes.data?.success || !posterRes.data?.result?.qrCode) {
|
||||
throw new Error(posterRes.data?.message || '葵花码生成失败')
|
||||
}
|
||||
const qrCode = `data:image/png;base64,${posterRes.data.result.qrCode}`
|
||||
|
||||
await waitCanvasReady()
|
||||
|
||||
dp = await DrawPoster.build({
|
||||
selector: CANVAS_SELECTOR,
|
||||
componentThis: getComponentThis(),
|
||||
loading: false,
|
||||
debugging: false,
|
||||
})
|
||||
|
||||
if (!dp?.canvas || !dp?.ctx) {
|
||||
throw new Error('画布初始化失败,请重试')
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
dp.canvas.width = st2(POSTER_WIDTH)
|
||||
dp.canvas.height = st2(POSTER_HEIGHT)
|
||||
// #endif
|
||||
|
||||
const results: string[] = []
|
||||
for (const image of images) {
|
||||
const localImage = await resolveImagePath(image)
|
||||
results.push(await drawPoster(dp, localImage, qrCode))
|
||||
}
|
||||
posterList.value = results
|
||||
}
|
||||
|
||||
async function drawPoster(dpInst: any, productImg: string, qrCode: string) {
|
||||
const goodsName = props.goodsName || '商品推荐'
|
||||
const goodsDesc = resolveGoodsDesc()
|
||||
const priceText =
|
||||
props.price !== null && props.price !== undefined ? unitPrice(props.price, '¥') : ''
|
||||
|
||||
await dpInst.draw((ctx: any) => {
|
||||
ctx.clearRect(st2(0), st2(0), st2(POSTER_WIDTH), st2(POSTER_HEIGHT))
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.fillRoundRect(st2(0), st2(0), st2(POSTER_WIDTH), st2(POSTER_HEIGHT), st2(20))
|
||||
ctx.clip()
|
||||
})
|
||||
|
||||
await dpInst.draw(async (ctx: any) => {
|
||||
ctx.fillStyle = '#f7f7f7'
|
||||
ctx.fillRect(st2(0), st2(0), st2(POSTER_WIDTH), st2(IMAGE_HEIGHT))
|
||||
const { width: imgW, height: imgH } = await getImageInfo(productImg)
|
||||
const fit = calcContainBox(imgW, imgH, 0, 0, POSTER_WIDTH, IMAGE_HEIGHT)
|
||||
await ctx.drawImage(
|
||||
productImg,
|
||||
st2(fit.x),
|
||||
st2(fit.y),
|
||||
st2(fit.width),
|
||||
st2(fit.height)
|
||||
)
|
||||
})
|
||||
|
||||
await dpInst.draw(async (ctx: any) => {
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.fillRect(st2(0), st2(IMAGE_HEIGHT), st2(POSTER_WIDTH), st2(POSTER_HEIGHT - IMAGE_HEIGHT))
|
||||
|
||||
const qrX = POSTER_WIDTH - QR_SIZE - QR_MARGIN_RIGHT
|
||||
const qrY = POSTER_HEIGHT - QR_SIZE - QR_MARGIN_BOTTOM
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(
|
||||
st2(qrX + QR_SIZE / 2),
|
||||
st2(qrY + QR_SIZE / 2),
|
||||
st2(QR_SIZE / 2),
|
||||
0,
|
||||
Math.PI * 2
|
||||
)
|
||||
ctx.clip()
|
||||
await ctx.drawImage(qrCode, st2(qrX), st2(qrY), st2(QR_SIZE), st2(QR_SIZE))
|
||||
ctx.restore()
|
||||
})
|
||||
|
||||
await dpInst.draw((ctx: any) => {
|
||||
const textLeft = 24
|
||||
const textMaxWidth = POSTER_WIDTH - QR_SIZE - QR_MARGIN_RIGHT - textLeft - 20
|
||||
const bottomTop = IMAGE_HEIGHT
|
||||
const bottomBottom = POSTER_HEIGHT - 16
|
||||
const qrY = POSTER_HEIGHT - QR_SIZE - QR_MARGIN_BOTTOM
|
||||
|
||||
const nameLineHeight = 36
|
||||
const descLineHeight = 28
|
||||
const priceLineHeight = 36
|
||||
const textGap = 16
|
||||
const nameLineCount = Math.min(2, Math.max(1, Math.ceil(goodsName.length / 16)))
|
||||
const descLineCount = goodsDesc ? Math.min(2, Math.max(1, Math.ceil(goodsDesc.length / 14))) : 0
|
||||
|
||||
let textBlockHeight = nameLineCount * nameLineHeight
|
||||
if (goodsDesc) {
|
||||
textBlockHeight += textGap + descLineCount * descLineHeight
|
||||
}
|
||||
if (priceText) {
|
||||
textBlockHeight += textGap + priceLineHeight
|
||||
}
|
||||
|
||||
const maxTextHeight = bottomBottom - bottomTop - 12
|
||||
if (textBlockHeight > maxTextHeight) {
|
||||
textBlockHeight = maxTextHeight
|
||||
}
|
||||
|
||||
let textY = qrY + (QR_SIZE - textBlockHeight) / 2
|
||||
textY = Math.max(bottomTop + 12, textY)
|
||||
if (textY + textBlockHeight > bottomBottom) {
|
||||
textY = bottomBottom - textBlockHeight
|
||||
}
|
||||
|
||||
ctx.textAlign = 'left'
|
||||
ctx.textBaseline = 'top'
|
||||
ctx.fillStyle = '#333333'
|
||||
ctx.font = `${st2(28)}px PingFang SC`
|
||||
ctx.fillWarpText({
|
||||
text: goodsName,
|
||||
maxWidth: st2(textMaxWidth),
|
||||
x: st2(textLeft),
|
||||
y: st2(textY),
|
||||
layer: 2,
|
||||
lineHeight: st2(nameLineHeight),
|
||||
})
|
||||
textY += nameLineCount * nameLineHeight
|
||||
if (goodsDesc) {
|
||||
textY += textGap
|
||||
ctx.fillStyle = '#999999'
|
||||
ctx.font = `${st2(22)}px PingFang SC`
|
||||
ctx.fillWarpText({
|
||||
text: goodsDesc,
|
||||
maxWidth: st2(textMaxWidth),
|
||||
x: st2(textLeft),
|
||||
y: st2(textY),
|
||||
layer: 2,
|
||||
lineHeight: st2(descLineHeight),
|
||||
})
|
||||
textY += descLineCount * descLineHeight
|
||||
}
|
||||
|
||||
if (priceText) {
|
||||
textY += textGap
|
||||
const priceY = Math.min(textY, bottomBottom - priceLineHeight)
|
||||
ctx.fillStyle = '#ff3c2a'
|
||||
ctx.font = `bold ${st2(36)}px PingFang SC`
|
||||
ctx.fillText(priceText, st2(textLeft), st2(priceY))
|
||||
}
|
||||
})
|
||||
|
||||
const path = await dpInst.createImagePath()
|
||||
if (!path || path === '---stop createImagePath---') {
|
||||
throw new Error('海报图片导出失败')
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
function resolveGoodsDesc() {
|
||||
const desc = (props.goodsDesc || '').trim()
|
||||
const name = (props.goodsName || '').trim()
|
||||
if (!desc || desc === name) return ''
|
||||
return desc.length > 28 ? `${desc.slice(0, 28)}...` : desc
|
||||
}
|
||||
|
||||
function saveCurrentPoster() {
|
||||
const filePath = posterList.value[current.value]
|
||||
if (!filePath) return
|
||||
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath,
|
||||
success: () => {
|
||||
uni.showToast({ title: '已保存到相册', icon: 'none' })
|
||||
},
|
||||
fail: (err: any) => {
|
||||
const errMsg = err?.errMsg || ''
|
||||
if (errMsg.includes('auth deny') || errMsg.includes('authorize')) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '需要您授权保存图片到相册',
|
||||
confirmText: '去设置',
|
||||
success: (modalRes) => {
|
||||
if (modalRes.confirm) {
|
||||
uni.openSetting({})
|
||||
}
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.showToast({ title: '保存失败', icon: 'none' })
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.poster-panel {
|
||||
padding: 8rpx 0 48rpx;
|
||||
}
|
||||
|
||||
.poster-hint {
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
color: #b3b3b3;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.poster-content {
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 80rpx 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
display: block;
|
||||
width: 100%;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.retry-btn,
|
||||
.save-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 64rpx;
|
||||
padding: 0 40rpx;
|
||||
border-radius: 32rpx;
|
||||
background: linear-gradient(90deg, #ff9f3f, #ff6b35);
|
||||
color: #fff;
|
||||
font-size: 26rpx;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.poster-swiper {
|
||||
height: 1000rpx;
|
||||
}
|
||||
|
||||
.poster-card-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
padding: 0 48rpx;
|
||||
}
|
||||
|
||||
.poster-image {
|
||||
width: 100%;
|
||||
max-width: 620rpx;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.poster-dots {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin-top: 24rpx;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
background: #e0e0e0;
|
||||
|
||||
&.active {
|
||||
width: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
background: #ff8f3f;
|
||||
}
|
||||
}
|
||||
|
||||
.save-btn-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.canvas-hide {
|
||||
position: fixed;
|
||||
left: -9999px;
|
||||
top: 0;
|
||||
width: 600px;
|
||||
height: 840px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
@@ -13,71 +13,62 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import config from "@/config/config";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config, // 设置工具类
|
||||
weChat: false, // 是否微信浏览器,该项为true时不显示 当前整个页面
|
||||
logo: require("@/icon.png"), //显示的圆形logo
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef H5
|
||||
// 判断是否是微信浏览器
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isWeixin = ua.indexOf("micromessenger") != -1;
|
||||
if (isWeixin) {
|
||||
this.weChat = true;
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import config from "@/config/config"
|
||||
import logoImg from "@/icon.png"
|
||||
|
||||
const weChat = ref(false)
|
||||
const logo = logoImg
|
||||
|
||||
onMounted(() => {
|
||||
// #ifdef H5
|
||||
// 判断是否是微信浏览器
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
var isWeixin = ua.indexOf("micromessenger") != -1;
|
||||
weChat.value = isWeixin ? true : false
|
||||
// #endif
|
||||
})
|
||||
|
||||
/**
|
||||
* 跳转到下载app页面
|
||||
*/
|
||||
const downloadApp = () => {
|
||||
setTimeout(function () {
|
||||
window.location.href = config.downloadLink;
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开app 仅在h5生效 使用ifream唤醒app
|
||||
*/
|
||||
const openApp = () => {
|
||||
let src: string = ''
|
||||
if (location.href) {
|
||||
src = location.href.split("/pages")[1];
|
||||
}
|
||||
let t = `${config.schemeLink}pages${src}`
|
||||
|
||||
try {
|
||||
var e = navigator.userAgent.toLowerCase(),
|
||||
n = e.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (
|
||||
((n = null !== n ? n[1].replace(/_/g, ".") : 0), parseInt(n) >= 9)
|
||||
) {
|
||||
window.location.href = t
|
||||
|
||||
downloadApp()
|
||||
} else {
|
||||
this.weChat = false;
|
||||
var r = document.createElement("iframe");
|
||||
(r.src = t), (r.style.display = "none"), document.body.appendChild(r)
|
||||
|
||||
downloadApp()
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 跳转到下载app页面
|
||||
*/
|
||||
downloadApp() {
|
||||
setTimeout(function () {
|
||||
window.location.href = config.downloadLink;
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开app 仅在h5生效 使用ifream唤醒app
|
||||
*/
|
||||
openApp() {
|
||||
let src;
|
||||
if (location.href) {
|
||||
src = location.href.split("/pages")[1];
|
||||
}
|
||||
let t = `${config.schemeLink}pages${src}`;
|
||||
|
||||
try {
|
||||
var e = navigator.userAgent.toLowerCase(),
|
||||
n = e.match(/cpu iphone os (.*?) like mac os/);
|
||||
if (
|
||||
((n = null !== n ? n[1].replace(/_/g, ".") : 0), parseInt(n) >= 9)
|
||||
) {
|
||||
window.location.href = t;
|
||||
|
||||
this.downloadApp();
|
||||
} else {
|
||||
var r = document.createElement("iframe");
|
||||
(r.src = t), (r.style.display = "none"), document.body.appendChild(r);
|
||||
|
||||
this.downloadApp();
|
||||
}
|
||||
} catch (e) {
|
||||
window.location.href = t;
|
||||
this.downloadApp();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
window.location.href = t
|
||||
downloadApp()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,475 +1,572 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<u-popup class="popup" v-model="buyMask" :height="setup.height" closeable :mode="setup.mode"
|
||||
:border-radius="setup.radius" @close="closeMask()">
|
||||
<!-- 商品 -->
|
||||
<view class="goods-box bottom">
|
||||
<view class="goods-header">
|
||||
<view class="goods-img">
|
||||
<u-image width="200rpx" border-radius="20" class="uimage" height="200rpx"
|
||||
:src="selectedSpecImg ? selectedSpecImg : goodsDetail.thumbnail"></u-image>
|
||||
</view>
|
||||
<view class="goods-skus">
|
||||
<div class="wrapper">
|
||||
<u-popup class="popup" :show="buyMask" :height="setup.height" closeable :mode="setup.mode" :border-radius="setup.radius" @close="closeMask()">
|
||||
<!-- 商品 -->
|
||||
<view class="goods-box bottom">
|
||||
<view class="goods-header">
|
||||
<view class="goods-img">
|
||||
<u-image width="200rpx" border-radius="20" class="uimage" height="200rpx" :src="selectedSpecImg ? selectedSpecImg : goodsDetail.thumbnail"></u-image>
|
||||
</view>
|
||||
<view class="goods-skus">
|
||||
<!-- 有活动商品价格 -->
|
||||
<view class="goods-price" v-if="goodsDetail.promotionPrice && ((isGroup && buyType === 'PINTUAN') || !isGroup)">
|
||||
<span v-if="goodsDetail.promotionPrice && !pointDetail">
|
||||
¥
|
||||
<span class="goods-price-promotionShow goods-price-bigshow">{{ goodsFormatPrice(goodsDetail.promotionPrice)[0] }}</span>
|
||||
.{{ goodsFormatPrice(goodsDetail.promotionPrice)[1] }}
|
||||
</span>
|
||||
<span v-if="pointDetail.points">
|
||||
<span class="goods-price-promotionShow goods-price-bigshow">{{ pointDetail.points }}</span>
|
||||
积分
|
||||
</span>
|
||||
<div class="promotion-box">
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{ goodsFormatPrice(goodsDetail.price)[0] }}</span>
|
||||
.{{ goodsFormatPrice(goodsDetail.price)[1] }}
|
||||
</div>
|
||||
</view>
|
||||
<!-- 正常商品的价格 -->
|
||||
<view v-else>
|
||||
<div class="goods-price">
|
||||
<span>
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{ goodsFormatPrice(goodsDetail.price)[0] }}</span>
|
||||
.{{ goodsFormatPrice(goodsDetail.price)[1] }}
|
||||
</span>
|
||||
</div>
|
||||
</view>
|
||||
<view class="goods-check-skus">
|
||||
已选
|
||||
<span class="goods-check-skus-name">
|
||||
{{ selectName }}
|
||||
<span>,{{ num }}个</span>
|
||||
</span>
|
||||
</view>
|
||||
<view class="goods-check-skus" v-if="isAppointmentGoods(goodsDetail?.goodsType)">
|
||||
可预约
|
||||
<span class="goods-check-skus-name">按时段选择</span>
|
||||
</view>
|
||||
<view class="goods-check-skus" v-else>
|
||||
库存
|
||||
<span class="goods-check-skus-name">
|
||||
<span>{{ availableStock }}</span>
|
||||
</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<scroll-view class="goods-skus-box" :scroll-y="true">
|
||||
<!-- 规格 -->
|
||||
<view class="goods-skus-view" :key="specIndex" v-for="(spec, specIndex) in formatList">
|
||||
<view class="skus-view-list">
|
||||
<view class="view-class-title">{{ spec.name }}</view>
|
||||
|
||||
<!-- 有活动商品价格 -->
|
||||
<view class="goods-price " v-if="goodsDetail.promotionPrice">
|
||||
<span v-if="goodsDetail.promotionPrice && !pointDetail">
|
||||
¥
|
||||
<span
|
||||
class="goods-price-promotionShow goods-price-bigshow">{{ formatPrice(goodsDetail.promotionPrice)[0] }}</span>
|
||||
.{{ formatPrice(goodsDetail.promotionPrice)[1] }}
|
||||
</span>
|
||||
<span v-if="pointDetail.points">
|
||||
<span class="goods-price-promotionShow goods-price-bigshow">{{ pointDetail.points }}</span>
|
||||
积分
|
||||
</span>
|
||||
<div class="promotion-box">
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{
|
||||
formatPrice(goodsDetail.price)[0]
|
||||
}}</span>
|
||||
.{{ formatPrice(goodsDetail.price)[1] }}
|
||||
<!-- 正常逻辑 循环出sku -->
|
||||
<view
|
||||
v-if="!parentOrder"
|
||||
:class="{ active: spec_val.value == currentSelected[specIndex] }"
|
||||
class="skus-view-item"
|
||||
v-for="(spec_val, spec_index) in spec.values"
|
||||
:key="spec_index"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>
|
||||
{{ spec_val.value }}
|
||||
</view>
|
||||
|
||||
</div>
|
||||
</view>
|
||||
<!-- 正常商品的价格 -->
|
||||
<view class="goods-price" v-else>
|
||||
<span>
|
||||
¥
|
||||
<span class="goods-price-bigshow">{{
|
||||
formatPrice(goodsDetail.price)[0]
|
||||
}}</span>
|
||||
.{{ formatPrice(goodsDetail.price)[1] }}
|
||||
|
||||
</span>
|
||||
</view>
|
||||
<view class="goods-check-skus">
|
||||
已选
|
||||
<span class="goods-check-skus-name">
|
||||
{{ selectName }}
|
||||
<span>,{{ num }}个</span>
|
||||
</span>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 商品信息 -->
|
||||
<scroll-view class="goods-skus-box" :scroll-y="true">
|
||||
<!-- 规格 -->
|
||||
<view class="goods-skus-view" :key="specIndex" v-for="(spec, specIndex) in formatList">
|
||||
<view class="skus-view-list">
|
||||
<view class="view-class-title">{{ spec.name }}</view>
|
||||
|
||||
|
||||
<!-- 正常逻辑 循环出sku -->
|
||||
<view v-if="!parentOrder" :class="{ active: spec_val.value == currentSelceted[specIndex] }" class="skus-view-item"
|
||||
v-for="(spec_val, spec_index) in spec.values" :key="spec_index"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)">{{ spec_val.value }} </view>
|
||||
|
||||
<!-- 拼团购买,仅筛选出当前拼团类型商品 -->
|
||||
<view v-if="parentOrder && spec_val.skuId == goodsDetail.id" :class="{ active: spec_val.value == currentSelceted[specIndex] }" class="skus-view-item"
|
||||
v-for="(spec_val, spec_index) in spec.values" :key="spec_index"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)">{{ spec_val.value }} </view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 数量 -->
|
||||
<view class="goods-skus-number">
|
||||
<view class="view-class-title">数量</view>
|
||||
<u-number-box :bg-color="numberBox.bgColor" :max="200" :color="numberBox.color"
|
||||
:input-width="numberBox.width" :input-height="numberBox.height" :size="numberBox.size" :min="1"
|
||||
v-model="num">
|
||||
</u-number-box>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 按钮 -->
|
||||
<view class="btns">
|
||||
|
||||
<view class="box-btn card" v-if="buyType != 'PINTUAN' && goodsDetail.goodsType!='VIRTUAL_GOODS'"
|
||||
@click="addToCartOrBuy('cart')">加入购物车</view>
|
||||
<view class="box-btn buy" @click="addToCartOrBuy('buy')">立即购买</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</div>
|
||||
<!-- 拼团购买,仅筛选出当前拼团类型商品 -->
|
||||
<template v-for="(spec_val, spec_index) in spec.values" :key="spec_index">
|
||||
<view
|
||||
v-if="parentOrder && spec_val.skuId == goodsDetail.id"
|
||||
:class="{ active: spec_val.value == currentSelected[specIndex] }"
|
||||
class="skus-view-item"
|
||||
@click="handleClickSpec(spec, specIndex, spec_val)"
|
||||
>
|
||||
{{ spec_val.value }}
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
<div class="soldout" v-if="availableStock === 0">
|
||||
<u-alert type="warning" title="商品已售罄" :description="'当前商品库存为0'"></u-alert>
|
||||
</div>
|
||||
<!-- 数量 -->
|
||||
<view v-if="availableStock !== 0" class="goods-skus-number flex flex-a-c flex-j-sb">
|
||||
<view class="view-class-title">数量</view>
|
||||
<uni-number-box class="uNumber" :min="1" :max="maxBuyNum" :disabled="availableStock === 0" v-model="num"></uni-number-box>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<!-- 按钮 -->
|
||||
<view class="btns" v-if="availableStock !== 0">
|
||||
<view class="box-btn card" v-if="showAddToCart" @click="addToCartOrBuy('cart')">加入购物车</view>
|
||||
<view class="box-btn buy" @click="addToCartOrBuy('buy')">
|
||||
{{ isAppointmentGoods(goodsDetail?.goodsType) ? '立即预约' : '立即购买' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import * as API_trade from "@/api/trade.js";
|
||||
import setup from "./popup";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
setup,
|
||||
num: 1,
|
||||
// 步进器的大小尺寸单位是 rpx
|
||||
numberBox: {
|
||||
width: "50",
|
||||
height: "50",
|
||||
size: "22",
|
||||
color: "#333",
|
||||
bgColor: "#fff",
|
||||
},
|
||||
selectName: "", //选中商品的昵称
|
||||
selectSkuList: "", //选中商铺sku,
|
||||
selectedSpecImg: "", //选中的图片路径
|
||||
buyType: "", //用于存储促销,拼团等活动类型
|
||||
parentOrder: "", //父级拼团活动的数据 - 如果是团员则有数据
|
||||
formatList: [],
|
||||
currentSelceted: [],
|
||||
skuList: "",
|
||||
isClose: false, //是否可以点击遮罩关闭
|
||||
};
|
||||
},
|
||||
props: {
|
||||
buyMask: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
goodsDetail: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
selectedSku: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
goodsSpec: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
addr: {
|
||||
default: "",
|
||||
type: null,
|
||||
},
|
||||
pointDetail:{
|
||||
default: "",
|
||||
type: null,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
buyType: {
|
||||
handler(val) {
|
||||
this.buyType = val;
|
||||
},
|
||||
immediate: true,
|
||||
},
|
||||
selectSkuList: {
|
||||
handler(val, oldval) {
|
||||
this.$emit("changed", val);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import * as API_trade from '@/api/trade.js'
|
||||
import setup from './popup.js'
|
||||
import uniNumberBox from '@/components/uni-number-box.vue'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
import {
|
||||
isECoupon,
|
||||
isVirtualGoods,
|
||||
isAppointmentGoods,
|
||||
getECouponStock,
|
||||
getECouponMaxBuyNum,
|
||||
getAppointmentStock,
|
||||
getAppointmentMaxBuyNum,
|
||||
} from '@/utils/goodsType.js'
|
||||
|
||||
methods: {
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
buyMask?: boolean
|
||||
isGroup?: boolean
|
||||
goodsDetail?: any
|
||||
selectedSku?: any | null
|
||||
goodsSpec?: any | null
|
||||
addr?: any | null
|
||||
pointDetail?: any | null
|
||||
}>(), {
|
||||
buyMask: false,
|
||||
isGroup: false,
|
||||
goodsDetail: () => ({}),
|
||||
selectedSku: '',
|
||||
goodsSpec: '',
|
||||
addr: '',
|
||||
pointDetail: ''
|
||||
})
|
||||
|
||||
// 格式化金钱 1999 --> [1999,00]
|
||||
formatPrice(val) {
|
||||
if (typeof val == "undefined") {
|
||||
return val;
|
||||
}
|
||||
return val.toFixed(2).split(".");
|
||||
},
|
||||
const emit = defineEmits(['closeBuy', 'changed', 'handleClickSku', 'queryCart'])
|
||||
|
||||
closeMask() {
|
||||
this.$emit("closeBuy", false);
|
||||
},
|
||||
const num = ref(1)
|
||||
|
||||
/**点击规格 */
|
||||
handleClickSpec(val, index, specValue) {
|
||||
this.currentSelceted[index] = specValue.value;
|
||||
let selectedSkuId = this.goodsSpec.find((i) => {
|
||||
let matched = true;
|
||||
let specValues = i.specValues.filter((j) => j.specName !== "images");
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== this.currentSelceted[n]) {
|
||||
matched = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i;
|
||||
}
|
||||
});
|
||||
if (selectedSkuId?.skuId) {
|
||||
this.$set(this.currentSelceted, index, specValue.value);
|
||||
this.selectSkuList = {
|
||||
spec: {
|
||||
specName: val.name,
|
||||
specValue: specValue.value,
|
||||
},
|
||||
data: this.goodsDetail,
|
||||
};
|
||||
this.selectName = specValue.value;
|
||||
const selectName = ref('')
|
||||
const selectSkuList = ref<any>('')
|
||||
const selectedSpecImg = ref('')
|
||||
const buyType = ref('')
|
||||
const parentOrder = ref<any>('')
|
||||
const formatList = ref<any[]>([])
|
||||
const currentSelected = ref<string[]>([])
|
||||
const skuList = ref('')
|
||||
|
||||
this.$emit("handleClickSku", {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: this.goodsDetail.goodsId,
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "暂无该商品!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
// E_COUPON 读 SKU quantity;预约商品 quantity 固定 0,库存走时段
|
||||
const availableStock = computed(() => {
|
||||
if (isECoupon(props.goodsDetail?.goodsType)) {
|
||||
return getECouponStock(props.goodsDetail)
|
||||
}
|
||||
if (isAppointmentGoods(props.goodsDetail?.goodsType)) {
|
||||
return getAppointmentStock(props.goodsDetail)
|
||||
}
|
||||
return Number(props.goodsDetail?.quantity) || 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 直接购买
|
||||
*/
|
||||
buy(data) {
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${
|
||||
data.cartType
|
||||
}&addr=${""}&parentOrder=${encodeURIComponent(
|
||||
JSON.stringify(this.parentOrder)
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
const maxBuyNum = computed(() => {
|
||||
if (isECoupon(props.goodsDetail?.goodsType)) {
|
||||
return getECouponMaxBuyNum(props.goodsDetail)
|
||||
}
|
||||
if (isAppointmentGoods(props.goodsDetail?.goodsType)) {
|
||||
return getAppointmentMaxBuyNum(props.goodsDetail)
|
||||
}
|
||||
return 999
|
||||
})
|
||||
|
||||
// 电子卡券/预约商品禁止加购
|
||||
const showAddToCart = computed(() => {
|
||||
return (
|
||||
buyType.value != 'PINTUAN' &&
|
||||
!isVirtualGoods(props.goodsDetail?.goodsType) &&
|
||||
!isECoupon(props.goodsDetail?.goodsType) &&
|
||||
!isAppointmentGoods(props.goodsDetail?.goodsType)
|
||||
)
|
||||
})
|
||||
|
||||
watch(num, (val) => {
|
||||
val == 0 ? num.value = 1 : ''
|
||||
if (val && val > maxBuyNum.value) {
|
||||
num.value = maxBuyNum.value
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => props.isGroup, (val) => {
|
||||
if (val) {
|
||||
buyType.value = 'PINTUAN'
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
watch(selectSkuList, (_val) => {
|
||||
emit('changed', selectSkuList.value)
|
||||
}, { deep: true })
|
||||
|
||||
watch(
|
||||
() => availableStock.value,
|
||||
(val) => {
|
||||
if (val == 0) {
|
||||
uni.showToast({
|
||||
title: '商品已售罄',
|
||||
duration: 2000,
|
||||
icon: 'none',
|
||||
})
|
||||
num.value = 1
|
||||
} else if (num.value > maxBuyNum.value) {
|
||||
num.value = maxBuyNum.value
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* 添加到购物车或购买
|
||||
*/
|
||||
addToCartOrBuy(val) {
|
||||
if (!this.selectSkuList) {
|
||||
uni.showToast({
|
||||
title: "请选择规格商品",
|
||||
icon: "none",
|
||||
});
|
||||
return;
|
||||
}
|
||||
let data = {
|
||||
skuId: this.goodsDetail.id,
|
||||
num: this.num,
|
||||
};
|
||||
|
||||
if (val == "cart") {
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({
|
||||
title: "商品已添加到购物车",
|
||||
icon: "none",
|
||||
});
|
||||
const closeMask = () => {
|
||||
emit('closeBuy', false)
|
||||
}
|
||||
|
||||
this.$emit("queryCart");
|
||||
this.closeMask();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 判断是否拼团商品
|
||||
if (this.buyType) {
|
||||
data.cartType = "PINTUAN";
|
||||
} else if (this.goodsDetail.goodsType == "VIRTUAL_GOODS") {
|
||||
data.cartType = "VIRTUAL";
|
||||
} else {
|
||||
data.cartType = "BUY_NOW";
|
||||
}
|
||||
/**点击规格 */
|
||||
const handleClickSpec = (val: any, index: number, specValue: any) => {
|
||||
currentSelected.value[index] = specValue.value
|
||||
let selectedSkuId = props.goodsSpec.find((i: any) => {
|
||||
let matched = true
|
||||
let specValues = i.specValues.filter((j: any) => j.specName !== 'images')
|
||||
for (let n = 0; n < specValues.length; n++) {
|
||||
if (specValues[n].specValue !== currentSelected.value[n]) {
|
||||
matched = false
|
||||
return
|
||||
}
|
||||
}
|
||||
if (matched) {
|
||||
return i
|
||||
}
|
||||
})
|
||||
if (selectedSkuId?.skuId) {
|
||||
currentSelected.value[index] = specValue.value
|
||||
selectSkuList.value = {
|
||||
spec: {
|
||||
specName: val.name,
|
||||
specValue: specValue.value
|
||||
},
|
||||
data: props.goodsDetail
|
||||
}
|
||||
selectName.value = specValue.value
|
||||
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${
|
||||
this.addr.id || ""
|
||||
}&parentOrder=${encodeURIComponent(
|
||||
JSON.stringify(this.parentOrder)
|
||||
)}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
formatSku(list) {
|
||||
// 格式化数据
|
||||
console.log(list);
|
||||
let arr = [{}];
|
||||
list.forEach((item, index) => {
|
||||
item.specValues.forEach((spec, specIndex) => {
|
||||
let name = spec.specName;
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
skuId: item.skuId,
|
||||
};
|
||||
if (name === "images") {
|
||||
return;
|
||||
}
|
||||
emit('handleClickSku', {
|
||||
skuId: selectedSkuId.skuId,
|
||||
goodsId: props.goodsDetail.goodsId
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '暂无该商品!',
|
||||
duration: 2000,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name == name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i) => {
|
||||
return i.value === values.value;
|
||||
})
|
||||
) {
|
||||
arrItem.values.push(values);
|
||||
}
|
||||
/**
|
||||
* 活动页/父组件直接调用购买(POINTS / KANJIA 等显式 cartType)
|
||||
*/
|
||||
const buy = (data: any) => {
|
||||
uni.hideLoading()
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.success) {
|
||||
const way = data.cartType || 'BUY_NOW'
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${way}&addr=${''}&parentOrder=${encodeURIComponent(JSON.stringify(parentOrder.value || ''))}`,
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message || '购买失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
}).finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
|
||||
let keys = arr.map((key) => {
|
||||
return key.name;
|
||||
});
|
||||
if (!keys.includes(name)) {
|
||||
console.log(name, values);
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
/** 解析 cartType:拼团 PINTUAN;核销 VIRTUAL;其余含 E_COUPON 为 BUY_NOW */
|
||||
function resolveCartType() {
|
||||
if (buyType.value === 'PINTUAN') {
|
||||
return 'PINTUAN'
|
||||
}
|
||||
if (isVirtualGoods(props.goodsDetail?.goodsType)) {
|
||||
return 'VIRTUAL'
|
||||
}
|
||||
return 'BUY_NOW'
|
||||
}
|
||||
|
||||
arr.shift();
|
||||
this.formatList = arr;
|
||||
/**
|
||||
* 添加到购物车或购买
|
||||
*/
|
||||
const addToCartOrBuy = (val: string) => {
|
||||
if (!selectSkuList.value) {
|
||||
uni.showToast({
|
||||
title: '请选择规格商品',
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
const data: Record<string, any> = {
|
||||
skuId: props.goodsDetail.id,
|
||||
num: num.value,
|
||||
}
|
||||
|
||||
list.forEach((item) => {
|
||||
// 默认选中
|
||||
if (item.skuId === this.goodsDetail.id) {
|
||||
item.specValues
|
||||
.filter((i) => i.specName !== "images")
|
||||
.forEach((value, _index) => {
|
||||
this.currentSelceted[_index] = value.specValue;
|
||||
if (val == 'cart') {
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.showToast({
|
||||
title: '商品已添加到购物车',
|
||||
icon: 'none',
|
||||
})
|
||||
emit('queryCart')
|
||||
closeMask()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message || '加购失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
})
|
||||
} else {
|
||||
if (isAppointmentGoods(props.goodsDetail?.goodsType)) {
|
||||
const goodsId = props.goodsDetail.goodsId || ''
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/appointment/appointmentCheckout?skuId=${props.goodsDetail.id}&goodsId=${goodsId}&num=${num.value}`,
|
||||
})
|
||||
closeMask()
|
||||
return
|
||||
}
|
||||
data.cartType = resolveCartType()
|
||||
API_trade.addToCart(data).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/order/fillorder?way=${data.cartType}&addr=${props.addr?.id || ''}&parentOrder=${encodeURIComponent(JSON.stringify(parentOrder.value || ''))}`,
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message || '购买失败',
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.selectName = value.specValue;
|
||||
const formatSku = (list: any[]) => {
|
||||
// 格式化数据
|
||||
let arr: any[] = [{}]
|
||||
|
||||
this.selectSkuList = {
|
||||
spec: value,
|
||||
data: this.goodsDetail,
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!Array.isArray(list)) {
|
||||
return false
|
||||
}
|
||||
list.forEach((_item, _index) => {
|
||||
let item = list[_index]
|
||||
item.specValues.forEach((spec: any, specIndex: number) => {
|
||||
let name = spec.specName
|
||||
let values = {
|
||||
value: spec.specValue,
|
||||
quantity: item.quantity,
|
||||
skuId: item.skuId
|
||||
}
|
||||
if (name === 'images') {
|
||||
return
|
||||
}
|
||||
|
||||
this.skuList = list;
|
||||
// console.log(" this.skuList", this.skuList)
|
||||
},
|
||||
},
|
||||
arr.forEach((arrItem, arrIndex) => {
|
||||
if (
|
||||
arrItem.name == name &&
|
||||
arrItem.values &&
|
||||
!arrItem.values.find((i: any) => {
|
||||
return i.value === values.value
|
||||
})
|
||||
) {
|
||||
arrItem.values.push(values)
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.formatSku(this.goodsSpec);
|
||||
},
|
||||
};
|
||||
let keys = arr.map(key => {
|
||||
return key.name
|
||||
})
|
||||
if (!keys.includes(name)) {
|
||||
arr.push({
|
||||
name: name,
|
||||
values: [values]
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
arr.shift()
|
||||
formatList.value = arr
|
||||
|
||||
list.forEach(item => {
|
||||
// 默认选中
|
||||
if (item.skuId === props.goodsDetail.id) {
|
||||
item.specValues
|
||||
.filter(i => i.specName !== 'images')
|
||||
.forEach((value, _index) => {
|
||||
currentSelected.value[_index] = value.specValue
|
||||
|
||||
selectName.value = value.specValue
|
||||
|
||||
selectSkuList.value = {
|
||||
spec: value,
|
||||
data: props.goodsDetail
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
skuList.value = list
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
formatSku(props.goodsSpec)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
buy,
|
||||
buyType,
|
||||
parentOrder,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./popup.scss";
|
||||
@import './popup.scss';
|
||||
|
||||
.price-row {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.buy {
|
||||
background-image: linear-gradient(135deg, #ffba0d, #ffc30d 69%, #ffcf0d);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
background-image: linear-gradient(135deg, $main-color, $light-color);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-image: linear-gradient(135deg, #f2140c, #f2270c 70%, #f24d0c);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
background-image: linear-gradient(91deg, $light-color 1%, $aider-light-color 99%);
|
||||
box-shadow: 0 2px 6px 0 rgba(255, 65, 66, 0.2);
|
||||
}
|
||||
|
||||
/deep/.u-icon-plus,
|
||||
.u-icon-minus,
|
||||
.u-icon-disabled {
|
||||
height: 30rpx !important;
|
||||
background: #fff !important;
|
||||
::v-deep .u-icon-plus,
|
||||
::v-deep .u-icon-minus,
|
||||
::v-deep .u-icon-disabled {
|
||||
height: 30rpx !important;
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.goods-skus-number {
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
display: flex;
|
||||
|
||||
> .view-class-title {
|
||||
flex: 8;
|
||||
}
|
||||
|
||||
> .view-class-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .uni-scroll-view {
|
||||
overflow: hidden !important;
|
||||
::v-deep .uni-scroll-view {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.active {
|
||||
background: $price-light-color !important;
|
||||
border: 2rpx solid $price-color;
|
||||
font-weight: bold;
|
||||
color: $price-color !important;
|
||||
box-sizing: border-box;
|
||||
background: $price-light-color !important;
|
||||
border: 2rpx solid $price-color;
|
||||
font-weight: bold;
|
||||
color: $price-color !important;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.goods-skus-box {
|
||||
overflow-y: auto;
|
||||
height: 610rpx;
|
||||
// #ifdef MP-WEIXIN
|
||||
height: 570rpx;
|
||||
// #endif
|
||||
margin-bottom: 10rpx;
|
||||
overflow-y: auto;
|
||||
height: 610rpx;
|
||||
// #ifdef MP-WEIXIN
|
||||
height: 570rpx;
|
||||
// #endif
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
.soldout{
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.goods-skus-view {
|
||||
overflow: hidden;
|
||||
overflow: hidden;
|
||||
|
||||
.skus-view-list {
|
||||
> .skus-view-item {
|
||||
flex: 1;
|
||||
padding: 0 36rpx;
|
||||
.skus-view-list {
|
||||
> .skus-view-item {
|
||||
flex: 1;
|
||||
padding: 0 36rpx;
|
||||
|
||||
overflow: hidden;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
float: left;
|
||||
text-align: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 22rpx;
|
||||
color: #262626;
|
||||
background: #f2f2f2;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
}
|
||||
overflow: hidden;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
float: left;
|
||||
text-align: center;
|
||||
margin-left: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
font-size: 22rpx;
|
||||
color: #262626;
|
||||
background: #f2f2f2;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.goods-header {
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 36rpx;
|
||||
height: 200rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 36rpx;
|
||||
}
|
||||
|
||||
.goods-box {
|
||||
padding: 50rpx 36rpx 0 36rpx;
|
||||
padding: 50rpx 36rpx 0 36rpx;
|
||||
}
|
||||
|
||||
.goods-skus {
|
||||
padding: 0 20rpx;
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.goods-price {
|
||||
color: $price-color;
|
||||
line-height: 80rpx;
|
||||
display: flex;
|
||||
color: $price-color;
|
||||
line-height: 80rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.promotion-box {
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
/deep/ span {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: line-through;
|
||||
color: #999;
|
||||
margin-left: 10rpx;
|
||||
|
||||
.goods-price-bigshow {
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.promotion {
|
||||
font-size: 30rpx;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.goods-price-promotionShow {
|
||||
font-size: 48rpx;
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.goods-check-skus {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
> .goods-check-skus-name {
|
||||
margin-left: 4rpx;
|
||||
}
|
||||
> span {
|
||||
color: #333;
|
||||
}
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
|
||||
> .goods-check-skus-name {
|
||||
margin-left: 4rpx;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
### 说明 https://ext.dcloud.net.cn/plugin?id=3237
|
||||
@@ -1,211 +0,0 @@
|
||||
<template>
|
||||
|
||||
<div class="index">
|
||||
<u-modal v-model="show" :show-title="false" :show-confirm-button="false" mask-close-able>
|
||||
<view class="slot-content">
|
||||
<image @click="downLoad()" class="img" :src="imgUrl" />
|
||||
<div class="canvas-hide">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<canvas id="canvas" type="2d" style="width: 600px; height: 960px" />
|
||||
<!-- #endif -->
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<canvas canvas-id="canvas" id="canvas" style="width: 600px; height: 960px" />
|
||||
<!-- #endif -->
|
||||
</div>
|
||||
</view>
|
||||
</u-modal>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
// 引入绘制插件
|
||||
import DrawPoster from "@/js_sdk/u-draw-poster";
|
||||
|
||||
export default {
|
||||
data: () => ({
|
||||
imgUrl: "", //绘制出来的图片路径
|
||||
show: false, //是否展示模态框
|
||||
dp: {}, //绘制的dp对象,用于存储绘制等一些方法。
|
||||
logo: require("@/pages/passport/static/logo-title.png"), //本地logo地址
|
||||
}),
|
||||
|
||||
props: {
|
||||
/**
|
||||
* 父级传参的数据
|
||||
*/
|
||||
res: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
onUnload() {},
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 解决微信小程序中图片模糊问题
|
||||
*/
|
||||
// #ifdef MP-WEIXIN
|
||||
st2: (size) => size * 2,
|
||||
// #endif
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
st2: (size) => size,
|
||||
// #endif
|
||||
|
||||
/**
|
||||
* 保存图片
|
||||
*/
|
||||
downLoad() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.imgUrl,
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "保存成功!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "保存失败,请稍后重试!",
|
||||
icon: "none",
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 创建canvas
|
||||
*/
|
||||
async init() {
|
||||
this.show = true;
|
||||
this.dp = await DrawPoster.build({
|
||||
selector: "canvas",
|
||||
componentThis: this,
|
||||
loading: true,
|
||||
debugging: true,
|
||||
});
|
||||
let dp = this.dp;
|
||||
// #ifdef MP-WEIXIN
|
||||
// 用于微信小程序中画布错乱问题
|
||||
dp.canvas.width = this.st2(600);
|
||||
dp.canvas.height = this.st2(960);
|
||||
// #endif
|
||||
this.draw(dp);
|
||||
},
|
||||
|
||||
async draw(dp) {
|
||||
const { width, height, background, title } = this.res.container;
|
||||
const { code, img, price } = this.res.bottom;
|
||||
|
||||
// /** 绘制背景 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = background;
|
||||
ctx.fillRoundRect(
|
||||
this.st2(0),
|
||||
this.st2(0),
|
||||
this.st2(width),
|
||||
this.st2(height),
|
||||
this.st2(12)
|
||||
);
|
||||
ctx.clip();
|
||||
});
|
||||
/** 绘制图片 */
|
||||
dp.draw(async (ctx) => {
|
||||
await Promise.all([
|
||||
// 绘制Logo
|
||||
ctx.drawImage(
|
||||
this.logo,
|
||||
this.st2(175),
|
||||
this.st2(0),
|
||||
this.st2(256),
|
||||
this.st2(144)
|
||||
),
|
||||
// 中间图片
|
||||
ctx.drawImage(
|
||||
img,
|
||||
this.st2(100),
|
||||
this.st2(150),
|
||||
this.st2(400),
|
||||
this.st2(400)
|
||||
),
|
||||
// 二维码
|
||||
ctx.drawImage(
|
||||
code,
|
||||
this.st2(39),
|
||||
this.st2(750),
|
||||
this.st2(150),
|
||||
this.st2(150)
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
/** 绘制中间文字*/
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#333";
|
||||
ctx.font = `bold ${this.st2(24)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillWarpText({
|
||||
text: title,
|
||||
maxWidth: this.st2(500),
|
||||
x: this.st2(300),
|
||||
y: this.st2(600),
|
||||
layer: 1,
|
||||
});
|
||||
|
||||
ctx.fillStyle = "#ff3c2a";
|
||||
ctx.font = `${this.st2(38)}px PingFang SC`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(price, this.st2(300), this.st2(680));
|
||||
});
|
||||
|
||||
// /** 绘制底部文字 */
|
||||
await dp.draw((ctx) => {
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("长按图片,识别二维码", this.st2(200), this.st2(866));
|
||||
ctx.fillStyle = "#666";
|
||||
ctx.font = `${this.st2(24)}px PingFang SC`;
|
||||
ctx.fillText("查看商品详情", this.st2(200), this.st2(900));
|
||||
});
|
||||
|
||||
this.imgUrl = await dp.createImagePath();
|
||||
|
||||
// console.log(posterImgUrl)
|
||||
},
|
||||
},
|
||||
|
||||
async mounted() {
|
||||
this.init();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page,
|
||||
.index {
|
||||
height: 100%;
|
||||
}
|
||||
.canvas-hide {
|
||||
/* 1 */
|
||||
position: fixed;
|
||||
right: 100vw;
|
||||
bottom: 100vh;
|
||||
/* 2 */
|
||||
z-index: -9999;
|
||||
/* 3 */
|
||||
opacity: 0;
|
||||
}
|
||||
.index {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
background: rgba($color: grey, $alpha: 0.2);
|
||||
}
|
||||
|
||||
image {
|
||||
display: block;
|
||||
}
|
||||
.img {
|
||||
width: 600rpx;
|
||||
height: 960rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -79,180 +79,182 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
import { getRegionsById } from "@/api/common.js"
|
||||
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
let windowWidth = 0
|
||||
import { getRegionsById } from "@/api/common.js";
|
||||
export default {
|
||||
name: "UniCityNvue",
|
||||
props: {
|
||||
headTitle: {
|
||||
//标题
|
||||
type: String,
|
||||
default: "区域选择",
|
||||
},
|
||||
pickerSize: {
|
||||
// 使用多少个tab
|
||||
type: [String, String],
|
||||
default: "1",
|
||||
},
|
||||
provinceData: {
|
||||
// 默认的省市区id,如果不使用id的情况下则为[];
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
headTitle?: string
|
||||
pickerSize?: string | string[]
|
||||
provinceData?: any[]
|
||||
}>(), {
|
||||
headTitle: "区域选择",
|
||||
pickerSize: "1",
|
||||
provinceData: () => [],
|
||||
})
|
||||
|
||||
const emit = defineEmits(['funcValue'])
|
||||
|
||||
const clearRightIcon = ref(true)
|
||||
const scrollLeft = ref(500)
|
||||
const scrollTop = ref(0)
|
||||
const enableScroll = ref(true)
|
||||
const tabCurrentIndex = ref(0)
|
||||
const tabbars = ref<any[]>(props.provinceData)
|
||||
const pickersize = ref(props.pickerSize)
|
||||
const showPicker = ref(false)
|
||||
|
||||
/**
|
||||
* 显示选择器
|
||||
*/
|
||||
const show = () => {
|
||||
showPicker.value = true
|
||||
if (tabbars.value[0].children.length == 0) {
|
||||
getRegionsById(0).then((res: any) => {
|
||||
tabbars.value[0].children = res.data.result
|
||||
})
|
||||
}
|
||||
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
const hide = () => {
|
||||
showPicker.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
*/
|
||||
const changeTab = (e: number) => {
|
||||
let index = e
|
||||
setScroll(index)
|
||||
//延迟300ms,等待swiper动画结束再修改tabbar
|
||||
tabCurrentIndex.value = index
|
||||
setTimeout(() => {
|
||||
getScroll("show" + index)
|
||||
}, 10)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得元素的大小
|
||||
*/
|
||||
const getElSize = (id: string): Promise<any> => {
|
||||
return new Promise((res) => {
|
||||
let el = uni
|
||||
.createSelectorQuery()
|
||||
.in(instance!.proxy)
|
||||
.select("#" + id)
|
||||
el.fields(
|
||||
{
|
||||
size: true,
|
||||
scrollOffset: true,
|
||||
rect: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
clearRightIcon: true, //是否显示右侧关闭icon
|
||||
scrollLeft: 500, //顶部选项卡左滑距离
|
||||
scrollTop: 0, //默认滚动顶部为0
|
||||
enableScroll: true, //是否启用滚动
|
||||
tabCurrentIndex: 0, //当前选项卡索引
|
||||
tabbars: this.provinceData, //默认的省市区id
|
||||
pickersize: this.pickerSize, //多少个tab 推荐为4级
|
||||
showPicker: false, //显示选取器
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**
|
||||
* 显示选择器
|
||||
*/
|
||||
show() {
|
||||
this.showPicker = true;
|
||||
if (this.tabbars[0].children.length == 0) {
|
||||
getRegionsById(0).then((res) => {
|
||||
this.tabbars[0].children = res.data.result;
|
||||
});
|
||||
(data: any) => {
|
||||
res(data)
|
||||
}
|
||||
).exec()
|
||||
})
|
||||
}
|
||||
|
||||
windowWidth = uni.getSystemInfoSync().windowWidth;
|
||||
},
|
||||
|
||||
/**
|
||||
* 关闭选择器
|
||||
*/
|
||||
hide() {
|
||||
this.showPicker = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* tab切换
|
||||
*/
|
||||
changeTab(e) {
|
||||
let index = e;
|
||||
this.setScroll(index);
|
||||
//延迟300ms,等待swiper动画结束再修改tabbar
|
||||
this.tabCurrentIndex = index;
|
||||
setTimeout(() => {
|
||||
this.getScroll("show" + index);
|
||||
}, 10);
|
||||
},
|
||||
|
||||
/**
|
||||
* 获得元素的大小
|
||||
*/
|
||||
getElSize(id) {
|
||||
return new Promise((res, rej) => {
|
||||
let el = uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id);
|
||||
el.fields(
|
||||
{
|
||||
size: true,
|
||||
scrollOffset: true,
|
||||
rect: true,
|
||||
},
|
||||
(data) => {
|
||||
res(data);
|
||||
}
|
||||
).exec();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 点击城市后回调
|
||||
*/
|
||||
async changCity(index, item) {
|
||||
if (this.tabbars[index].id != item.id) {
|
||||
this.tabbars[index].localName = item.name;
|
||||
this.tabbars[index].id = item.id;
|
||||
if (index < this.tabbars.length - 1) {
|
||||
this.tabbars.splice(index + 1, this.tabbars.length - index - 1);
|
||||
}
|
||||
if (this.tabbars.length < this.pickersize) {
|
||||
let data = await getRegionsById(item.id);
|
||||
// 当前选项级为最后一级时回调,将选中的数据返回
|
||||
if (data.data.result.length == 0) {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
} else {
|
||||
// 将新的数据填充进下一级
|
||||
var current = {
|
||||
localName: "请选择",
|
||||
id: "",
|
||||
children: data.data.result,
|
||||
};
|
||||
this.tabbars.push(current);
|
||||
this.tabCurrentIndex++;
|
||||
|
||||
// 当前距离重新为最上面
|
||||
this.scrollTop = 0;
|
||||
}
|
||||
/**
|
||||
* 点击城市后回调
|
||||
*/
|
||||
const changCity = async (index: number, item: any) => {
|
||||
if (tabbars.value[index].id != item.id) {
|
||||
tabbars.value[index].localName = item.name
|
||||
tabbars.value[index].id = item.id
|
||||
tabbars.value[index].center = item.center
|
||||
if (index < tabbars.value.length - 1) {
|
||||
tabbars.value.splice(index + 1, tabbars.value.length - index - 1)
|
||||
}
|
||||
if (tabbars.value.length < pickersize.value as number) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
mask: true
|
||||
})
|
||||
try {
|
||||
let data = await getRegionsById(item.id)
|
||||
uni.hideLoading()
|
||||
// 当前选项级为最后一级时回调,将选中的数据返回
|
||||
if (data.data.result.length == 0) {
|
||||
emit("funcValue", tabbars.value)
|
||||
hide()
|
||||
} else {
|
||||
this.$emit("funcValue", this.tabbars);
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
// 将新的数据填充进下一级
|
||||
var current = {
|
||||
localName: "请选择",
|
||||
id: "",
|
||||
children: data.data.result,
|
||||
}
|
||||
tabbars.value.push(current)
|
||||
tabCurrentIndex.value++
|
||||
|
||||
/**
|
||||
* 获取当前tab中滚动的距离
|
||||
*/
|
||||
async setScroll(index) {
|
||||
let width = 0;
|
||||
let nowWidth = 0;
|
||||
for (let i = 0; i <= index; i++) {
|
||||
let result = await this.getElSize("tab" + i);
|
||||
width += result.width;
|
||||
if (i === index) {
|
||||
nowWidth = result.width;
|
||||
// 当前距离重新为最上面
|
||||
scrollTop.value = 0
|
||||
}
|
||||
} catch (error) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
if (width + nowWidth > windowWidth) {
|
||||
this.scrollLeft = width + nowWidth;
|
||||
} else {
|
||||
this.scrollLeft = 0;
|
||||
}
|
||||
},
|
||||
} else {
|
||||
emit("funcValue", tabbars.value)
|
||||
hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前的滚动距离
|
||||
*/
|
||||
getScroll(id) {
|
||||
/**
|
||||
* 获取当前tab中滚动的距离
|
||||
*/
|
||||
const setScroll = async (index: number) => {
|
||||
let width = 0
|
||||
let nowWidth = 0
|
||||
for (let i = 0; i <= index; i++) {
|
||||
let result = await getElSize("tab" + i)
|
||||
width += result.width
|
||||
if (i === index) {
|
||||
nowWidth = result.width
|
||||
}
|
||||
}
|
||||
if (width + nowWidth > windowWidth) {
|
||||
scrollLeft.value = width + nowWidth
|
||||
} else {
|
||||
scrollLeft.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前的滚动距离
|
||||
*/
|
||||
const getScroll = (id: string) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(instance!.proxy)
|
||||
.select(".panel-scroll-box")
|
||||
.boundingClientRect((data: any) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(".panel-scroll-box")
|
||||
.boundingClientRect((data) => {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select("#" + id)
|
||||
.boundingClientRect((res) => {
|
||||
if (res != undefined && res != null && res != "") {
|
||||
this.scrollTop = res.top - data.top;
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
.in(instance!.proxy)
|
||||
.select("#" + id)
|
||||
.boundingClientRect((res: any) => {
|
||||
if (res != undefined && res != null && res != "") {
|
||||
scrollTop.value = res.top - data.top
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
},
|
||||
};
|
||||
.exec()
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
|
||||
// 暴露方法给父组件调用
|
||||
defineExpose({ show, hide })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -379,12 +381,10 @@ export default {
|
||||
flex-direction: column;
|
||||
/* #endif */
|
||||
margin-right: 5px;
|
||||
margin-left: 40rpx;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.color {
|
||||
color: $light-color;
|
||||
}
|
||||
icon {
|
||||
margin-left: 40rpx;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
18
components/m-goods-list/README.md
Normal file
18
components/m-goods-list/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
## 商品列表展示
|
||||
|
||||
### OBJECT 参数说明
|
||||
|
||||
| 属性 | 说明 | 类型 | 必填 |
|
||||
| ----------- | ---------------------------------------------------------- | ------- | ---- |
|
||||
| `res` | 显示数据 | Array | 是 |
|
||||
| `type` | 商品展示类型 oneColumns twoColumns ,默认展示一行两列商品 | String | 否 |
|
||||
| `storeName` | 是否展示店铺名称,默认展示 | Boolean | 否 |
|
||||
| `keywords` | 高亮展示搜索内容 | String | 否 |
|
||||
| `showFields` | 商品卡片字段开关(Object,null=现状渲染全量字段)。键:`subTitle` 副标题、`spec` 规格摘要(取 attrList 前 2 项)、`tags` 促销标签、`selfTag` 自营、`price` 价格、`linePrice` 划线价(仅 marketPrice>price 时显示)、`sales` 已售/评论、`cart` 购物车按钮。新增字段(副标题/规格/划线价/购物车)仅在 showFields 提供时才会渲染;目前仅 oneColumns 布局支持开关 | Object | 否 |
|
||||
|
||||
### EVENTS 事件说明
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
| ------ | ---- | -------- |
|
||||
| `addCart` | 点击购物车按钮(showFields.cart 开启时渲染) | 当前商品对象 |
|
||||
|
||||
259
components/m-goods-list/base-list.vue
Normal file
259
components/m-goods-list/base-list.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 一行两列商品展示 -->
|
||||
<view class="goods-list" v-if="type == 'twoColumns'">
|
||||
<view v-for="(item, index) in res" :key="index" class="goods-item">
|
||||
<view class="image-wrapper" @click="navigateToDetailPage(item)">
|
||||
<u-image
|
||||
:src="item.thumbnail"
|
||||
width="100%"
|
||||
height="330rpx"
|
||||
mode="aspectFit"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-detail">
|
||||
<div
|
||||
class="title clamp"
|
||||
v-html="lightSearchStr(keyword, item.goodsName)"
|
||||
@click="navigateToDetailPage(item)"
|
||||
></div>
|
||||
<view class="price-box" @click="navigateToDetailPage(item)">
|
||||
<div class="price" v-if="item.price != undefined">
|
||||
¥<span
|
||||
>{{
|
||||
goodsFormatPrice(item.price)[0]
|
||||
}} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</div>
|
||||
</view>
|
||||
<div class="count-config" @click="navigateToDetailPage(item)">
|
||||
<span>已售 {{ item.buyCount || "0" }}</span>
|
||||
<span>{{ item.commentNum || "0" }}条评论</span>
|
||||
</div>
|
||||
<div
|
||||
class="store-seller-name"
|
||||
v-if="storeName"
|
||||
@click="navigateToStoreDetailPage(item)"
|
||||
>
|
||||
<div class="text-hidden">
|
||||
<u-tag
|
||||
style="margin-right: 10rpx"
|
||||
size="mini"
|
||||
mode="dark"
|
||||
v-if="item.selfOperated"
|
||||
text="自营"
|
||||
type="error"
|
||||
/>
|
||||
<span>{{ item.storeName || "暂无" }}</span>
|
||||
</div>
|
||||
<span>
|
||||
<u-icon name="arrow-right"></u-icon>
|
||||
</span>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 一行一列商品展示 -->
|
||||
<div v-if="type == 'oneColumns'">
|
||||
div v-for="(item, index) in res" :key="index" class="goods-row">
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img" @click="navigateToDetailPage(item)">
|
||||
<u-image
|
||||
width="230rpx"
|
||||
border-radius="16"
|
||||
height="230rpx"
|
||||
mode="aspectFit"
|
||||
:src="item.goodsImage || item.thumbnail"
|
||||
>
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
<div class="title clamp3" @click="navigateToDetailPage(item)">
|
||||
{{ item.goodsName }}
|
||||
</div>
|
||||
<view class="price-box" @click="navigateToDetailPage(item)">
|
||||
<div class="price" v-if="item.price != undefined">
|
||||
¥<span
|
||||
>{{ goodsFormatPrice(item.price)[0] }} </span
|
||||
>.{{ goodsFormatPrice(item.price)[1] }}
|
||||
</div>
|
||||
</view>
|
||||
<div class="promotion" @click="navigateToDetailPage(item)">
|
||||
<div
|
||||
v-for="(promotionItem, promotionIndex) in getPromotion(item)"
|
||||
:key="promotionIndex"
|
||||
>
|
||||
<span v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
|
||||
<span v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1"
|
||||
>满减</span
|
||||
>
|
||||
<span v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style="overflow: hidden"
|
||||
@click="navigateToDetailPage(item)"
|
||||
class="count-config"
|
||||
>
|
||||
<span style="float: left; font-size: 22rpx"
|
||||
>已售 {{ item.buyCount || "0" }}</span
|
||||
>
|
||||
<span style="float: right; font-size: 22rpx"
|
||||
>{{ item.commentNum || "0" }}条评论</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useGoodsListCommon } from './common'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const { lightSearchStr, getPromotion, navigateToDetailPage, navigateToStoreDetailPage } = useGoodsListCommon()
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
type?: string
|
||||
res?: any[]
|
||||
storeName?: boolean
|
||||
keyword?: string | null
|
||||
}>(), {
|
||||
type: 'oneColumns',
|
||||
res: () => [],
|
||||
storeName: true,
|
||||
keyword: ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.goods-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 10rpx 20rpx 284rpx;
|
||||
width: 100%;
|
||||
|
||||
> .goods-item {
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
border-radius: 16rpx;
|
||||
flex-direction: column;
|
||||
width: calc(50% - 30rpx);
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
&:nth-child(2n + 1) {
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
height: 330rpx;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.count-config,
|
||||
.store-seller-name {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
|
||||
.text-hidden {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-row {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
|
||||
> .goods-col {
|
||||
display: flex;
|
||||
|
||||
> .goods-img {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
> .goods-detail {
|
||||
flex: 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
|
||||
> .title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
line-height: 1.5;
|
||||
height: 86rpx;
|
||||
padding: 10rpx 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.promotion {
|
||||
margin-top: 4rpx;
|
||||
display: flex;
|
||||
|
||||
div {
|
||||
span {
|
||||
font-size: 24rpx;
|
||||
color: $light-color;
|
||||
margin-right: 10rpx;
|
||||
padding: 0 4rpx;
|
||||
border-radius: 2rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.store-seller-name {
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
> .price-box {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
> .price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
|
||||
::v-deep span:nth-of-type(1) {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
90
components/m-goods-list/common.ts
Normal file
90
components/m-goods-list/common.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { useStore } from '@/store'
|
||||
|
||||
/**
|
||||
* 商品列表公共逻辑(原 common.vue mixin)
|
||||
* 提供高亮搜索、unicode 转换、促销标签、导航跳转等通用方法
|
||||
*/
|
||||
export function useGoodsListCommon() {
|
||||
const store = useStore()
|
||||
|
||||
const lightColor = store.getters.lightColor
|
||||
|
||||
/**
|
||||
* 高亮显示搜索内容
|
||||
*/
|
||||
const lightSearchStr = (keyword: string, str: string) => {
|
||||
if (!keyword) {
|
||||
return str
|
||||
} else {
|
||||
let unicodes = ''
|
||||
for (let i of Array.from(keyword)) {
|
||||
unicodes += unicode(i) + "|"
|
||||
}
|
||||
const rule = '(' + unicodes + ')'
|
||||
const reg = new RegExp(rule, 'gi')
|
||||
return str ? str.replace(reg, (matchValue) =>
|
||||
`<span style="color:${lightColor}">${matchValue}</span>`
|
||||
) : ''
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 unicode
|
||||
*/
|
||||
const unicode = (str: string) => {
|
||||
var value = ''
|
||||
for (var i = 0; i < str.length; i++) {
|
||||
value += '\\u' + leftZero4(parseInt(str.charCodeAt(i).toString(16)))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
const leftZero4 = (str: string) => {
|
||||
if (str != null && str != '' && str != 'undefined') {
|
||||
if (str.length == 2) {
|
||||
return '00' + str
|
||||
}
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据去重 只显示一次 减免 劵等
|
||||
*/
|
||||
const getPromotion = (item: any) => {
|
||||
if (item.promotionMap) {
|
||||
let array: string[] = []
|
||||
Object.keys(item.promotionMap).forEach((child) => {
|
||||
if (!array.includes(child.split("-")[0])) {
|
||||
array.push(child.split("-")[0])
|
||||
}
|
||||
})
|
||||
return array
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到商品详情
|
||||
*/
|
||||
const navigateToDetailPage = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到店铺详情
|
||||
*/
|
||||
const navigateToStoreDetailPage = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/shopPage?id=${item.storeId}`,
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
lightSearchStr,
|
||||
getPromotion,
|
||||
navigateToDetailPage,
|
||||
navigateToStoreDetailPage,
|
||||
}
|
||||
}
|
||||
523
components/m-goods-list/list.vue
Normal file
523
components/m-goods-list/list.vue
Normal file
@@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<view>
|
||||
<!-- 一行两列商品展示 -->
|
||||
<view class="goods-list" :class="{ 'goods-list--embedded': !tabBarGap }" v-if="type == 'twoColumns'">
|
||||
<view v-for="(item, index) in res" :key="index" class="goods-item">
|
||||
<view class="image-wrapper" @click="navigateToDetailPage(item)">
|
||||
<u-image :src="item.thumbnail" width="100%" height='330rpx' mode="aspectFit">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</view>
|
||||
<view class="goods-detail">
|
||||
<div class="title clamp" v-html="lightSearchStr(keyword,item.goodsName)"
|
||||
@click="navigateToDetailPage(item)">
|
||||
|
||||
</div>
|
||||
<view class="price-box" @click="navigateToDetailPage(item)">
|
||||
<div class="price" v-if="item.price!=undefined">
|
||||
¥<span class="price-int">{{ goodsFormatPrice(item.price )[0] }} </span>.{{
|
||||
goodsFormatPrice(item.price )[1]
|
||||
}}
|
||||
</div>
|
||||
</view>
|
||||
<div class="promotion" @click="navigateToDetailPage(item)">
|
||||
<div v-for="(promotionItem,promotionIndex) in getPromotion(item)" :key="promotionIndex">
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1">满减</span>
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="count-config" @click="navigateToDetailPage(item)">
|
||||
<span>已售 {{ item.buyCount || "0" }}</span>
|
||||
<span>{{ item.commentNum || "0" }}条评论</span>
|
||||
</div>
|
||||
<div class="store-seller-name" v-if="storeName" @click="navigateToStoreDetailPage(item)">
|
||||
<view class="store-name-row">
|
||||
<text class="self-tag" v-if="item.selfOperated">自营</text>
|
||||
<text class="store-name-text">{{ item.storeName || "暂无" }}</text>
|
||||
<text class="to-store">进店</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="12" color="#c5c5c5"></u-icon>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 一行一列商品展示 -->
|
||||
<div v-if="type == 'oneColumns'" class="goods-one-row" :class="{ 'goods-one-row--embedded': !tabBarGap }">
|
||||
<div v-for="(item, index) in res" :key="index" class="goods-row">
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img" @click="navigateToDetailPage(item)">
|
||||
<u-image :width="!tabBarGap ? '170rpx' : '230rpx'" mode="aspectFit" :border-radius="!tabBarGap ? '12' : '16'" :height="!tabBarGap ? '170rpx' : '230rpx'" :src="item.thumbnail">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
<div class="title clamp2" @click="navigateToDetailPage(item)">
|
||||
<text class="self-tag" v-if="(!storeName || flag('selfTag')) && item.selfOperated">自营</text>
|
||||
{{ item.goodsName }}
|
||||
</div>
|
||||
<div class="selling-point" v-if="extra('subTitle') && item.sellingPoint">{{ item.sellingPoint }}</div>
|
||||
<div class="spec-summary" v-if="extra('spec') && specSummary(item)">{{ specSummary(item) }}</div>
|
||||
<div class="promotion" v-if="flag('tags')" @click="navigateToDetailPage(item)">
|
||||
<div v-for="(promotionItem,promotionIndex) in getPromotion(item)" :key="promotionIndex">
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('COUPON') != -1">劵</span>
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('FULL_DISCOUNT') != -1">满减</span>
|
||||
<span class="promotion-tag" v-if="promotionItem.indexOf('SECKILL') != -1">秒杀</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow: hidden" v-if="flag('sales')" @click="navigateToDetailPage(item)" class="count-config">
|
||||
<span style="float: left; font-size: 22rpx">已售 {{ item.buyCount || '0' }}</span>
|
||||
<span style="float: right; font-size: 22rpx" v-if="item.commentNum">{{ item.commentNum }}条评论</span>
|
||||
</div>
|
||||
<view class="price-box" @click="navigateToDetailPage(item)">
|
||||
<div class="price" v-if="flag('price') && item.price!=undefined">
|
||||
¥<span class="price-int">{{ goodsFormatPrice(item.price )[0] }} </span>.{{
|
||||
goodsFormatPrice(item.price )[1]
|
||||
}}
|
||||
</div>
|
||||
<div class="market-price" v-if="extra('linePrice') && hasMarketPrice(item)">¥{{ item.marketPrice }}</div>
|
||||
<div class="cart-btn" v-if="extra('cart')" @click.stop="emit('addCart', item)">
|
||||
<u-icon name="shopping-cart" size="16" :color="mainColor"></u-icon>
|
||||
</div>
|
||||
</view>
|
||||
<div class="store-seller-name" v-if="storeName" @click="navigateToStoreDetailPage(item)">
|
||||
<view class="store-name-row">
|
||||
<text class="self-tag" v-if="item.selfOperated && flag('selfTag')">自营</text>
|
||||
<text class="store-name-text">{{ item.storeName }}</text>
|
||||
<text class="to-store">进店</text>
|
||||
</view>
|
||||
<u-icon name="arrow-right" size="12" color="#c5c5c5"></u-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, watch } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import { useGoodsListCommon } from './common'
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const store = useStore()
|
||||
const mainColor = computed(() => (store && store.getters && store.getters.mainColor) || '#ff3c2a')
|
||||
|
||||
const { lightSearchStr, getPromotion, navigateToDetailPage, navigateToStoreDetailPage } = useGoodsListCommon()
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
res?: any[]
|
||||
type?: string
|
||||
storeName?: boolean
|
||||
tabBarGap?: boolean
|
||||
keyword?: string | null
|
||||
showFields?: Record<string, boolean> | null
|
||||
}>(), {
|
||||
res: () => [],
|
||||
type: 'twoColumns',
|
||||
storeName: true,
|
||||
tabBarGap: true,
|
||||
keyword: '',
|
||||
showFields: null
|
||||
})
|
||||
|
||||
const emit = defineEmits(['addCart'])
|
||||
|
||||
// 存量字段(促销/销量/自营/价格):showFields 为 null 时维持现状渲染
|
||||
const flag = (key: string) => props.showFields == null || props.showFields[key] !== false
|
||||
// 新增字段(副标题/规格/划线价/购物车按钮):仅 showFields 提供时可渲染
|
||||
const extra = (key: string) => props.showFields != null && props.showFields[key] !== false
|
||||
const hasMarketPrice = (item: any) =>
|
||||
item.marketPrice != null && item.price != null && item.marketPrice > item.price
|
||||
const specSummary = (item: any) =>
|
||||
Array.isArray(item.attrList)
|
||||
? item.attrList.slice(0, 2).map((a: any) => `${a.name}: ${a.value}`).join(' · ')
|
||||
: ''
|
||||
|
||||
watch(() => props.keyword, (val) => {
|
||||
if (val) {
|
||||
lightSearchStr(val as string, '')
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.goods-one-row{
|
||||
padding-bottom: 250rpx;
|
||||
}
|
||||
/* 商品列表 */
|
||||
.goods-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin: 10rpx 20rpx 284rpx;
|
||||
width: calc(100% - 40rpx);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.goods-list--embedded {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
>.goods-item {
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
border-radius: 16rpx;
|
||||
flex-direction: column;
|
||||
width: calc(50% - 10rpx);
|
||||
margin-bottom: 20rpx;
|
||||
padding-bottom: 20rpx;
|
||||
|
||||
|
||||
.image-wrapper {
|
||||
width: 100%;
|
||||
height: 330rpx;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.count-config,
|
||||
.store-seller-name {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
|
||||
.store-seller-name {
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.store-name-row {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.store-name-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.to-store {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.self-tag {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
background: $main-color;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.goods-one-row--embedded {
|
||||
.goods-row {
|
||||
padding: 20rpx 16rpx;
|
||||
border-bottom: 1rpx solid #f6f6f6;
|
||||
background: #ffffff;
|
||||
|
||||
>.goods-col {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
>.goods-img {
|
||||
flex: 0 0 170rpx;
|
||||
width: 170rpx;
|
||||
height: 170rpx;
|
||||
border-radius: 12rpx;
|
||||
overflow: hidden;
|
||||
background: #f8f8f8;
|
||||
}
|
||||
|
||||
>.goods-detail {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin: 0 0 0 16rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
>.title {
|
||||
font-size: 26rpx;
|
||||
color: #222;
|
||||
line-height: 1.4;
|
||||
max-height: 74rpx;
|
||||
padding: 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.self-tag {
|
||||
display: inline-block;
|
||||
margin-right: 8rpx;
|
||||
padding: 1rpx 8rpx;
|
||||
border-radius: 6rpx;
|
||||
background: $main-color;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.4;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.selling-point {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.3;
|
||||
margin-top: 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.spec-summary {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.3;
|
||||
margin-top: 2rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.price-box {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
margin-top: auto;
|
||||
padding-top: 8rpx;
|
||||
|
||||
.price {
|
||||
font-size: 22rpx;
|
||||
color: $main-color;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
|
||||
.price-int {
|
||||
font-size: 34rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.market-price {
|
||||
font-size: 22rpx;
|
||||
color: #bbb;
|
||||
text-decoration: line-through;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid $main-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 60, 42, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-row {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
|
||||
>.goods-col {
|
||||
display: flex;
|
||||
|
||||
>.goods-img {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
|
||||
>.goods-detail {
|
||||
flex: 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
|
||||
>.title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
line-height: 1.5;
|
||||
height: 86rpx;
|
||||
padding: 10rpx 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.selling-point {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
margin-top: 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.spec-summary {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
margin-top: 4rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.market-price {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
text-decoration: line-through;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.cart-btn {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid $main-color;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2rpx 8rpx rgba(255, 60, 42, 0.12);
|
||||
}
|
||||
|
||||
.promotion {
|
||||
margin-top: 4rpx;
|
||||
display: flex;
|
||||
|
||||
.promotion-tag {
|
||||
font-size: 24rpx;
|
||||
color: $light-color;
|
||||
margin-right: 10rpx;
|
||||
padding: 0 4rpx;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.store-seller-name {
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 4rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.store-name-row {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.store-name-text {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.to-store {
|
||||
flex-shrink: 0;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
.self-tag {
|
||||
flex-shrink: 0;
|
||||
margin-right: 8rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
border-radius: 8rpx;
|
||||
background: $main-color;
|
||||
color: #fff;
|
||||
font-size: 20rpx;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
>.price-box {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
>.price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
|
||||
.price-int {
|
||||
font-size: 38rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
145
components/m-goods-list/promotion.vue
Normal file
145
components/m-goods-list/promotion.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(item, index) in res" :key="index" class="goods-row" @click="onItemClick(item)">
|
||||
<div class="flex goods-col">
|
||||
<div class="goods-img">
|
||||
<u-image width="230rpx" mode="aspectFit" border-radius='16' height="230rpx" :src="item.goodsImage || item.thumbnail">
|
||||
<template #loading><u-loading-icon></u-loading-icon></template>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-detail">
|
||||
<div class="title clamp3">{{ item.goodsName }}</div>
|
||||
<div class='flex flex-a-c flex-j-sb'>
|
||||
<view class="price-box">
|
||||
<!-- 秒杀 / 拼团 -->
|
||||
<div class="price" v-if="!type && item.price!=undefined">
|
||||
¥<span class="price-int">{{ goodsFormatPrice(item.price )[0] }} </span>.{{
|
||||
goodsFormatPrice(item.price )[1]
|
||||
}}
|
||||
</div>
|
||||
<!-- 砍价 -->
|
||||
<div class="price" v-if="type && item.purchasePrice!=undefined">
|
||||
最低:
|
||||
¥<span class="price-int">{{ goodsFormatPrice(item.purchasePrice )[0] }} </span>.{{
|
||||
goodsFormatPrice(item.purchasePrice )[1]
|
||||
}}
|
||||
</div>
|
||||
<!-- 兜底策略如果金额是0 -->
|
||||
<div class="price" v-if="!item.price && !type">
|
||||
¥<span class="price-int">0 </span>.00
|
||||
</div>
|
||||
</view>
|
||||
<div>
|
||||
<image class="buy" src="/static/buy.png"></image>
|
||||
</div>
|
||||
</div>
|
||||
<div class='count-config' v-if="!type">
|
||||
<text class="count-config-text">即将恢复{{ item.originalPrice}}元</text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { goodsFormatPrice } from '@/utils/filters.js'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
res?: any[]
|
||||
type?: string | null
|
||||
}>(), {
|
||||
res: () => [],
|
||||
type: ''
|
||||
})
|
||||
|
||||
// 促销列表跳转:拼团/秒杀 PromotionGoods 用 skuId;砍价走活动详情
|
||||
function onItemClick(item: any) {
|
||||
if (props.type == 'kanJia') {
|
||||
uni.navigateTo({
|
||||
url: `/pages/promotion/bargain/detail?id=${item.id}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
const skuId = item.skuId || item.id
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${skuId}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.buy {
|
||||
width: 152rpx;
|
||||
height: 108rpx;
|
||||
}
|
||||
.flex-j-sb {
|
||||
width: 100%;
|
||||
}
|
||||
.goods-row {
|
||||
background: #fff;
|
||||
padding: 16rpx;
|
||||
>.goods-col {
|
||||
display: flex;
|
||||
>.goods-img {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
}
|
||||
>.goods-detail {
|
||||
flex: 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods-detail {
|
||||
margin: 0 20rpx;
|
||||
>.title {
|
||||
font-size: $font-base;
|
||||
color: $font-color-dark;
|
||||
line-height: 1.5;
|
||||
height: 86rpx;
|
||||
padding: 10rpx 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.count-config {
|
||||
padding: 5rpx 0;
|
||||
color: #666;
|
||||
display: flex;
|
||||
font-size: 24rpx;
|
||||
letter-spacing:2rpx;
|
||||
padding-left: 10rpx;
|
||||
|
||||
.count-config-text {
|
||||
font-size: 24rpx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
.price-box {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 10rpx;
|
||||
font-size: 24rpx;
|
||||
color: $font-color-light;
|
||||
|
||||
>.price {
|
||||
font-size: 26rpx;
|
||||
line-height: 1;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
|
||||
.price-int {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,88 +1,63 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="goods-recommend">{{title ? `--${title}-- `:''}}</div>
|
||||
<div class="goods-list">
|
||||
<div @click="handleClick(item)" class="goods-item" v-for="(item, item_index) in goodsList" :key="item_index">
|
||||
<div class="goods-img">
|
||||
<u-image :src="item.content.thumbnail" mode="aspectFill" height="350rpx" width="100%">
|
||||
<u-loading slot="loading"></u-loading>
|
||||
</u-image>
|
||||
</div>
|
||||
<div class="goods-desc">
|
||||
<div class="goods-title">
|
||||
{{ item.content.goodsName }}
|
||||
</div>
|
||||
<div class="goods-bottom">
|
||||
<div class="goods-price">¥{{ item.content.price | unitPrice }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<view>
|
||||
<view class="goods-recommend">{{title ? `--${title}-- `:''}}</view>
|
||||
<goodsTemplate :res='goodsList' />
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import goodsTemplate from '@/components/m-goods-list/list.vue'
|
||||
import { getGoodsList } from "@/api/goods.js"
|
||||
|
||||
<script>
|
||||
import { getGoodsList } from "@/api/goods.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
goodsList: [],
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
},
|
||||
};
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
pageSize: {
|
||||
type: null,
|
||||
default: 12,
|
||||
},
|
||||
categoryId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
storeId: {
|
||||
type: null,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initGoods();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 初始化商品
|
||||
*/
|
||||
async initGoods() {
|
||||
let submit = JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(this.params, {
|
||||
pageSize: this.pageSize,
|
||||
categoryId: this.categoryId,
|
||||
storeId: this.storeId,
|
||||
})
|
||||
)
|
||||
);
|
||||
const props = withDefaults(defineProps<{
|
||||
title?: string
|
||||
pageSize?: number | null
|
||||
categoryId?: string | null
|
||||
storeId?: string | null
|
||||
}>(), {
|
||||
title: '',
|
||||
pageSize: 12,
|
||||
categoryId: '',
|
||||
storeId: ''
|
||||
})
|
||||
|
||||
Object.keys(submit).map((key) => {
|
||||
if (!submit[key] || submit[key].length == 0) {
|
||||
delete submit[key];
|
||||
}
|
||||
});
|
||||
let goodsList = await getGoodsList(submit);
|
||||
this.goodsList.push(...goodsList.data.result.content);
|
||||
},
|
||||
handleClick(item) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.content.id}&goodsId=${item.content.goodsId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const goodsList = ref<any[]>([])
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
initGoods()
|
||||
})
|
||||
|
||||
/**
|
||||
* 初始化商品
|
||||
*/
|
||||
const initGoods = async () => {
|
||||
let submit = JSON.parse(
|
||||
JSON.stringify(
|
||||
Object.assign(params.value, {
|
||||
pageSize: props.pageSize,
|
||||
categoryId: props.categoryId,
|
||||
storeId: props.storeId,
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
Object.keys(submit).map((key) => {
|
||||
if (!submit[key] || (submit[key] as any).length == 0) {
|
||||
delete submit[key]
|
||||
}
|
||||
})
|
||||
let res = await getGoodsList(submit)
|
||||
goodsList.value.push(...(res.data.result.records as any[]))
|
||||
}
|
||||
|
||||
const handleClick = (item: any) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/goods?id=${item.id}&goodsId=${item.goodsId}`,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@@ -115,10 +90,6 @@ $w_94: 94%;
|
||||
border-top-left-radius: 20rpx;
|
||||
border-top-right-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.goods-desc {
|
||||
border-bottom-left-radius: 20rpx;
|
||||
@@ -149,4 +120,4 @@ $w_94: 94%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
<template>
|
||||
<view class="serach">
|
||||
<view class="left-box" @tap="onClickLeft">
|
||||
<u-icon name="arrow-left" size="40" color="#666"></u-icon>
|
||||
<u-icon name="arrow-left" size="28rpx" color="#606266"></u-icon>
|
||||
</view>
|
||||
<view class="content" :style="{ 'border-radius': radius + 'px' }">
|
||||
<!-- HM修改 增加进入输入状态的点击范围 -->
|
||||
<view class="content-box" :class="{ center: mode === 2 }">
|
||||
<u-icon name="search" size="32" style="padding:0 15rpx;"></u-icon>
|
||||
<u-icon name="search" size="30rpx" color="#909399" style="padding:0 14rpx;"></u-icon>
|
||||
<!-- HM修改 增加placeholder input confirm-type confirm-->
|
||||
<input style="width:100%; " :placeholder="placeholder" placeholder-class="placeholder-color"
|
||||
@input="inputChange" confirm-type="search" @confirm="triggerConfirm" class="input"
|
||||
:class="{ center: !active && mode === 2 }" :focus="isFocus" v-model="inputVal" @focus="focus" @blur="blur" />
|
||||
<u-icon name="close" v-if="isDelShow" style="padding:0 30rpx;" @click="clear"></u-icon>
|
||||
<view
|
||||
class="clear-icon"
|
||||
v-if="showClear && isDelShow"
|
||||
@tap.stop.prevent="clear"
|
||||
>
|
||||
<u-icon name="close" size="28rpx" color="#909399"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -21,196 +27,214 @@
|
||||
</view>
|
||||
|
||||
<view v-else class="button-item">
|
||||
<u-icon name="grid-fill" size="50" @click="handelListClass()" v-if="!switchLayout"></u-icon>
|
||||
<u-icon v-else @click="handelListClass()" name="list-dot" size="50"></u-icon>
|
||||
<u-icon name="grid-fill" size="32rpx" color="var(--theme-light, #ff6b35)" @click="handelListClass()" v-if="!switchLayout"></u-icon>
|
||||
<u-icon v-else @click="handelListClass()" name="list-dot" size="32rpx" color="var(--theme-light, #ff6b35)"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
mode: {
|
||||
value: Number,
|
||||
default: 1,
|
||||
},
|
||||
//HM修改 定义默认搜索关键词(水印文字)
|
||||
placeholder: {
|
||||
value: String,
|
||||
default: "请输入搜索内容",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
// 默认半径为60
|
||||
radius: {
|
||||
value: String,
|
||||
default: 60,
|
||||
},
|
||||
// 是否获取焦点
|
||||
isFocusVal: {
|
||||
value: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShowSeachGoods: false, //是否显示查询的商品
|
||||
active: false, //是否选中
|
||||
inputVal: "", //Input中内容
|
||||
isDelShow: false, //是否显示右侧删除icon
|
||||
isFocus: false, //是否获取焦点
|
||||
switchLayout: true, //切换当前商品的布局,默认为两列
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.isFocus = this.isFocusVal;
|
||||
},
|
||||
methods: {
|
||||
//
|
||||
out() {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/home/index",
|
||||
});
|
||||
},
|
||||
// 切换排列顺序
|
||||
handelListClass() {
|
||||
this.switchLayout = !this.switchLayout;
|
||||
this.$emit("SwitchType");
|
||||
},
|
||||
//HM修改 触发组件confirm事件
|
||||
triggerConfirm() {
|
||||
this.$emit("confirm", false);
|
||||
uni.hideKeyboard();
|
||||
},
|
||||
//HM修改 触发组件input事件
|
||||
inputChange(event) {
|
||||
var keyword = event.detail.value;
|
||||
this.$emit("input", keyword);
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
focus() {
|
||||
this.active = true;
|
||||
//HM修改 增加获取焦点判断
|
||||
if (this.inputVal) {
|
||||
this.isDelShow = true;
|
||||
}
|
||||
},
|
||||
blur() {
|
||||
this.isFocus = false;
|
||||
if (!this.inputVal) {
|
||||
this.active = false;
|
||||
}
|
||||
},
|
||||
clear() {
|
||||
//HM修改 收起键盘
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.inputVal = "";
|
||||
this.active = false;
|
||||
//HM修改 清空内容时候触发组件input
|
||||
this.$emit("input", "");
|
||||
//this.$emit('search', '');//HM修改 清空内容时候不进行搜索
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
|
||||
/**
|
||||
* 回退到上一级
|
||||
*/
|
||||
onClickLeft() {
|
||||
uni.navigateBack();
|
||||
},
|
||||
const props = withDefaults(defineProps<{
|
||||
mode?: number
|
||||
placeholder?: string
|
||||
modelValue?: string
|
||||
value?: string
|
||||
radius?: string
|
||||
isFocusVal?: boolean
|
||||
showClear?: boolean
|
||||
}>(), {
|
||||
mode: 1,
|
||||
placeholder: "请输入搜索内容",
|
||||
modelValue: "",
|
||||
value: "",
|
||||
radius: "60",
|
||||
isFocusVal: true,
|
||||
showClear: true,
|
||||
})
|
||||
|
||||
/**
|
||||
* 内容为空时,输入默认关键字
|
||||
*/
|
||||
search() {
|
||||
if (!this.inputVal) {
|
||||
if (this.searchName == "取消") {
|
||||
uni.hideKeyboard();
|
||||
this.isFocus = false;
|
||||
this.active = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.$emit("search", this.inputVal ? this.inputVal : this.placeholder);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
/**
|
||||
* 监听当前是否有值 是否显示清除图标
|
||||
*/
|
||||
inputVal(newVal) {
|
||||
newVal ? (this.isDelShow = true) : (this.isDelShow = false);
|
||||
},
|
||||
},
|
||||
};
|
||||
const emit = defineEmits(['confirm', 'input', 'update:modelValue', 'search', 'SwitchType'])
|
||||
|
||||
const isShowSeachGoods = ref(false)
|
||||
const active = ref(false)
|
||||
const inputVal = ref("")
|
||||
const isDelShow = ref(false)
|
||||
const isFocus = ref(false)
|
||||
const switchLayout = ref(true)
|
||||
|
||||
onMounted(() => {
|
||||
isFocus.value = props.isFocusVal
|
||||
inputVal.value = props.modelValue || props.value || ''
|
||||
})
|
||||
|
||||
//
|
||||
const out = () => {
|
||||
uni.reLaunch({
|
||||
url: "/pages/tabbar/home/index",
|
||||
})
|
||||
}
|
||||
// 切换排列顺序
|
||||
const handelListClass = () => {
|
||||
switchLayout.value = !switchLayout.value
|
||||
emit("SwitchType")
|
||||
}
|
||||
//HM修改 触发组件confirm事件
|
||||
const triggerConfirm = () => {
|
||||
emit("confirm", false)
|
||||
uni.hideKeyboard()
|
||||
}
|
||||
//HM修改 触发组件input事件
|
||||
const inputChange = (event: any) => {
|
||||
var keyword = event.detail.value
|
||||
emit("input", keyword)
|
||||
emit("update:modelValue", keyword)
|
||||
if (inputVal.value) {
|
||||
isDelShow.value = true
|
||||
}
|
||||
}
|
||||
const focus = () => {
|
||||
active.value = true
|
||||
//HM修改 增加获取焦点判断
|
||||
if (inputVal.value) {
|
||||
isDelShow.value = true
|
||||
}
|
||||
}
|
||||
const blur = () => {
|
||||
isFocus.value = false
|
||||
if (!inputVal.value) {
|
||||
active.value = false
|
||||
}
|
||||
}
|
||||
const clear = () => {
|
||||
//HM修改 收起键盘
|
||||
uni.hideKeyboard()
|
||||
isFocus.value = false
|
||||
inputVal.value = ""
|
||||
active.value = false
|
||||
isDelShow.value = false
|
||||
//HM修改 清空内容时候触发组件input
|
||||
emit("input", "")
|
||||
emit("update:modelValue", "")
|
||||
//this.$emit('search', '');//HM修改 清空内容时候不进行搜索
|
||||
}
|
||||
|
||||
const setInputValue = (value: string) => {
|
||||
inputVal.value = value || ''
|
||||
emit("input", inputVal.value)
|
||||
emit("update:modelValue", inputVal.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 回退到上一级
|
||||
*/
|
||||
const onClickLeft = () => {
|
||||
const paths = getCurrentPages()
|
||||
console.log(paths)
|
||||
if(paths.length > 1){
|
||||
uni.navigateBack()
|
||||
}else{
|
||||
uni.switchTab({
|
||||
url:"/pages/tabbar/home/index"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容为空时,输入默认关键字
|
||||
*/
|
||||
const search = () => {
|
||||
if (!inputVal.value) {
|
||||
if (false) { // searchName == "取消" - 兼容旧逻辑
|
||||
uni.hideKeyboard()
|
||||
isFocus.value = false
|
||||
active.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
emit("search", inputVal.value ? inputVal.value : props.placeholder)
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听当前是否有值 是否显示清除图标
|
||||
*/
|
||||
watch(inputVal, (newVal) => {
|
||||
newVal ? (isDelShow.value = true) : (isDelShow.value = false)
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
if (newVal !== inputVal.value) {
|
||||
inputVal.value = newVal || ''
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
isShowSeachGoods,
|
||||
inputVal,
|
||||
clear,
|
||||
setInputValue,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.serach {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
//border-bottom: 1px #f5f5f5 solid; //HM修改 去掉边框
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
font-size: $uni-font-size-base;
|
||||
font-size: 24rpx;
|
||||
|
||||
.left-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 15%;
|
||||
/* #ifndef APP-NVUE */
|
||||
text-align: center;
|
||||
// display: flex;
|
||||
// /* #endif */
|
||||
flex-shrink: 0;
|
||||
width: auto;
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
height: 70rpx;
|
||||
color: #999;
|
||||
background: #eee;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s linear;
|
||||
border-radius: 1000rpx;
|
||||
|
||||
.content-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 0 15rpx;
|
||||
|
||||
&.icon-serach:before {
|
||||
content: "\e61c";
|
||||
}
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
line-height: 60rpx;
|
||||
height: 60rpx;
|
||||
transition: all 0.2s linear;
|
||||
|
||||
&.center {
|
||||
width: 200rpx;
|
||||
}
|
||||
|
||||
&.sub {
|
||||
// position: absolute;
|
||||
width: auto;
|
||||
color: grey;
|
||||
}
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 72rpx;
|
||||
height: 70rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,16 +242,16 @@ export default {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
width: 0;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s linear;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
&.active {
|
||||
padding-left: 15rpx;
|
||||
width: 100rpx;
|
||||
padding-left: 12rpx;
|
||||
width: auto;
|
||||
min-width: 80rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<!-- 遮罩层 -->
|
||||
<u-popup @close="close" v-model="show" mode="bottom" border-radius="30" height="260rpx">
|
||||
<u-popup @close="close" v-model:show="show" mode="bottom" border-radius="30" height="260rpx">
|
||||
|
||||
<view class="share-title">
|
||||
<span>分享至</span>
|
||||
@@ -8,140 +8,145 @@
|
||||
<view class="share-list">
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view class="share-item">
|
||||
<button class="share-btn" open-type="share">
|
||||
<button class="share-btn" @click="weChatShare" open-type="share">
|
||||
<u-icon color="#04BE02" size="80" name="weixin-fill"></u-icon>微信好友
|
||||
</button>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<view class="share-item" @click="handleShare(item)" v-for="(item, index) in list" :key="index">
|
||||
<u-icon :color="item.color" size="80" :name="item.icon"></u-icon>
|
||||
<view>{{ item.title }}</view>
|
||||
<u-icon class="share-icon" :color="item.color" size="80" :name="item.icon"></u-icon>
|
||||
<view class="share-label">{{ item.title }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef H5 -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view class="share-item" @click="copyLink()">
|
||||
<u-icon color="#b4aee8" size="80" name="share-fill"></u-icon>
|
||||
<view>{{ '复制链接' }}</view>
|
||||
<u-icon class="share-icon" color="#b4aee8" size="80" name="share-fill"></u-icon>
|
||||
<view class="share-label">{{ '复制链接' }}</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
<script>
|
||||
import { h5Copy } from "@/js_sdk/h5-copy/h5-copy.js";
|
||||
import configs from "@/config/config";
|
||||
import mpShare from "uview-ui/libs/mixin/mpShare.js";
|
||||
|
||||
export default {
|
||||
mixins: [mpShare],
|
||||
data() {
|
||||
return {
|
||||
configs,
|
||||
show: true,
|
||||
list: [
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "微信好友",
|
||||
icon: "weixin-fill",
|
||||
type: 0,
|
||||
},
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "朋友圈",
|
||||
icon: "weixin-circle-fill",
|
||||
type: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import configs from "@/config/config"
|
||||
import mpShare from "@/utils/mpShare.js"
|
||||
import { setClipboard } from "@/utils/filters.js"
|
||||
|
||||
// mixin 处理:mpShare 提供小程序分享能力
|
||||
mpShare
|
||||
|
||||
const props = defineProps<{
|
||||
thumbnail?: string
|
||||
goodsName?: string
|
||||
type?: string
|
||||
goodsId?: string | number
|
||||
link?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const show = ref(true)
|
||||
const config = configs
|
||||
const list = ref([
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "微信好友",
|
||||
icon: "weixin-fill",
|
||||
type: 0,
|
||||
},
|
||||
// 图片缩略图、 商品名称 、 type(goods,shop,pintuan) 拼团商品分享以及店铺分享
|
||||
{
|
||||
color: "#04BE02",
|
||||
title: "朋友圈",
|
||||
icon: "weixin-circle-fill",
|
||||
type: 1,
|
||||
},
|
||||
])
|
||||
|
||||
props: ["thumbnail", "goodsName", "type", "goodsId", "link"],
|
||||
methods: {
|
||||
close() {
|
||||
this.$emit("close");
|
||||
},
|
||||
// h5复制链接
|
||||
// #ifdef H5
|
||||
copyLink() {
|
||||
let content;
|
||||
if (this.link) {
|
||||
content = this.configs.shareLink + this.link;
|
||||
} else {
|
||||
content =
|
||||
this.configs.shareLink +
|
||||
getCurrentPages()[getCurrentPages().length - 1].__page__.fullPath;
|
||||
}
|
||||
if (content === null || content === undefined) {
|
||||
content = "";
|
||||
} else content = content + "";
|
||||
const result = h5Copy(content);
|
||||
if (result === false) {
|
||||
const close = () => {
|
||||
emit("close")
|
||||
}
|
||||
|
||||
const weChatShare = () => {
|
||||
// @ts-ignore
|
||||
const proxy = getCurrentInstance()?.proxy
|
||||
if (proxy) {
|
||||
// @ts-ignore
|
||||
proxy.$u.mpShare = {
|
||||
title: shareTitle(),
|
||||
path: '',
|
||||
imageUrl: props.thumbnail || ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// h5复制链接
|
||||
// #ifdef H5
|
||||
const copyLink = () => {
|
||||
let content: string
|
||||
if (props.link) {
|
||||
content = config.shareLink + props.link
|
||||
} else {
|
||||
content =
|
||||
config.shareLink +
|
||||
getCurrentPages()[getCurrentPages().length - 1].__page__.fullPath
|
||||
}
|
||||
setClipboard(content)
|
||||
}
|
||||
// #endif
|
||||
|
||||
const shareTitle = (): string => {
|
||||
let shareTitle: string = ''
|
||||
if (props.type == "goods") {
|
||||
shareTitle = `[好友推荐]${props.goodsName}快来跟我一起看看吧`
|
||||
} else if (props.type == "shops") {
|
||||
shareTitle = `[好友发现]${props.goodsName}快来跟我一起看看吧`
|
||||
} else if (props.type == "pintuan") {
|
||||
shareTitle = `[好友邀请]${props.goodsName}快来跟我一起抢购吧!`
|
||||
} else if (props.type == "kanjia") {
|
||||
shareTitle = `[好友邀请]请快来帮我砍一刀${props.goodsName}`
|
||||
}
|
||||
return shareTitle
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const handleShare = (val: any) => {
|
||||
console.log("12312312")
|
||||
if (val.type <= 1) {
|
||||
let scene: string // "WXSenceTimeline 朋友圈 WXSceneSession 微信好友"
|
||||
val.type == 1
|
||||
? (scene = "WXSenceTimeline")
|
||||
: (scene = "WXSceneSession")
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: scene,
|
||||
href: config.shareLink + props.link,
|
||||
imageUrl: props.thumbnail,
|
||||
type: 0,
|
||||
summary: props.goodsName,
|
||||
title: shareTitle(),
|
||||
success: function () {
|
||||
uni.showToast({
|
||||
title: "不支持",
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "复制成功",
|
||||
title: "分享成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
|
||||
shareTitle() {
|
||||
let shareTitle;
|
||||
if (this.type == "goods") {
|
||||
shareTitle = `我发现了一个${this.goodsName}商品快来跟我一起看看吧`;
|
||||
} else if (this.type == "shops") {
|
||||
shareTitle = `我发现了一个${this.goodsName}店铺快来跟我一起看看吧`;
|
||||
} else if (this.type == "pintuan") {
|
||||
shareTitle = `我拼了一个${this.goodsName}快来跟我一起抢购吧!`;
|
||||
} else if (this.type == "kanjia") {
|
||||
shareTitle = `请快来帮我砍一刀${this.goodsName}`;
|
||||
}
|
||||
return shareTitle;
|
||||
},
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
handleShare(val) {
|
||||
console.log("12312312")
|
||||
if (val.type <= 1) {
|
||||
let scene; // "WXSenceTimeline 朋友圈 WXSceneSession 微信好友"
|
||||
val.type == 1
|
||||
? (scene = "WXSenceTimeline")
|
||||
: (scene = "WXSceneSession");
|
||||
uni.share({
|
||||
provider: "weixin",
|
||||
scene: scene,
|
||||
href: configs.shareLink + this.link,
|
||||
imageUrl: this.thumbnail,
|
||||
type: 0,
|
||||
summary: this.goodsName,
|
||||
title: this.shareTitle(),
|
||||
success: function (res) {
|
||||
uni.showToast({
|
||||
title: "分享成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
fail: function (err) {
|
||||
uni.showToast({
|
||||
title: "分享失败!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
this.$emit("close");
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
};
|
||||
})
|
||||
emit("close")
|
||||
},
|
||||
fail: function () {
|
||||
uni.showToast({
|
||||
title: "分享失败!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
})
|
||||
emit("close")
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "./mp-share.scss";
|
||||
@@ -158,7 +163,7 @@ export default {
|
||||
top: 30rpx;
|
||||
}
|
||||
}
|
||||
button:after {
|
||||
.share-btn:after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -171,9 +176,10 @@ button:after {
|
||||
width: 25%;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
> * {
|
||||
> .share-icon,
|
||||
> .share-label {
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
28
components/m-take-down-sale-goods/index.vue
Normal file
28
components/m-take-down-sale-goods/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<view>
|
||||
<u-popup v-model:show="show" mode="bottom" height="800rpx" border-radius="14">
|
||||
<view class="wrapper">
|
||||
<view class="down-goods-tips">该商品已下架</view>
|
||||
<scroll-view scroll-y="true" style="height: 670rpx">
|
||||
<goodsRecommend title="其他商品" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import goodsRecommend from "@/components/m-goods-recommend/index.vue"
|
||||
|
||||
const show = ref(true)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.down-goods-tips {
|
||||
font-size: 36rpx;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
padding: 40rpx 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,343 +0,0 @@
|
||||
<template>
|
||||
<view class="shadow" :class="!show?'':'shadow-show'" :style="{backgroundColor:show?maskBg:'rgba(0,0,0,0)'}" @tap="tapMask">
|
||||
<view class="popups" :class="[theme]" :style="{top: popupsTop ,left: popupsLeft,flexDirection:direction}">
|
||||
<text :class="dynPlace" :style="{width:'0px',height:'0px'}" v-if="triangle"></text>
|
||||
<view v-for="(item,index) in popData" :key="index" @tap.stop="tapItem(item)" class="itemChild view" :class="[direction=='row'?'solid-right':'solid-bottom',item.disabled?'disabledColor':'']">
|
||||
<u-icon size="35" :name="item.icon" v-if="item.icon"></u-icon><span class="title">{{item.title}}</span>
|
||||
</view>
|
||||
<slot></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
maskBg: {
|
||||
type: String,
|
||||
default: "rgba(0,0,0,0)",
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: "default", //default top-start top-end bottom-start bottom-end
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: "column", //column row
|
||||
},
|
||||
x: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
y: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: "light", //light dark
|
||||
},
|
||||
dynamic: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
gap: {
|
||||
type: Number,
|
||||
default: 20,
|
||||
},
|
||||
triangle: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
popupsTop: "0rpx",
|
||||
popupsLeft: "0rpx",
|
||||
show: false,
|
||||
dynPlace: "",
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.popupsPosition();
|
||||
},
|
||||
methods: {
|
||||
tapMask() {
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
tapItem(item) {
|
||||
if (item.disabled) return;
|
||||
this.$emit("tapPopup", item);
|
||||
this.$emit("input", !this.value);
|
||||
},
|
||||
getStatusBar() {
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
uni.getSystemInfo({
|
||||
success: function (e) {
|
||||
let customBar;
|
||||
// #ifdef H5
|
||||
|
||||
customBar = e.statusBarHeight + e.windowTop;
|
||||
|
||||
// #endif
|
||||
resolve(customBar);
|
||||
},
|
||||
});
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
async popupsPosition() {
|
||||
let statusBar = await this.getStatusBar();
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let popupsDom = uni.createSelectorQuery().in(this).select(".popups");
|
||||
popupsDom
|
||||
.fields(
|
||||
{
|
||||
size: true,
|
||||
},
|
||||
(data) => {
|
||||
let width = data.width;
|
||||
let height = data.height;
|
||||
|
||||
|
||||
|
||||
let y = this.dynamic
|
||||
? this.dynamicGetY(this.y, this.gap)
|
||||
: this.transformRpx(this.y);
|
||||
|
||||
let x = this.dynamic
|
||||
? this.dynamicGetX(this.x, this.gap)
|
||||
: this.transformRpx(this.x);
|
||||
|
||||
// #ifdef H5
|
||||
y = this.dynamic
|
||||
? this.y + statusBar
|
||||
: this.transformRpx(this.y + statusBar);
|
||||
// #endif
|
||||
|
||||
this.dynPlace =
|
||||
this.placement == "default"
|
||||
? this.getPlacement(x, y)
|
||||
: this.placement;
|
||||
|
||||
switch (this.dynPlace) {
|
||||
case "top-start":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "top-end":
|
||||
this.popupsTop = `${y + 9}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
case "bottom-start":
|
||||
this.popupsTop = `${y - 18 - height}rpx`;
|
||||
this.popupsLeft = `${x - 15}rpx`;
|
||||
break;
|
||||
case "bottom-end":
|
||||
this.popupsTop = `${y - 9 - height}rpx`;
|
||||
this.popupsLeft = `${x + 15 - width}rpx`;
|
||||
break;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
)
|
||||
.exec();
|
||||
});
|
||||
return promise;
|
||||
},
|
||||
getPlacement(x, y) {
|
||||
let width = uni.getSystemInfoSync().windowWidth;
|
||||
let height = uni.getSystemInfoSync().windowHeight;
|
||||
if (x > width / 2 && y > height / 2) {
|
||||
return "bottom-end";
|
||||
} else if (x < width / 2 && y < height / 2) {
|
||||
return "top-start";
|
||||
} else if (x > width / 2 && y < height / 2) {
|
||||
return "top-end";
|
||||
} else if (x < width / 2 && y > height / 2) {
|
||||
return "bottom-start";
|
||||
} else if (x > width / 2) {
|
||||
return "top-end";
|
||||
} else {
|
||||
return "top-start";
|
||||
}
|
||||
},
|
||||
dynamicGetY(y, gap) {
|
||||
let height = uni.getSystemInfoSync().windowHeight;
|
||||
y = y < gap ? gap : y;
|
||||
y = height - y < gap ? height - gap : y;
|
||||
|
||||
return y;
|
||||
},
|
||||
dynamicGetX(x, gap) {
|
||||
let width = uni.getSystemInfoSync().windowWidth;
|
||||
x = x < gap ? gap : x;
|
||||
x = width - x < gap ? width - gap : x;
|
||||
return x;
|
||||
},
|
||||
transformRpx(params) {
|
||||
return (params * uni.getSystemInfoSync().screenWidth) / 375;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
immediate: true,
|
||||
handler: async function (newVal, oldVal) {
|
||||
if (newVal) await this.popupsPosition();
|
||||
this.show = newVal;
|
||||
},
|
||||
},
|
||||
placement: {
|
||||
immediate: true,
|
||||
handler(newVal, oldVal) {
|
||||
this.dynPlace = newVal;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
.shadow {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
transition: background 0.3s ease-in-out;
|
||||
visibility: hidden;
|
||||
|
||||
&.shadow-show {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.popups {
|
||||
position: absolute;
|
||||
padding: 20rpx;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
.view {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 15rpx 10rpx;
|
||||
font-size: 25rpx;
|
||||
}
|
||||
.image {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
margin-right: 20rpx;
|
||||
}
|
||||
}
|
||||
.dark {
|
||||
background-color: #4c4c4c;
|
||||
color: #fff;
|
||||
.top-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #4c4c4c;
|
||||
}
|
||||
.top-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #4c4c4c;
|
||||
}
|
||||
.bottom-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #4c4c4c transparent transparent;
|
||||
}
|
||||
.bottom-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #4c4c4c transparent transparent;
|
||||
}
|
||||
.disabledColor {
|
||||
color: #c5c8ce;
|
||||
}
|
||||
}
|
||||
.light {
|
||||
color: #515a6e;
|
||||
box-shadow: 0upx 0upx 30upx rgba(0, 0, 0, 0.2);
|
||||
background: #fff;
|
||||
.top-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #fff;
|
||||
}
|
||||
.top-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 0 20rpx 20rpx;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent #fff;
|
||||
}
|
||||
.bottom-start:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
left: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #fff transparent transparent;
|
||||
}
|
||||
.bottom-end:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: -18rpx;
|
||||
right: 10rpx;
|
||||
border-width: 20rpx 20rpx 0;
|
||||
border-style: solid;
|
||||
border-color: #fff transparent transparent;
|
||||
}
|
||||
.disabledColor {
|
||||
color: #c5c8ce;
|
||||
}
|
||||
}
|
||||
.solid-bottom {
|
||||
border-bottom: 1px solid #f3f5f7;
|
||||
}
|
||||
.solid-right {
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
.popups .itemChild:last-child {
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
78
components/u-time-line/u-time-line-item.vue
Normal file
78
components/u-time-line/u-time-line-item.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<view class="u-time-line-item">
|
||||
<view class="u-time-line-item__node" :style="nodeStyle">
|
||||
<slot name="node">
|
||||
<view class="u-time-line-item__dot" :style="{ backgroundColor: bgColor }"></view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="u-time-line-item__content">
|
||||
<slot name="content" />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
nodeTop?: string | number
|
||||
bgColor?: string
|
||||
}>()
|
||||
|
||||
const nodeStyle = computed(() => {
|
||||
if (props.nodeTop === '' || props.nodeTop === null || props.nodeTop === undefined) {
|
||||
return {}
|
||||
}
|
||||
const top = typeof props.nodeTop === 'number' ? `${props.nodeTop}rpx` : props.nodeTop
|
||||
return { top }
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-time-line-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
position: relative;
|
||||
padding-bottom: 32rpx;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&__node {
|
||||
position: relative;
|
||||
width: 32rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
padding-left: 12rpx;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15rpx;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
&:last-child::before {
|
||||
bottom: auto;
|
||||
height: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
16
components/u-time-line/u-time-line.vue
Normal file
16
components/u-time-line/u-time-line.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<template>
|
||||
<view class="u-time-line">
|
||||
<slot />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// UTimeLine 组件
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.u-time-line {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@@ -2,22 +2,22 @@
|
||||
<view class="uni-load-more">
|
||||
<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
|
||||
<view class="load1">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
</view>
|
||||
<view class="load2">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
</view>
|
||||
<view class="load3">
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
<view class="load-item" :style="{ background: color }"></view>
|
||||
</view>
|
||||
</view>
|
||||
<text class="uni-load-more__text" :style="{ color: color }" v-if="showText">
|
||||
@@ -26,42 +26,28 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'uni-load-more',
|
||||
props: {
|
||||
status: {
|
||||
//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
|
||||
type: String,
|
||||
default: 'more'
|
||||
},
|
||||
showIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showText: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#777777'
|
||||
},
|
||||
contentText: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了'
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {};
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{
|
||||
status?: string
|
||||
showIcon?: boolean
|
||||
showText?: boolean
|
||||
color?: string
|
||||
contentText?: {
|
||||
contentdown: string
|
||||
contentrefresh: string
|
||||
contentnomore: string
|
||||
}
|
||||
};
|
||||
}>(), {
|
||||
status: 'more',
|
||||
showIcon: true,
|
||||
showText: true,
|
||||
color: '#777777',
|
||||
contentText: () => ({
|
||||
contentdown: '上拉显示更多',
|
||||
contentrefresh: '正在加载...',
|
||||
contentnomore: '没有更多数据了',
|
||||
}),
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -86,11 +72,13 @@ export default {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view {
|
||||
.load1,
|
||||
.load2,
|
||||
.load3 {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view {
|
||||
.load-item {
|
||||
width: 6px;
|
||||
height: 2px;
|
||||
border-top-left-radius: 1px;
|
||||
@@ -102,25 +90,25 @@ export default {
|
||||
animation: load 1.56s ease infinite;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(1) {
|
||||
.load-item:nth-child(1) {
|
||||
transform: rotate(90deg);
|
||||
top: 2px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(2) {
|
||||
.load-item:nth-child(2) {
|
||||
transform: rotate(180deg);
|
||||
top: 11px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(3) {
|
||||
.load-item:nth-child(3) {
|
||||
transform: rotate(270deg);
|
||||
bottom: 2px;
|
||||
left: 9px;
|
||||
}
|
||||
|
||||
.uni-load-more__img > view view:nth-child(4) {
|
||||
.load-item:nth-child(4) {
|
||||
top: 11px;
|
||||
left: 0;
|
||||
}
|
||||
@@ -140,51 +128,51 @@ export default {
|
||||
transform: rotate(60deg);
|
||||
}
|
||||
|
||||
.load1 view:nth-child(1) {
|
||||
.load1 .load-item:nth-child(1) {
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(1) {
|
||||
.load2 .load-item:nth-child(1) {
|
||||
animation-delay: 0.13s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(1) {
|
||||
.load3 .load-item:nth-child(1) {
|
||||
animation-delay: 0.26s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(2) {
|
||||
.load1 .load-item:nth-child(2) {
|
||||
animation-delay: 0.39s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(2) {
|
||||
.load2 .load-item:nth-child(2) {
|
||||
animation-delay: 0.52s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(2) {
|
||||
.load3 .load-item:nth-child(2) {
|
||||
animation-delay: 0.65s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(3) {
|
||||
.load1 .load-item:nth-child(3) {
|
||||
animation-delay: 0.78s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(3) {
|
||||
.load2 .load-item:nth-child(3) {
|
||||
animation-delay: 0.91s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(3) {
|
||||
.load3 .load-item:nth-child(3) {
|
||||
animation-delay: 1.04s;
|
||||
}
|
||||
|
||||
.load1 view:nth-child(4) {
|
||||
.load1 .load-item:nth-child(4) {
|
||||
animation-delay: 1.17s;
|
||||
}
|
||||
|
||||
.load2 view:nth-child(4) {
|
||||
.load2 .load-item:nth-child(4) {
|
||||
animation-delay: 1.3s;
|
||||
}
|
||||
|
||||
.load3 view:nth-child(4) {
|
||||
.load3 .load-item:nth-child(4) {
|
||||
animation-delay: 1.43s;
|
||||
}
|
||||
|
||||
|
||||
192
components/uni-number-box.vue
Normal file
192
components/uni-number-box.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<view class="uni-numbox">
|
||||
<view @click="_calcValue('minus')" class="uni-numbox__minus uni-numbox-btns" :style="{background}">
|
||||
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue <= min || disabled }" :style="{color}">-</text>
|
||||
</view>
|
||||
<input :disabled="disabled" @focus="_onFocus" @blur="_onBlur" class="uni-numbox__value" type="number"
|
||||
v-model="inputValue" :style="{background, color}" />
|
||||
<view @click="_calcValue('plus')" class="uni-numbox__plus uni-numbox-btns" :style="{background}">
|
||||
<text class="uni-numbox--text" :class="{ 'uni-numbox--disabled': inputValue >= max || disabled }" :style="{color}">+</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
value?: number | string
|
||||
modelValue?: number | string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
background?: string
|
||||
color?: string
|
||||
disabled?: boolean
|
||||
}>(), {
|
||||
value: 1,
|
||||
modelValue: 1,
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1,
|
||||
background: '#f5f5f5',
|
||||
color: '#333',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
change: [value: number]
|
||||
input: [value: number]
|
||||
'update:modelValue': [value: number]
|
||||
blur: [event: Event]
|
||||
focus: [event: Event]
|
||||
}>()
|
||||
|
||||
const inputValue = ref(0)
|
||||
|
||||
watch(() => props.value, (val) => {
|
||||
inputValue.value = +val
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
inputValue.value = +val
|
||||
})
|
||||
|
||||
if (props.value === 1) {
|
||||
inputValue.value = +props.modelValue
|
||||
}
|
||||
if (props.modelValue === 1) {
|
||||
inputValue.value = +props.value
|
||||
}
|
||||
|
||||
function _getDecimalScale() {
|
||||
let scale = 1
|
||||
if (~~props.step !== props.step) {
|
||||
scale = Math.pow(10, String(props.step).split('.')[1].length)
|
||||
}
|
||||
return scale
|
||||
}
|
||||
|
||||
function _emitValue() {
|
||||
const val = +inputValue.value
|
||||
emit('change', val)
|
||||
emit('input', val)
|
||||
emit('update:modelValue', val)
|
||||
}
|
||||
|
||||
function _calcValue(type: 'minus' | 'plus') {
|
||||
if (props.disabled) {
|
||||
return
|
||||
}
|
||||
const scale = _getDecimalScale()
|
||||
let value = inputValue.value * scale
|
||||
const step = props.step * scale
|
||||
if (type === 'minus') {
|
||||
value -= step
|
||||
if (value < props.min * scale) {
|
||||
return
|
||||
}
|
||||
if (value > props.max * scale) {
|
||||
value = props.max * scale
|
||||
}
|
||||
}
|
||||
|
||||
if (type === 'plus') {
|
||||
value += step
|
||||
if (value > props.max * scale) {
|
||||
return
|
||||
}
|
||||
if (value < props.min * scale) {
|
||||
value = props.min * scale
|
||||
}
|
||||
}
|
||||
|
||||
inputValue.value = Number((value / scale).toFixed(String(scale).length - 1))
|
||||
_emitValue()
|
||||
}
|
||||
|
||||
function _onBlur(event: any) {
|
||||
emit('blur', event)
|
||||
let value = event.detail.value
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
value = +value
|
||||
if (value > props.max) {
|
||||
value = props.max
|
||||
} else if (value < props.min) {
|
||||
value = props.min
|
||||
}
|
||||
const scale = _getDecimalScale()
|
||||
inputValue.value = Number(value.toFixed(String(scale).length - 1))
|
||||
_emitValue()
|
||||
}
|
||||
|
||||
function _onFocus(event: Event) {
|
||||
emit('focus', event)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$box-height: 48rpx;
|
||||
$bg: #f5f5f5;
|
||||
$br: 4rpx;
|
||||
$color: #333;
|
||||
|
||||
.uni-numbox {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.uni-numbox-btns {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
background-color: $bg;
|
||||
/* #ifdef H5 */
|
||||
cursor: pointer;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.uni-numbox__value {
|
||||
margin: 0 4rpx;
|
||||
background-color: $bg;
|
||||
width: 70rpx;
|
||||
height: $box-height;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.uni-numbox__minus {
|
||||
border-top-left-radius: $br;
|
||||
border-bottom-left-radius: $br;
|
||||
}
|
||||
|
||||
.uni-numbox__plus {
|
||||
border-top-right-radius: $br;
|
||||
border-bottom-right-radius: $br;
|
||||
}
|
||||
|
||||
.uni-numbox--text {
|
||||
line-height: 40rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 300;
|
||||
color: $color;
|
||||
}
|
||||
|
||||
.uni-numbox .uni-numbox--disabled {
|
||||
color: #c0c0c0 !important;
|
||||
/* #ifdef H5 */
|
||||
cursor: not-allowed;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
||||
99
components/uniMap.vue
Normal file
99
components/uniMap.vue
Normal file
@@ -0,0 +1,99 @@
|
||||
<template></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue'
|
||||
import { getAddressCode } from '@/api/address'
|
||||
import config from '@/config/config'
|
||||
|
||||
const emit = defineEmits<{
|
||||
callback: [payload: Record<string, any>]
|
||||
close: []
|
||||
}>()
|
||||
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
|
||||
function initMap() {
|
||||
uni.chooseLocation({
|
||||
success(res) {
|
||||
posToCity(res.latitude, res.longitude).then((val) => {
|
||||
getAddressCode(
|
||||
val.regeocode.addressComponent.citycode,
|
||||
val.regeocode.addressComponent.township,
|
||||
).then((code) => {
|
||||
emit('callback', { ...val, ...res, ...code })
|
||||
emit('close')
|
||||
})
|
||||
})
|
||||
},
|
||||
fail(e) {
|
||||
console.log(e)
|
||||
emit('close')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function init() {
|
||||
// #ifdef MP-WEIXIN
|
||||
wechatMap()
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
initMap()
|
||||
// #endif
|
||||
}
|
||||
|
||||
function wechatMap() {
|
||||
uni.authorize({
|
||||
scope: 'scope.userLocation',
|
||||
success() {
|
||||
initMap()
|
||||
},
|
||||
fail() {
|
||||
uni.showModal({
|
||||
content: '检测到您没打开获取地址功能权限,是否去设置打开?',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
uni.openSetting({
|
||||
success: () => {
|
||||
initMap()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
emit('close')
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function posToCity(latitude: number, longitude: number) {
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
uni.request({
|
||||
url: 'https://restapi.amap.com/v3/geocode/regeo',
|
||||
method: 'GET',
|
||||
data: {
|
||||
key: config.aMapKey,
|
||||
location: `${longitude},${latitude}`,
|
||||
},
|
||||
success: ({ data }: any) => {
|
||||
const { status, info } = data
|
||||
if (status === '1') {
|
||||
resolve(data)
|
||||
} else {
|
||||
reject(info)
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err)
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -1,230 +1,350 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-if="!hid" class="flex-row-center" :style="{ top: scHight }" style="width: 750rpx; position: fixed; z-index: 100; left: 0">
|
||||
<view class="flex-column-center" style="background-color: #fcfcfc; padding: 30rpx; border-radius: 10rpx">
|
||||
<movable-area class="flex" style="width: 100%" animation="false" :style="{ height: originalHeight }">
|
||||
<movable-view scale-value="1" animation="false" damping="5000" :x="moveX" :style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
<view
|
||||
v-if="!state.hid"
|
||||
class="flex-row-center"
|
||||
:style="{ top: state.scHight }"
|
||||
style="width: 750rpx; position: fixed; z-index: 100; left: 0; background-color: #ffffff; padding: 40rpx 0"
|
||||
>
|
||||
<view class="flex-column-center">
|
||||
<movable-area
|
||||
class="flex"
|
||||
style="width: 100%"
|
||||
animation="false"
|
||||
:style="{ height: state.originalHeight }"
|
||||
>
|
||||
<movable-view
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="5000"
|
||||
:x="state.moveX"
|
||||
:style="{
|
||||
height: state.sliderHeight,
|
||||
width: state.sliderWidth,
|
||||
'z-index': 101,
|
||||
}" direction="horizontal">
|
||||
<image :src="imgbk" class="image" mode="aspectFit" :style="{
|
||||
height: sliderHeight,
|
||||
width: sliderWidth,
|
||||
'margin-top': imgbKH,
|
||||
}"></image>
|
||||
}"
|
||||
direction="horizontal"
|
||||
>
|
||||
<image
|
||||
:src="state.imgbk"
|
||||
class="image"
|
||||
mode="aspectFit"
|
||||
:style="{
|
||||
height: state.sliderHeight,
|
||||
width: state.sliderWidth,
|
||||
'margin-top': state.imgbKH,
|
||||
}"
|
||||
></image>
|
||||
</movable-view>
|
||||
<image :src="img" mode="aspectFit" :style="{ height: originalHeight, width: originalWidth }" style="border-radius: 10rpx"></image>
|
||||
<image
|
||||
:src="state.img"
|
||||
mode="aspectFit"
|
||||
:style="{ height: state.originalHeight, width: state.originalWidth }"
|
||||
style="border-radius: 24rpx"
|
||||
></image>
|
||||
</movable-area>
|
||||
|
||||
<movable-area class="flex-row-start" style="
|
||||
width: 100%;
|
||||
background-color: #efefef;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
margin-top: 30rpx;
|
||||
">
|
||||
<movable-view scale-value="1" animation="false" damping="50" :x="movePv" class="flex-row-center" style="
|
||||
border-radius: 50%;
|
||||
height: 100rpx;
|
||||
width: 100rpx;
|
||||
background-color: #ffffff;
|
||||
border: 2rpx solid #e3e3e3;
|
||||
margin-top: -13rpx;
|
||||
" direction="horizontal" @change="moveChange" @touchend="end">
|
||||
<view :class="endLoad ? 'cuIcon-right' : 'cuIcon-loading turn-load'" class="loadIcon" style="">
|
||||
</view>
|
||||
</movable-view>
|
||||
|
||||
<text style="padding-left: 140rpx" :style="{ color: col }">{{
|
||||
hasImg
|
||||
<movable-area class="v-track" animation="false">
|
||||
<view class="v-fill" :style="{ width: fillWidth }"></view>
|
||||
<text class="v-track-text" :style="{ color: state.col }">{{
|
||||
state.hasImg
|
||||
}}</text>
|
||||
<movable-view
|
||||
scale-value="1"
|
||||
animation="false"
|
||||
damping="50"
|
||||
:x="state.movePv"
|
||||
class="v-handle"
|
||||
:style="handleStyle"
|
||||
direction="horizontal"
|
||||
@change="moveChange"
|
||||
@touchend="end"
|
||||
>
|
||||
<u-icon
|
||||
color="#ffffff"
|
||||
size="38"
|
||||
v-if="state.endLoad"
|
||||
name="arrow-right"
|
||||
></u-icon>
|
||||
<u-icon color="#ffffff" size="38" v-else name="reload"></u-icon>
|
||||
</movable-view>
|
||||
</movable-area>
|
||||
<view class="flex-row-around padding-top" style="width: 100%">
|
||||
<view @click="hide" class="cuIcon-close" style="font-size: 50rpx; color: #e25915">
|
||||
</view>
|
||||
|
||||
<text class="cu-tag bg-cyan round" @click="getCode">刷新拼图</text>
|
||||
<text class="my-neirong-sm cuIcon-safe" style="color: #c1c1c1">Lili-FRAMEWORK</text>
|
||||
<view class="v-foot">
|
||||
<view class="v-icon-btn" @click="getCode">
|
||||
<u-icon color="#888888" size="32" name="reload"></u-icon>
|
||||
</view>
|
||||
<view class="v-icon-btn" @click="hide">
|
||||
<u-icon color="#888888" size="30" name="close"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="v-mark">
|
||||
<text class="cuIcon-safe v-mark-icon"></text>
|
||||
<text>Lili-FRAMEWORK 安全验证</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import api from "@/config/api.js";
|
||||
import storage from "@/utils/storage.js";
|
||||
import uuid from "@/utils/uuid.modified.js";
|
||||
const phone = uni.getSystemInfoSync();
|
||||
const l = phone.screenWidth / 750;
|
||||
export default {
|
||||
name: "verification",
|
||||
created() {
|
||||
// 可自行调整
|
||||
this.scHight = phone.screenHeight / 2 - 200 + "px";
|
||||
this.getCode();
|
||||
},
|
||||
props: {
|
||||
height: {
|
||||
type: String,
|
||||
default: "80rpx",
|
||||
<script setup lang="ts">
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import api from '@/config/api.js'
|
||||
import storage from '@/utils/storage.js'
|
||||
import uuid from '@/utils/uuid.modified.js'
|
||||
|
||||
const emit = defineEmits(['send'])
|
||||
const props = withDefaults(defineProps<{
|
||||
height?: string
|
||||
width?: string
|
||||
left?: string
|
||||
top?: string
|
||||
business?: string
|
||||
}>(), {
|
||||
height: '80rpx',
|
||||
width: '350rpx',
|
||||
left: '180rpx',
|
||||
top: '30rpx',
|
||||
business: 'LOGIN'
|
||||
})
|
||||
|
||||
const store = useStore()
|
||||
const phone = uni.getSystemInfoSync()
|
||||
const l = phone.screenWidth / 750
|
||||
|
||||
const mainColor = computed(() => store.getters.mainColor)
|
||||
const fillWidth = computed(() => `${state.moveX + 42 * l}px`)
|
||||
const handleStyle = computed(() => {
|
||||
const glow = /^#[0-9a-fA-F]{6}$/.test(mainColor.value)
|
||||
? mainColor.value + '59'
|
||||
: 'rgba(0, 0, 0, 0.18)'
|
||||
return { background: mainColor.value, boxShadow: `0 6rpx 20rpx ${glow}` }
|
||||
})
|
||||
const state = reactive({
|
||||
flage: false,
|
||||
key: '', // key
|
||||
vsrtx: '点击进行验证', // 按钮提示语
|
||||
vsr: false,
|
||||
hid: true,
|
||||
col: '#838383',
|
||||
movePv: 0,
|
||||
hasImg: '拖动滑块已完成拼图',
|
||||
spcode: '',
|
||||
tl: 0,
|
||||
moveCode: 0,
|
||||
// X轴移动距离
|
||||
moveX: 0,
|
||||
// 模版高度
|
||||
originalHeight: '',
|
||||
// 模版宽度
|
||||
originalWidth: '',
|
||||
// 拼图高度
|
||||
sliderHeight: '',
|
||||
// 平涂宽度
|
||||
sliderWidth: '',
|
||||
scHight: 0,
|
||||
// 原图
|
||||
img: '',
|
||||
// 拼图
|
||||
imgbk: '',
|
||||
endLoad: true,
|
||||
imgbKH: ''
|
||||
})
|
||||
|
||||
// 可自行调整
|
||||
state.scHight = phone.screenHeight / 2 - 200 + 'px'
|
||||
getCode()
|
||||
|
||||
function show() {
|
||||
state.hid = false
|
||||
}
|
||||
|
||||
function hide() {
|
||||
if (!state.vsr) {
|
||||
// vsr判断是否验证成功,成功隐藏验证框
|
||||
state.hid = !state.hid
|
||||
}
|
||||
}
|
||||
|
||||
function error() {
|
||||
state.vsr = false
|
||||
state.hid = false
|
||||
state.moveX = 0
|
||||
state.moveCode = 0
|
||||
}
|
||||
|
||||
// 获取验证图片
|
||||
function getCode() {
|
||||
state.col = '#b3afae'
|
||||
state.hasImg = '图片加载中...'
|
||||
if (!storage.getUuid()) {
|
||||
storage.setUuid(uuid.v1())
|
||||
}
|
||||
uni.request({
|
||||
url: api.buyer + '/common/slider/' + props.business,
|
||||
header: {
|
||||
uuid: storage.getUuid()
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: "350rpx",
|
||||
success: (res: any) => {
|
||||
state.col = '#838383'
|
||||
state.hasImg = '拖动滑块以完成拼图'
|
||||
const data = res.data.result
|
||||
|
||||
// base64的图片
|
||||
state.img = data.backImage
|
||||
state.imgbk = data.slidingImage
|
||||
// 根据参数动态适应验证图片的高宽
|
||||
state.imgbKH = data.randomY * 1.8 + 'rpx'
|
||||
state.originalHeight = data.originalHeight * 1.8 + 'rpx'
|
||||
state.originalWidth = data.originalWidth * 1.8 + 'rpx'
|
||||
state.sliderHeight = data.sliderHeight * 1.8 + 'rpx'
|
||||
state.sliderWidth = data.sliderWidth * 1.8 + 'rpx'
|
||||
// 适应比率,用来适应滑动距离
|
||||
state.tl = 1 / (1.8 * l)
|
||||
// 无用信息
|
||||
state.spcode = data.capcode
|
||||
// 验证令牌
|
||||
state.key = data.key
|
||||
store.state.verificationKey = data.key
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function end() {
|
||||
state.endLoad = false
|
||||
// 验证拼图位置是否正确
|
||||
uni.request({
|
||||
method: 'POST',
|
||||
url:
|
||||
api.buyer +
|
||||
'/common/slider/' +
|
||||
props.business +
|
||||
'?xPos=' +
|
||||
parseInt(String(state.moveCode * state.tl)),
|
||||
header: {
|
||||
uuid: storage.getUuid()
|
||||
},
|
||||
left: {
|
||||
type: String,
|
||||
default: "180rpx",
|
||||
},
|
||||
top: {
|
||||
type: String,
|
||||
default: "30rpx",
|
||||
},
|
||||
business: {
|
||||
type: String,
|
||||
default: "LOGIN",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
flage: false,
|
||||
key: "", //key
|
||||
vsrtx: "点击进行验证", //按钮提示语
|
||||
vsr: false, //
|
||||
hid: true,
|
||||
col: "#838383",
|
||||
movePv: 0,
|
||||
hasImg: "拖动滑块已完成拼图",
|
||||
spcode: "",
|
||||
tl: 0,
|
||||
moveCode: 0,
|
||||
//X轴移动距离
|
||||
moveX: 0,
|
||||
//模版高度
|
||||
originalHeight: "",
|
||||
//模版宽度
|
||||
originalWidth: "",
|
||||
//拼图高度
|
||||
sliderHeight: "",
|
||||
//平涂宽度
|
||||
sliderWidth: "",
|
||||
scHight: 0,
|
||||
//原图
|
||||
img: "",
|
||||
//拼图
|
||||
imgbk: "",
|
||||
endLoad: true,
|
||||
imgbKH: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
show() {
|
||||
this.hid = false;
|
||||
},
|
||||
hide() {
|
||||
if (!this.vsr) {
|
||||
// vsr判断是否验证成功,成功隐藏验证框
|
||||
this.hid = !this.hid;
|
||||
success: (res: any) => {
|
||||
state.endLoad = true
|
||||
if (res.data.result == false) {
|
||||
res.data.result = false
|
||||
} else {
|
||||
res.data.result = true
|
||||
}
|
||||
|
||||
if (res.data && res.data.result) {
|
||||
// 验证成功后把key发送出去,后端会把验证信息存在缓存里
|
||||
emit('send', state.key)
|
||||
hide()
|
||||
state.vsr = true
|
||||
state.vsrtx = '已通过验证'
|
||||
} else {
|
||||
getCode() // 让滑块回到起始位置
|
||||
if (state.movePv == 1) {
|
||||
state.movePv = 0
|
||||
} else {
|
||||
state.movePv = 1
|
||||
}
|
||||
}
|
||||
},
|
||||
error() {
|
||||
this.vsr = false;
|
||||
this.hid = false;
|
||||
this.moveX = 0;
|
||||
this.moveCode = 0;
|
||||
},
|
||||
// 获取验证图片
|
||||
getCode() {
|
||||
this.col = "#b3afae";
|
||||
this.hasImg = "图片加载中...";
|
||||
if (!storage.getUuid()) {
|
||||
storage.setUuid(uuid.v1());
|
||||
}
|
||||
uni.request({
|
||||
url: api.common + "/slider/" + this.business,
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.col = "#838383";
|
||||
this.hasImg = "拖动滑块以完成拼图";
|
||||
var data = res.data.result;
|
||||
fail: () => {
|
||||
uni.showToast({ title: '连接服务器失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// base64的图片
|
||||
this.img = data.backImage;
|
||||
this.imgbk = data.slidingImage;
|
||||
// 根据参数动态适应验证图片的高宽
|
||||
this.imgbKH = data.randomY * 1.8 + "rpx";
|
||||
this.originalHeight = data.originalHeight * 1.8 + "rpx";
|
||||
this.originalWidth = data.originalWidth * 1.8 + "rpx";
|
||||
this.sliderHeight = data.sliderHeight * 1.8 + "rpx";
|
||||
this.sliderWidth = data.sliderWidth * 1.8 + "rpx";
|
||||
// 适应比率,用来适应滑动距离
|
||||
this.tl = 1 / (1.8 * l);
|
||||
// 无用信息
|
||||
this.spcode = data.capcode;
|
||||
// 验证令牌
|
||||
this.key = data.key;
|
||||
this.$store.state.verificationKey = data.key;
|
||||
},
|
||||
});
|
||||
},
|
||||
end(e) {
|
||||
this.endLoad = false;
|
||||
// 验证拼图位置是否正确
|
||||
uni.request({
|
||||
method: "POST",
|
||||
url:
|
||||
api.common +
|
||||
"/slider/" +
|
||||
this.business +
|
||||
"?xPos=" +
|
||||
parseInt(this.moveCode * this.tl),
|
||||
header: {
|
||||
uuid: storage.getUuid(),
|
||||
},
|
||||
success: (res) => {
|
||||
this.endLoad = true;
|
||||
res.data.result == false
|
||||
? (res.data.result = false)
|
||||
: (res.data.result = true);
|
||||
// 绑定拼图位置
|
||||
function moveChange(e: any) {
|
||||
state.moveX = e.detail.x
|
||||
state.moveCode = e.detail.x
|
||||
}
|
||||
|
||||
if (res.data && res.data.result) {
|
||||
//验证成功后把key发送出去,后端会把验证信息存在缓存里
|
||||
this.$emit("send", this.key);
|
||||
this.hide();
|
||||
this.vsr = true;
|
||||
this.vsrtx = "已通过验证";
|
||||
} else {
|
||||
this.getCode(); // 让滑块回到起始位置
|
||||
if (this.movePv == 1) {
|
||||
this.movePv = 0;
|
||||
} else {
|
||||
this.movePv = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
this.$msg("连接服务器失败");
|
||||
},
|
||||
});
|
||||
},
|
||||
// 绑定拼图位置
|
||||
moveChange(e) {
|
||||
this.moveX = e.detail.x;
|
||||
this.moveCode = e.detail.x;
|
||||
},
|
||||
},
|
||||
};
|
||||
defineExpose({
|
||||
show,
|
||||
hide,
|
||||
error,
|
||||
getCode,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./animation.css";
|
||||
@import "./icon.css";
|
||||
// @import './main.css';
|
||||
|
||||
.v-track {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 84rpx;
|
||||
margin-top: 36rpx;
|
||||
background-color: #f7f7f8;
|
||||
border-radius: 42rpx;
|
||||
}
|
||||
|
||||
.v-fill {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: #ffe9e5;
|
||||
border-radius: 42rpx;
|
||||
}
|
||||
|
||||
.v-track-text {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
line-height: 84rpx;
|
||||
text-align: center;
|
||||
font-size: 26rpx;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.v-handle {
|
||||
width: 84rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.v-foot {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
margin-top: 28rpx;
|
||||
}
|
||||
|
||||
.v-icon-btn {
|
||||
width: 68rpx;
|
||||
height: 68rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #f4f4f5;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
.v-mark {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 32rpx;
|
||||
font-size: 22rpx;
|
||||
color: #d2d2d2;
|
||||
}
|
||||
|
||||
.v-mark-icon {
|
||||
margin-right: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #d9d9d9;
|
||||
}
|
||||
.dh-wt {
|
||||
animation: at 1.1s ease;
|
||||
animation-iteration-count: infinite;
|
||||
@@ -252,10 +372,6 @@ export default {
|
||||
.border-index {
|
||||
border: 1rpx solid $main-color;
|
||||
}
|
||||
.loadIcon {
|
||||
color: $main-color;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.status_bar {
|
||||
height: var(--status-bar-height);
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
<view class="xt__verify-code">
|
||||
<!-- 输入框 -->
|
||||
<input
|
||||
ref="codeInput"
|
||||
id="xt__input"
|
||||
:value="code"
|
||||
class="xt__input"
|
||||
:focus="isFocus"
|
||||
:focus="focused"
|
||||
:password="isPassword"
|
||||
:type="inputType"
|
||||
:maxlength="size"
|
||||
@input="input"
|
||||
@input="handleInput"
|
||||
@focus="inputFocus"
|
||||
@blur="inputBlur"
|
||||
/>
|
||||
@@ -23,21 +24,20 @@
|
||||
></view>
|
||||
|
||||
<!-- 输入框 - 组 -->
|
||||
<view id="xt__input-ground" class="xt__input-ground">
|
||||
<template v-for="(item, index) in size">
|
||||
<view id="xt__input-ground" class="xt__input-ground" @click="focusInput">
|
||||
<template v-for="(item, index) in size" :key="index">
|
||||
<view
|
||||
:key="index"
|
||||
:style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
|
||||
:class="['xt__box', `xt__box-${type + ''}`, `xt__box::after`]"
|
||||
>
|
||||
<view :style="{ borderColor: boxActiveColor }" class="xt__middle-line" v-if="type === 'middle' && !code[index]"></view>
|
||||
<text class="xt__code-text">{{ code[index] | codeFormat(isPassword) }}</text>
|
||||
<text class="xt__code-text">{{ codeFormat(code[index], isPassword) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* @description 输入验证码组件
|
||||
* @property {string} type = [box|middle|bottom] - 显示类型 默认:box -eg:bottom
|
||||
@@ -50,151 +50,156 @@
|
||||
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认:#000000
|
||||
* @event {Function(data)} confirm - 输入完成
|
||||
*/
|
||||
export default {
|
||||
name: 'xt-verify-code',
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: () => 'box'
|
||||
},
|
||||
inputType: {
|
||||
type: String,
|
||||
default: () => 'number'
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: () => 6
|
||||
},
|
||||
isFocus: {
|
||||
type: Boolean,
|
||||
default: () => true
|
||||
},
|
||||
isPassword: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
cursorColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxNormalColor: {
|
||||
type: String,
|
||||
default: () => '#cccccc'
|
||||
},
|
||||
boxActiveColor: {
|
||||
type: String,
|
||||
default: () => '#000000'
|
||||
}
|
||||
},
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'input'
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
cursorVisible: false,
|
||||
cursorHeight: 35,
|
||||
code: '', // 输入的验证码
|
||||
codeCursorLeft: [] // 向左移动的距离数组
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.cursorVisible = this.isFocus;
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* @description 初始化
|
||||
*/
|
||||
init() {
|
||||
this.getCodeCursorLeft();
|
||||
this.setCursorHeight();
|
||||
},
|
||||
/**
|
||||
* @description 获取元素节点
|
||||
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
|
||||
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
getElement(elm, type = 'single', callback) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
[type === 'array' ? 'selectAll' : 'select'](elm)
|
||||
.boundingClientRect()
|
||||
.exec(data => {
|
||||
callback(data[0]);
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 计算光标的高度
|
||||
*/
|
||||
setCursorHeight() {
|
||||
this.getElement('.xt__box', 'single', boxElm => {
|
||||
this.cursorHeight = boxElm.height * 0.6;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 获取光标在每一个box的left位置
|
||||
*/
|
||||
getCodeCursorLeft() {
|
||||
// 获取父级框的位置信息
|
||||
this.getElement('#xt__input-ground', 'single', parentElm => {
|
||||
const parentLeft = parentElm.left;
|
||||
// 获取各个box信息
|
||||
this.getElement('.xt__box', 'array', elms => {
|
||||
this.codeCursorLeft = [];
|
||||
elms.forEach(elm => {
|
||||
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
import { ref, watch, onMounted, nextTick, getCurrentInstance } from 'vue'
|
||||
|
||||
// 输入框输入变化的回调
|
||||
input(e) {
|
||||
const value = e.detail.value;
|
||||
this.cursorVisible = value.length !== this.size;
|
||||
this.$emit('input', value);
|
||||
this.inputSuccess(value);
|
||||
},
|
||||
const emit = defineEmits(['update:modelValue', 'input', 'confirm'])
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue?: string
|
||||
value?: string
|
||||
type?: string
|
||||
inputType?: string
|
||||
size?: number
|
||||
isFocus?: boolean
|
||||
isPassword?: boolean
|
||||
cursorColor?: string
|
||||
boxNormalColor?: string
|
||||
boxActiveColor?: string
|
||||
}>(), {
|
||||
modelValue: '',
|
||||
value: '',
|
||||
type: 'box',
|
||||
inputType: 'number',
|
||||
size: 6,
|
||||
isFocus: true,
|
||||
isPassword: false,
|
||||
cursorColor: '#cccccc',
|
||||
boxNormalColor: '#cccccc',
|
||||
boxActiveColor: '#000000'
|
||||
})
|
||||
|
||||
// 输入完成回调
|
||||
inputSuccess(value) {
|
||||
if (value.length === this.size) {
|
||||
this.$emit('confirm', value);
|
||||
}
|
||||
},
|
||||
// 输入聚焦
|
||||
inputFocus() {
|
||||
this.cursorVisible = this.code.length !== this.size;
|
||||
},
|
||||
// 输入失去焦点
|
||||
inputBlur() {
|
||||
this.cursorVisible = false;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(val) {
|
||||
this.code = val;
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
codeFormat(val, isPassword) {
|
||||
let value = '';
|
||||
if (val) {
|
||||
value = isPassword ? '*' : val;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
const instance = getCurrentInstance()
|
||||
const codeInput = ref<any>(null)
|
||||
const focused = ref(false)
|
||||
const cursorVisible = ref(false)
|
||||
const cursorHeight = ref(35)
|
||||
const code = ref('') // 输入的验证码
|
||||
const codeCursorLeft = ref<number[]>([]) // 向左移动的距离数组
|
||||
|
||||
// 初始化
|
||||
watch([() => props.modelValue, () => props.value], ([modelVal, val]) => {
|
||||
code.value = modelVal || val || ''
|
||||
}, { immediate: true })
|
||||
|
||||
onMounted(() => {
|
||||
focused.value = props.isFocus
|
||||
cursorVisible.value = props.isFocus
|
||||
init()
|
||||
if (props.isFocus) {
|
||||
nextTick(() => {
|
||||
focusInput()
|
||||
})
|
||||
}
|
||||
};
|
||||
})
|
||||
|
||||
function focusInput() {
|
||||
focused.value = false
|
||||
nextTick(() => {
|
||||
focused.value = true
|
||||
// #ifdef H5
|
||||
const inputEl = codeInput.value
|
||||
if (inputEl && typeof inputEl.focus === 'function') {
|
||||
inputEl.focus()
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 初始化
|
||||
*/
|
||||
function init() {
|
||||
getCodeCursorLeft()
|
||||
setCursorHeight()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取元素节点
|
||||
* @param {string} elm - 节点的id、class 相当于 document.querySelect的参数 -eg: #id
|
||||
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
|
||||
* @param {Function} callback - 回调函数
|
||||
*/
|
||||
function getElement(elm: string, type: string = 'single', callback: Function) {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.in(instance?.proxy)
|
||||
[type === 'array' ? 'selectAll' : 'select'](elm)
|
||||
.boundingClientRect()
|
||||
.exec((data: any) => {
|
||||
callback(data[0])
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 计算光标的高度
|
||||
*/
|
||||
function setCursorHeight() {
|
||||
getElement('.xt__box', 'single', (boxElm: any) => {
|
||||
cursorHeight.value = boxElm.height * 0.6
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取光标在每一个box的left位置
|
||||
*/
|
||||
function getCodeCursorLeft() {
|
||||
// 获取父级框的位置信息
|
||||
getElement('#xt__input-ground', 'single', (parentElm: any) => {
|
||||
const parentLeft = parentElm.left
|
||||
// 获取各个box信息
|
||||
getElement('.xt__box', 'array', (elms: any) => {
|
||||
codeCursorLeft.value = []
|
||||
elms.forEach((elm: any) => {
|
||||
codeCursorLeft.value.push(elm.left - parentLeft + elm.width / 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 输入框输入变化的回调
|
||||
function handleInput(e: any) {
|
||||
const inputValue = e?.detail?.value ?? e?.target?.value ?? ''
|
||||
const value = String(inputValue).replace(/\D/g, '').slice(0, props.size)
|
||||
code.value = value
|
||||
cursorVisible.value = value.length !== props.size
|
||||
emit('update:modelValue', value)
|
||||
emit('input', value)
|
||||
inputSuccess(value)
|
||||
}
|
||||
|
||||
// 输入完成回调
|
||||
function inputSuccess(value: string) {
|
||||
if (value.length === props.size) {
|
||||
emit('confirm', value)
|
||||
}
|
||||
}
|
||||
|
||||
// 输入聚焦
|
||||
function inputFocus() {
|
||||
cursorVisible.value = code.value.length !== props.size
|
||||
}
|
||||
|
||||
// 输入失去焦点
|
||||
function inputBlur() {
|
||||
cursorVisible.value = false
|
||||
}
|
||||
|
||||
function codeFormat(val: string | undefined, isPassword: boolean): string {
|
||||
let value = ''
|
||||
if (val) {
|
||||
value = isPassword ? '*' : val
|
||||
}
|
||||
return value
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.xt__verify-code {
|
||||
@@ -203,11 +208,13 @@ export default {
|
||||
box-sizing: border-box;
|
||||
|
||||
.xt__input {
|
||||
height: 100%;
|
||||
width: 200%;
|
||||
position: absolute;
|
||||
left: -100%;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
.xt__cursor {
|
||||
position: absolute;
|
||||
@@ -215,6 +222,8 @@ export default {
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
width: 2px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
animation-name: cursor;
|
||||
animation-duration: 0.8s;
|
||||
animation-iteration-count: infinite;
|
||||
@@ -226,11 +235,14 @@ export default {
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
.xt__box {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 76rpx;
|
||||
height: 112rpx;
|
||||
pointer-events: none;
|
||||
&-bottom {
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
@@ -259,13 +271,40 @@ export default {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
font-size:52rpx;
|
||||
z-index: 1;
|
||||
color: #333;
|
||||
font-size: 52rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* #ifdef APP-PLUS */
|
||||
/*
|
||||
* App 的原生 input 如果覆盖在验证码格上,部分 Android 设备会出现
|
||||
* 原生光标/输入绘制层与自绘数字冲突。沿用 u-code-input 的隐藏输入方式,
|
||||
* 将输入内容和原生光标移出可视区域,验证码格只负责展示响应式状态。
|
||||
*/
|
||||
.xt__verify-code {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xt__input {
|
||||
left: -750rpx;
|
||||
width: 1500rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xt__input-ground {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.xt__cursor {
|
||||
z-index: 3;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
@keyframes cursor {
|
||||
0% {
|
||||
opacity: 1;
|
||||
|
||||
@@ -1,34 +1,35 @@
|
||||
/**
|
||||
* base : 基础业务API
|
||||
* buyer : 买家API
|
||||
* buyer : 买家 API(baseURL 为 buyer + /buyer)
|
||||
*/
|
||||
// 开发环境
|
||||
const dev = {
|
||||
common: "https://common-api.pickmall.cn",
|
||||
buyer: "https://buyer-api.pickmall.cn",
|
||||
|
||||
im: "https://im-api.pickmall.cn",
|
||||
mqtt: "wss://lilishop-mqtt-pull.dllll.xyz/mqtt",
|
||||
};
|
||||
// 生产环境
|
||||
const prod = {
|
||||
common: "https://common-api.pickmall.cn",
|
||||
buyer: "https://buyer-api.pickmall.cn",
|
||||
im: "https://im-api.pickmall.cn",
|
||||
mqtt: "wss://lilishop-mqtt-pull.dllll.xyz/mqtt",
|
||||
};
|
||||
|
||||
//默认生产环境
|
||||
let api = dev;
|
||||
//如果是开发环境
|
||||
if (process.env.NODE_ENV == "development") {
|
||||
api = dev;
|
||||
} else {
|
||||
api = prod;
|
||||
}
|
||||
//微信小程序,app的打包方式建议为生产环境,所以这块直接条件编译赋值
|
||||
// 微信小程序、App 打包使用生产环境配置
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
api = prod;
|
||||
// #endif
|
||||
|
||||
api.buyer += "/buyer";
|
||||
api.common += "/common";
|
||||
const buyerHost = api.buyer;
|
||||
api.buyer = buyerHost + "/buyer";
|
||||
api.im = api.im + "/im";
|
||||
|
||||
export default {
|
||||
...api,
|
||||
buyerHost,
|
||||
};
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
const name = "lilishop"; //全局商城name
|
||||
const schemeName = 'lilishop' //唤醒app需要的schemeName
|
||||
const schemeName = "lilishop"; //唤醒app需要的schemeName
|
||||
export default {
|
||||
name: name,
|
||||
schemeLink: `${schemeName}://`, //唤起app地址
|
||||
downloadLink: "https://pickmall.cn/download-page/index.html", //下载地址,下载app的地址
|
||||
shareLink: "https://m-b2b2c.pickmall.cn", //分享地址,也就是在h5中默认的复制地址
|
||||
appid: "wx6f10f29075dc1b0b", //小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
|
||||
appSecret: "6dfbe0c72380dce5d49d65b3c91059b1", //可在 manifest.json 查看
|
||||
aMapKey: "d649892b3937a5ad20b76dacb2bcb5bd", //在高德中申请web端key
|
||||
scanAuthNavigation:['https://m-b2b2c.pickmall.cn/'], //扫码认证跳转域名配置 会根据此处配置的路由进行跳转
|
||||
iosAppId:"id1564638363", //AppStore的应用地址id 具体在分享->拷贝链接中查看
|
||||
logo:"https://lilishop-oss.oss-cn-beijing.aliyuncs.com/4c864e133c2944efad1f7282ac8a3b9e.png", //logo地址
|
||||
customerServiceMobile:"13161366885", //客服电话
|
||||
customerServiceEmail:"lili@lili.com" //客服邮箱
|
||||
};
|
||||
aMapKey: "1f78544934b66c9fbc0104117f663973", //在高德中申请Web服务key
|
||||
scanAuthNavigation: ["https://m-b2b2c.pickmall.cn/"], //扫码认证跳转域名配置 会根据此处配置的路由进行跳转
|
||||
iosAppId: "id1564638363", //AppStore的应用地址id 具体在分享->拷贝链接中查看
|
||||
logo: "https://lilishop-oss.oss-cn-beijing.aliyuncs.com/4c864e133c2944efad1f7282ac8a3b9e.png", //logo地址
|
||||
customerServiceMobile: "13161366885", //客服电话
|
||||
customerServiceEmail: "lili@lili.com", //客服邮箱
|
||||
imWebSrc: "https://im.pickmall.cn", //IM地址
|
||||
baseWsUrl: "wss://im-api.pickmall.cn/lili/webSocket", // IM WS 地址
|
||||
enableGetClipboard: false, //是否启用粘贴板获取 scanAuthNavigation 中的链接,如果匹配则会跳转到对应页面
|
||||
enableMiniBarStartUpApp: false, //是否在h5中右侧浮空按钮点击启动app
|
||||
/**
|
||||
* 如需更换主题请修改此处以及uni.scss中的全局颜色
|
||||
*/
|
||||
mainColor: "#ff3c2a", // 主题色
|
||||
lightColor: "#ff6b35", // 高亮主题色
|
||||
aiderLightColor: "#ff9f28", // 辅助高亮颜色
|
||||
defaultUserPhoto: "/static/missing-face.png", // 默认用户头像
|
||||
enableFetchMobileLogin: false // 是否启用获取手机号登录 如果微信小程序提示封禁手机号获取权限 可将此选项设置成false作为备用登录方案
|
||||
};
|
||||
|
||||
23
index.html
Normal file
23
index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
<title></title>
|
||||
<!-- H5 构建会把 process.env 替换成空对象,但运行时仍会读取 process,浏览器中不存在该变量 -->
|
||||
<script>
|
||||
if (typeof process === "undefined") {
|
||||
window.process = { env: {} };
|
||||
}
|
||||
</script>
|
||||
<!--preload-links-->
|
||||
<!--app-context-->
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"><!--app-html--></div>
|
||||
<script type="module" src="/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -7,8 +7,8 @@
|
||||
* @param {sn,price}
|
||||
*/
|
||||
|
||||
import { initiatePay } from "@/api/trade";
|
||||
import { getWeChatMpMessage } from "@/api/message.js";
|
||||
import { initiatePay } from "@/api/trade";
|
||||
class LiLiWXPay {
|
||||
constructor(...payList) {
|
||||
this.data = payList[0];
|
||||
@@ -65,52 +65,40 @@ class LiLiWXPay {
|
||||
}
|
||||
|
||||
function sendMessage(price) {
|
||||
//判断用户是否已经进行了订阅
|
||||
if (!uni.getStorageSync("acceptSubscribeMessage")) {
|
||||
//订阅消息
|
||||
getWeChatMpMessage().then((res) => {
|
||||
var message = res.data.result;
|
||||
var templateid = message.map((item) => item.code);
|
||||
uni.requestSubscribeMessage({
|
||||
tmplIds: templateid,
|
||||
success: (res) => {
|
||||
for (let key in res) {
|
||||
// 表示用户拒绝订阅该信息
|
||||
if (res[key] == "reject") {
|
||||
this.checked = false;
|
||||
} else {
|
||||
uni.setStorageSync("acceptSubscribeMessage", res);
|
||||
}
|
||||
}
|
||||
},
|
||||
fail: (res) => {
|
||||
uni.removeStorageSync("acceptSubscribeMessage");
|
||||
this.checked = false;
|
||||
},
|
||||
complete: () => {
|
||||
/**
|
||||
* 已经支付成功
|
||||
*/
|
||||
uni.redirectTo({
|
||||
url:
|
||||
"/pages/cart/payment/success?paymentMethod=WECHAT" +
|
||||
"&payPrice=" +
|
||||
price,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
//订阅消息
|
||||
getWeChatMpMessage().then((res) => {
|
||||
var message = res.data.result;
|
||||
var templateid = message.map((item) => item.code);
|
||||
uni.requestSubscribeMessage({
|
||||
tmplIds: templateid,
|
||||
success: (res) => {
|
||||
|
||||
},
|
||||
fail: (res) => {
|
||||
console.log('fail', res)
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "订阅消息失败",
|
||||
})
|
||||
},
|
||||
complete: (res) => {
|
||||
console.log('complete', res)
|
||||
|
||||
/**
|
||||
* 已经支付成功
|
||||
*/
|
||||
uni.redirectTo({
|
||||
url:
|
||||
"/pages/cart/payment/success?paymentMethod=WECHAT" +
|
||||
"&payPrice=" +
|
||||
price,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* 已经支付成功
|
||||
*/
|
||||
uni.redirectTo({
|
||||
url:
|
||||
"/pages/cart/payment/success?paymentMethod=WECHAT" +
|
||||
"&payPrice=" +
|
||||
price,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
export default LiLiWXPay;
|
||||
|
||||
76
js_sdk/t-jwt/jwt.js
Normal file
76
js_sdk/t-jwt/jwt.js
Normal file
@@ -0,0 +1,76 @@
|
||||
"use strict";
|
||||
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
var b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
|
||||
export function weBtoa(string) {
|
||||
string = String(string);
|
||||
var bitmap, a, b, c, result = "", i = 0, rest = string.length % 3;
|
||||
for (; i < string.length;) {
|
||||
if ((a = string.charCodeAt(i++)) > 255 ||
|
||||
(b = string.charCodeAt(i++)) > 255 ||
|
||||
(c = string.charCodeAt(i++)) > 255)
|
||||
throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
|
||||
bitmap = (a << 16) | (b << 8) | c;
|
||||
result += b64.charAt(bitmap >> 18 & 63) + b64.charAt(bitmap >> 12 & 63) +
|
||||
b64.charAt(bitmap >> 6 & 63) + b64.charAt(bitmap & 63);
|
||||
}
|
||||
return rest ? result.slice(0, rest - 3) + "===".substring(rest) : result;
|
||||
}
|
||||
export function weAtob(string) {
|
||||
string = String(string).replace(/[\t\n\f\r ]+/g, "");
|
||||
if (!b64re.test(string))
|
||||
throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
|
||||
string += "==".slice(2 - (string.length & 3));
|
||||
var bitmap, result = "", r1, r2, i = 0;
|
||||
for (; i < string.length;) {
|
||||
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12 |
|
||||
(r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
|
||||
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255) :
|
||||
r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255) :
|
||||
String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function b64DecodeUnicode(str) {
|
||||
return decodeURIComponent(weAtob(str).replace(/(.)/g, function (p) {
|
||||
var code = p.charCodeAt(0).toString(16).toUpperCase();
|
||||
if (code.length < 2) {
|
||||
code = "0" + code;
|
||||
}
|
||||
return "%" + code;
|
||||
}));
|
||||
}
|
||||
function base64_url_decode(str) {
|
||||
var output = str.replace(/-/g, "+").replace(/_/g, "/");
|
||||
switch (output.length % 4) {
|
||||
case 0:
|
||||
break;
|
||||
case 2:
|
||||
output += "==";
|
||||
break;
|
||||
case 3:
|
||||
output += "=";
|
||||
break;
|
||||
default:
|
||||
throw "Illegal base64url string!";
|
||||
}
|
||||
try {
|
||||
return b64DecodeUnicode(output);
|
||||
}
|
||||
catch (err) {
|
||||
return weAtob(output);
|
||||
}
|
||||
}
|
||||
function weappJwtDecode(token, options) {
|
||||
if (typeof token !== "string") {
|
||||
throw ("Invalid token specified");
|
||||
}
|
||||
options = options || {};
|
||||
var pos = options.header === true ? 0 : 1;
|
||||
try {
|
||||
return JSON.parse(base64_url_decode(token.split(".")[pos]));
|
||||
}
|
||||
catch (e) {
|
||||
throw ("Invalid token specified: " + e.message);
|
||||
}
|
||||
}
|
||||
export default weappJwtDecode;
|
||||
@@ -1,5 +1,20 @@
|
||||
var _a;
|
||||
export const PLATFORM = typeof process !== 'undefined' ? (_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.VUE_APP_PLATFORM : undefined;
|
||||
function detectPlatform() {
|
||||
if (typeof process !== 'undefined' && (_a = process === null || process === void 0 ? void 0 : process.env) !== null && _a !== void 0 && _a.VUE_APP_PLATFORM) {
|
||||
return process.env.VUE_APP_PLATFORM;
|
||||
}
|
||||
if (typeof wx !== 'undefined' && typeof wx.getSystemInfoSync === 'function') {
|
||||
return 'mp-weixin';
|
||||
}
|
||||
if (typeof uni !== 'undefined' && typeof uni.getSystemInfoSync === 'function') {
|
||||
const info = uni.getSystemInfoSync();
|
||||
if (info && info.uniPlatform === 'mp-weixin') {
|
||||
return 'mp-weixin';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
export const PLATFORM = detectPlatform();
|
||||
/** 全局对象 */
|
||||
const _uni = (function () {
|
||||
if (typeof uni != "undefined")
|
||||
|
||||
@@ -37,7 +37,9 @@ export const handleBuildOpts = (options) => {
|
||||
}
|
||||
const oldSelector = defaultOpts.selector;
|
||||
if (PLATFORM === 'mp-weixin' && defaultOpts.type2d) {
|
||||
defaultOpts.selector = '#' + defaultOpts.selector;
|
||||
defaultOpts.selector = oldSelector.startsWith('#')
|
||||
? oldSelector
|
||||
: '#' + oldSelector;
|
||||
}
|
||||
if (!PLATFORM) {
|
||||
console.error('注意! draw-poster未开启uni条件编译! 当环境是微信小程序将不会动态切换为type2d模式');
|
||||
|
||||
@@ -265,7 +265,7 @@ function checkSystemEnableLocation() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
judgeIosPermission: judgeIosPermission,
|
||||
requestAndroidPermission: requestAndroidPermission,
|
||||
checkSystemEnableLocation: checkSystemEnableLocation,
|
||||
|
||||
140
main.js
140
main.js
@@ -1,53 +1,87 @@
|
||||
import Vue from "vue";
|
||||
import App from "./App";
|
||||
import * as filters from "./utils/filters.js"; // global filter
|
||||
import uView from "uview-ui";
|
||||
import store from "./store";
|
||||
|
||||
|
||||
/**
|
||||
* 仅在h5中显示唤醒app功能
|
||||
* 在h5页面手动挂载
|
||||
*
|
||||
*/
|
||||
// #ifdef H5
|
||||
import airBtn from "@/components/m-airbtn/index.vue";
|
||||
let btn = Vue.component("airBtn", airBtn); //全局注册
|
||||
document.body.appendChild(new btn().$mount().$el);
|
||||
// #endif
|
||||
|
||||
|
||||
/**
|
||||
* 全局filters
|
||||
*/
|
||||
|
||||
Object.keys(filters).forEach((key) => {
|
||||
Vue.filter(key, filters[key]);
|
||||
});
|
||||
|
||||
// 引入Vuex
|
||||
Vue.prototype.$store = store;
|
||||
Vue.use(uView);
|
||||
Vue.config.productionTip = false;
|
||||
|
||||
|
||||
/**
|
||||
* 注意!
|
||||
* 此处将常用的颜色嵌入到原型链上面
|
||||
* 颜色使用驼峰命名对应 uni.scss中全局颜色变量名
|
||||
* 如需更换主题请修改此处以及uni.scss中的全局颜色
|
||||
*/
|
||||
// 主题色
|
||||
Vue.prototype.$mainColor = "#ff3c2a";
|
||||
// 高亮主题色
|
||||
Vue.prototype.$lightColor = "#ff6b35";
|
||||
// 辅助高亮颜色
|
||||
Vue.prototype.$aiderLightColor = "#ff9f28";
|
||||
|
||||
|
||||
App.mpType = "app";
|
||||
|
||||
const app = new Vue({
|
||||
...App,
|
||||
});
|
||||
app.$mount();
|
||||
import { createSSRApp, computed } from 'vue'
|
||||
import App from './App'
|
||||
import uviewPlus, { setConfig } from 'uview-plus'
|
||||
import store from './store'
|
||||
import config from '@/config/config'
|
||||
// #ifdef H5
|
||||
import airBtn from '@/components/m-airbtn/index.vue'
|
||||
// #endif
|
||||
import mpShare from '@/utils/mpShare.js'
|
||||
import * as filterUtils from './utils/filters.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
export function createApp() {
|
||||
const app = createSSRApp(App)
|
||||
|
||||
// uview-plus 内部会触发 Vue3 开发态告警(枚举组件实例 keys),不影响运行
|
||||
app.config.warnHandler = (msg, _instance, trace) => {
|
||||
if (
|
||||
typeof msg === 'string' &&
|
||||
msg.includes('enumerating keys on a component instance')
|
||||
) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
console.warn(`[Vue warn]: ${msg}${trace || ''}`)
|
||||
} catch (e) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信开发者工具部分版本在输出 Vue warn 时会触发
|
||||
// TypeError: Cannot read property '__global' of null
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
app.use(store)
|
||||
app.use(uviewPlus)
|
||||
|
||||
// uview-plus 的图标字体默认每个 u-icon 实例都会触发 uni.loadFontFace,
|
||||
// 导致进入页面时同一字体被反复请求。开启 loadFontOnce 后全局只加载一次。
|
||||
setConfig({
|
||||
config: {
|
||||
loadFontOnce: true,
|
||||
iconUrl: 'https://lilishop-uniapp.oss-cn-beijing.aliyuncs.com/font_2225171_8kdcwk4po24.ttf',
|
||||
},
|
||||
props: {
|
||||
form: {
|
||||
labelWidth: '180rpx',
|
||||
labelPosition: 'left',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
app.config.globalProperties.$store = store
|
||||
app.config.globalProperties.$mainColor = computed(() => store.getters.mainColor)
|
||||
app.config.globalProperties.$lightColor = computed(() => store.getters.lightColor)
|
||||
app.config.globalProperties.$aiderLightColor = computed(
|
||||
() => store.getters.aiderLightColor
|
||||
)
|
||||
|
||||
const filterMethods = {}
|
||||
Object.keys(filterUtils).forEach((key) => {
|
||||
if (typeof filterUtils[key] === 'function') {
|
||||
filterMethods[key] = filterUtils[key]
|
||||
}
|
||||
})
|
||||
|
||||
app.mixin({
|
||||
methods: filterMethods,
|
||||
computed: {
|
||||
themeStyle() {
|
||||
return getThemeStyle(this.$store.state.theme)
|
||||
},
|
||||
},
|
||||
})
|
||||
app.mixin(mpShare)
|
||||
|
||||
// #ifdef H5
|
||||
if (config.enableMiniBarStartUpApp) {
|
||||
const mountPoint = document.createElement('div')
|
||||
document.body.appendChild(mountPoint)
|
||||
createSSRApp(airBtn).mount(mountPoint)
|
||||
}
|
||||
// #endif
|
||||
|
||||
return {
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name" : "lili商城",
|
||||
"appid" : "__UNI__C100675",
|
||||
"vueVersion" : "3",
|
||||
"name" : "lilishop",
|
||||
"appid" : "__UNI__ED807EA",
|
||||
"description" : "",
|
||||
"versionName" : "4.0.47",
|
||||
"versionCode" : 4000047,
|
||||
"versionName" : "4.5.3",
|
||||
"versionCode" : 4000503,
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
"compatible" : {
|
||||
@@ -63,8 +64,19 @@
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
|
||||
"schemes" : "lilishop"
|
||||
"schemes" : "lilishop",
|
||||
//安卓自添加 权限 应用市场 过度索取权限
|
||||
"permissionExternalStorage" : {
|
||||
"request" : "none",
|
||||
"prompt" : "应用保存运行状态等信息,需要获取读写手机存储(系统提示为访问设备上的照片、媒体内容和文件)权限,请允许。"
|
||||
},
|
||||
"permissionPhoneState" : {
|
||||
"request" : "none",
|
||||
"prompt" : "为保证您正常、安全地使用,需要获取设备识别码(部分手机提示为获取手机号码)使用权限,请允许"
|
||||
},
|
||||
"autoSdkPermissions" : false
|
||||
},
|
||||
//安卓自添加 权限 应用市场 过度索取权限
|
||||
"ios" : {
|
||||
"idfa" : false,
|
||||
"privacyDescription" : {
|
||||
@@ -75,7 +87,8 @@
|
||||
"NSLocationAlwaysAndWhenInUseUsageDescription" : "位置信息将用于高德地图的效果展示",
|
||||
"NSMicrophoneUsageDescription" : "用户上传视频时需使用音频信息"
|
||||
},
|
||||
"urltypes" : "lilishop"
|
||||
"urltypes" : "lilishop",
|
||||
"dSYMs" : false
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"sdkConfigs" : {
|
||||
@@ -179,6 +192,7 @@
|
||||
"mp-weixin" : {
|
||||
/* 小程序特有相关 */
|
||||
"usingComponents" : true,
|
||||
"lazyCodeLoading" : "requiredComponents",
|
||||
"appid" : "wx6f10f29075dc1b0b",
|
||||
"optimization" : {
|
||||
"subPackages" : true
|
||||
@@ -186,24 +200,19 @@
|
||||
"setting" : {
|
||||
"urlCheck" : false,
|
||||
"minified" : true,
|
||||
"postcss" : false,
|
||||
"es6" : true
|
||||
"postcss" : true,
|
||||
"es6" : true,
|
||||
"enhance" : true
|
||||
},
|
||||
"permission" : {
|
||||
"scope.userLocation" : {
|
||||
"desc" : "位置信息将用于高德地图的效果展示"
|
||||
}
|
||||
},
|
||||
"plugins" : {
|
||||
"myPlugin" : {
|
||||
"version" : "1.1.0",
|
||||
"provider" : "wx738958e0f4c894f9"
|
||||
},
|
||||
"live-player-plugin" : {
|
||||
"version" : "1.3.0",
|
||||
"provider" : "wx2b03c6e691cd7370"
|
||||
}
|
||||
}
|
||||
"plugins" : {},
|
||||
"libVersion" : "3.5.8",
|
||||
"__usePrivacyCheck__" : false,
|
||||
"requiredPrivateInfos" : [ "chooseLocation", "getLocation" ]
|
||||
},
|
||||
"h5" : {
|
||||
"devServer" : {
|
||||
|
||||
3027
package-lock.json
generated
Normal file
3027
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
package.json
Normal file
27
package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "lilishop-uniapp",
|
||||
"version": "4.5.3",
|
||||
"description": "Lilishop uni-app mobile client (Vue 3)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"postinstall": "patch-package && node scripts/patch-mqtt-abort.js",
|
||||
"test:mqtt-app-runtime": "node scripts/test-mqtt-app-runtime.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/beijing_hongye_huicheng/lilishop-uniapp"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@tencentcloud/chat": "^3.5.5",
|
||||
"hls.js": "^1.6.16",
|
||||
"mqtt": "5.15.1",
|
||||
"sass": "^1.89.2",
|
||||
"uview-plus": "3.8.42",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"patch-package": "^8.0.1",
|
||||
"postinstall-postinstall": "^2.1.0"
|
||||
}
|
||||
}
|
||||
1803
pages.json
1803
pages.json
File diff suppressed because it is too large
Load Diff
@@ -1,299 +1,262 @@
|
||||
<template>
|
||||
<view class="coupon-center">
|
||||
<swiper class="swiper-box">
|
||||
<swiper-item class="swiper-item">
|
||||
<scroll-view class="scroll-v" enableBackToTop="true" scroll-y @scrolltolower="loadMore">
|
||||
<u-empty mode="coupon" text="没有优惠券了" v-if="whetherEmpty"></u-empty>
|
||||
<view v-else class="coupon-item" v-for="(item, index) in couponList" :key="index">
|
||||
<view class="left">
|
||||
<view class="wave-line">
|
||||
<view class="wave" v-for="(item, index) in 12" :key="index"></view>
|
||||
</view>
|
||||
<view class="message">
|
||||
<view>
|
||||
<!--判断当前优惠券类型 couponType PRICE || DISCOUNT -->
|
||||
<span v-if="item.couponType == 'DISCOUNT'">{{ item.couponDiscount }}折</span>
|
||||
<span v-else>{{ item.price }}元</span>
|
||||
</view>
|
||||
<view>满{{ item.consumeThreshold | unitPrice }}元可用</view>
|
||||
</view>
|
||||
<view class="circle circle-top"></view>
|
||||
<view class="circle circle-bottom"></view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view>
|
||||
<!-- 根据scopeType 判断是否是 平台、品类或店铺 -->
|
||||
<view v-if="item.scopeType">
|
||||
<span v-if="item.scopeType == 'ALL' && item.id == 'platform'">全平台</span>
|
||||
<span v-if="item.scopeType == 'PORTION_CATEGORY'">仅限品类</span>
|
||||
<view v-else>{{ item.storeName == 'platform' ? '全平台' :item.storeName+'店铺' }}使用</view>
|
||||
</view>
|
||||
<view>有效期至:{{ item.endTime.split(" ")[0] }}</view>
|
||||
</view>
|
||||
<view class="receive" @click="receive(item)">
|
||||
<text>点击</text><br />
|
||||
<text>领取</text>
|
||||
</view>
|
||||
<view class="bg-quan"> 券 </view>
|
||||
</view>
|
||||
</view>
|
||||
<uni-load-more :status="loadStatus"></uni-load-more>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
<view class="coupon-center">
|
||||
<view class="coupon-list">
|
||||
<u-empty mode="coupon" style='margin-top: 20%;' text="没有优惠券了" v-if="whetherEmpty"></u-empty>
|
||||
<view v-else class="coupon-card" v-for="(item, index) in couponList" :key="index">
|
||||
<view class="coupon-card-left">
|
||||
<text class="coupon-price-symbol">¥</text>
|
||||
<text class="coupon-price-value">{{ item.couponType == 'DISCOUNT' ? item.couponDiscount + '折' : unitPrice(item.price) }}</text>
|
||||
</view>
|
||||
<view class="coupon-divider"></view>
|
||||
<view class="coupon-card-right">
|
||||
<view class="coupon-info">
|
||||
<text class="coupon-card-name">{{ item.storeName == 'platform' ? '全平台' : item.storeName + '店铺' }}使用</text>
|
||||
<text class="coupon-card-desc">满{{unitPrice(item.consumeThreshold) }}元可用</text>
|
||||
<text class="coupon-card-time" v-if="item.endTime">有效期至:{{ item.endTime.split(" ")[0] }}</text>
|
||||
</view>
|
||||
<view class="coupon-claim-btn" @click="receive(item)">
|
||||
领取
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { receiveCoupons } from "@/api/members.js";
|
||||
import { getAllCoupons } from "@/api/promotions.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
loadStatus: "more", //下拉状态
|
||||
whetherEmpty: false, //是否为空
|
||||
couponList: [], // 优惠券列表
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
},
|
||||
storeId: "", //店铺 id
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
this.storeId = option.storeId;
|
||||
this.getCoupon();
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
//下拉刷新
|
||||
this.params.pageNumber = 1;
|
||||
this.couponList = [];
|
||||
this.getCoupon();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 获取当前优惠券
|
||||
*/
|
||||
getCoupon() {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
let submitData = { ...this.params };
|
||||
// 判断当前是否有店铺
|
||||
this.storeId ? (submitData = { ...this.params, storeId: this.storeId }): "",
|
||||
getAllCoupons(submitData)
|
||||
.then((res) => {
|
||||
uni.hideLoading();
|
||||
uni.stopPullDownRefresh();
|
||||
if (res.data.code == 200) {
|
||||
// 如果请求成功,展示数据并进行展示
|
||||
let data = res.data.result;
|
||||
if (data.total == 0) {
|
||||
// 当本次请求数据为空展示空信息
|
||||
this.whetherEmpty = true;
|
||||
} else {
|
||||
this.couponList.push(...data.records);
|
||||
this.loadStatus = "noMore";
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 领取优惠券
|
||||
*/
|
||||
receive(item) {
|
||||
receiveCoupons(item.id).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({
|
||||
title: "领取成功",
|
||||
icon: "none",
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 加载更多
|
||||
*/
|
||||
loadMore() {
|
||||
if (this.loadStatus != "noMore") {
|
||||
this.params.pageNumber++;
|
||||
this.getAllCoupons();
|
||||
}
|
||||
},
|
||||
},
|
||||
onNavigationBarButtonTap(e) {
|
||||
uni.navigateTo({
|
||||
url: "/pages/cart/coupon/couponIntro",
|
||||
});
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
page {
|
||||
height: 100%;
|
||||
<script setup lang="ts">
|
||||
import { receiveCoupons } from '@/api/members.js'
|
||||
import { getAllCoupons } from '@/api/promotions.js'
|
||||
import { useStore } from '@/store'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import {
|
||||
onLoad,
|
||||
onNavigationBarButtonTap,
|
||||
onPullDownRefresh,
|
||||
onReachBottom,
|
||||
} from '@dcloudio/uni-app'
|
||||
import { getCurrentInstance, ref } from 'vue'
|
||||
|
||||
const store = useStore()
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const loadStatus = ref('more')
|
||||
const whetherEmpty = ref(false)
|
||||
const couponList = ref<any[]>([])
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
})
|
||||
const storeId = ref('')
|
||||
const couponData = ref<any>(null)
|
||||
|
||||
onLoad((option) => {
|
||||
storeId.value = option.storeId || ''
|
||||
getCoupon()
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
loadMore()
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
params.value.pageNumber = 1
|
||||
couponList.value = []
|
||||
getCoupon()
|
||||
})
|
||||
|
||||
onNavigationBarButtonTap(() => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/cart/coupon/couponIntro',
|
||||
})
|
||||
})
|
||||
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
</style>
|
||||
|
||||
function getCoupon() {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
const submitData = storeId.value
|
||||
? { ...params.value, storeId: storeId.value }
|
||||
: { ...params.value }
|
||||
|
||||
getAllCoupons(submitData)
|
||||
.then((res) => {
|
||||
hideLoadingIfNeeded()
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.data.code == 200) {
|
||||
couponData.value = res.data.result
|
||||
if (couponData.value.total == 0) {
|
||||
whetherEmpty.value = true
|
||||
} else {
|
||||
couponList.value.push(...couponData.value.records)
|
||||
loadStatus.value = 'noMore'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
hideLoadingIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
function receive(val: any) {
|
||||
proxy.$u.throttle(() => {
|
||||
fetchCoupon(val)
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
function fetchCoupon(val: any) {
|
||||
receiveCoupons(val.id).then((res) => {
|
||||
if (res.data.code == 200) {
|
||||
uni.showToast({ title: '领取成功', icon: 'none' })
|
||||
} else {
|
||||
uni.showToast({ title: res.data.message, icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (couponData.value?.total > params.value.pageNumber * params.value.pageSize) {
|
||||
params.value.pageNumber++
|
||||
getCoupon()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.coupon-center {
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
background: #f7f8fa;
|
||||
}
|
||||
|
||||
.couponList-scroll-content {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
white-space: nowrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: $main-color;
|
||||
color: #ffffff;
|
||||
.coupon-list {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
width: 160rpx;
|
||||
height: 80rpx;
|
||||
line-height: 60rpx;
|
||||
text-align: center;
|
||||
display: inline-block;
|
||||
}
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: none;
|
||||
border: 1rpx solid #f0f1f5;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
.active {
|
||||
border-bottom: 2px solid #ffffff;
|
||||
broder-width: 60rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: 100%;
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 50%;
|
||||
left: 204rpx;
|
||||
z-index: 2;
|
||||
box-shadow: inset 0 0 0 1rpx #eceef2;
|
||||
}
|
||||
|
||||
.scroll-v {
|
||||
height: 100%;
|
||||
}
|
||||
&::before {
|
||||
top: -16rpx;
|
||||
}
|
||||
|
||||
.coupon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 220rpx;
|
||||
margin: 20rpx;
|
||||
&::after {
|
||||
bottom: -16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.left {
|
||||
height: 100%;
|
||||
width: 260rpx;
|
||||
background-color: $light-color;
|
||||
position: relative;
|
||||
.message {
|
||||
color: $font-color-white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 40rpx;
|
||||
.coupon-card-left {
|
||||
width: 220rpx;
|
||||
min-height: 180rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: #ff3b30;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffecec 100%);
|
||||
}
|
||||
|
||||
view:nth-child(1) {
|
||||
font-weight: bold;
|
||||
font-size: 60rpx;
|
||||
}
|
||||
.coupon-price-symbol {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
view:nth-child(2) {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
}
|
||||
.coupon-price-value {
|
||||
font-size: 56rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
letter-spacing: -1rpx;
|
||||
}
|
||||
|
||||
.wave-line {
|
||||
height: 220rpx;
|
||||
width: 8rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $light-color;
|
||||
overflow: hidden;
|
||||
.coupon-divider {
|
||||
position: absolute;
|
||||
left: 220rpx;
|
||||
top: 24rpx;
|
||||
bottom: 24rpx;
|
||||
width: 0;
|
||||
border-left: 2rpx dashed #eceef2;
|
||||
}
|
||||
|
||||
.wave {
|
||||
width: 8rpx;
|
||||
height: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 0 16rpx 16rpx 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
.circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: $bg-color;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
z-index: 111;
|
||||
}
|
||||
.circle-top {
|
||||
top: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
.circle-bottom {
|
||||
bottom: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
}
|
||||
.coupon-card-right {
|
||||
flex: 1;
|
||||
padding: 32rpx 32rpx 32rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 450rpx;
|
||||
font-size: $font-sm;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
> view:nth-child(1) {
|
||||
color: #666666;
|
||||
margin-left: 20rpx;
|
||||
line-height: 3em;
|
||||
> view:nth-child(1) {
|
||||
color: #ff6262;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
.coupon-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.receive {
|
||||
color: #ffffff;
|
||||
background-color: $main-color;
|
||||
border-radius: 50%;
|
||||
width: 86rpx;
|
||||
height: 86rpx;
|
||||
text-align: center;
|
||||
margin-right: 30rpx;
|
||||
vertical-align: middle;
|
||||
padding-top: 8rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.coupon-card-name {
|
||||
font-size: 30rpx;
|
||||
color: #111;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bg-quan {
|
||||
width: 244rpx;
|
||||
height: 244rpx;
|
||||
border: 6rpx solid $main-color;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
color: $main-color;
|
||||
text-align: center;
|
||||
padding-top: 30rpx;
|
||||
font-size: 130rpx;
|
||||
position: absolute;
|
||||
right: -54rpx;
|
||||
bottom: -60rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.coupon-card-desc {
|
||||
font-size: 24rpx;
|
||||
color: #8a8f99;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.coupon-card-time {
|
||||
font-size: 22rpx;
|
||||
color: #b0b3bf;
|
||||
}
|
||||
|
||||
.coupon-claim-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #ff6b35, #ff4b2b);
|
||||
box-shadow: 0 8rpx 16rpx rgba(255, 75, 43, 0.25);
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,51 @@
|
||||
<template>
|
||||
<view class="content">
|
||||
<view :style="themeStyle" class="content">
|
||||
<view class="body">
|
||||
<view class="top-view">
|
||||
<view class="title">{{coupon.title}}</view>
|
||||
<view class="price"><text>¥</text>{{coupon.price | unitPrice}}</view>
|
||||
<view class="text">满{{coupon.consumeThreshold}}元可用</view>
|
||||
<view class="bg-quan">
|
||||
券
|
||||
</view>
|
||||
<view class="jiao-1" :class="{'used-color':coupon.used_status!=0}">
|
||||
<text class="text-1">{{coupon.used_status == 0?'新到':coupon.used_status_text}}</text>
|
||||
<view class="title">{{ coupon.title }}</view>
|
||||
<view class="price" v-if="coupon.couponType == 'PRICE'"><text>¥</text>{{ unitPrice(coupon.price) }}</view>
|
||||
<view class="price" v-if="coupon.couponType == 'DISCOUNT'">{{ coupon.discount }}折</view>
|
||||
<view class="text">满{{ coupon.consumeThreshold }}元可用</view>
|
||||
<view class="bg-quan">券</view>
|
||||
<view class="jiao-1" :class="{ 'used-color': coupon.used_status != 0 }">
|
||||
<text class="text-1">{{ coupon.used_status == 0 ? '新到' : coupon.used_status_text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bottom-view">
|
||||
<view class="text">• 使用平台:{{
|
||||
coupon.scopeType == 'ALL' && coupon.id == 'platform'
|
||||
? "全平台"
|
||||
: coupon.scopeType == "PORTION_CATEGORY"
|
||||
? "仅限品类"
|
||||
: coupon.storeName == 'platform' ? '全平台' :coupon.storeName+''
|
||||
}}使用</view>
|
||||
<view class="text">• 有效期至:{{coupon.endTime}}</view>
|
||||
|
||||
<view class="text">• 使用范围:{{
|
||||
coupon.scopeType == 'ALL' && coupon.storeId == '0'
|
||||
? '全平台'
|
||||
: coupon.scopeType == 'PORTION_GOODS'
|
||||
? '部分商品'
|
||||
: coupon.scopeType == 'PORTION_GOODS_CATEGORY'
|
||||
? '部分分类商品'
|
||||
: coupon.storeName == 'platform' ? '全平台' : coupon.storeName + ''
|
||||
}}使用</view>
|
||||
<view class="text">• 有效期至:{{ coupon.endTime }}</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
coupon: {}, //优惠券数据
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
this.coupon = JSON.parse(decodeURIComponent(option.item));
|
||||
},
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const coupon = ref<Record<string, any>>({})
|
||||
|
||||
onLoad((option) => {
|
||||
coupon.value = JSON.parse(decodeURIComponent(option.item))
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page,
|
||||
.content {
|
||||
// background: $main-color;
|
||||
height: 100%;
|
||||
}
|
||||
.body {
|
||||
|
||||
@@ -1,256 +1,323 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<u-tabs :list="list" :is-scroll="false" :active-color="lightColor" :current="current" @change="(i)=>{current = i}">
|
||||
</u-tabs>
|
||||
|
||||
<div class="empty" v-if="couponsList.length <= 0">
|
||||
<u-empty text="暂无优惠券" mode="coupon"></u-empty>
|
||||
</div>
|
||||
<view class="coupon-item" v-for="(item, index) in couponsList" :key="index" >
|
||||
<view class="left">
|
||||
<view class="wave-line">
|
||||
<view class="wave" v-for="(item, index) in 12" :key="index"></view>
|
||||
</view>
|
||||
<view class="message">
|
||||
<view>
|
||||
<span v-if="item.couponType == 'DISCOUNT'">{{ item.discount }}折</span>
|
||||
<span v-else>{{ item.price }}元</span>
|
||||
</view>
|
||||
<view>满{{ item.consumeThreshold | unitPrice }}元可用</view>
|
||||
</view>
|
||||
<view class="circle circle-top"></view>
|
||||
<view class="circle circle-bottom"></view>
|
||||
</view>
|
||||
<view class="right">
|
||||
<view class="desc">
|
||||
<view v-if="item.scopeType">
|
||||
<span v-if="item.scopeType == 'ALL' && item.id == 'platform'">全平台</span>
|
||||
<span v-if="item.scopeType == 'PORTION_CATEGORY'">仅限品类</span>
|
||||
<view v-else>{{ item.storeName == 'platform' ? '全平台' :item.storeName+'店铺' }}使用</view>
|
||||
</view>
|
||||
<view class="reason" v-if="item.reason">{{item.reason}}</view>
|
||||
<view class="end-time">有效期至:{{item.endTime}}</view>
|
||||
</view>
|
||||
<view class="receive" v-if="current ==0" @click="clickWay(item)">
|
||||
<text>立即</text><br />
|
||||
<text>使用</text>
|
||||
</view>
|
||||
<view class="bg-quan">券</view>
|
||||
</view>
|
||||
<view class="b-content" :style="themeStyle">
|
||||
<view class="coupon-tabs">
|
||||
<u-tabs
|
||||
:list="list"
|
||||
:scrollable="false"
|
||||
:inactiveStyle="{ color: '#333' }"
|
||||
v-model:current="current"
|
||||
:lineColor="lightColor"
|
||||
:activeStyle="{ color: lightColor }"
|
||||
></u-tabs>
|
||||
</view>
|
||||
</div>
|
||||
|
||||
<scroll-view class="list-scroll-content" scroll-y>
|
||||
<u-empty
|
||||
mode="coupon"
|
||||
text="暂无优惠券"
|
||||
v-if="couponsList.length <= 0"
|
||||
></u-empty>
|
||||
|
||||
<view
|
||||
class="coupon-card"
|
||||
:class="{ 'coupon-used': current != 0 }"
|
||||
v-for="(item, index) in couponsList"
|
||||
:key="item.id || index"
|
||||
>
|
||||
<view class="coupon-card-left">
|
||||
<text class="coupon-price-symbol" v-if="item.couponType != 'DISCOUNT'">¥</text>
|
||||
<text class="coupon-price-value">
|
||||
{{ item.couponType == 'DISCOUNT' ? item.discount + '折' : unitPrice(item.price) }}
|
||||
</text>
|
||||
</view>
|
||||
<view class="coupon-divider"></view>
|
||||
<view class="coupon-card-right">
|
||||
<view class="coupon-info">
|
||||
<text class="coupon-card-name">
|
||||
<template v-if="item.scopeType == 'ALL' && item.storeId == '0'">全平台</template>
|
||||
<template v-else-if="item.scopeType == 'PORTION_GOODS_CATEGORY'">仅限品类</template>
|
||||
<template v-else>
|
||||
{{ item.storeName == 'platform' ? '全平台' : (item.storeName || '') + '店铺' }}
|
||||
</template>
|
||||
使用
|
||||
</text>
|
||||
<text class="coupon-card-desc">满{{ unitPrice(item.consumeThreshold) }}元可用</text>
|
||||
<text class="coupon-card-time" v-if="item.reason">{{ item.reason }}</text>
|
||||
<text class="coupon-card-time" v-if="item.endTime">有效期至:{{ item.endTime }}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="coupon-status-btn"
|
||||
v-if="current == 0 && !isSelected(item)"
|
||||
@click="clickWay(item)"
|
||||
>
|
||||
立即使用
|
||||
</view>
|
||||
<view
|
||||
class="coupon-status-btn cancel"
|
||||
v-else-if="current == 0 && isSelected(item)"
|
||||
@click="clickWay(item)"
|
||||
>
|
||||
取消使用
|
||||
</view>
|
||||
<view class="coupon-status-btn disabled" v-else>
|
||||
不可用
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { useCoupon } from "@/api/trade.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
lightColor: this.$lightColor,
|
||||
current: 0,
|
||||
list: [
|
||||
{
|
||||
name: "可用优惠券",
|
||||
},
|
||||
{
|
||||
name: "不可用优惠券",
|
||||
},
|
||||
],
|
||||
couponsList: [], //优惠券集合
|
||||
params: {
|
||||
//传参
|
||||
memberCouponStatus: "NEW", //优惠券状态
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
scopeId: "", //商品skuid
|
||||
storeId: "", //店铺id
|
||||
totalPrice: "", //价格
|
||||
},
|
||||
routerVal: "", //上级传参
|
||||
};
|
||||
},
|
||||
onLoad(options){
|
||||
this.routerVal = options
|
||||
|
||||
},
|
||||
watch: {
|
||||
current(val) {
|
||||
console.log(this.$store.state.cantUseCoupons);
|
||||
val == 0
|
||||
? (this.couponsList = this.$store.state.canUseCoupons)
|
||||
: (this.couponsList = this.$store.state.cantUseCoupons);
|
||||
},
|
||||
},
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { useCoupon } from '@/api/trade.js'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
const store = useStore()
|
||||
|
||||
methods: {
|
||||
/**
|
||||
* 从vuex中拿取优惠券信息
|
||||
*/
|
||||
init() {
|
||||
this.couponsList = this.$store.state.canUseCoupons;
|
||||
},
|
||||
/**
|
||||
* 领取优惠券
|
||||
*/
|
||||
clickWay(coupon) {
|
||||
useCoupon({
|
||||
memberCouponId: coupon.id,
|
||||
used: true,
|
||||
way: this.routerVal.way,
|
||||
}).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
const current = ref(0)
|
||||
const list = [
|
||||
{ name: '可用优惠券' },
|
||||
{ name: '不可用优惠券' },
|
||||
]
|
||||
const couponsList = ref<any[]>([])
|
||||
const routerVal = ref<Record<string, any>>({
|
||||
selectedCoupon: [],
|
||||
way: '',
|
||||
})
|
||||
|
||||
onLoad((options) => {
|
||||
const selectedCoupon = options?.selectedCoupon
|
||||
let selected: string[] = []
|
||||
if (Array.isArray(selectedCoupon)) {
|
||||
selected = selectedCoupon
|
||||
} else if (typeof selectedCoupon === 'string' && selectedCoupon) {
|
||||
selected = selectedCoupon.split(',')
|
||||
}
|
||||
routerVal.value = {
|
||||
...(options || {}),
|
||||
selectedCoupon: selected,
|
||||
}
|
||||
})
|
||||
|
||||
watch(current, (val) => {
|
||||
couponsList.value = val == 0
|
||||
? store.state.canUseCoupons
|
||||
: store.state.cantUseCoupons
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
couponsList.value = store.state.canUseCoupons || []
|
||||
})
|
||||
|
||||
function isSelected(coupon: any) {
|
||||
return (routerVal.value.selectedCoupon || []).includes(coupon.id)
|
||||
}
|
||||
|
||||
function clickWay(coupon: any) {
|
||||
useCoupon({
|
||||
memberCouponId: coupon.id,
|
||||
used: !isSelected(coupon),
|
||||
way: routerVal.value.way,
|
||||
}).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
duration: 2000,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.desc {
|
||||
height: 220rpx;
|
||||
flex: 2;
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.b-content {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.end-time,
|
||||
.reason {
|
||||
color: #999;
|
||||
line-height: 1.5;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.empty {
|
||||
margin-top: 20px;
|
||||
text-align: center;
|
||||
.coupon-tabs {
|
||||
background: #fff;
|
||||
height: 88rpx;
|
||||
box-shadow: 0 1rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.wrapper {
|
||||
background: #f9f9f9;
|
||||
overflow: hidden;
|
||||
|
||||
:deep(.u-tabs),
|
||||
:deep(.u-tabs__wrapper),
|
||||
:deep(.u-tabs__wrapper__scroll-view-wrapper),
|
||||
:deep(.u-tabs__wrapper__scroll-view),
|
||||
:deep(.u-tabs__wrapper__nav) {
|
||||
background: #fff;
|
||||
height: 88rpx;
|
||||
}
|
||||
.coupon-item {
|
||||
|
||||
:deep(.u-tabs__wrapper__nav__item) {
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
:deep(.u-tabs__wrapper__nav__item__text) {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.list-scroll-content {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 220rpx;
|
||||
margin: 20rpx;
|
||||
align-items: stretch;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
border: 1rpx solid #f0f1f5;
|
||||
overflow: hidden;
|
||||
|
||||
.left {
|
||||
height: 100%;
|
||||
width: 260rpx;
|
||||
background-color: $light-color;
|
||||
position: relative;
|
||||
.message {
|
||||
color: $font-color-white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 40rpx;
|
||||
|
||||
view:nth-child(1) {
|
||||
font-weight: bold;
|
||||
font-size: 60rpx;
|
||||
}
|
||||
|
||||
view:nth-child(2) {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-line {
|
||||
height: 220rpx;
|
||||
width: 8rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $light-color;
|
||||
overflow: hidden;
|
||||
|
||||
.wave {
|
||||
width: 8rpx;
|
||||
height: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 0 16rpx 16rpx 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
.circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: $bg-color;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
z-index: 111;
|
||||
}
|
||||
.circle-top {
|
||||
top: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
.circle-bottom {
|
||||
bottom: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 450rpx;
|
||||
font-size: $font-sm;
|
||||
height: 220rpx;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
> view:nth-child(1) {
|
||||
color: #666666;
|
||||
margin-left: 20rpx;
|
||||
|
||||
> view:nth-child(1) {
|
||||
color: #ff6262;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
}
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 50%;
|
||||
left: 204rpx;
|
||||
z-index: 2;
|
||||
box-shadow: inset 0 0 0 1rpx #eceef2;
|
||||
}
|
||||
|
||||
.receive {
|
||||
color: #ffffff;
|
||||
background-color: $main-color;
|
||||
border-radius: 50%;
|
||||
width: 86rpx;
|
||||
height: 86rpx;
|
||||
text-align: center;
|
||||
margin-right: 30rpx;
|
||||
vertical-align: middle;
|
||||
padding-top: 8rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
&::before {
|
||||
top: -16rpx;
|
||||
}
|
||||
|
||||
.bg-quan {
|
||||
width: 244rpx;
|
||||
height: 244rpx;
|
||||
border: 6rpx solid $main-color;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
color: $main-color;
|
||||
text-align: center;
|
||||
padding-top: 30rpx;
|
||||
font-size: 130rpx;
|
||||
position: absolute;
|
||||
right: -54rpx;
|
||||
bottom: -60rpx;
|
||||
&::after {
|
||||
bottom: -16rpx;
|
||||
}
|
||||
|
||||
&.coupon-used {
|
||||
.coupon-card-left {
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-card-left {
|
||||
width: 220rpx;
|
||||
min-height: 180rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: #ff3b30;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffecec 100%);
|
||||
}
|
||||
|
||||
.coupon-price-symbol {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.coupon-price-value {
|
||||
font-size: 56rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
letter-spacing: -1rpx;
|
||||
}
|
||||
|
||||
.coupon-divider {
|
||||
position: absolute;
|
||||
left: 220rpx;
|
||||
top: 24rpx;
|
||||
bottom: 24rpx;
|
||||
width: 0;
|
||||
border-left: 2rpx dashed #eceef2;
|
||||
}
|
||||
|
||||
.coupon-card-right {
|
||||
flex: 1;
|
||||
padding: 32rpx 32rpx 32rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.coupon-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.coupon-card-name {
|
||||
font-size: 30rpx;
|
||||
color: #111;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-card-desc {
|
||||
font-size: 24rpx;
|
||||
color: #8a8f99;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.coupon-card-time {
|
||||
font-size: 22rpx;
|
||||
color: #b0b3bf;
|
||||
}
|
||||
|
||||
.coupon-status-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 28rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, var(--theme-light, #ff6b35), var(--theme-primary, #ff4b2b));
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #ccc;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,420 +1,393 @@
|
||||
<template>
|
||||
<view class="b-content">
|
||||
<view class="navbar">
|
||||
<!-- 循环出头部tab栏 -->
|
||||
<view v-for="(item, index) in navList" :key="index" class="nav-item" @click="handleTabClick(index)"><text
|
||||
:class="{ current: tabCurrentIndex === index }">{{
|
||||
item.text
|
||||
}}</text></view>
|
||||
</view>
|
||||
<swiper :current="tabCurrentIndex" class="swiper-box" duration="300" @change="changeTab">
|
||||
<swiper-item class="tab-content" v-for="(navItem, navIndex) in navList" :key="navIndex">
|
||||
<scroll-view class="list-scroll-content" scroll-y @scrolltolower="loadData">
|
||||
<!-- 空白页 -->
|
||||
<u-empty mode="coupon" text="暂无优惠券了" v-if="navItem.wheterEmpty"></u-empty>
|
||||
|
||||
<!-- 数据 -->
|
||||
<view v-if="navItem.dataList && coupon" class="coupon-item" :class="{ 'coupon-used': navIndex != 0 }"
|
||||
v-for="(coupon, index) in navItem.dataList" :key="index">
|
||||
<view class="left">
|
||||
<view class="wave-line">
|
||||
<view class="wave" v-for="(item, index) in 12" :key="index"></view>
|
||||
</view>
|
||||
<view class="message">
|
||||
<view class="price" v-if="coupon.couponType == 'DISCOUNT'">{{ coupon.discount }}折</view>
|
||||
<view class="price" v-else>{{ coupon.price }}元</view>
|
||||
<view class="sub-price">满{{ coupon.consumeThreshold | unitPrice }}可用</view>
|
||||
</view>
|
||||
<view class="circle circle-top"></view>
|
||||
<view class="circle circle-bottom"></view>
|
||||
</view>
|
||||
<view class="right" v-if="coupon">
|
||||
<view class="content">
|
||||
<view class="title-1">{{ coupon.title }}</view>
|
||||
<view class="title-2">使用平台:{{
|
||||
coupon.scopeType == 'ALL' && coupon.id == 'platform'
|
||||
? "全平台"
|
||||
: coupon.scopeType == "PORTION_CATEGORY"
|
||||
? "仅限品类"
|
||||
: coupon.storeName == 'platform' ? '全平台' :coupon.storeName+''
|
||||
}}使用</view>
|
||||
<view v-if="coupon.endTime">{{
|
||||
coupon.endTime
|
||||
}}</view>
|
||||
<view @click="couponDetail(coupon)">详细说明
|
||||
<u-icon style="float: right; margin-top: 10rpx" name="arrow-right"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view class="jiao-1" v-if="navIndex == 0">
|
||||
<text class="text-1">新到</text>
|
||||
<text class="text-2" v-if="coupon.used_status == 1">将过期</text>
|
||||
</view>
|
||||
<image class="no-icon" v-if="navIndex == 1" src="@/static/img/used.png"></image>
|
||||
<image class="no-icon" v-if="navIndex == 2" src="@/static/img/overdue.png"></image>
|
||||
<view class="receive" v-if="navIndex == 0" @click="useItNow(coupon)">
|
||||
<text>立即</text><br />
|
||||
<text>使用</text>
|
||||
</view>
|
||||
<view class="bg-quan"> 券 </view>
|
||||
</view>
|
||||
</view>
|
||||
<uni-load-more :status="navItem.loadStatus"></uni-load-more>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getMemberCoupons } from "@/api/members.js";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
tabCurrentIndex: 0, //tab栏下标默认为0 未使用
|
||||
navList: [
|
||||
//每个tab存储的信息
|
||||
{
|
||||
text: "未使用",
|
||||
loadStatus: "more",
|
||||
dataList: [],
|
||||
params: {
|
||||
memberCouponStatus: "NEW",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 1,
|
||||
},
|
||||
wheterEmpty: false,
|
||||
},
|
||||
{
|
||||
text: "已使用",
|
||||
loadStatus: "more",
|
||||
dataList: [],
|
||||
params: {
|
||||
memberCouponStatus: "USED",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 2,
|
||||
},
|
||||
wheterEmpty: false,
|
||||
},
|
||||
{
|
||||
text: "已过期",
|
||||
loadStatus: "more",
|
||||
dataList: [],
|
||||
params: {
|
||||
memberCouponStatus: "EXPIRE",
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 3,
|
||||
},
|
||||
wheterEmpty: false,
|
||||
},
|
||||
],
|
||||
couponList: [], //优惠券列表
|
||||
};
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.navList[this.tabCurrentIndex].dataList = [];
|
||||
this.getData();
|
||||
},
|
||||
|
||||
watch: {
|
||||
/**
|
||||
* 监听切换顶部tab栏实现刷新数据
|
||||
*/
|
||||
tabCurrentIndex(val) {
|
||||
if (this.navList[val].dataList.length == 0) this.getData();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 顶部tab点击
|
||||
*/
|
||||
handleTabClick(index) {
|
||||
this.tabCurrentIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* 读取优惠券
|
||||
*/
|
||||
getData() {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
let index = this.tabCurrentIndex;
|
||||
getMemberCoupons(this.navList[index].params).then((res) => {
|
||||
uni.stopPullDownRefresh();
|
||||
if (res.data.success) {
|
||||
let data = res.data.result.records;
|
||||
if (data.length == 0) {
|
||||
if (res.data.pageNumber == 1) {
|
||||
this.navList[index].wheterEmpty = true;
|
||||
} else {
|
||||
this.navList[index].loadStatus = "noMore";
|
||||
}
|
||||
} else if (data.length < 10) {
|
||||
this.navList[index].loadStatus = "noMore";
|
||||
this.navList[index].dataList.push(...data);
|
||||
} else {
|
||||
this.navList[index].dataList.push(...data);
|
||||
}
|
||||
}
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换tab
|
||||
*/
|
||||
changeTab(e) {
|
||||
this.tabCurrentIndex = e.target.current;
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
*/
|
||||
loadData() {
|
||||
let index = this.tabCurrentIndex;
|
||||
if (this.navList[index].loadStatus != "noMore") {
|
||||
this.navList[index].params.pageNumber++;
|
||||
this.getData();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 立即使用优惠券
|
||||
*/
|
||||
useItNow(item) {
|
||||
if (item.storeId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/product/shopPage?id=${item.storeId}`,
|
||||
});
|
||||
} else {
|
||||
uni.switchTab({
|
||||
url: "/pages/tabbar/home/index",
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 优惠券详情
|
||||
*/
|
||||
couponDetail(item) {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/pages/cart/coupon/couponDetail?item=" +
|
||||
encodeURIComponent(JSON.stringify(item)),
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
page {
|
||||
height: 100%;
|
||||
}
|
||||
$item-color: #fff;
|
||||
|
||||
.b-content {
|
||||
background: $page-color-base;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
.list-scroll-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
.coupon-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 220rpx;
|
||||
margin: 20rpx;
|
||||
|
||||
.left {
|
||||
height: 100%;
|
||||
width: 260rpx;
|
||||
background-color: $light-color;
|
||||
position: relative;
|
||||
|
||||
.message {
|
||||
color: $font-color-white;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-top: 40rpx;
|
||||
|
||||
view:nth-child(1) {
|
||||
font-weight: bold;
|
||||
font-size: 60rpx;
|
||||
}
|
||||
|
||||
view:nth-child(2) {
|
||||
font-size: $font-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.wave-line {
|
||||
height: 220rpx;
|
||||
width: 8rpx;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: $light-color;
|
||||
overflow: hidden;
|
||||
|
||||
.wave {
|
||||
width: 8rpx;
|
||||
height: 16rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 0 16rpx 16rpx 0;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background-color: $bg-color;
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
z-index: 111;
|
||||
}
|
||||
|
||||
.circle-top {
|
||||
top: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
|
||||
.circle-bottom {
|
||||
bottom: -20rpx;
|
||||
right: -20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 450rpx;
|
||||
font-size: $font-sm;
|
||||
height: 100%;
|
||||
background-color: #ffffff;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
.content {
|
||||
color: #666666;
|
||||
margin-left: 20rpx;
|
||||
line-height: 2em;
|
||||
> view:nth-child(1) {
|
||||
color: #ff6262;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.title-1,
|
||||
.title-2,
|
||||
.title-3 {
|
||||
font-size: 25rpx;
|
||||
}
|
||||
}
|
||||
.receive {
|
||||
color: #ffffff;
|
||||
background-color: $main-color;
|
||||
border-radius: 50%;
|
||||
width: 86rpx;
|
||||
height: 86rpx;
|
||||
text-align: center;
|
||||
margin-right: 48rpx;
|
||||
vertical-align: middle;
|
||||
padding-top: 8rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.jiao-1 {
|
||||
background-color: #ffc71c;
|
||||
width: 400rpx;
|
||||
transform: rotate(45deg);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
color: #ffffff;
|
||||
right: -130rpx;
|
||||
top: 0;
|
||||
.text-1 {
|
||||
margin-left: 68rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.text-2 {
|
||||
margin-left: 68rpx;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
.no-icon {
|
||||
border-radius: 50%;
|
||||
width: 86rpx;
|
||||
height: 86rpx;
|
||||
margin-right: 48rpx;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
.bg-quan {
|
||||
width: 244rpx;
|
||||
height: 244rpx;
|
||||
border: 6rpx solid $main-color;
|
||||
border-radius: 50%;
|
||||
opacity: 0.1;
|
||||
color: $main-color;
|
||||
text-align: center;
|
||||
padding-top: 30rpx;
|
||||
font-size: 130rpx;
|
||||
position: absolute;
|
||||
right: -54rpx;
|
||||
bottom: -60rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
height: 80rpx;
|
||||
padding: 0 5px;
|
||||
background: #fff;
|
||||
color: $light-color;
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.06);
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
|
||||
.nav-item {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-size: 26rpx;
|
||||
color: $light-color;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
text {
|
||||
line-height: 80rpx;
|
||||
}
|
||||
.current {
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 10rpx;
|
||||
left: 108rpx;
|
||||
width: 30rpx;
|
||||
border-bottom: 2px solid $light-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view class="b-content">
|
||||
<view class="coupon-tabs">
|
||||
<u-tabs
|
||||
:list="navList"
|
||||
keyName="text"
|
||||
:scrollable="false"
|
||||
:inactiveStyle="{ color: '#333' }"
|
||||
v-model:current="tabCurrentIndex"
|
||||
:lineColor="lightColor"
|
||||
:activeStyle="{ color: lightColor }"
|
||||
></u-tabs>
|
||||
</view>
|
||||
<swiper
|
||||
:current="tabCurrentIndex"
|
||||
class="swiper-box"
|
||||
duration="300"
|
||||
@change="changeTab"
|
||||
>
|
||||
<swiper-item
|
||||
class="tab-content"
|
||||
v-for="(navItem, navIndex) in navList"
|
||||
:key="navIndex"
|
||||
>
|
||||
<scroll-view
|
||||
class="list-scroll-content"
|
||||
scroll-y
|
||||
@scrolltolower="loadData"
|
||||
>
|
||||
<u-empty
|
||||
mode="coupon"
|
||||
text="暂无优惠券了"
|
||||
v-if="navItem.whetherEmpty"
|
||||
></u-empty>
|
||||
|
||||
<view
|
||||
class="coupon-card"
|
||||
:class="{ 'coupon-used': navIndex != 0, 'coupon-expired': navIndex == 2 }"
|
||||
v-for="(coupon, index) in navItem.dataList"
|
||||
:key="index"
|
||||
>
|
||||
<view class="coupon-card-left">
|
||||
<text class="coupon-price-symbol" v-if="coupon.couponType != 'DISCOUNT'">¥</text>
|
||||
<text class="coupon-price-value">{{ coupon.couponType == 'DISCOUNT' ? coupon.discount + '折' : unitPrice(coupon.price) }}</text>
|
||||
</view>
|
||||
<view class="coupon-divider"></view>
|
||||
<view class="coupon-card-right">
|
||||
<view class="coupon-info">
|
||||
<text class="coupon-card-name">{{ coupon.title }}</text>
|
||||
<text class="coupon-card-desc">使用范围:{{
|
||||
coupon.scopeType == "ALL" && coupon.storeId == "0"
|
||||
? "全平台"
|
||||
: coupon.scopeType == "PORTION_GOODS"
|
||||
? "部分商品"
|
||||
: coupon.scopeType == "PORTION_GOODS_CATEGORY"
|
||||
? "部分分类商品"
|
||||
: coupon.storeName == "platform"
|
||||
? "全平台"
|
||||
: coupon.storeName + ""
|
||||
}}使用</text>
|
||||
<text class="coupon-card-time" v-if="coupon.endTime">{{ coupon.endTime }}</text>
|
||||
</view>
|
||||
<view class="coupon-status-btn" v-if="navIndex == 0" @click="useItNow(coupon)">
|
||||
立即使用
|
||||
</view>
|
||||
<view class="coupon-status-btn disabled" v-else-if="navIndex == 1">
|
||||
已使用
|
||||
</view>
|
||||
<view class="coupon-status-btn disabled" v-else>
|
||||
已过期
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<uni-load-more :status="navItem.loadStatus"></uni-load-more>
|
||||
</scroll-view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { getMemberCoupons } from '@/api/members.js'
|
||||
import { useStore } from '@/store'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
const tabCurrentIndex = ref(0)
|
||||
const navList = ref([
|
||||
{
|
||||
text: '未使用',
|
||||
loadStatus: 'more',
|
||||
dataList: [] as any[],
|
||||
params: {
|
||||
memberCouponStatus: 'NEW',
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 1,
|
||||
},
|
||||
whetherEmpty: false,
|
||||
},
|
||||
{
|
||||
text: '已使用',
|
||||
loadStatus: 'more',
|
||||
dataList: [] as any[],
|
||||
params: {
|
||||
memberCouponStatus: 'USED',
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 2,
|
||||
},
|
||||
whetherEmpty: false,
|
||||
},
|
||||
{
|
||||
text: '已过期',
|
||||
loadStatus: 'more',
|
||||
dataList: [] as any[],
|
||||
params: {
|
||||
memberCouponStatus: 'EXPIRE',
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
status: 3,
|
||||
},
|
||||
whetherEmpty: false,
|
||||
},
|
||||
])
|
||||
|
||||
onShow(() => {
|
||||
navList.value[tabCurrentIndex.value].params.pageNumber = 1
|
||||
navList.value[tabCurrentIndex.value].dataList = []
|
||||
getData()
|
||||
})
|
||||
|
||||
watch(tabCurrentIndex, (val) => {
|
||||
if (navList.value[val].dataList.length == 0) getData()
|
||||
})
|
||||
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
function handleTabClick(index: number) {
|
||||
tabCurrentIndex.value = index
|
||||
}
|
||||
|
||||
function getData() {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
const index = tabCurrentIndex.value
|
||||
getMemberCoupons(navList.value[index].params).then((res) => {
|
||||
uni.stopPullDownRefresh()
|
||||
if (res.data.success) {
|
||||
const data = res.data.result.records
|
||||
if (data.length == 0) {
|
||||
if (res.data.pageNumber == 1) {
|
||||
navList.value[index].whetherEmpty = true
|
||||
} else {
|
||||
navList.value[index].loadStatus = 'noMore'
|
||||
}
|
||||
} else if (data.length < 10) {
|
||||
navList.value[index].loadStatus = 'noMore'
|
||||
navList.value[index].dataList.push(...data)
|
||||
} else {
|
||||
navList.value[index].dataList.push(...data)
|
||||
}
|
||||
}
|
||||
hideLoadingIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
function changeTab(e: any) {
|
||||
tabCurrentIndex.value = e.detail.current
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
const index = tabCurrentIndex.value
|
||||
if (navList.value[index].loadStatus != 'noMore') {
|
||||
navList.value[index].params.pageNumber++
|
||||
getData()
|
||||
}
|
||||
}
|
||||
|
||||
function useItNow(item: any) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/navigation/search/searchPage?promotionsId=${item.couponId}&promotionType=COUPON`,
|
||||
})
|
||||
}
|
||||
|
||||
function couponDetail(item: any) {
|
||||
uni.navigateTo({
|
||||
url: '/pages/cart/coupon/couponDetail?item=' + encodeURIComponent(JSON.stringify(item)),
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.b-content {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.coupon-tabs {
|
||||
background: #fff;
|
||||
height: 88rpx;
|
||||
box-shadow: 0 1rpx 8rpx rgba(0, 0, 0, 0.04);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
:deep(.u-tabs),
|
||||
:deep(.u-tabs__wrapper),
|
||||
:deep(.u-tabs__wrapper__scroll-view-wrapper),
|
||||
:deep(.u-tabs__wrapper__scroll-view),
|
||||
:deep(.u-tabs__wrapper__nav) {
|
||||
background: #fff;
|
||||
height: 88rpx;
|
||||
}
|
||||
|
||||
:deep(.u-tabs__wrapper__nav__item) {
|
||||
min-height: 88rpx;
|
||||
}
|
||||
|
||||
:deep(.u-tabs__wrapper__nav__item__text) {
|
||||
color: #333;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
height: calc(100vh - 88rpx - var(--status-bar-height));
|
||||
}
|
||||
|
||||
.list-scroll-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
box-shadow: none;
|
||||
border: 1rpx solid #f0f1f5;
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 50%;
|
||||
left: 204rpx;
|
||||
z-index: 2;
|
||||
box-shadow: inset 0 0 0 1rpx #eceef2;
|
||||
}
|
||||
|
||||
&::before {
|
||||
top: -16rpx;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: -16rpx;
|
||||
}
|
||||
|
||||
&.coupon-used {
|
||||
.coupon-card-left {
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
|
||||
color: #999;
|
||||
}
|
||||
.coupon-status-btn {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.coupon-expired {
|
||||
.coupon-card-left {
|
||||
background: linear-gradient(135deg, #f5f5f5 0%, #eeeeee 100%);
|
||||
color: #999;
|
||||
}
|
||||
.coupon-status-btn {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.coupon-card-left {
|
||||
width: 220rpx;
|
||||
min-height: 180rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: #ff3b30;
|
||||
position: relative;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffecec 100%);
|
||||
}
|
||||
|
||||
.coupon-price-symbol {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin-right: 4rpx;
|
||||
}
|
||||
|
||||
.coupon-price-value {
|
||||
font-size: 56rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
letter-spacing: -1rpx;
|
||||
}
|
||||
|
||||
.coupon-divider {
|
||||
position: absolute;
|
||||
left: 220rpx;
|
||||
top: 24rpx;
|
||||
bottom: 24rpx;
|
||||
width: 0;
|
||||
border-left: 2rpx dashed #eceef2;
|
||||
}
|
||||
|
||||
.coupon-card-right {
|
||||
flex: 1;
|
||||
padding: 32rpx 32rpx 32rpx 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.coupon-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.coupon-card-name {
|
||||
font-size: 30rpx;
|
||||
color: #111;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-card-desc {
|
||||
font-size: 24rpx;
|
||||
color: #8a8f99;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.coupon-card-time {
|
||||
font-size: 22rpx;
|
||||
color: #b0b3bf;
|
||||
}
|
||||
|
||||
.coupon-status-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 16rpx 32rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #ff6b35, #ff4b2b);
|
||||
|
||||
&:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
209
pages/cart/gift-card/index.vue
Normal file
209
pages/cart/gift-card/index.vue
Normal file
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<view class="b-content" :style="themeStyle">
|
||||
<scroll-view class="list-scroll-content" scroll-y>
|
||||
<u-empty
|
||||
mode="coupon"
|
||||
text="暂无可用礼品卡"
|
||||
v-if="!cardList.length"
|
||||
></u-empty>
|
||||
|
||||
<view class="coupon-card" v-for="item in cardList" :key="item.id">
|
||||
<view class="coupon-card-left">
|
||||
<view class="price-row">
|
||||
<text class="coupon-price-symbol">¥</text>
|
||||
<text class="coupon-price-value">{{ unitPrice(item.balance) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="coupon-divider"></view>
|
||||
<view class="coupon-card-right">
|
||||
<text class="coupon-card-name">{{ item.giftCardName || '礼品卡' }}</text>
|
||||
<view class="coupon-card-foot">
|
||||
<view
|
||||
class="coupon-status-btn"
|
||||
:class="{ cancel: item.selected }"
|
||||
@click="clickWay(item)"
|
||||
>
|
||||
{{ item.selected ? '取消使用' : '立即使用' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="notice" v-if="cardList.length">{{ GIFT_CARD_PAY_NOTICE }}</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { selectGiftCard } from '@/api/trade.js'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { decorateGiftCard, GIFT_CARD_PAY_NOTICE } from '@/utils/giftCard.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
const way = ref('')
|
||||
const cardList = ref<any[]>([])
|
||||
|
||||
onLoad((options) => {
|
||||
way.value = options?.way || ''
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
const selectedIds = (store.state as any).selectedGiftCardIds || []
|
||||
const cards = (store.state as any).canUseGiftCards || []
|
||||
cardList.value = cards.map((item: any) => decorateGiftCard(item, selectedIds))
|
||||
})
|
||||
|
||||
function clickWay(item: any) {
|
||||
if (!item?.id) return
|
||||
selectGiftCard({
|
||||
way: way.value,
|
||||
credentialId: String(item.id),
|
||||
used: !item.selected,
|
||||
}).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message || '礼品卡选择失败',
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.b-content {
|
||||
background: #f7f8fa;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-scroll-content {
|
||||
flex: 1;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
padding: 24rpx 24rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.coupon-card {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
background: #fff;
|
||||
border-radius: 24rpx;
|
||||
margin-bottom: 24rpx;
|
||||
position: relative;
|
||||
border: 1rpx solid #f0f1f5;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.coupon-card-left {
|
||||
width: 200rpx;
|
||||
min-height: 148rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
color: #ff3b30;
|
||||
background: linear-gradient(135deg, #fff5f5 0%, #ffecec 100%);
|
||||
}
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.coupon-price-symbol {
|
||||
font-size: 26rpx;
|
||||
font-weight: 700;
|
||||
margin-right: 2rpx;
|
||||
}
|
||||
|
||||
.coupon-price-value {
|
||||
font-size: 44rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.coupon-price-label {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #ff8a80;
|
||||
}
|
||||
|
||||
.coupon-divider {
|
||||
position: absolute;
|
||||
left: 200rpx;
|
||||
top: 24rpx;
|
||||
bottom: 24rpx;
|
||||
width: 0;
|
||||
border-left: 2rpx dashed #f0d6d6;
|
||||
}
|
||||
|
||||
.coupon-card-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 24rpx 24rpx 20rpx 28rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.coupon-card-name {
|
||||
font-size: 28rpx;
|
||||
color: #111;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.coupon-card-desc,
|
||||
.coupon-card-time {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #8a8f99;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.coupon-card-time {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.coupon-card-foot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.coupon-status-btn {
|
||||
flex-shrink: 0;
|
||||
padding: 12rpx 24rpx;
|
||||
border-radius: 40rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, var(--theme-light, #ff6b35), var(--theme-primary, #ff4b2b));
|
||||
|
||||
&.cancel {
|
||||
background: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
431
pages/cart/gift-card/myGiftCards.vue
Normal file
431
pages/cart/gift-card/myGiftCards.vue
Normal file
@@ -0,0 +1,431 @@
|
||||
<template>
|
||||
<view class="page">
|
||||
<view class="bind-section">
|
||||
<view class="section-title">兑换礼品卡</view>
|
||||
<view class="bind-row">
|
||||
<text class="bind-label">卡号</text>
|
||||
<u-input
|
||||
v-model="bindForm.cardNo"
|
||||
border="none"
|
||||
maxlength="64"
|
||||
placeholder="请输入卡号"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
<view class="bind-row">
|
||||
<text class="bind-label">兑换码</text>
|
||||
<u-input
|
||||
v-model="bindForm.cardSecret"
|
||||
border="none"
|
||||
maxlength="128"
|
||||
placeholder="请输入兑换码(卡密)"
|
||||
clearable
|
||||
/>
|
||||
</view>
|
||||
<view class="bind-btn" :class="{ disabled: bindSubmitting }" @click="submitBind">
|
||||
{{ bindSubmitting ? '兑换中...' : '兑换' }}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabs-wrap">
|
||||
<u-tabs
|
||||
:list="tabList"
|
||||
keyName="text"
|
||||
:scrollable="false"
|
||||
:inactiveStyle="{ color: '#333' }"
|
||||
v-model:current="tabCurrentIndex"
|
||||
:lineColor="lightColor"
|
||||
:activeStyle="{ color: lightColor }"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<scroll-view class="list-scroll" scroll-y @scrolltolower="loadMore">
|
||||
<u-empty v-if="!loading && !list.length" mode="coupon" text="暂无礼品卡" />
|
||||
<view
|
||||
class="gcc-card"
|
||||
:class="{ 'is-disabled': statusTab === 'UNAVAILABLE' }"
|
||||
v-for="item in list"
|
||||
:key="item.id || item.cardNo"
|
||||
>
|
||||
<view class="gcc-header">
|
||||
<view class="gcc-header-pattern" />
|
||||
<view class="gcc-header-inner">
|
||||
<view class="gcc-header-left">
|
||||
<view class="gcc-title">{{ item.giftCardName || '礼品卡' }}</view>
|
||||
<view class="gcc-face">面值{{ item.faceText }}元</view>
|
||||
<view class="gcc-cardno">{{ item.cardNo }}</view>
|
||||
</view>
|
||||
<view class="gcc-header-right">
|
||||
<view class="gcc-type">现金卡</view>
|
||||
<view class="gcc-expire">{{ item.expireText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="gcc-body">
|
||||
<view class="gcc-row">
|
||||
当前余额:
|
||||
<text class="gcc-strong" v-if="item.balance != null">¥{{ unitPrice(item.balance) }}</text>
|
||||
<text class="gcc-strong" v-else>—</text>
|
||||
</view>
|
||||
<view
|
||||
class="gcc-action"
|
||||
:class="{ 'is-disabled': actionDisabled || isActivating(item) }"
|
||||
@click="onCardPrimaryAction(item)"
|
||||
>
|
||||
<text v-if="isActivating(item)">提交中...</text>
|
||||
<text v-else>{{ actionLabel }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="load-more" v-if="list.length">{{ loadStatusText }}</view>
|
||||
<view class="notice">{{ useNotice }}</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
activateGiftCardCashMemberCard,
|
||||
bindGiftCardCashMemberCard,
|
||||
getGiftCardCashMemberCardPage,
|
||||
} from '@/api/promotions.js'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import {
|
||||
decorateGiftCard,
|
||||
GIFT_CARD_STATUS,
|
||||
GIFT_CARD_USE_NOTICE,
|
||||
giftCardActionLabel,
|
||||
resolveMemberCardId,
|
||||
} from '@/utils/giftCard.js'
|
||||
import { useStore } from '@/store'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const store = useStore()
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
const useNotice = GIFT_CARD_USE_NOTICE
|
||||
|
||||
const tabList = [
|
||||
{ text: '可用', status: GIFT_CARD_STATUS.AVAILABLE },
|
||||
{ text: '不可用', status: GIFT_CARD_STATUS.UNAVAILABLE },
|
||||
{ text: '待激活', status: GIFT_CARD_STATUS.PENDING_ACTIVATION },
|
||||
]
|
||||
const tabCurrentIndex = ref(0)
|
||||
const statusTab = computed(() => tabList[tabCurrentIndex.value].status)
|
||||
const actionLabel = computed(() => giftCardActionLabel(statusTab.value))
|
||||
const actionDisabled = computed(() => statusTab.value === GIFT_CARD_STATUS.UNAVAILABLE)
|
||||
|
||||
const bindForm = ref({ cardNo: '', cardSecret: '' })
|
||||
const bindSubmitting = ref(false)
|
||||
const loading = ref(false)
|
||||
const list = ref<any[]>([])
|
||||
const pageNumber = ref(1)
|
||||
const loadStatus = ref('more')
|
||||
const activatingId = ref('')
|
||||
|
||||
const loadStatusText = computed(() => (loadStatus.value === 'noMore' ? '没有更多了' : ''))
|
||||
|
||||
onShow(() => {
|
||||
resetAndLoad()
|
||||
})
|
||||
|
||||
watch(tabCurrentIndex, () => {
|
||||
resetAndLoad()
|
||||
})
|
||||
|
||||
function resetAndLoad() {
|
||||
pageNumber.value = 1
|
||||
list.value = []
|
||||
loadStatus.value = 'more'
|
||||
getList()
|
||||
}
|
||||
|
||||
function getList() {
|
||||
if (loadStatus.value === 'noMore' && pageNumber.value > 1) return
|
||||
loading.value = true
|
||||
getGiftCardCashMemberCardPage({
|
||||
pageNumber: pageNumber.value,
|
||||
pageSize: 10,
|
||||
sort: 'createTime',
|
||||
order: 'desc',
|
||||
memberCardStatus: statusTab.value,
|
||||
}).then((res) => {
|
||||
loading.value = false
|
||||
if (!res.data.success) return
|
||||
const records = ((res.data.result && res.data.result.records) || []).map((item: any) =>
|
||||
decorateGiftCard(item)
|
||||
)
|
||||
if (pageNumber.value === 1) {
|
||||
list.value = records
|
||||
} else {
|
||||
list.value = list.value.concat(records)
|
||||
}
|
||||
loadStatus.value = records.length < 10 ? 'noMore' : 'more'
|
||||
}).catch(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
if (loadStatus.value === 'noMore' || loading.value) return
|
||||
pageNumber.value += 1
|
||||
getList()
|
||||
}
|
||||
|
||||
function submitBind() {
|
||||
const cardNo = String(bindForm.value.cardNo || '').trim()
|
||||
const cardSecret = String(bindForm.value.cardSecret || '').trim()
|
||||
if (!cardNo) {
|
||||
uni.showToast({ title: '请输入卡号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!cardSecret) {
|
||||
uni.showToast({ title: '请输入兑换码', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (bindSubmitting.value) return
|
||||
bindSubmitting.value = true
|
||||
bindGiftCardCashMemberCard({ cardNo, cardSecret })
|
||||
.then((res) => {
|
||||
bindSubmitting.value = false
|
||||
if (!res.data.success) return
|
||||
uni.showToast({ title: res.data.message || '兑换成功', icon: 'none' })
|
||||
bindForm.value = { cardNo: '', cardSecret: '' }
|
||||
const pendingIndex = tabList.findIndex((item) => item.status === GIFT_CARD_STATUS.PENDING_ACTIVATION)
|
||||
if (pendingIndex >= 0 && tabCurrentIndex.value !== pendingIndex) {
|
||||
tabCurrentIndex.value = pendingIndex
|
||||
return
|
||||
}
|
||||
resetAndLoad()
|
||||
})
|
||||
.catch(() => {
|
||||
bindSubmitting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function isActivating(item: any) {
|
||||
const id = resolveMemberCardId(item)
|
||||
return !!activatingId.value && activatingId.value === id
|
||||
}
|
||||
|
||||
function onCardPrimaryAction(item: any) {
|
||||
if (actionDisabled.value) return
|
||||
if (statusTab.value === GIFT_CARD_STATUS.PENDING_ACTIVATION) {
|
||||
const memberCardId = resolveMemberCardId(item)
|
||||
if (!memberCardId) {
|
||||
uni.showToast({ title: '卡片信息异常,无法激活', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (activatingId.value) return
|
||||
activatingId.value = memberCardId
|
||||
activateGiftCardCashMemberCard(memberCardId)
|
||||
.then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.showToast({ title: res.data.message || '激活成功', icon: 'none' })
|
||||
const availableIndex = tabList.findIndex((item) => item.status === GIFT_CARD_STATUS.AVAILABLE)
|
||||
if (availableIndex >= 0 && tabCurrentIndex.value !== availableIndex) {
|
||||
tabCurrentIndex.value = availableIndex
|
||||
return
|
||||
}
|
||||
resetAndLoad()
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
activatingId.value = ''
|
||||
})
|
||||
return
|
||||
}
|
||||
if (statusTab.value === GIFT_CARD_STATUS.AVAILABLE) {
|
||||
uni.switchTab({ url: '/pages/tabbar/home/index' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
height: 100vh;
|
||||
background: #f7f8fa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bind-section {
|
||||
margin: 24rpx;
|
||||
padding: 28rpx 24rpx 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.bind-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 88rpx;
|
||||
border-bottom: 1rpx solid #f0f1f5;
|
||||
}
|
||||
|
||||
.bind-label {
|
||||
width: 110rpx;
|
||||
flex-shrink: 0;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.bind-btn {
|
||||
margin-top: 24rpx;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
border-radius: 40rpx;
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
background: linear-gradient(135deg, #ff6b35, #ff4b2b);
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-wrap {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.list-scroll {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.gcc-card {
|
||||
border-radius: 20rpx;
|
||||
overflow: hidden;
|
||||
background: #fff;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
|
||||
&.is-disabled {
|
||||
opacity: 0.72;
|
||||
}
|
||||
}
|
||||
|
||||
.gcc-header {
|
||||
position: relative;
|
||||
min-height: 180rpx;
|
||||
background: linear-gradient(125deg, #ff9a4a 0%, #ff7729 42%, #ff8f3d 100%);
|
||||
}
|
||||
|
||||
.gcc-header-pattern {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
opacity: 0.22;
|
||||
pointer-events: none;
|
||||
background-image: repeating-linear-gradient(
|
||||
-36deg,
|
||||
transparent,
|
||||
transparent 10rpx,
|
||||
rgba(255, 255, 255, 0.45) 10rpx,
|
||||
rgba(255, 255, 255, 0.45) 12rpx
|
||||
);
|
||||
}
|
||||
|
||||
.gcc-header-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 24rpx 24rpx;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.gcc-header-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 16rpx;
|
||||
}
|
||||
|
||||
.gcc-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.gcc-face {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.gcc-cardno {
|
||||
margin-top: 28rpx;
|
||||
font-size: 22rpx;
|
||||
opacity: 0.75;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.gcc-header-right {
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.gcc-type {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.gcc-expire {
|
||||
margin-top: 16rpx;
|
||||
font-size: 22rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.gcc-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.gcc-row {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.gcc-strong {
|
||||
color: #ff6b22;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.gcc-action {
|
||||
margin-top: 24rpx;
|
||||
height: 72rpx;
|
||||
line-height: 72rpx;
|
||||
text-align: center;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: #e95b6c;
|
||||
background: #faf4f5;
|
||||
|
||||
&.is-disabled {
|
||||
color: #c5c8ce;
|
||||
background: #f7f7f7;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more,
|
||||
.notice {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
line-height: 1.6;
|
||||
padding: 8rpx 8rpx 40rpx;
|
||||
}
|
||||
|
||||
.notice {
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<view class="error-page">
|
||||
<u-navbar title="支付失败" :border="false" :fixed="true" :placeholder="true" :auto-back="true"></u-navbar>
|
||||
<view class="error-content">
|
||||
<u-icon name="close-circle-fill" color="#f56c6c" size="120"></u-icon>
|
||||
<text class="error-title">支付失败</text>
|
||||
<text class="error-desc">{{ errorMessage || '支付过程中出现错误,请重试' }}</text>
|
||||
<view class="btn-group">
|
||||
<u-button type="primary" text="返回订单" @click="goBack"></u-button>
|
||||
<u-button type="warning" text="去支付" @click="rePay"></u-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
|
||||
const errorMessage = ref('')
|
||||
const orderSn = ref('')
|
||||
|
||||
onLoad((options: any) => {
|
||||
if (options) {
|
||||
errorMessage.value = options.message || ''
|
||||
orderSn.value = options.orderSn || ''
|
||||
}
|
||||
})
|
||||
|
||||
function goBack() {
|
||||
uni.navigateBack({
|
||||
fail: () => {
|
||||
uni.switchTab({ url: '/pages/tabbar/user/my' })
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function rePay() {
|
||||
if (orderSn.value) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cart/payment/payOrder?orderSn=${orderSn.value}`
|
||||
})
|
||||
} else {
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.error-page {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 200rpx 40rpx;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.error-desc {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
margin-top: 20rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
margin-top: 60rpx;
|
||||
width: 100%;
|
||||
|
||||
:deep(.u-button) {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,461 +1,425 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div :style="themeStyle" class="wrapper">
|
||||
<div class="box">
|
||||
<div class="block block-1">
|
||||
<image class="img" src="@/pages/cart/static/pay.png" />
|
||||
<p class="ptips">收银台</p>
|
||||
|
||||
<p class="ptips">剩余支付时间:
|
||||
<u-count-down :show-days="false" :show-border="true" font-size="28" color="#008ffa" border-color="#008ffa" ref="uCountDown" :timestamp="autoCancel"></u-count-down>
|
||||
</p>
|
||||
<p class="ptips">
|
||||
<view class="ptips ptips-countdown">
|
||||
剩余支付时间:
|
||||
<u-count-down
|
||||
v-if="autoCancelTime > 0"
|
||||
:time="autoCancelTime"
|
||||
format="HH:mm:ss"
|
||||
@finish="onPayTimeout"
|
||||
>
|
||||
<template #default="{ hours, minutes, seconds }">
|
||||
<view class="countdown-box">
|
||||
<text class="countdown-num">{{ padTime(hours) }}</text>
|
||||
<text class="countdown-colon">:</text>
|
||||
<text class="countdown-num">{{ padTime(minutes) }}</text>
|
||||
<text class="countdown-colon">:</text>
|
||||
<text class="countdown-num">{{ padTime(seconds) }}</text>
|
||||
</view>
|
||||
</template>
|
||||
</u-count-down>
|
||||
<view v-else class="countdown-box">
|
||||
<text class="countdown-num">00</text>
|
||||
<text class="countdown-colon">:</text>
|
||||
<text class="countdown-num">00</text>
|
||||
<text class="countdown-colon">:</text>
|
||||
<text class="countdown-num">00</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="ptips ptips-price">
|
||||
支付金额
|
||||
<span>¥{{ cashierParams.price | unitPrice }}</span>
|
||||
</p>
|
||||
<text class="price-text">¥{{ unitPrice(cashierParams.price) }}</text>
|
||||
</view>
|
||||
</div>
|
||||
</div>
|
||||
<div class="__pay_form__">
|
||||
</div>
|
||||
<div class="block-4" v-if="cashierParams.price > 0">
|
||||
<div class="payItem">支付方式</div>
|
||||
<div class="payItem" v-for="(item, index) in payList" :key="index">
|
||||
<u-row class="row">
|
||||
<div class="col1" @click="awaitPay(item, index)" size="100" style="text-align:left;">
|
||||
<div v-if="item == 'ALIPAY'">
|
||||
<u-icon class="method_icon" name="zhifubao-circle-fill" color="#008ffa" size="80"></u-icon>
|
||||
<span class="method_name">支付宝</span>
|
||||
</div>
|
||||
<div v-if="item == 'WECHAT'">
|
||||
<u-icon class="method_icon" name="weixin-circle-fill" color="#00c98b" size="80"></u-icon>
|
||||
<span class="method_name">微信</span>
|
||||
</div>
|
||||
<div v-if="item == 'WALLET'">
|
||||
<u-icon class="method_icon" name="red-packet-fill" color="#dd6161" size="80"></u-icon>
|
||||
<span class="method_name">余额支付(当前余额:¥{{ walletValue | unitPrice }})</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col3" @click="awaitPay(item)" textAlign="right">
|
||||
<u-icon size="26" color="#b1b1b1" name="arrow-right"></u-icon>
|
||||
</div>
|
||||
</u-row>
|
||||
<div class="payItem payItem-title">支付方式</div>
|
||||
<div
|
||||
class="payItem payItem-method"
|
||||
v-for="(item, index) in payList"
|
||||
:key="index"
|
||||
@click="awaitPay(item, index)"
|
||||
>
|
||||
<view class="method-left">
|
||||
<template v-if="item == 'ALIPAY'">
|
||||
<u-icon class="method_icon" name="zhifubao-circle-fill" color="#008ffa" size="44"></u-icon>
|
||||
<text class="method_name">支付宝</text>
|
||||
</template>
|
||||
<template v-if="item == 'WECHAT'">
|
||||
<u-icon class="method_icon" name="weixin-circle-fill" color="#00c98b" size="44"></u-icon>
|
||||
<text class="method_name">微信</text>
|
||||
</template>
|
||||
<template v-if="item == 'WALLET'">
|
||||
<u-icon class="method_icon" name="red-packet-fill" color="#dd6161" size="44"></u-icon>
|
||||
<text class="method_name">余额支付(当前余额:¥{{ unitPrice(walletValue) }})</text>
|
||||
</template>
|
||||
</view>
|
||||
<u-icon size="28" color="#b1b1b1" name="arrow-right"></u-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import * as API_Trade from "@/api/trade";
|
||||
import {payCallback} from '@/api/members'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
//路径传参
|
||||
routerVal: "",
|
||||
//收银台参数
|
||||
cashierParams: "",
|
||||
//支付方式集合
|
||||
payList: "",
|
||||
//支付sn
|
||||
sn: "",
|
||||
//订单类型
|
||||
orderType: "",
|
||||
//支付异常
|
||||
exception: {},
|
||||
//支付表单
|
||||
payForm: {},
|
||||
//支付类型 APP/WECHAT_MP/H5/NATIVE app/微信小程序/h5/二维码
|
||||
paymentType: "",
|
||||
// 支付客户端 APP/NATIVE/JSAPI/H5
|
||||
paymentClient: "",
|
||||
//余额
|
||||
walletValue: 0.0,
|
||||
// 支付倒计时
|
||||
autoCancel: 0,
|
||||
|
||||
};
|
||||
},
|
||||
onLoad(val) {
|
||||
this.routerVal = val;
|
||||
<script setup lang="ts">
|
||||
import { ref, getCurrentInstance, onMounted, computed } from 'vue'
|
||||
import { onLoad, onBackPress } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import * as API_Trade from '@/api/trade'
|
||||
import { payCallback } from '@/api/members'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
//初始化参数
|
||||
// #ifdef APP-PLUS
|
||||
this.paymentType = "APP";
|
||||
this.paymentClient = "APP";
|
||||
//#endif
|
||||
// #ifdef MP-WEIXIN
|
||||
this.paymentType = "WECHAT_MP";
|
||||
this.paymentClient = "MP";
|
||||
//#endif
|
||||
// #ifdef H5
|
||||
this.paymentType = "H5";
|
||||
//如果是微信浏览器,则使用公众号支付,否则使用h5,
|
||||
// 区别是:h5是通过浏览器外部调用微信app进行支付,而JSAPI则是 在微信浏览器内部,或者小程序 调用微信支付
|
||||
this.paymentClient = this.isWeiXin() ? "JSAPI" : "H5";
|
||||
//#endif
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const cashierParams = ref<Record<string, any>>({ price: 0 })
|
||||
const payList = ref<string[]>([])
|
||||
const sn = ref('')
|
||||
const orderType = ref('')
|
||||
const paymentType = ref('')
|
||||
const paymentClient = ref('')
|
||||
const walletValue = ref(0)
|
||||
const autoCancelTime = ref(0)
|
||||
|
||||
onLoad((val) => {
|
||||
routerVal.value = val || {}
|
||||
// #ifdef APP-PLUS
|
||||
paymentType.value = 'APP'
|
||||
paymentClient.value = 'APP'
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
paymentType.value = 'WECHAT_MP'
|
||||
paymentClient.value = 'MP'
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
paymentType.value = 'H5'
|
||||
paymentClient.value = isWeiXin() ? 'JSAPI' : 'H5'
|
||||
// #endif
|
||||
})
|
||||
|
||||
//
|
||||
},
|
||||
onBackPress(e) {
|
||||
if (e.from == "backbutton") {
|
||||
if(this.routerVal.recharge_sn){
|
||||
uni.switchTab({
|
||||
url: '/pages/tabbar/user/my'
|
||||
});
|
||||
}
|
||||
else{
|
||||
uni.redirectTo({
|
||||
url: "/pages/order/myOrder?status=0",
|
||||
});
|
||||
}
|
||||
return true; //阻止默认返回行为
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.cashierData();
|
||||
},
|
||||
methods: {
|
||||
onBackPress((e) => {
|
||||
if (e.from == 'backbutton') {
|
||||
if (routerVal.value.recharge_sn) {
|
||||
uni.switchTab({ url: '/pages/tabbar/user/my' })
|
||||
} else {
|
||||
uni.navigateTo({ url: '/pages/order/myOrder?status=0' })
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
/**
|
||||
* 支付成功后跳转
|
||||
*/
|
||||
callback(paymentMethod){
|
||||
uni.navigateTo({
|
||||
url: "/pages/cart/payment/success?paymentMethod=" +
|
||||
paymentMethod +
|
||||
"&payPrice=" +
|
||||
this.cashierParams.price+
|
||||
"&orderType="+this.orderType
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取收银详情
|
||||
*/
|
||||
cashierData() {
|
||||
let parms = {};
|
||||
onMounted(() => {
|
||||
cashierData()
|
||||
})
|
||||
|
||||
if (this.routerVal.recharge_sn) {
|
||||
// 判断当前是否是充值
|
||||
this.sn = this.routerVal.recharge_sn;
|
||||
this.orderType = "RECHARGE";
|
||||
} else if (this.routerVal.trade_sn) {
|
||||
this.sn = this.routerVal.trade_sn;
|
||||
this.orderType = "TRADE";
|
||||
} else {
|
||||
this.sn = this.routerVal.order_sn;
|
||||
this.orderType = "ORDER";
|
||||
}
|
||||
parms.sn = this.sn;
|
||||
parms.orderType = this.orderType;
|
||||
parms.clientType = this.paymentType;
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
API_Trade.getCashierData(parms).then((res) => {
|
||||
|
||||
if(res.data.success){
|
||||
this.cashierParams = res.data.result;
|
||||
function callback(paymentMethod: string) {
|
||||
uni.redirectTo({
|
||||
url:
|
||||
'/pages/cart/payment/success?paymentMethod=' +
|
||||
paymentMethod +
|
||||
'&payPrice=' +
|
||||
cashierParams.value.price +
|
||||
'&orderType=' +
|
||||
orderType.value,
|
||||
})
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
this.payList = res.data.result.support.filter((item) => {
|
||||
return item != "ALIPAY";
|
||||
});
|
||||
// #endif
|
||||
function cashierData() {
|
||||
const parms: Record<string, string> = {}
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
if(this.routerVal.recharge_sn){
|
||||
this.payList = res.data.result.support.filter((item) => {
|
||||
return item != "WALLET";
|
||||
})
|
||||
}
|
||||
else{
|
||||
this.payList = res.data.result.support;
|
||||
}
|
||||
// #ifndef APP-PLUS
|
||||
//判断是否微信浏览器
|
||||
var ua = window.navigator.userAgent.toLowerCase();
|
||||
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
|
||||
|
||||
this.payList = res.data.result.support.filter((item) => {
|
||||
return item != "ALIPAY";
|
||||
});
|
||||
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #endif
|
||||
|
||||
if (routerVal.value.recharge_sn) {
|
||||
sn.value = routerVal.value.recharge_sn
|
||||
orderType.value = 'RECHARGE'
|
||||
} else if (routerVal.value.trade_sn) {
|
||||
sn.value = routerVal.value.trade_sn
|
||||
orderType.value = 'TRADE'
|
||||
} else {
|
||||
sn.value = routerVal.value.order_sn
|
||||
orderType.value = 'ORDER'
|
||||
}
|
||||
parms.sn = sn.value
|
||||
parms.orderType = orderType.value
|
||||
parms.clientType = paymentType.value
|
||||
|
||||
this.walletValue = res.data.result.walletValue;
|
||||
this.autoCancel =
|
||||
(res.data.result.autoCancel - new Date().getTime()) / 1000;
|
||||
}
|
||||
else if(res.data.code == 32000){
|
||||
setTimeout(()=>{
|
||||
uni.redirectTo({
|
||||
url: `/pages/order/myOrder?status=0`
|
||||
});
|
||||
},500)
|
||||
|
||||
}
|
||||
});
|
||||
},
|
||||
API_Trade.getCashierData(parms).then((res) => {
|
||||
if (res.data.success) {
|
||||
cashierParams.value = res.data.result
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
payList.value = res.data.result.support.filter((item: string) => item != 'ALIPAY')
|
||||
// #endif
|
||||
|
||||
awaitPay(payment){
|
||||
this.$u.throttle(()=>{
|
||||
this.pay(payment)
|
||||
}, 2000)
|
||||
},
|
||||
if (routerVal.value.recharge_sn) {
|
||||
payList.value = res.data.result.support.filter((item: string) => item != 'WALLET')
|
||||
} else {
|
||||
payList.value = res.data.result.support
|
||||
}
|
||||
|
||||
//订单支付
|
||||
async pay(payment) {
|
||||
|
||||
// 支付编号
|
||||
const sn = this.sn;
|
||||
// 交易类型【交易号|订单号】
|
||||
const orderType = this.orderType;
|
||||
// #ifdef H5
|
||||
const ua = window.navigator.userAgent.toLowerCase()
|
||||
if (ua.match(/MicroMessenger/i) == 'micromessenger') {
|
||||
payList.value = res.data.result.support.filter((item: string) => item != 'ALIPAY')
|
||||
if (orderType.value == 'RECHARGE') {
|
||||
payList.value = res.data.result.support.filter((item: string) => item == 'WECHAT')
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
const clientType = this.paymentType;
|
||||
let params = {
|
||||
sn,
|
||||
orderType,
|
||||
clientType,
|
||||
};
|
||||
walletValue.value = res.data.result.walletValue
|
||||
const cancelAt = Number(res.data.result.autoCancel)
|
||||
autoCancelTime.value = cancelAt > 0 ? Math.max(cancelAt - Date.now(), 0) : 0
|
||||
} else if (res.data.code == 32000) {
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: '/pages/order/myOrder?status=0' })
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//支付方式 WECHAT/ALIPAY
|
||||
const paymentMethod = payment;
|
||||
// 客户端类型 APP/NATIVE/JSAPI/H5
|
||||
const paymentClient = this.paymentClient;
|
||||
function awaitPay(payment: string) {
|
||||
proxy.$u.throttle(() => {
|
||||
pay(payment)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
//APP pay
|
||||
// 初始化支付签名
|
||||
await API_Trade.initiatePay(paymentMethod, paymentClient, params).then(
|
||||
(signXml) => {
|
||||
function padTime(val: number) {
|
||||
return String(val != null ? val : 0).padStart(2, '0')
|
||||
}
|
||||
|
||||
//如果支付异常
|
||||
if (!signXml.data.success) {
|
||||
uni.showToast({
|
||||
title: signXml.data.message,
|
||||
duration: 2000
|
||||
});
|
||||
return;
|
||||
}
|
||||
function onPayTimeout() {
|
||||
uni.showToast({ title: '支付超时,请重新下单', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: '/pages/order/myOrder?status=0' })
|
||||
}, 1500)
|
||||
}
|
||||
|
||||
let payForm = signXml.data.result;
|
||||
function goPayError(message = '支付失败,如果您已支付,请勿反复支付') {
|
||||
const query = [`orderSn=${encodeURIComponent(sn.value)}`, `message=${encodeURIComponent(message)}`]
|
||||
uni.navigateTo({ url: `/pages/cart/payment/error?${query.join('&')}` })
|
||||
}
|
||||
|
||||
let paymentType = paymentMethod === "WECHAT" ? "wxpay" : "alipay";
|
||||
uni.requestPayment({
|
||||
provider: paymentType,
|
||||
orderInfo: payForm,
|
||||
success: (e) => {
|
||||
console.log(e);
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "支付成功!",
|
||||
});
|
||||
async function pay(payment: string) {
|
||||
const params = {
|
||||
sn: sn.value,
|
||||
orderType: orderType.value,
|
||||
clientType: paymentType.value,
|
||||
}
|
||||
const paymentMethod = payment
|
||||
const client = paymentClient.value
|
||||
|
||||
this.callback(paymentMethod)
|
||||
|
||||
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log(this);
|
||||
this.exception = e;
|
||||
uni.showModal({
|
||||
content: "支付失败,如果您已支付,请勿反复支付",
|
||||
showCancel: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
//APP pay
|
||||
// #endif
|
||||
uni.showLoading({ title: '正在唤起支付...', mask: true })
|
||||
|
||||
//#ifdef H5
|
||||
//H5 pay
|
||||
await API_Trade.initiatePay(paymentMethod, paymentClient, params).then(
|
||||
(res) => {
|
||||
let response = res.data;
|
||||
//如果非支付宝支付才需要进行判定,因为支付宝h5支付是直接输出的,没有返回所谓的消息状态
|
||||
if(paymentMethod !== "ALIPAY"){
|
||||
//如果支付异常
|
||||
if (!response.success) {
|
||||
uni.showToast({
|
||||
title: response.message,
|
||||
duration: 2000,
|
||||
icon:"none"
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (paymentMethod === "ALIPAY") {
|
||||
document.write(response);
|
||||
} else if (paymentMethod === "WECHAT") {
|
||||
if (this.isWeiXin()) {
|
||||
//微信公众号支付
|
||||
WeixinJSBridge.invoke(
|
||||
"getBrandWCPayRequest",
|
||||
response.result,
|
||||
(res) => {
|
||||
if (res.err_msg == "get_brand_wcpay_request:ok") {
|
||||
// 使用以上方式判断前端返回,微信团队郑重提示:
|
||||
//res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "支付成功!",
|
||||
});
|
||||
this.callback(paymentMethod)
|
||||
|
||||
} else {
|
||||
uni.showModal({
|
||||
content: "支付失败,如果您已支付,请勿反复支付",
|
||||
showCancel: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
window.location.href = JSON.parse(response.result).h5_url;
|
||||
}
|
||||
} else if (paymentMethod === "WALLET") {
|
||||
uni.showToast({
|
||||
title: response.message,
|
||||
icon: "none",
|
||||
});
|
||||
if (response.success) {
|
||||
this.callback(paymentMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
//H5pay
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
await API_Trade.initiatePay(paymentMethod, client, params).then((signXml) => {
|
||||
hideLoadingIfNeeded()
|
||||
if (!signXml.data.success) {
|
||||
uni.showToast({ title: signXml.data.message, duration: 2000 })
|
||||
return
|
||||
}
|
||||
const payForm = signXml.data.result
|
||||
const provider = paymentMethod === 'WECHAT' ? 'wxpay' : 'alipay'
|
||||
if (paymentMethod === 'WALLET') {
|
||||
uni.showToast({ icon: 'none', title: '支付成功!' })
|
||||
callback(paymentMethod)
|
||||
} else {
|
||||
uni.requestPayment({
|
||||
provider,
|
||||
orderInfo: payForm || '',
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'none', title: '支付成功!' })
|
||||
callback(paymentMethod)
|
||||
},
|
||||
fail: () => {
|
||||
goPayError()
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
//#ifdef MP-WEIXIN
|
||||
//微信小程序
|
||||
await API_Trade.initiatePay(paymentMethod, paymentClient, params).then(
|
||||
(res) => {
|
||||
let response = res.data.result;
|
||||
//如果支付异常
|
||||
if (!res.data.success) {
|
||||
uni.showModal({
|
||||
content: res.data.message,
|
||||
showCancel: false,
|
||||
})
|
||||
return;
|
||||
}
|
||||
if (paymentMethod === "WECHAT") {
|
||||
uni.requestPayment({
|
||||
provider: "wxpay",
|
||||
appid: response.appid,
|
||||
timeStamp: response.timeStamp,
|
||||
nonceStr: response.nonceStr,
|
||||
package: response.package,
|
||||
signType: response.signType,
|
||||
paySign: response.paySign,
|
||||
success: (e) => {
|
||||
console.log(e);
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "支付成功!",
|
||||
});
|
||||
this.callback(paymentMethod)
|
||||
|
||||
},
|
||||
fail: (e) => {
|
||||
console.log(e);
|
||||
this.exception = e;
|
||||
uni.showModal({
|
||||
content: "支付失败,如果您已支付,请勿反复支付",
|
||||
showCancel: false,
|
||||
});
|
||||
},
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "支付成功!",
|
||||
});
|
||||
this.callback(paymentMethod)
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
// #endif
|
||||
},
|
||||
isWeiXin() {
|
||||
var ua = window.navigator.userAgent.toLowerCase();
|
||||
if (ua.match(/MicroMessenger/i) == "micromessenger") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
// #ifdef H5
|
||||
await API_Trade.initiatePay(paymentMethod, client, params).then((res) => {
|
||||
const response = res.data
|
||||
if (paymentMethod !== 'ALIPAY' && !response.success) {
|
||||
uni.showToast({ title: response.message, duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (paymentMethod === 'ALIPAY') {
|
||||
document.write(response)
|
||||
} else if (paymentMethod === 'WECHAT') {
|
||||
if (isWeiXin()) {
|
||||
WeixinJSBridge.invoke('getBrandWCPayRequest', response.result, (payRes: any) => {
|
||||
if (payRes.err_msg == 'get_brand_wcpay_request:ok') {
|
||||
uni.showToast({ icon: 'none', title: '支付成功!' })
|
||||
callback(paymentMethod)
|
||||
} else {
|
||||
goPayError()
|
||||
}
|
||||
})
|
||||
hideLoadingIfNeeded()
|
||||
} else {
|
||||
window.location.href = JSON.parse(response.result).h5_url
|
||||
const searchParams = { ...params, price: cashierParams.value }
|
||||
const timer = setInterval(() => {
|
||||
payCallback(searchParams).then((cbRes) => {
|
||||
if (cbRes.data.result) {
|
||||
clearInterval(timer)
|
||||
uni.navigateTo({ url: '/pages/order/myOrder' })
|
||||
}
|
||||
})
|
||||
}, 3000)
|
||||
hideLoadingIfNeeded()
|
||||
}
|
||||
} else if (paymentMethod === 'WALLET') {
|
||||
uni.showToast({ title: response.message, icon: 'none' })
|
||||
if (response.success) callback(paymentMethod)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
await API_Trade.initiatePay(paymentMethod, client, params).then((res) => {
|
||||
const response = res.data.result
|
||||
if (!res.data.success) {
|
||||
uni.showModal({ content: res.data.message, showCancel: false })
|
||||
return
|
||||
}
|
||||
if (paymentMethod === 'WECHAT') {
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
appid: response.appid,
|
||||
timeStamp: response.timeStamp,
|
||||
nonceStr: response.nonceStr,
|
||||
package: response.package,
|
||||
signType: response.signType,
|
||||
paySign: response.paySign,
|
||||
success: () => {
|
||||
uni.showToast({ icon: 'none', title: '支付成功!' })
|
||||
callback(paymentMethod)
|
||||
},
|
||||
fail: () => {
|
||||
goPayError()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: '支付成功!' })
|
||||
callback(paymentMethod)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
||||
function isWeiXin() {
|
||||
const ua = window.navigator.userAgent.toLowerCase()
|
||||
return ua.match(/MicroMessenger/i) == 'micromessenger'
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.method_icon {
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.method-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.method_name {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
padding-left: 24rpx;
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/deep/ .u-row {
|
||||
width: 100% !important;
|
||||
display: flex;
|
||||
justify-content: space-between !important;
|
||||
}
|
||||
|
||||
.method_name,
|
||||
.col1 {
|
||||
color: #333;
|
||||
padding-left: 20rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.col1 {
|
||||
text-align: center;
|
||||
flex: 99;
|
||||
}
|
||||
|
||||
.col3 {
|
||||
text-align: right;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.payItem {
|
||||
padding: 13px 25rpx;
|
||||
border-top: 1px solid #f9f9f9;
|
||||
|
||||
line-height: 100rpx;
|
||||
font-size: 36rpx;
|
||||
padding: 0 30rpx;
|
||||
border-top: 1px solid #f5f5f5;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.payItem-title {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
padding-top: 24rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.payItem-method {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
min-height: 100rpx;
|
||||
padding-top: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.ptips {
|
||||
font-size: 32rpx;
|
||||
margin: 20rpx 0;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
> span {
|
||||
.ptips-countdown {
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.ptips-price {
|
||||
.price-text {
|
||||
font-size: 40rpx;
|
||||
color: #df5a52;
|
||||
color: $main-color;
|
||||
font-weight: 600;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.countdown-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.countdown-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 44rpx;
|
||||
height: 44rpx;
|
||||
padding: 0 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #008ffa;
|
||||
border: 1rpx solid #008ffa;
|
||||
border-radius: 8rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.countdown-colon {
|
||||
margin: 0 6rpx;
|
||||
font-size: 28rpx;
|
||||
color: #008ffa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.img {
|
||||
width: 392rpx !important;
|
||||
height: 296rpx !important;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<view class="wrapper" v-if="flage">
|
||||
<view :style="themeStyle" class="wrapper" v-if="flag">
|
||||
<div class='goods' v-if="selectedGoods">
|
||||
<image class="goods-image" :src="selectedGoods.thumbnail" alt="">
|
||||
<p class="goodsName">{{selectedGoods.goodsName}}</p>
|
||||
<div class="goodsPrice">{{(selectedGoods.promotionPrice || selectedGoods.price ) | unitPrice('¥')}}</div>
|
||||
<image class="goods-image" :src="selectedGoods.thumbnail" alt="" />
|
||||
<p class="goodsName">{{selectedGoods.goodsName}}</p>
|
||||
<div class="goodsPrice">{{ unitPrice(selectedGoods.promotionPrice || selectedGoods.price, '¥') }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tips">
|
||||
@@ -14,6 +14,9 @@
|
||||
<span v-if="isBuy &&!master.toBeGroupedNum >0">
|
||||
已成功拼团
|
||||
</span>
|
||||
<span v-if="!master.toBeGroupedNum >0">
|
||||
拼团已结束
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-if="isMaster && !isOver">
|
||||
@@ -24,7 +27,7 @@
|
||||
去首页逛逛
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!isMaster && !isOver && !isBuy">
|
||||
<div v-if="!isMaster && !isOver && !isBuy && master.toBeGroupedNum">
|
||||
<div class="share-user" @click="toBuy">
|
||||
参与拼团
|
||||
</div>
|
||||
@@ -45,184 +48,203 @@
|
||||
</div>
|
||||
|
||||
<!-- 倒计时 -->
|
||||
<div class="count-down" v-if="!isOver">
|
||||
<u-count-down bg-color="#ededed" :hide-zero-day="true" @end="isOver" :timestamp="timeStamp"></u-count-down>
|
||||
<div class="count-down" v-if="!isOver && master.toBeGroupedNum">
|
||||
<u-count-down
|
||||
v-if="timeStamp > 0"
|
||||
bgColor="#ededed"
|
||||
:time="timeStamp * 1000"
|
||||
format="HH:mm:ss"
|
||||
@finish="onCountDownEnd"
|
||||
></u-count-down>
|
||||
</div>
|
||||
|
||||
<div class="user-list" v-if="data.pintuanMemberVOS">
|
||||
<div class="user-item" v-for="(item,index) in data.pintuanMemberVOS" :key="index">
|
||||
<span class="master" v-if="item.orderSn == ''">团长</span>
|
||||
<image class="img" :src="item.face" alt="">
|
||||
<image class="img" :src="item.face" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<popupGoods :addr="addr" ref="popupGoods" :buyMask="maskFlag" @closeBuy="closePopupBuy" :goodsDetail="goodsDetail" :goodsSpec="goodsSpec" v-if="goodsDetail.id " @handleClickSku="getGoodsDetail" />
|
||||
<shares @close="closeShare" :link="'/pages/cart/payment/shareOrderGoods?sn='+this.routers.sn+'&sku='+this.routers.sku+'&goodsId='+this.routers.goodsId" type="pintuan"
|
||||
:thumbnail="data.promotionGoods.thumbnail" :goodsName="data.promotionGoods.goodsName" v-if="shareFlage " />
|
||||
<popupGoods
|
||||
:addr="addr"
|
||||
ref="popupGoodsRef"
|
||||
:buyMask="maskFlag"
|
||||
@closeBuy="closePopupBuy"
|
||||
:goodsDetail="goodsDetail"
|
||||
:goodsSpec="goodsSpec"
|
||||
v-if="goodsDetail.id"
|
||||
@handleClickSku="getGoodsDetail"
|
||||
/>
|
||||
<shares
|
||||
@close="closeShare"
|
||||
:link="shareLink"
|
||||
type="pintuan"
|
||||
:thumbnail="data.promotionGoods.thumbnail"
|
||||
:goodsName="data.promotionGoods.goodsName"
|
||||
v-if="shareFlag"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getGoods } from "@/api/goods.js";
|
||||
import { getPinTuanShare } from "@/api/order";
|
||||
import shares from "@/components/m-share/index";
|
||||
import storage from "@/utils/storage.js";
|
||||
import popupGoods from "@/components/m-buy/goods"; //购物车商品的模块
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getGoods } from '@/api/goods.js'
|
||||
import { getPinTuanShare } from '@/api/order'
|
||||
import shares from '@/components/m-share/index'
|
||||
import storage from '@/utils/storage.js'
|
||||
import popupGoods from '@/components/m-buy/goods'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
flage: false, //判断接口是否正常请求
|
||||
addr: {
|
||||
id: "",
|
||||
},
|
||||
maskFlag: false, //商品弹框
|
||||
timeStamp: 0,
|
||||
shareFlage: false,
|
||||
data: "",
|
||||
isMaster: true,
|
||||
selectedGoods: "", //选择的商品规格昵称
|
||||
routers: "", //传参数据
|
||||
goodsDetail: "", //商品详情
|
||||
goodsSpec: "",
|
||||
master: "", // 团长
|
||||
PromotionList: "", //优惠集合
|
||||
isGroup: false, //是否拼团
|
||||
isOver: false, //是否结束活动
|
||||
isBuy: false, //当前用户是是否购买
|
||||
};
|
||||
},
|
||||
components: {
|
||||
shares,
|
||||
popupGoods,
|
||||
},
|
||||
watch: {
|
||||
isGroup(val) {
|
||||
if (val) {
|
||||
let timer = setInterval(() => {
|
||||
this.$refs.popupGoods.buyType = "PINTUAN";
|
||||
clearInterval(timer);
|
||||
}, 100);
|
||||
} else {
|
||||
this.$refs.popupGoods.buyType = "";
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const popupGoodsRef = ref<any>(null)
|
||||
const flag = ref(false)
|
||||
const addr = ref({ id: '' })
|
||||
const maskFlag = ref(false)
|
||||
const timeStamp = ref(0)
|
||||
const shareFlag = ref(false)
|
||||
const data = ref<any>(null)
|
||||
const isMaster = ref(true)
|
||||
const selectedGoods = ref<any>(null)
|
||||
const routers = ref<Record<string, string>>({})
|
||||
const goodsDetail = ref<any>({})
|
||||
const goodsSpec = ref<any>(null)
|
||||
const master = ref<any>(null)
|
||||
const PromotionList = ref<any>(null)
|
||||
const isGroup = ref(false)
|
||||
const isOver = ref(false)
|
||||
const isBuy = ref(false)
|
||||
|
||||
const shareLink = computed(() => {
|
||||
const { sn, sku, goodsId } = routers.value
|
||||
return `/pages/cart/payment/shareOrderGoods?sn=${sn}&sku=${sku}&goodsId=${goodsId}`
|
||||
})
|
||||
|
||||
watch(isGroup, (val) => {
|
||||
if (val) {
|
||||
const timer = setInterval(() => {
|
||||
if (popupGoodsRef.value) {
|
||||
popupGoodsRef.value.buyType = 'PINTUAN'
|
||||
}
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.routers = options;
|
||||
},
|
||||
mounted() {
|
||||
this.init(this.routers.sn, this.routers.sku);
|
||||
},
|
||||
methods: {
|
||||
closeShare() {
|
||||
this.shareFlage = false;
|
||||
},
|
||||
// 这里的话得先跳到商品详情才能购买商品
|
||||
toBuy() {
|
||||
this.maskFlag = true;
|
||||
this.$refs.popupGoods.parentOrder = {
|
||||
...this.master,
|
||||
orderSn: this.routers.sn,
|
||||
};
|
||||
this.$refs.popupGoods.isMask = true;
|
||||
this.$refs.popupGoods.isClose = true;
|
||||
this.$refs.popupGoods.buyType = "PINTUAN";
|
||||
},
|
||||
// 分享
|
||||
share() {
|
||||
this.shareFlage = true;
|
||||
},
|
||||
closePopupBuy(val) {
|
||||
this.maskFlag = false;
|
||||
},
|
||||
// 实例化本页面
|
||||
async init(sn, sku) {
|
||||
let res = await getPinTuanShare(sn, sku);
|
||||
if (res.data.success && res.data.result.promotionGoods) {
|
||||
this.flage = true;
|
||||
this.data = res.data.result;
|
||||
this.selectedGoods = res.data.result.promotionGoods;
|
||||
let endTime = Date.parse(
|
||||
res.data.result.promotionGoods.endTime.replace(/-/g, "/")
|
||||
);
|
||||
// 获取当前剩余的拼团商品时间
|
||||
let timeStamp = Date.parse(new Date(endTime)) / 1000;
|
||||
clearInterval(timer)
|
||||
}, 100)
|
||||
} else if (popupGoodsRef.value) {
|
||||
popupGoodsRef.value.buyType = ''
|
||||
}
|
||||
})
|
||||
|
||||
// 获取当前时间时间戳
|
||||
let dateTime = Date.parse(new Date()) / 1000;
|
||||
onLoad((options) => {
|
||||
routers.value = options || {}
|
||||
})
|
||||
|
||||
this.timeStamp = parseInt(timeStamp - dateTime);
|
||||
onMounted(() => {
|
||||
init(routers.value.sn, routers.value.sku)
|
||||
})
|
||||
|
||||
this.timeStamp <= 0 ? (this.isOver = true) : (this.isOver = false);
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
// 获取剩余拼团人数
|
||||
this.master =
|
||||
res.data.result.pintuanMemberVOS.length != 0 &&
|
||||
res.data.result.pintuanMemberVOS.filter((item) => {
|
||||
return item.orderSn == "";
|
||||
})[0];
|
||||
function closeShare() {
|
||||
shareFlag.value = false
|
||||
}
|
||||
|
||||
// 获取当前是否是拼团本人
|
||||
if (
|
||||
storage.getUserInfo(this.routers.sku, this.routers.goodsId).id ==
|
||||
this.master.memberId
|
||||
) {
|
||||
this.isMaster = true;
|
||||
} else {
|
||||
this.isMaster = false;
|
||||
// 获取商品详情
|
||||
this.getGoodsDetail({
|
||||
id: this.routers.sku,
|
||||
goodsId: this.routers.goodsId,
|
||||
});
|
||||
function onCountDownEnd() {
|
||||
isOver.value = true
|
||||
}
|
||||
|
||||
function toBuy() {
|
||||
maskFlag.value = true
|
||||
if (!popupGoodsRef.value) return
|
||||
popupGoodsRef.value.parentOrder = {
|
||||
...master.value,
|
||||
orderSn: routers.value.sn,
|
||||
}
|
||||
popupGoodsRef.value.isMask = true
|
||||
popupGoodsRef.value.isClose = true
|
||||
popupGoodsRef.value.buyType = 'PINTUAN'
|
||||
}
|
||||
|
||||
function share() {
|
||||
shareFlag.value = true
|
||||
}
|
||||
|
||||
function closePopupBuy() {
|
||||
maskFlag.value = false
|
||||
}
|
||||
|
||||
async function init(sn: string, sku: string) {
|
||||
const res = await getPinTuanShare(sn, sku)
|
||||
if (res.data.success && res.data.result.promotionGoods) {
|
||||
flag.value = true
|
||||
data.value = res.data.result
|
||||
selectedGoods.value = res.data.result.promotionGoods
|
||||
const endTime = Date.parse(
|
||||
res.data.result.promotionGoods.endTime.replace(/-/g, '/')
|
||||
)
|
||||
const endTimestamp = Date.parse(new Date(endTime) as any) / 1000
|
||||
const dateTime = Date.parse(new Date() as any) / 1000
|
||||
timeStamp.value = parseInt(String(endTimestamp - dateTime))
|
||||
isOver.value = timeStamp.value <= 0
|
||||
|
||||
master.value =
|
||||
res.data.result.pintuanMemberVOS.length != 0 &&
|
||||
res.data.result.pintuanMemberVOS.filter((item: any) => item.orderSn == '')[0]
|
||||
|
||||
if (
|
||||
storage.getUserInfo(routers.value.sku, routers.value.goodsId).id ==
|
||||
master.value.memberId
|
||||
) {
|
||||
isMaster.value = true
|
||||
} else {
|
||||
isMaster.value = false
|
||||
getGoodsDetail({
|
||||
id: routers.value.sku,
|
||||
goodsId: routers.value.goodsId,
|
||||
})
|
||||
}
|
||||
|
||||
if (storage.getUserInfo().id) {
|
||||
const bought = res.data.result.pintuanMemberVOS.filter(
|
||||
(item: any) => item.memberId == storage.getUserInfo().id
|
||||
)
|
||||
isBuy.value = bought.length != 0
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '当前拼团单有误!请联系管理员重试',
|
||||
duration: 2000,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getGoodsDetail(val: { id: string; goodsId: string }) {
|
||||
const { id, goodsId } = val
|
||||
uni.showLoading({ title: '加载中', mask: true })
|
||||
getGoods(id, goodsId).then((response) => {
|
||||
goodsDetail.value = response.data.result.data
|
||||
selectedGoods.value = response.data.result.data
|
||||
goodsSpec.value = response.data.result.specs
|
||||
hideLoadingIfNeeded()
|
||||
PromotionList.value = response.data.result.promotionMap
|
||||
if (PromotionList.value) {
|
||||
Object.keys(PromotionList.value).forEach((item) => {
|
||||
if (item.indexOf('PINTUAN') == 0) {
|
||||
isGroup.value = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取当前商品是否已经购买
|
||||
if (storage.getUserInfo().id) {
|
||||
let isBuy = res.data.result.pintuanMemberVOS.filter((item) => {
|
||||
return item.memberId == storage.getUserInfo().id;
|
||||
});
|
||||
isBuy.length != 0 ? (this.isBuy = true) : (this.isBuy = false);
|
||||
}
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "当前拼团单有误!请联系管理员重试",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
// 获取商品详情
|
||||
getGoodsDetail(val) {
|
||||
let { id, goodsId } = val;
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
mask: true,
|
||||
});
|
||||
getGoods(id, goodsId).then((response) => {
|
||||
this.goodsDetail = response.data.result.data;
|
||||
this.selectedGoods = response.data.result.data;
|
||||
this.goodsSpec = response.data.result.specs;
|
||||
uni.hideLoading();
|
||||
this.PromotionList = response.data.result.promotionMap;
|
||||
|
||||
// 判断是否拼团活动 如果有则显示拼团活动信息
|
||||
this.PromotionList &&
|
||||
Object.keys(this.PromotionList).forEach((item) => {
|
||||
if (item.indexOf("PINTUAN") == 0) {
|
||||
this.isGroup = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
handleClickHome() {
|
||||
uni.switchTab({
|
||||
url: "/pages/tabbar/home/index",
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
function handleClickHome() {
|
||||
uni.switchTab({ url: '/pages/tabbar/home/index' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -318,7 +340,7 @@ page {
|
||||
background: $main-color;
|
||||
}
|
||||
.disabled {
|
||||
background: rgba($main-color, $alpha: 0.2);
|
||||
background: var(--theme-primary-20, rgba(255, 60, 42, 0.2));
|
||||
}
|
||||
.home {
|
||||
color: $main-color;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div :style="themeStyle" class="wrapper">
|
||||
<div class="pay-wrapper">
|
||||
<div class="pay-money">
|
||||
¥{{ Number(payPrice) | unitPrice }}
|
||||
¥{{unitPrice(Number(payPrice)) }}
|
||||
</div>
|
||||
<div class="pay-btns">
|
||||
<div v-show="!from" @click="checkOrder">查看{{this.orderType == "RECHARGE" ? '余额' : '订单'}}</div>
|
||||
<div v-show="!from" @click="checkOrder">查看{{ orderType == "RECHARGE" ? '余额' : '订单' }}</div>
|
||||
<div @click="navigateTo('/pages/tabbar/home/index', 'switch')">回到首页</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -16,7 +16,7 @@
|
||||
<div>
|
||||
支付方式:
|
||||
</div>
|
||||
<div>{{paymentMethod | paymentTypeFilter}}</div>
|
||||
<div>{{ paymentTypeFilter(paymentMethod) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,80 +24,61 @@
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
import goodsRecommend from "@/components/m-goods-recommend";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
checked: false,
|
||||
paymentMethod: "",
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import goodsRecommend from '@/components/m-goods-recommend'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
from: "",
|
||||
payPrice: 0,
|
||||
goodsList: [],
|
||||
activeColor: this.$mainColor,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
goodsRecommend,
|
||||
},
|
||||
filters: {
|
||||
paymentTypeFilter(val) {
|
||||
switch (val) {
|
||||
case "WECHAT":
|
||||
return "微信";
|
||||
case "ALIPAY":
|
||||
return "支付宝";
|
||||
case "WALLET":
|
||||
return "余额支付";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.paymentMethod = options.paymentMethod || "";
|
||||
this.from = options.from || "";
|
||||
this.payPrice = options.payPrice || 0;
|
||||
this.orderType = options.orderType;
|
||||
// this.sendMessage()
|
||||
},
|
||||
methods: {
|
||||
checkOrder() {
|
||||
/**
|
||||
* 查看订单
|
||||
* 1.充值跳转到明细里面
|
||||
* 2.支付跳转到订单详情
|
||||
*/
|
||||
if (this.orderType == "RECHARGE") {
|
||||
uni.reLaunch({
|
||||
url: `/pages/mine/deposit/operation`,
|
||||
});
|
||||
} else {
|
||||
this.navigateTo("/pages/order/myOrder?status=0");
|
||||
}
|
||||
},
|
||||
changeStatus(val) {
|
||||
if (val) {
|
||||
this.sendMessage();
|
||||
}
|
||||
},
|
||||
const paymentMethod = ref('')
|
||||
const from = ref('')
|
||||
const payPrice = ref<number | string>(0)
|
||||
const orderType = ref('')
|
||||
|
||||
navigateTo(url, type) {
|
||||
if (type === "switch") {
|
||||
uni.switchTab({
|
||||
url,
|
||||
});
|
||||
} else {
|
||||
uni.redirectTo({
|
||||
url,
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
onLoad((options) => {
|
||||
paymentMethod.value = options.paymentMethod || ''
|
||||
from.value = options.from || ''
|
||||
payPrice.value = options.payPrice || 0
|
||||
orderType.value = options.orderType || ''
|
||||
})
|
||||
|
||||
function paymentTypeFilter(val: string) {
|
||||
switch (val) {
|
||||
case 'WECHAT':
|
||||
return '微信'
|
||||
case 'ALIPAY':
|
||||
return '支付宝'
|
||||
case 'WALLET':
|
||||
return '余额支付'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
function checkOrder() {
|
||||
if (orderType.value == 'RECHARGE') {
|
||||
uni.reLaunch({
|
||||
url: '/pages/mine/deposit/operation',
|
||||
})
|
||||
return
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: '/pages/order/myOrder?status=0',
|
||||
})
|
||||
}
|
||||
|
||||
function navigateTo(url: string, type?: string) {
|
||||
if (type === 'switch') {
|
||||
uni.switchTab({ url })
|
||||
} else {
|
||||
uni.redirectTo({ url })
|
||||
}
|
||||
}
|
||||
</script><style scoped lang="scss">
|
||||
.subscribe {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -105,6 +86,7 @@ export default {
|
||||
padding: 0 20rpx 20rpx;
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.pay-btns {
|
||||
display: flex;
|
||||
width: 50%;
|
||||
@@ -112,7 +94,7 @@ export default {
|
||||
margin: 0 auto;
|
||||
color: #fff;
|
||||
|
||||
> div {
|
||||
>div {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 100px;
|
||||
@@ -132,7 +114,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 24rpx;
|
||||
color: rgba($color: $main-color, $alpha: 0.8);
|
||||
color: var(--theme-primary-70, rgba(255, 60, 42, 0.7));
|
||||
}
|
||||
|
||||
.pay-box {
|
||||
@@ -144,9 +126,9 @@ export default {
|
||||
margin: 80rpx auto 40rpx auto;
|
||||
padding: 20rpx;
|
||||
border-radius: 20rpx;
|
||||
background: rgba($color: $main-color, $alpha: 0.2);
|
||||
background: var(--theme-primary-20, rgba(255, 60, 42, 0.2));
|
||||
|
||||
> h2 {
|
||||
>h2 {
|
||||
margin-top: 20rpx;
|
||||
font-size: 40rpx;
|
||||
color: $main-color;
|
||||
@@ -154,7 +136,7 @@ export default {
|
||||
}
|
||||
|
||||
.pay-wrapper {
|
||||
background-image: linear-gradient(90deg, #fa123b, #ff6b35, #ff9f28, #ffcc03);
|
||||
background-image: linear-gradient(90deg, $main-color, $light-color, $aider-light-color);
|
||||
height: 480rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
||||
@@ -2,21 +2,16 @@
|
||||
<default-page v-if="type" :type="type" title="您的设备已断网" :isBtn="false" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import defaultPage from '@/components/default-page/default-page.vue';
|
||||
export default {
|
||||
components: {
|
||||
defaultPage
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: undefined
|
||||
}
|
||||
},
|
||||
onLoad(options) {
|
||||
|
||||
}
|
||||
}
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import defaultPage from '@/components/default-page/default-page.vue'
|
||||
|
||||
const type = ref<string>()
|
||||
|
||||
onLoad(() => {
|
||||
type.value = 'msg'
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,353 +1,388 @@
|
||||
<template>
|
||||
<view class="add-address">
|
||||
<div class="uForm">
|
||||
<u-form :border-bottom="false" :model="form" ref="uForm" :error-type="['toast']" :rule="rules">
|
||||
<view class="selectAddress" @click="clickUniMap">
|
||||
选择收货地址
|
||||
</view>
|
||||
<u-form-item class="border" label="收货人" label-width="130" prop="name">
|
||||
<u-input v-model="form.name" clearable placeholder="请输入收货人姓名" />
|
||||
</u-form-item>
|
||||
|
||||
<u-form-item label="手机号码" label-width="130" prop="mobile">
|
||||
<u-input v-model="form.mobile" type="number" maxlength="11" placeholder="请输入收货人手机号码" />
|
||||
</u-form-item>
|
||||
<u-form-item label="所在区域" label-width="130" prop="___path">
|
||||
<u-input v-model="form.___path" type="select" @click="showPicker" placeholder="请选择所在地区" />
|
||||
</u-form-item>
|
||||
<u-form-item class="detailAddress" label="详细地址" label-width="130" prop="detail">
|
||||
<u-input type="textarea" v-model="form.detail" maxlength="100" height="150" placeholder="街道楼牌号等" />
|
||||
</u-form-item>
|
||||
<u-form-item label="地址别名" label-width="130">
|
||||
<u-input v-model="form.alias" placeholder="请输入地址别名" />
|
||||
</u-form-item>
|
||||
<u-checkbox-group shape="circle" size="30">
|
||||
<u-checkbox :active-color="lightColor" v-model="form.isDefault">设为默认地址</u-checkbox>
|
||||
</u-checkbox-group>
|
||||
|
||||
<div class="saveBtn" @click="save">保存</div>
|
||||
</u-form>
|
||||
|
||||
<m-city :provinceData="list" headTitle="区域选择" ref="cityPicker" @funcValue="getpickerParentValue" pickerSize="4">
|
||||
</m-city>
|
||||
|
||||
<uniMap v-if="mapFlage" @close="closeMap" @callback="callBackAddress" />
|
||||
</div>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { addAddress, editAddress, getAddressDetail } from "@/api/address.js";
|
||||
import gkcity from "@/components/m-city/m-city.vue";
|
||||
import uniMap from "./uniMap";
|
||||
import permision from "@/js_sdk/wa-permission/permission.js";
|
||||
export default {
|
||||
components: {
|
||||
"m-city": gkcity,
|
||||
uniMap,
|
||||
},
|
||||
onShow() {
|
||||
// 判断当前系统权限定位是否开启
|
||||
},
|
||||
methods: {
|
||||
// 关闭地图
|
||||
closeMap() {
|
||||
this.mapFlage = false;
|
||||
},
|
||||
// 打开地图并访问权限
|
||||
clickUniMap() {
|
||||
// #ifdef APP-PLUS
|
||||
if (plus.os.name == "iOS") {
|
||||
// ios系统
|
||||
permision.judgeIosPermission("location")
|
||||
? (this.mapFlage = true)
|
||||
: this.refuseMapOuther();
|
||||
} else {
|
||||
// 安卓
|
||||
this.requestAndroidPermission(
|
||||
"android.permission.ACCESS_FINE_LOCATION"
|
||||
);
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
this.mapFlage = true;
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 如果拒绝权限 提示区设置
|
||||
refuseMapOuther() {
|
||||
uni.showModal({
|
||||
title: "温馨提示",
|
||||
content: "您已拒绝定位,请开启",
|
||||
confirmText: "去设置",
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
//打开授权设置
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.getSystemInfo({
|
||||
success(res) {
|
||||
if (res.platform == "ios") {
|
||||
//IOS
|
||||
plus.runtime.openURL("app-settings://");
|
||||
} else if (res.platform == "android") {
|
||||
//安卓
|
||||
let main = plus.android.runtimeMainActivity();
|
||||
let Intent = plus.android.importClass(
|
||||
"android.content.Intent"
|
||||
);
|
||||
let mIntent = new Intent("android.settings.ACTION_SETTINGS");
|
||||
main.startActivity(mIntent);
|
||||
}
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 获取安卓是否拥有地址权限
|
||||
async requestAndroidPermission(permisionID) {
|
||||
var result = await permision.requestAndroidPermission(permisionID);
|
||||
|
||||
if (result == 1) {
|
||||
this.mapFlage = true;
|
||||
} else {
|
||||
this.refuseMapOuther();
|
||||
}
|
||||
},
|
||||
|
||||
// 选择地址后数据的回调
|
||||
callBackAddress(val) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
|
||||
if (val.regeocode && val) {
|
||||
let address = val.regeocode;
|
||||
this.form.detail = address.formatted_address; //地址详情
|
||||
this.form.___path = val.data.result.name;
|
||||
this.form.consigneeAddressIdPath = val.data.result.id; // 地址id分割
|
||||
this.form.consigneeAddressPath = val.data.result.name; //地址名称, ','分割
|
||||
this.form.lat = val.latitude; //纬度
|
||||
this.form.lon = val.longitude; //经度
|
||||
uni.hideLoading();
|
||||
}
|
||||
|
||||
this.mapFlage = !this.mapFlage; //关闭地图
|
||||
},
|
||||
|
||||
// 保存当前 地址
|
||||
save() {
|
||||
this.$refs.uForm.validate((valid) => {
|
||||
if (valid) {
|
||||
let pages = getCurrentPages(); //获取页面栈
|
||||
let beforePage = pages[pages.length - 2]; //上个页面
|
||||
|
||||
// 如果没有id则为新增地址
|
||||
if (!this.form.id) {
|
||||
// 删除没有的数据
|
||||
delete this.form.___path;
|
||||
addAddress(this.form).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// 修改地址
|
||||
delete this.form.___path;
|
||||
delete this.form.updateBy;
|
||||
delete this.form.updateTime;
|
||||
editAddress(this.form).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/${beforePage.route}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 三级地址联动回调
|
||||
getpickerParentValue(e) {
|
||||
// 将需要绑定的地址设置为空,并赋值
|
||||
this.form.consigneeAddressIdPath = [];
|
||||
this.form.consigneeAddressPath = [];
|
||||
let name = "";
|
||||
|
||||
e.forEach((item, index) => {
|
||||
if (item.id) {
|
||||
// 遍历数据
|
||||
this.form.consigneeAddressIdPath.push(item.id);
|
||||
this.form.consigneeAddressPath.push(item.localName);
|
||||
name += item.localName;
|
||||
this.form.___path = name;
|
||||
}
|
||||
if (index == e.length - 1) {
|
||||
//如果是最后一个
|
||||
let _town = item.children.filter((_child) => {
|
||||
return _child.id == item.id;
|
||||
});
|
||||
|
||||
this.form.lat = _town[0].center.split(",")[1];
|
||||
this.form.lon = _town[0].center.split(",")[0];
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// 显示三级地址联动
|
||||
showPicker() {
|
||||
this.$refs.cityPicker.show();
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
data() {
|
||||
return {
|
||||
lightColor: this.$lightColor, //高亮颜色
|
||||
mapFlage: false, // 地图选择开
|
||||
routerVal: "",
|
||||
form: {
|
||||
detail: "", //地址详情
|
||||
name: "", //收货人姓名
|
||||
mobile: "", //手机号码
|
||||
consigneeAddressIdPath: [], //地址id
|
||||
consigneeAddressPath: [], //地址名字
|
||||
___path: "", //所在区域
|
||||
isDefault: false, //是否默认地址
|
||||
},
|
||||
// 表单提交校验规则
|
||||
rules: {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: "收货人姓名不能为空",
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: "手机号码不能为空",
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
return this.$u.test.mobile(value);
|
||||
},
|
||||
message: "手机号码不正确",
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
___path: [
|
||||
{
|
||||
required: true,
|
||||
message: "请选择所在区域",
|
||||
trigger: ["change"],
|
||||
},
|
||||
],
|
||||
detail: [
|
||||
{
|
||||
required: true,
|
||||
message: "请填写详细地址",
|
||||
trigger: ["blur", "change"],
|
||||
},
|
||||
],
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "",
|
||||
localName: "请选择",
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
onLoad(option) {
|
||||
uni.showLoading({
|
||||
title: "加载中",
|
||||
});
|
||||
this.routerVal = option;
|
||||
// 如果当前是编辑地址,则需要查询出地址详情信息
|
||||
if (option.id) {
|
||||
getAddressDetail(option.id).then((res) => {
|
||||
const params = res.data.result;
|
||||
params.___path = params.consigneeAddressPath;
|
||||
this.$set(this, "form", params);
|
||||
|
||||
uni.hideLoading();
|
||||
});
|
||||
}
|
||||
uni.hideLoading();
|
||||
},
|
||||
// 初始化rules必须要在onReady生命周期,因为onLoad生命周期组件可能尚未创建完毕
|
||||
onReady() {
|
||||
this.$refs.uForm.setRules(this.rules);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.detailAddress {
|
||||
/deep/ .u-form-item--left {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.saveBtn,
|
||||
.selectAddress {
|
||||
height: 70rpx;
|
||||
|
||||
line-height: 70rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
background: $aider-light-color;
|
||||
color: #fff;
|
||||
width: 70%;
|
||||
margin: 40rpx auto 0 auto;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.selectAddress {
|
||||
margin-top: 40rpx;
|
||||
background: #fff;
|
||||
|
||||
color: $aider-light-color;
|
||||
border: 2rpx solid $aider-light-color;
|
||||
}
|
||||
|
||||
.uForm {
|
||||
width: 94%;
|
||||
overflow: hidden;
|
||||
left: 3%;
|
||||
position: relative;
|
||||
top: 2%;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
padding: 0 0 40rpx 0;
|
||||
}
|
||||
.add-address {
|
||||
width: 100%;
|
||||
padding-top: 3%;
|
||||
|
||||
/deep/ .u-form-item {
|
||||
background-color: #fff;
|
||||
padding: 24rpx 30rpx;
|
||||
}
|
||||
.u-btn {
|
||||
margin: 30rpx 30rpx 0 30rpx;
|
||||
background-color: $main-color;
|
||||
}
|
||||
|
||||
/deep/.u-checkbox {
|
||||
margin: 30rpx 30rpx 0 30rpx;
|
||||
|
||||
.u-label-class.u-checkbox__label {
|
||||
color: $font-color-light;
|
||||
font-size: $font-sm;
|
||||
}
|
||||
}
|
||||
}
|
||||
/deep/ .u-checkbox__label {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<view :style="themeStyle" class="add-address">
|
||||
<up-form
|
||||
:model="form"
|
||||
ref="uForm"
|
||||
error-type="toast"
|
||||
:rules="rules"
|
||||
label-position="left"
|
||||
label-width="180rpx"
|
||||
>
|
||||
<up-form-item label="收货人" label-width="180rpx" prop="name" :border-bottom="true">
|
||||
<u-input
|
||||
v-model="form.name"
|
||||
border="none"
|
||||
input-align="right"
|
||||
clearable
|
||||
placeholder="请输入收货人姓名"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="手机号码" label-width="180rpx" prop="mobile" :border-bottom="true">
|
||||
<u-input
|
||||
v-model="form.mobile"
|
||||
type="number"
|
||||
maxlength="11"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入收货人手机号码"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="所在区域" label-width="180rpx" prop="___path" :border-bottom="true">
|
||||
<view class="form-value" @click="showPicker">
|
||||
{{ form.___path || '请选择所在地区' }}
|
||||
</view>
|
||||
<template #right>
|
||||
<u-icon name="arrow-right" color="#ccc" size="16"></u-icon>
|
||||
</template>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item class="detailAddress" label="详细地址" label-width="180rpx" prop="detail" :border-bottom="true">
|
||||
<u-input
|
||||
type="textarea"
|
||||
v-model="form.detail"
|
||||
maxlength="100"
|
||||
height="150"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="街道楼牌号等"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<up-form-item label="地址别名" label-width="180rpx" :border-bottom="false">
|
||||
<u-input
|
||||
v-model="form.alias"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入地址别名"
|
||||
/>
|
||||
</up-form-item>
|
||||
|
||||
<view class="default-row">
|
||||
<u-checkbox
|
||||
usedAlone
|
||||
shape="circle"
|
||||
size="30"
|
||||
:active-color="lightColor"
|
||||
v-model:checked="form.isDefault"
|
||||
label="设为默认地址"
|
||||
></u-checkbox>
|
||||
</view>
|
||||
|
||||
<view class="saveBtn" @click="save">保存</view>
|
||||
</up-form>
|
||||
|
||||
<m-city
|
||||
:provinceData="list"
|
||||
headTitle="区域选择"
|
||||
ref="cityPicker"
|
||||
@funcValue="getpickerParentValue"
|
||||
pickerSize="4"
|
||||
></m-city>
|
||||
|
||||
<uniMap v-if="mapFlag" @close="closeMap" @callback="callBackAddress" />
|
||||
</view>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, getCurrentInstance } from 'vue'
|
||||
import { onLoad, onShow, onReady } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { addAddress as addAddressApi, editAddress, getAddressDetail } from '@/api/address.js'
|
||||
import MCity from '@/components/m-city/m-city.vue'
|
||||
import uniMap from '@/components/uniMap'
|
||||
import permision from '@/js_sdk/wa-permission/permission.js'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
const mapFlag = ref(false)
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const uForm = ref<any>(null)
|
||||
const cityPicker = ref<any>(null)
|
||||
|
||||
const form = reactive<Record<string, any>>({
|
||||
detail: '',
|
||||
name: '',
|
||||
mobile: '',
|
||||
consigneeAddressIdPath: [],
|
||||
consigneeAddressPath: [],
|
||||
___path: '',
|
||||
isDefault: false,
|
||||
})
|
||||
|
||||
const list = ref([
|
||||
{
|
||||
id: '',
|
||||
localName: '请选择',
|
||||
children: [],
|
||||
},
|
||||
])
|
||||
|
||||
const rules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '收货人姓名不能为空',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
mobile: [
|
||||
{
|
||||
required: true,
|
||||
message: '手机号码不能为空',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
{
|
||||
validator: (_rule: unknown, value: string) => proxy.$u.test.mobile(value),
|
||||
message: '手机号码不正确',
|
||||
trigger: ['change', 'blur'],
|
||||
},
|
||||
],
|
||||
___path: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择所在区域',
|
||||
trigger: ['change'],
|
||||
},
|
||||
],
|
||||
detail: [
|
||||
{
|
||||
required: true,
|
||||
message: '请填写详细地址',
|
||||
trigger: ['blur', 'change'],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
onShow(() => {})
|
||||
|
||||
onLoad((option) => {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
routerVal.value = option || {}
|
||||
if (option.id) {
|
||||
getAddressDetail(option.id).then((res) => {
|
||||
const params = res.data.result
|
||||
params.___path = params.consigneeAddressPath
|
||||
Object.assign(form, params)
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
})
|
||||
}
|
||||
uni.hideLoading()
|
||||
})
|
||||
|
||||
onReady(() => {
|
||||
uForm.value?.setRules(rules)
|
||||
})
|
||||
|
||||
function closeMap() {
|
||||
mapFlag.value = false
|
||||
}
|
||||
|
||||
function refuseMap() {
|
||||
uni.showModal({
|
||||
title: '温馨提示',
|
||||
content: '您已拒绝定位,请开启',
|
||||
confirmText: '去设置',
|
||||
success(res) {
|
||||
if (res.confirm) {
|
||||
// #ifndef MP-WEIXIN
|
||||
uni.getSystemInfo({
|
||||
success(sysRes) {
|
||||
if (sysRes.platform == 'ios') {
|
||||
plus.runtime.openURL('app-settings://')
|
||||
} else if (sysRes.platform == 'android') {
|
||||
const main = plus.android.runtimeMainActivity()
|
||||
const Intent = plus.android.importClass('android.content.Intent')
|
||||
const mIntent = new Intent('android.settings.ACTION_SETTINGS')
|
||||
main.startActivity(mIntent)
|
||||
}
|
||||
},
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
async function requestAndroidPermission(permisionID: string) {
|
||||
const result = await permision.requestAndroidPermission(permisionID)
|
||||
if (result == 1) {
|
||||
mapFlag.value = true
|
||||
} else {
|
||||
refuseMap()
|
||||
}
|
||||
}
|
||||
|
||||
function callBackAddress(val: any) {
|
||||
uni.showLoading({ title: '加载中' })
|
||||
if (val.regeocode && val) {
|
||||
const address = val.regeocode
|
||||
form.detail = address.formatted_address
|
||||
form.___path = val.data.result.name
|
||||
form.consigneeAddressIdPath = val.data.result.id
|
||||
form.consigneeAddressPath = val.data.result.name
|
||||
form.lat = val.latitude
|
||||
form.lon = val.longitude
|
||||
uni.hideLoading()
|
||||
}
|
||||
mapFlag.value = !mapFlag.value
|
||||
}
|
||||
|
||||
function save() {
|
||||
uForm.value?.validate().then(() => {
|
||||
const params = { ...form }
|
||||
delete params.___path
|
||||
|
||||
if (Array.isArray(params.consigneeAddressIdPath)) {
|
||||
params.consigneeAddressIdPath = params.consigneeAddressIdPath.join(',')
|
||||
}
|
||||
if (Array.isArray(params.consigneeAddressPath)) {
|
||||
params.consigneeAddressPath = params.consigneeAddressPath.join(',')
|
||||
}
|
||||
|
||||
if (!params.id) {
|
||||
addAddressApi(params).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.showToast({ title: res.data.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
delete params.updateBy
|
||||
delete params.updateTime
|
||||
editAddress(params).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.navigateBack()
|
||||
} else {
|
||||
uni.showToast({ title: res.data.message || '保存失败', icon: 'none' })
|
||||
}
|
||||
})
|
||||
}
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
function getpickerParentValue(e: any[]) {
|
||||
form.consigneeAddressIdPath = []
|
||||
form.consigneeAddressPath = []
|
||||
let name = ''
|
||||
e.forEach((item, index) => {
|
||||
if (item.id) {
|
||||
form.consigneeAddressIdPath.push(item.id)
|
||||
form.consigneeAddressPath.push(item.localName)
|
||||
name += item.localName
|
||||
form.___path = name
|
||||
}
|
||||
if (index == e.length - 1) {
|
||||
const _town = item.children.filter((_child: any) => _child.id == item.id)
|
||||
form.lat = _town[0].center.split(',')[1]
|
||||
form.lon = _town[0].center.split(',')[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function showPicker() {
|
||||
cityPicker.value?.show()
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
page {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.add-address {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
padding-bottom: 40rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item) {
|
||||
padding: 0 30rpx;
|
||||
min-height: 100rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body) {
|
||||
min-height: 100rpx;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__left__content__label) {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__right__content__slot) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
:deep(.u-input) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.u-input__content__field-wrapper__field) {
|
||||
font-size: 28rpx !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.form-value {
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.detailAddress {
|
||||
:deep(.u-form-item__body) {
|
||||
align-items: flex-start;
|
||||
padding-top: 24rpx;
|
||||
padding-bottom: 24rpx;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__left) {
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
:deep(.u-form-item__body__right__content__slot) {
|
||||
justify-content: flex-end;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
:deep(.u-textarea__field),
|
||||
:deep(.u-input__content__field-wrapper__field) {
|
||||
text-align: right !important;
|
||||
}
|
||||
|
||||
:deep(.uni-textarea-placeholder),
|
||||
:deep(.input-placeholder) {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.default-row {
|
||||
padding: 24rpx 30rpx;
|
||||
}
|
||||
|
||||
:deep(.default-row .u-checkbox__label) {
|
||||
font-size: 28rpx;
|
||||
color: $font-color-light;
|
||||
}
|
||||
|
||||
.saveBtn {
|
||||
margin: 40rpx 30rpx 0;
|
||||
height: 80rpx;
|
||||
line-height: 80rpx;
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -20,6 +20,9 @@
|
||||
padding: 2rpx 20rpx;
|
||||
}
|
||||
.list {
|
||||
padding-bottom: calc(140rpx + constant(safe-area-inset-bottom));
|
||||
padding-bottom: calc(140rpx + env(safe-area-inset-bottom));
|
||||
|
||||
.item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
@@ -115,18 +118,33 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 99;
|
||||
padding: 20rpx 30rpx calc(20rpx + constant(safe-area-inset-bottom));
|
||||
padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: $page-color-base;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: $light-color;
|
||||
position: fixed;
|
||||
width: 690rpx;
|
||||
bottom: 60rpx;
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
left: 30rpx;
|
||||
font-size: 30rpx;
|
||||
line-height: 80rpx;
|
||||
|
||||
.u-icon {
|
||||
margin-right: 10rpx;
|
||||
color: #fff;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
|
||||
.btn-text {
|
||||
margin-left: 12rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,169 +1,135 @@
|
||||
<template>
|
||||
<view class="address">
|
||||
|
||||
<u-empty class="empty" v-if="addressList.length == 0" text="暂无收货地址" mode="address"></u-empty>
|
||||
<view class="list" v-else>
|
||||
<view class="item c-content" v-for="(item, index) in addressList" :key="index">
|
||||
<view class="basic" @click="selectAddressData(item)">
|
||||
<text>{{ item.name }}</text>
|
||||
<text>{{ item.mobile }}</text>
|
||||
<text class="default" v-show="item.isDefault">默认</text>
|
||||
<view>
|
||||
<div class="region">
|
||||
<span v-if="item.consigneeAddressPath[0]">{{item.consigneeAddressPath[0]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[1]">{{item.consigneeAddressPath[1]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[2]">{{item.consigneeAddressPath[2]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[3]">{{item.consigneeAddressPath[3]}}</span>
|
||||
<span>{{ item.detail }}</span>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit">
|
||||
<view class="relative" @click="setDefault(item)">
|
||||
<view v-if="item.isDefault" class="alifont icon-xuanzhong"></view>
|
||||
<text v-else class="unchecked"></text>
|
||||
<text>{{ item.isDefault ? "默认地址" : "设为默认" }}</text>
|
||||
</view>
|
||||
<view class="relative">
|
||||
<view class="alifont icon-bianji-copy"></view>
|
||||
<text class="mr-40" @click="addAddress(item.id)">编辑</text>
|
||||
<view class="alifont icon-lajitong"></view>
|
||||
<text @click="removeAddress(item.id)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 100px"></view>
|
||||
</view>
|
||||
<button type="default" class="btn" @click="addAddress('')">
|
||||
<u-icon name="plus-circle"></u-icon>
|
||||
添加新收货人
|
||||
</button>
|
||||
<u-action-sheet :list="removeList" :tips="tips" v-model="showAction" @click="deleteAddressMessage"></u-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as API_Trade from "@/api/trade";
|
||||
import * as API_Address from "@/api/address.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addressList: [], //地址列表
|
||||
showAction: false, //是否显示下栏框
|
||||
removeList: [
|
||||
{
|
||||
text: "确定",
|
||||
},
|
||||
],
|
||||
tips: {
|
||||
text: "确定要删除该收货人信息吗?",
|
||||
},
|
||||
removeId: "", //删除的地址id
|
||||
routerVal: "",
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 1000,
|
||||
},
|
||||
};
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
//下拉刷新
|
||||
this.addressList = [];
|
||||
this.getAddressList();
|
||||
},
|
||||
onLoad: function (val) {
|
||||
this.routerVal = val;
|
||||
},
|
||||
onShow() {
|
||||
this.addressList = [];
|
||||
this.getAddressList();
|
||||
},
|
||||
onHide() {},
|
||||
methods: {
|
||||
async selectAddressData(val) {
|
||||
await API_Trade.setAddressId(val.id, this.routerVal.way);
|
||||
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
},
|
||||
//获取地址列表
|
||||
getAddressList() {
|
||||
uni.showLoading();
|
||||
|
||||
API_Address.getAddressList(
|
||||
this.params.pageNumber,
|
||||
this.params.pageSize
|
||||
).then((res) => {
|
||||
res.data.result.records.forEach((item) => {
|
||||
item.consigneeAddressPath = item.consigneeAddressPath.split(",");
|
||||
});
|
||||
this.addressList = res.data.result.records;
|
||||
console.log(this.addressList);
|
||||
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
//删除地址
|
||||
removeAddress(id) {
|
||||
this.removeId = id;
|
||||
this.showAction = true;
|
||||
},
|
||||
deleteAddressMessage() {
|
||||
API_Address.deleteAddress(this.removeId).then((res) => {
|
||||
if (res.statusCode == 200) {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "删除成功",
|
||||
});
|
||||
this.getAddressList();
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: res.data.message,
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
//新建。编辑地址
|
||||
addAddress(id) {
|
||||
if (id) {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/pages/mine/address/add?id=" +
|
||||
id +
|
||||
"&way=" +
|
||||
this.routerVal.way +
|
||||
"&type=order",
|
||||
});
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url:
|
||||
"/pages/mine/address/add?way=" + this.routerVal.way + "&type=order",
|
||||
});
|
||||
}
|
||||
},
|
||||
//设为默认地址
|
||||
setDefault(item) {
|
||||
delete item.updateBy;
|
||||
delete item.updateTime;
|
||||
delete item.deleteFlag;
|
||||
|
||||
item.isDefault ? "" : (item.isDefault = !item.isDefault);
|
||||
|
||||
API_Address.editAddress(item).then((res) => {
|
||||
uni.showToast({
|
||||
title: "设置默认地址成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.getAddressList();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./address.scss";
|
||||
</style>
|
||||
<template>
|
||||
<view class="address">
|
||||
|
||||
<u-empty class="empty" v-if="addressList.length == 0" text="暂无收货地址" mode="address"></u-empty>
|
||||
<view class="list" v-else>
|
||||
<view class="item c-content" v-for="(item, index) in addressList" :key="index">
|
||||
<view class="basic" @click="selectAddressData(item)">
|
||||
<text>{{ item.name }}</text>
|
||||
<text>{{ item.mobile }}</text>
|
||||
<text class="default" v-show="item.isDefault">默认</text>
|
||||
<view>
|
||||
<div class="region">
|
||||
<span v-if="item.consigneeAddressPath[0]">{{item.consigneeAddressPath[0]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[1]">{{item.consigneeAddressPath[1]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[2]">{{item.consigneeAddressPath[2]}}</span>
|
||||
<span v-if="item.consigneeAddressPath[3]">{{item.consigneeAddressPath[3]}}</span>
|
||||
<span>{{ item.detail }}</span>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
<view class="edit">
|
||||
<view class="relative" @click="setDefault(item)">
|
||||
<view v-if="item.isDefault" class="alifont icon-xuanzhong"></view>
|
||||
<text v-else class="unchecked"></text>
|
||||
<text>{{ item.isDefault ? "默认地址" : "设为默认" }}</text>
|
||||
</view>
|
||||
<view class="relative">
|
||||
<view class="alifont icon-bianji-copy"></view>
|
||||
<text class="mr-40" @click="addAddress(item.id)">编辑</text>
|
||||
<view class="alifont icon-lajitong" @click.stop="removeAddress(item.id)"></view>
|
||||
<text @click.stop="removeAddress(item.id)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 100px"></view>
|
||||
</view>
|
||||
<view class="btn" @click="addAddress('')">
|
||||
<u-icon name="plus-circle" color="#ffffff" size="18"></u-icon>
|
||||
<text class="btn-text">添加新收货人</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import * as API_Trade from '@/api/trade'
|
||||
import * as API_Address from '@/api/address.js'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const addressList = ref<any[]>([])
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const params = { pageNumber: 1, pageSize: 1000 }
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
addressList.value = []
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
onLoad((val) => {
|
||||
routerVal.value = val || {}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
addressList.value = []
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
async function selectAddressData(val: any) {
|
||||
await API_Trade.setAddressId(val.id, routerVal.value.way)
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
function getAddressList() {
|
||||
uni.showLoading()
|
||||
API_Address.getAddressList(params.pageNumber, params.pageSize).then((res) => {
|
||||
res.data.result.records.forEach((item: any) => {
|
||||
item.consigneeAddressPath = item.consigneeAddressPath.split(',')
|
||||
})
|
||||
addressList.value = res.data.result.records
|
||||
hideLoadingIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
function removeAddress(id: string) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该收货人信息吗?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
API_Address.deleteAddress(id).then((result) => {
|
||||
if (result.statusCode == 200) {
|
||||
uni.showToast({ icon: 'none', title: '删除成功' })
|
||||
getAddressList()
|
||||
} else {
|
||||
uni.showToast({ icon: 'none', title: result.data.message, duration: 2000 })
|
||||
}
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function addAddress(id: string) {
|
||||
if (id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mine/address/add?id=${id}&way=${routerVal.value.way}&type=order`,
|
||||
})
|
||||
} else {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mine/address/add?way=${routerVal.value.way}&type=order`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function setDefault(item: any) {
|
||||
delete item.updateBy
|
||||
delete item.updateTime
|
||||
delete item.deleteFlag
|
||||
if (!item.isDefault) item.isDefault = true
|
||||
API_Address.editAddress(item).then(() => {
|
||||
uni.showToast({ title: '设置默认地址成功', icon: 'none' })
|
||||
getAddressList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./address.scss";
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<view class="address">
|
||||
<u-empty class="empty" v-if="this.addressList == 0" text="暂无收货地址" mode="address"></u-empty>
|
||||
<u-empty class="empty" v-if="addressList.length === 0" text="暂无收货地址" mode="address"></u-empty>
|
||||
<view class="list" >
|
||||
<view class="item c-content" v-for="(item, index) in addressList" :key="index">
|
||||
<view class="basic">
|
||||
@@ -26,147 +26,104 @@
|
||||
<view class="relative">
|
||||
<view class="alifont icon-bianji-copy"></view>
|
||||
<text class="mr-40" @click="addAddress(item.id)">编辑</text>
|
||||
<view class="alifont icon-lajitong"></view>
|
||||
<text @click="removeAddress(item.id)">删除</text>
|
||||
<view class="alifont icon-lajitong" @click.stop="removeAddress(item.id)"></view>
|
||||
<text @click.stop="removeAddress(item.id)">删除</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 100px"></view>
|
||||
</view>
|
||||
|
||||
<button type="default" class="btn" @click="addAddress('')">
|
||||
<u-icon name="plus-circle"></u-icon>
|
||||
添加新收货人
|
||||
</button>
|
||||
|
||||
<u-action-sheet :list="removeList" :tips="tips" v-model="showAction" @click="deleteAddressMessage"></u-action-sheet>
|
||||
<view class="btn-bar">
|
||||
<view class="btn" @click="addAddress('')">
|
||||
<u-icon name="plus-circle" color="#ffffff" size="18"></u-icon>
|
||||
<text class="btn-text">添加新收货人</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as API_Address from "@/api/address.js";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
addressList: [], //地址列表
|
||||
showAction: false, //是否显示下栏框
|
||||
|
||||
removeList: [
|
||||
{
|
||||
text: "确定",
|
||||
},
|
||||
],
|
||||
tips: {
|
||||
text: "确定要删除该收货人信息吗?",
|
||||
},
|
||||
removeId: "", //删除的地址id
|
||||
routerVal: "",
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 1000,
|
||||
},
|
||||
};
|
||||
},
|
||||
// 返回上一级
|
||||
onBackPress(e) {
|
||||
uni.switchTab({
|
||||
url: "/pages/tabbar/user/my",
|
||||
});
|
||||
return true;
|
||||
},
|
||||
onLoad: function (val) {
|
||||
this.routerVal = val;
|
||||
},
|
||||
onPullDownRefresh() {
|
||||
//下拉刷新
|
||||
this.addressList = [];
|
||||
this.getAddressList();
|
||||
},
|
||||
/**
|
||||
* 进入页面检测当前账户是否登录
|
||||
*/
|
||||
onShow() {
|
||||
let that = this;
|
||||
if (this.$options.filters.isLogin("auth")) {
|
||||
this.getAddressList();
|
||||
} else {
|
||||
uni.showModal({
|
||||
title: "提示",
|
||||
content: "检测到您的账号还未登录,是否去登录?",
|
||||
confirmColor: this.$lightColor,
|
||||
success: function (res) {
|
||||
if (res.confirm) {
|
||||
that.$options.filters.navigateToLogin();
|
||||
} else if (res.cancel) {
|
||||
uni.navigateBack();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//获取地址列表
|
||||
getAddressList() {
|
||||
uni.showLoading();
|
||||
API_Address.getAddressList(
|
||||
this.params.pageNumber,
|
||||
this.params.pageSize
|
||||
).then((res) => {
|
||||
res.data.result.records.forEach((item) => {
|
||||
item.consigneeAddressPath = item.consigneeAddressPath.split(",");
|
||||
});
|
||||
this.addressList = res.data.result.records;
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onShow, onPullDownRefresh, onBackPress } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { tipsToLogin } from '@/utils/filters.js'
|
||||
import * as API_Address from '@/api/address.js'
|
||||
|
||||
uni.hideLoading();
|
||||
});
|
||||
},
|
||||
//删除地址
|
||||
removeAddress(id) {
|
||||
this.removeId = id;
|
||||
this.showAction = true;
|
||||
},
|
||||
// 删除地址
|
||||
deleteAddressMessage() {
|
||||
API_Address.deleteAddress(this.removeId).then((res) => {
|
||||
if (res.statusCode == 200) {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: "删除成功",
|
||||
});
|
||||
this.getAddressList();
|
||||
const store = useStore()
|
||||
|
||||
const addressList = ref<any[]>([])
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const params = { pageNumber: 1, pageSize: 1000 }
|
||||
|
||||
onBackPress(() => {
|
||||
uni.switchTab({ url: '/pages/tabbar/user/my' })
|
||||
return true
|
||||
})
|
||||
|
||||
onLoad((val) => {
|
||||
routerVal.value = val || {}
|
||||
})
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
addressList.value = []
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
if (tipsToLogin()) {
|
||||
getAddressList()
|
||||
}
|
||||
})
|
||||
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
function getAddressList() {
|
||||
uni.showLoading()
|
||||
API_Address.getAddressList(params.pageNumber, params.pageSize).then((res) => {
|
||||
res.data.result.records.forEach((item: any) => {
|
||||
item.consigneeAddressPath = item.consigneeAddressPath.split(',')
|
||||
})
|
||||
addressList.value = res.data.result.records
|
||||
hideLoadingIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
function removeAddress(id: string) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '确定要删除该收货人信息吗?',
|
||||
success: (res) => {
|
||||
if (!res.confirm) return
|
||||
API_Address.deleteAddress(id).then((result) => {
|
||||
if (result.statusCode == 200) {
|
||||
uni.showToast({ icon: 'none', title: '删除成功' })
|
||||
getAddressList()
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
title: res.data.message,
|
||||
duration: 2000,
|
||||
});
|
||||
uni.showToast({ icon: 'none', title: result.data.message, duration: 2000 })
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
//新建。编辑地址
|
||||
addAddress(id) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mine/address/add${id ? "?id=" + id : ""}`,
|
||||
});
|
||||
},
|
||||
//设为默认地址
|
||||
setDefault(item) {
|
||||
delete item.updateBy;
|
||||
delete item.updateTime;
|
||||
delete item.deleteFlag;
|
||||
})
|
||||
}
|
||||
|
||||
item.isDefault ? "" : (item.isDefault = !item.isDefault);
|
||||
function addAddress(id?: string) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mine/address/add${id ? '?id=' + id : ''}`,
|
||||
})
|
||||
}
|
||||
|
||||
API_Address.editAddress(item).then(() => {
|
||||
uni.showToast({
|
||||
title: "设置默认地址成功",
|
||||
icon: "none",
|
||||
});
|
||||
this.getAddressList();
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
function setDefault(item: any) {
|
||||
delete item.updateBy
|
||||
delete item.updateTime
|
||||
delete item.deleteFlag
|
||||
if (!item.isDefault) item.isDefault = true
|
||||
API_Address.editAddress(item).then(() => {
|
||||
uni.showToast({ title: '设置默认地址成功', icon: 'none' })
|
||||
getAddressList()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
84
pages/mine/address/storeAddress.vue
Normal file
84
pages/mine/address/storeAddress.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<view class="address">
|
||||
|
||||
<u-empty class="empty" v-if="storeAddressList.length == 0" text="暂无自提地址" mode="address"></u-empty>
|
||||
<view class="list" v-else>
|
||||
<view class="item c-content" v-for="(item, index) in storeAddressList" :key="index">
|
||||
<view class="basic" @click="selectAddressData(item)">
|
||||
<text>{{ item.addressName }}</text>
|
||||
<text>{{ item.mobile }}</text>
|
||||
<view>
|
||||
<div class="region">
|
||||
<span>{{ item.address }}</span>
|
||||
</div>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view style="height: 100px"></view>
|
||||
</view>
|
||||
<u-action-sheet
|
||||
:actions="removeList"
|
||||
:description="tips.text"
|
||||
cancel-text="取消"
|
||||
v-model:show="showAction"
|
||||
@select="deleteAddressMessage"
|
||||
></u-action-sheet>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import * as API_Trade from '@/api/trade'
|
||||
import * as API_Store from '@/api/store.js'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const storeAddressList = ref<any[]>([])
|
||||
const showAction = ref(false)
|
||||
const removeList = [{ name: '确定' }]
|
||||
const tips = { text: '确定要删除该收货人信息吗?' }
|
||||
const removeId = ref('')
|
||||
const routerVal = ref<Record<string, string>>({})
|
||||
const params = { pageNumber: 1, pageSize: 1000 }
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
storeAddressList.value = []
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
onLoad((val) => {
|
||||
routerVal.value = val || {}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
storeAddressList.value = []
|
||||
getAddressList()
|
||||
})
|
||||
|
||||
function hideLoadingIfNeeded() {
|
||||
if (store.state.isShowToast) uni.hideLoading()
|
||||
}
|
||||
|
||||
async function selectAddressData(val: any) {
|
||||
await API_Trade.setStoreAddressId(val.id, routerVal.value.way)
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}
|
||||
|
||||
function getAddressList() {
|
||||
uni.showLoading()
|
||||
API_Store.getStoreAddress(routerVal.value.storeId, params).then((res) => {
|
||||
storeAddressList.value = res.data.result.records
|
||||
hideLoadingIfNeeded()
|
||||
})
|
||||
}
|
||||
|
||||
function deleteAddressMessage() {
|
||||
// 保留 action-sheet 回调占位,当前页面无删除入口
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./address.scss";
|
||||
</style>
|
||||
@@ -1,110 +0,0 @@
|
||||
<template></template>
|
||||
<script>
|
||||
import { getAddressCode } from "@/api/address";
|
||||
import config from '@/config/config'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
// 初始化地图并且调用
|
||||
initMap() {
|
||||
let that = this;
|
||||
uni.chooseLocation({
|
||||
success: function (res) {
|
||||
/**获取地址详情地址 */
|
||||
that.posToCity(res.latitude, res.longitude).then((val) => {
|
||||
/**获取地址code */
|
||||
getAddressCode(
|
||||
val.regeocode.addressComponent.citycode,
|
||||
val.regeocode.addressComponent.township
|
||||
).then((code) => {
|
||||
that.$emit("callback", { ...val, ...res, ...code });
|
||||
that.$emit("close");
|
||||
});
|
||||
});
|
||||
},
|
||||
fail() {
|
||||
that.$emit("close");
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// 根据当前客户端判断展示不同类型数据
|
||||
init() {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.wechatMap();
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
this.initMap();
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 如果是微信小程序单独走微信小程序授权模式
|
||||
wechatMap() {
|
||||
let that = this;
|
||||
uni.authorize({
|
||||
scope: "scope.userLocation",
|
||||
success() {
|
||||
// 允许授权
|
||||
that.initMap();
|
||||
},
|
||||
fail() {
|
||||
//拒绝授权
|
||||
uni.showModal({
|
||||
content: "检测到您没打开获取地址功能权限,是否去设置打开?",
|
||||
confirmText: "确认",
|
||||
cancelText: "取消",
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// 打开设置好后重新刷新地图
|
||||
uni.openSetting({
|
||||
success: (res) => {
|
||||
that.initMap();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// 取消后关闭
|
||||
that.$emit("close");
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
return false;
|
||||
},
|
||||
});
|
||||
},
|
||||
// 获取城市的数据
|
||||
posToCity(latitude, longitude) {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.request({
|
||||
url: `https://restapi.amap.com/v3/geocode/regeo`,
|
||||
method: "GET",
|
||||
data: {
|
||||
key: config.aMapKey, //web服务的key
|
||||
location: `${longitude},${latitude}`,
|
||||
},
|
||||
success: ({ data }) => {
|
||||
const { status, info } = data;
|
||||
if (status === "1") {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(info);
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
@@ -1,226 +1,239 @@
|
||||
<template>
|
||||
<view class="wap">
|
||||
<u-navbar back-text="" title="预存款列表">
|
||||
</u-navbar>
|
||||
<view :style="themeStyle" class="wap">
|
||||
<u-navbar
|
||||
title="预存款列表"
|
||||
:fixed="true"
|
||||
:placeholder="true"
|
||||
:border="false"
|
||||
:auto-back="true"
|
||||
></u-navbar>
|
||||
|
||||
<view class="wrapper-show-money">
|
||||
<view class="money-view">
|
||||
<h3>预存款金额 </h3>
|
||||
<view class="money">¥{{walletNum | unitPrice }}</view>
|
||||
|
||||
<text class="money-label">预存款金额</text>
|
||||
<text class="money">¥{{ unitPrice(walletNum) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="wrapper-tabs">
|
||||
|
||||
<swiper class="swiper-box" :current="swiperCurrent">
|
||||
<view class="wrapper-tabs">
|
||||
<swiper class="swiper-box" :current="swiperCurrent">
|
||||
<swiper-item class="swiper-item" v-for="index in list.length" :key="index">
|
||||
<scroll-view class="scroll-v view-wrapper" enableBackToTop="true" scroll-with-animation scroll-y @scrolltolower="loadMore">
|
||||
<view v-if="datas.length!=0" class="view-item" v-for="(logItem, logIndex) in datas" :key="logIndex">
|
||||
<view class="view-item-detail">
|
||||
<view class="-title">{{logItem.detail}}</view>
|
||||
<!-- <view class="-number">{{logItem.detail}}</view> -->
|
||||
</view>
|
||||
<view class="view-item-change">
|
||||
<view class="-money green" v-if="logItem.serviceType == 'WALLET_PAY' || logItem.serviceType == 'WALLET_WITHDRAWAL'"> {{logItem.money | unitPrice}} </view>
|
||||
<view class="-money" v-if="logItem.serviceType == 'WALLET_REFUND' || logItem.serviceType == 'WALLET_RECHARGE' || logItem.serviceType == 'WALLET_COMMISSION' ">
|
||||
+{{logItem.money | unitPrice}} </view>
|
||||
<view class="-time">{{logItem.createTime}}</view>
|
||||
<scroll-view
|
||||
class="scroll-v view-wrapper"
|
||||
enable-back-to-top
|
||||
scroll-with-animation
|
||||
scroll-y
|
||||
@scrolltolower="loadMore"
|
||||
>
|
||||
<view v-if="depositData.length != 0" class="list-card">
|
||||
<view
|
||||
class="view-item"
|
||||
v-for="(logItem, logIndex) in depositData"
|
||||
:key="logIndex"
|
||||
>
|
||||
<view class="view-item-detail">
|
||||
<text class="item-title">{{ logItem.detail }}</text>
|
||||
</view>
|
||||
<view class="view-item-change">
|
||||
<text
|
||||
class="item-money"
|
||||
:class="getMoneyClass(logItem.serviceType)"
|
||||
>{{ formatLogMoney(logItem) }}</text>
|
||||
<text class="item-time">{{ logItem.createTime }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-empty v-if="datas.length==0" mode="history" text="暂无记录" />
|
||||
<u-loadmore v-else bg-color='#f8f8f8' :status="status" />
|
||||
<u-empty v-if="depositData.length == 0" mode="history" text="暂无记录" />
|
||||
</scroll-view>
|
||||
|
||||
</swiper-item>
|
||||
|
||||
</swiper>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUserRecharge, getWalletLog } from "@/api/members";
|
||||
import { getUserWallet } from "@/api/members";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
walletNum: 0,
|
||||
status: "loadmore",
|
||||
current: 0,
|
||||
swiperCurrent: 0,
|
||||
userInfo: "", //用户详情信息
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
order: "desc",
|
||||
},
|
||||
datas: [], //遍历的数据集合
|
||||
rechargeList: "", //充值明细列表
|
||||
walletLogList: "", //钱包变动列表
|
||||
list: [
|
||||
// {
|
||||
// name: "充值明细",
|
||||
// },
|
||||
{
|
||||
name: "预存款变动明细",
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
swiperCurrent(index) {
|
||||
this.swiperCurrent = index;
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
this.getWallet();
|
||||
let result = await getUserWallet(); //预存款
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { getWalletLog, getUserWallet } from '@/api/members'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
this.walletNum = result.data.result.memberWallet;
|
||||
},
|
||||
methods: {
|
||||
|
||||
/**分页获取预存款充值记录 */
|
||||
getRecharge() {
|
||||
this.status = "loading";
|
||||
getUserRecharge(this.params).then((res) => {
|
||||
if (res.data.success) {
|
||||
if (res.data.result.records.length != 0) {
|
||||
this.status = "loadmore";
|
||||
this.datas.push(...res.data.result.records);
|
||||
} else {
|
||||
this.status = "nomore";
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
const walletNum = ref(0)
|
||||
const swiperCurrent = ref(0)
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
order: 'desc',
|
||||
})
|
||||
const depositData = ref<any[]>([])
|
||||
const list = [{ name: '预存款变动明细' }]
|
||||
|
||||
getWallet() {
|
||||
this.status = "loading";
|
||||
getWalletLog(this.params).then((res) => {
|
||||
if (res.data.success) {
|
||||
if (res.data.result.records.length != 0) {
|
||||
this.datas.push(...res.data.result.records);
|
||||
} else {
|
||||
this.status = "nomore";
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
onMounted(async () => {
|
||||
getWallet()
|
||||
const result = await getUserWallet()
|
||||
walletNum.value = result.data.result.memberWallet
|
||||
})
|
||||
|
||||
|
||||
changed(index) {
|
||||
this.datas = [];
|
||||
this.swiperCurrent = index;
|
||||
this.params.pageNumber = 1;
|
||||
if (index == 0) {
|
||||
// this.getRecharge();
|
||||
this.getWallet();
|
||||
} else {
|
||||
this.getWallet();
|
||||
}
|
||||
},
|
||||
function isOutgoing(serviceType: string) {
|
||||
return serviceType === 'WALLET_PAY' || serviceType === 'WALLET_WITHDRAWAL'
|
||||
}
|
||||
|
||||
loadMore() {
|
||||
this.params.pageNumber++;
|
||||
this.getWallet();
|
||||
},
|
||||
},
|
||||
};
|
||||
function isIncoming(serviceType: string) {
|
||||
return (
|
||||
serviceType === 'WALLET_REFUND' ||
|
||||
serviceType === 'WALLET_RECHARGE' ||
|
||||
serviceType === 'WALLET_COMMISSION'
|
||||
)
|
||||
}
|
||||
|
||||
function getMoneyClass(serviceType: string) {
|
||||
if (isOutgoing(serviceType)) return 'out'
|
||||
if (isIncoming(serviceType)) return 'in'
|
||||
return ''
|
||||
}
|
||||
|
||||
function formatLogMoney(logItem: any) {
|
||||
const amount = unitPrice(logItem.money)
|
||||
if (isOutgoing(logItem.serviceType)) return `-${amount}`
|
||||
if (isIncoming(logItem.serviceType)) return `+${amount}`
|
||||
return amount
|
||||
}
|
||||
|
||||
function getWallet() {
|
||||
getWalletLog(params.value).then((res) => {
|
||||
if (res.data.success && res.data.result.records.length) {
|
||||
depositData.value.push(...res.data.result.records)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
params.value.pageNumber++
|
||||
getWallet()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.green {
|
||||
color: $aider-color-green !important;
|
||||
.wap {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.wrapper-show-money {
|
||||
flex-shrink: 0;
|
||||
height: 200rpx;
|
||||
}
|
||||
|
||||
.money-view {
|
||||
height: 100%;
|
||||
padding: 32rpx 40rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
color: #fff;
|
||||
background-image: linear-gradient(
|
||||
25deg,
|
||||
$main-color,
|
||||
$light-color,
|
||||
$aider-light-color
|
||||
);
|
||||
}
|
||||
|
||||
.money-label {
|
||||
font-size: 28rpx;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.money {
|
||||
margin-top: 12rpx;
|
||||
max-width: 100%;
|
||||
font-size: 56rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.wrapper-tabs {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.swiper-box,
|
||||
.swiper-item,
|
||||
.scroll-v {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.list-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.view-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 32rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.view-item {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.view-item-detail {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 24rpx;
|
||||
}
|
||||
|
||||
.item-title {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.view-item-change {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.item-money {
|
||||
font-size: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
|
||||
&.in {
|
||||
color: $aider-color-green;
|
||||
}
|
||||
.view-item-change {
|
||||
text-align: right;
|
||||
> .-money {
|
||||
font-size: 36rpx;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
> .-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.view-item-detail {
|
||||
line-height: 1.75;
|
||||
> .-title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
> .-number {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
.submit-btn {
|
||||
line-height: 90rpx;
|
||||
text-align: center;
|
||||
|
||||
color: #fff;
|
||||
background: $main-color;
|
||||
|
||||
margin: 0 auto;
|
||||
height: 90rpx;
|
||||
}
|
||||
|
||||
.operation {
|
||||
font-size: 32rpx;
|
||||
margin-right: 24rpx;
|
||||
color: rgb(96, 98, 102);
|
||||
}
|
||||
.money {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.money-view {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 32rpx;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
background-image: linear-gradient(
|
||||
25deg,
|
||||
$main-color,
|
||||
$light-color,
|
||||
$aider-light-color
|
||||
);
|
||||
}
|
||||
|
||||
.swiper-item,
|
||||
.scroll-v {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.swiper-box {
|
||||
/* #ifndef H5 */
|
||||
height: calc(100vh - 200rpx);
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef H5 */
|
||||
height: calc(100vh - 288rpx);
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.wap {
|
||||
width: 100%;
|
||||
height: calc(100vh - 44px);
|
||||
}
|
||||
|
||||
.wrapper-show-money {
|
||||
height: 200rpx;
|
||||
// background-image: url('/static/img/main-bg.jpg');
|
||||
|
||||
&.out {
|
||||
color: #ff5a5f;
|
||||
}
|
||||
}
|
||||
|
||||
.item-time {
|
||||
margin-top: 8rpx;
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<template>
|
||||
<view class="wrapper">
|
||||
<u-cell-group>
|
||||
<u-cell-item title="申请单号"></u-cell-item>
|
||||
<u-cell-item title="提现金额" value="新版本"></u-cell-item>
|
||||
<u-cell-item title="收款银行"></u-cell-item>
|
||||
<u-cell-item title="收款账号" value="新版本"></u-cell-item>
|
||||
<u-cell-item title="开户人姓名"></u-cell-item>
|
||||
<u-cell-item title="创建时间" value="新版本"></u-cell-item>
|
||||
<u-cell-item title="提现状态" value="新版本"></u-cell-item>
|
||||
<u-cell title="申请单号"></u-cell>
|
||||
<u-cell title="提现金额" value="新版本"></u-cell>
|
||||
<u-cell title="收款银行"></u-cell>
|
||||
<u-cell title="收款账号" value="新版本"></u-cell>
|
||||
<u-cell title="开户人姓名"></u-cell>
|
||||
<u-cell title="创建时间" value="新版本"></u-cell>
|
||||
<u-cell title="提现状态" value="新版本"></u-cell>
|
||||
</u-cell-group>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -1,120 +1,152 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<u-navbar :custom-back="back" title="余额"></u-navbar>
|
||||
<div class="box">
|
||||
<div class="deposit">预存款金额</div>
|
||||
<div class="money">¥{{walletNum | unitPrice }}</div>
|
||||
<div class="operation-btns">
|
||||
<div class="operation-btn light" @click="navgition('/pages/mine/deposit/withdrawal')">提现</div>
|
||||
<div class="operation-btn" @click="navgition('/pages/mine/deposit/recharge')">充值</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box list" @click="navgition('/pages/mine/deposit/index')">
|
||||
<div class="list-left">预存款明细</div>
|
||||
<div class="list-right">
|
||||
<u-icon name="arrow-right"></u-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<view :style="themeStyle" class="page">
|
||||
<u-navbar
|
||||
title="余额"
|
||||
:auto-back="false"
|
||||
:placeholder="true"
|
||||
:border="false"
|
||||
@leftClick="back"
|
||||
></u-navbar>
|
||||
|
||||
<view class="page-body">
|
||||
<view class="balance-card">
|
||||
<view class="deposit">预存款金额</view>
|
||||
<view class="money">¥{{ unitPrice(walletNum) }}</view>
|
||||
<view class="operation-btns">
|
||||
<view
|
||||
class="operation-btn light"
|
||||
@click="navigateTo('/pages/mine/deposit/withdrawal')"
|
||||
>提现</view>
|
||||
<view
|
||||
class="operation-btn primary"
|
||||
@click="navigateTo('/pages/mine/deposit/recharge')"
|
||||
>充值</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="list-item" @click="navigateTo('/pages/mine/deposit/index')">
|
||||
<text class="list-label">预存款明细</text>
|
||||
<u-icon name="arrow-right" color="#ccc" size="16"></u-icon>
|
||||
</view>
|
||||
<view class="list-item" @click="navigateTo('/pages/mine/deposit/withdrawApply')">
|
||||
<text class="list-label">提现记录</text>
|
||||
<u-icon name="arrow-right" color="#ccc" size="16"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getUserWallet } from "@/api/members";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
walletNum: 0,
|
||||
};
|
||||
},
|
||||
async onShow() {
|
||||
if (this.$options.filters.isLogin("auth")) {
|
||||
let result = await getUserWallet(); //预存款
|
||||
this.walletNum = result.data.result.memberWallet;
|
||||
} else {
|
||||
uni.showToast({
|
||||
icon: "none",
|
||||
duration: 3000,
|
||||
title: "请先登录!",
|
||||
});
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { getUserWallet } from '@/api/members'
|
||||
import { isLogin, navigateToLogin, unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
this.$options.filters.navigateToLogin("redirectTo");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
back() {
|
||||
uni.switchTab({
|
||||
url: "/pages/tabbar/user/my",
|
||||
});
|
||||
},
|
||||
/**
|
||||
* 跳转
|
||||
*/
|
||||
navgition(url) {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
const walletNum = ref(0)
|
||||
|
||||
onShow(async () => {
|
||||
if (isLogin('auth')) {
|
||||
const result = await getUserWallet()
|
||||
walletNum.value = result.data.result.memberWallet
|
||||
} else {
|
||||
navigateToLogin('redirectTo')
|
||||
}
|
||||
})
|
||||
|
||||
function back() {
|
||||
uni.switchTab({ url: '/pages/tabbar/user/my' })
|
||||
}
|
||||
|
||||
function navigateTo(url: string) {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.list {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
.list-left {
|
||||
flex: 8;
|
||||
}
|
||||
.list-right {
|
||||
flex: 2;
|
||||
text-align: right;
|
||||
}
|
||||
.wrapper {
|
||||
width: 94%;
|
||||
margin: 0 3%;
|
||||
}
|
||||
.box {
|
||||
margin: 20rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
|
||||
padding: 40rpx;
|
||||
.page-body {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 48rpx 32rpx 40rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.deposit {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.money {
|
||||
margin: 24rpx 0 40rpx;
|
||||
font-size: 64rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
|
||||
.operation-btns {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
.money {
|
||||
text-align: center;
|
||||
color: #333;
|
||||
font-size: 50rpx;
|
||||
margin: 20rpx 0 40rpx 0;
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.deposit {
|
||||
margin-top: 50rpx;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
|
||||
letter-spacing: 2rpx;
|
||||
}
|
||||
.operation-btn {
|
||||
background: #ee6d41;
|
||||
color: #fff;
|
||||
height: 90rpx;
|
||||
width: 240rpx;
|
||||
margin: 0 20rpx;
|
||||
border-radius: 10rpx;
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
line-height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.light {
|
||||
background: #fdf2ee;
|
||||
color: #ee6d41;
|
||||
color: $light-color;
|
||||
border: 1px solid rgba(238, 109, 65, 0.25);
|
||||
}
|
||||
</style>
|
||||
|
||||
.primary {
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
margin-top: 24rpx;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 32rpx;
|
||||
border-bottom: 1px solid #f5f5f5;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.list-label {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,50 +1,130 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="-list">
|
||||
<view :style="themeStyle" class="page">
|
||||
<view class="form-card">
|
||||
<view class="title">充值金额</view>
|
||||
<view class="content">
|
||||
<view class="price">
|
||||
<span> ¥</span>
|
||||
<u-input v-model="price" placeholder="" type="number" />
|
||||
</view>
|
||||
|
||||
<view class="amount-row">
|
||||
<text class="currency">¥</text>
|
||||
<u-input
|
||||
v-model="price"
|
||||
type="digit"
|
||||
border="none"
|
||||
placeholder="请输入充值金额"
|
||||
input-align="left"
|
||||
class="amount-input"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="submit" :class="{'light':flag}" @click="handlerRecharge">充值</view>
|
||||
<view class="submit" :class="{ disabled: flag }" @click="handlerRecharge">充值</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { recharge } from "@/api/members";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
price: 0,
|
||||
flag: true,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
price(val) {
|
||||
val <= 0 ? (this.flag = true) : (this.flag = false);
|
||||
},
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
// 充值
|
||||
async handlerRecharge() {
|
||||
if (this.price > 0) {
|
||||
|
||||
let res = await recharge({ price: this.price });
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cart/payment/payOrder?orderType=RECHARGE&recharge_sn=${res.data.result.rechargeSn}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, computed } from 'vue'
|
||||
import { recharge } from '@/api/members'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const price = ref('')
|
||||
const flag = ref(true)
|
||||
|
||||
watch(price, (val) => {
|
||||
flag.value = !(Number(val) > 0)
|
||||
})
|
||||
|
||||
async function handlerRecharge() {
|
||||
const amount = Number(price.value)
|
||||
if (!(amount > 0)) {
|
||||
uni.showToast({ title: '请输入充值金额', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const res = await recharge({ price: amount })
|
||||
if (res.data.success) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/cart/payment/payOrder?orderType=RECHARGE&recharge_sn=${res.data.result.rechargeSn}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import './style.scss';
|
||||
</style>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.amount-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 16rpx 0 8rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.currency {
|
||||
flex-shrink: 0;
|
||||
margin-right: 16rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content__field-wrapper__field) {
|
||||
height: 72rpx !important;
|
||||
min-height: 72rpx !important;
|
||||
font-size: 56rpx !important;
|
||||
font-weight: 600 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content__field-wrapper__field--placeholder) {
|
||||
font-size: 32rpx !important;
|
||||
font-weight: 400 !important;
|
||||
color: #c0c4cc !important;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: 80rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/deep/ .u-input__input,
|
||||
::v-deep .u-input__input,
|
||||
.u-input {
|
||||
font-size: 80rpx !important;
|
||||
height: 102rpx !important;
|
||||
}
|
||||
/deep/ .u-input__input {
|
||||
::v-deep .u-input__input {
|
||||
height: 100%;
|
||||
font-size: 80rpx;
|
||||
}
|
||||
@@ -43,4 +43,4 @@
|
||||
border-radius: 10rpx;
|
||||
text-align: center;
|
||||
line-height: 90rpx;
|
||||
}
|
||||
}
|
||||
|
||||
229
pages/mine/deposit/withdrawApply.vue
Normal file
229
pages/mine/deposit/withdrawApply.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<view :style="themeStyle" class="wap">
|
||||
<u-navbar
|
||||
title="提现记录"
|
||||
:fixed="true"
|
||||
:placeholder="true"
|
||||
:border="false"
|
||||
:auto-back="true"
|
||||
></u-navbar>
|
||||
<scroll-view class="scroll-v view-wrapper" enableBackToTop="true" scroll-with-animation scroll-y @scrolltolower="loadMore">
|
||||
<view v-if="records.length != 0" class="view-item" v-for="(item, index) in records" :key="index">
|
||||
<view class="view-item-detail">
|
||||
<view class="-title">{{ withdrawStatusText(item.applyStatus) }}</view>
|
||||
<view class="-number" v-if="item.inspectRemark || item.errorMessage">{{ item.inspectRemark || item.errorMessage }}</view>
|
||||
</view>
|
||||
<view class="view-item-change">
|
||||
<view class="-money">-{{unitPrice(item.applyMoney) }}</view>
|
||||
<view class="-time">{{ item.createTime || item.inspectTime || "" }}</view>
|
||||
<view v-if="item.applyStatus === 'WAIT_USER_CONFIRM'" class="confirm-btn" @click.stop="confirmWechatReceive(item)">确认收款</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<u-empty v-if="loaded && records.length == 0" mode="history" text="暂无记录" />
|
||||
</scroll-view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { getWithdrawApplyPage, getWithdrawApplyWechatTransferInfo } from '@/api/members'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const loaded = ref(false)
|
||||
const params = ref({
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
order: 'desc',
|
||||
})
|
||||
const records = ref<any[]>([])
|
||||
|
||||
onShow(() => {
|
||||
params.value.pageNumber = 1
|
||||
records.value = []
|
||||
loaded.value = false
|
||||
getData()
|
||||
})
|
||||
|
||||
function withdrawStatusText(applyStatus: string) {
|
||||
switch (applyStatus) {
|
||||
case 'APPLY':
|
||||
return '申请中'
|
||||
case 'VIA_AUDITING':
|
||||
return '审核通过'
|
||||
case 'D_VIA_AUDITING':
|
||||
return '分销提现审核通过'
|
||||
case 'FAIL_AUDITING':
|
||||
return '审核未通过'
|
||||
case 'D_FAIL_AUDITING':
|
||||
return '分销提现审核未通过'
|
||||
case 'WAIT_USER_CONFIRM':
|
||||
return '等待用户确认'
|
||||
case 'SUCCESS':
|
||||
return '提现成功'
|
||||
case 'ERROR':
|
||||
return '提现失败'
|
||||
default:
|
||||
return applyStatus || ''
|
||||
}
|
||||
}
|
||||
|
||||
function getData() {
|
||||
getWithdrawApplyPage(params.value).then((res) => {
|
||||
loaded.value = true
|
||||
if (res.data.success && res.data.result.records.length != 0) {
|
||||
records.value.push(...res.data.result.records)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function loadMore() {
|
||||
params.value.pageNumber++
|
||||
getData()
|
||||
}
|
||||
|
||||
function confirmWechatReceive(item: any) {
|
||||
const id = item && item.id
|
||||
if (!id) {
|
||||
uni.showToast({ title: '缺少提现记录ID', duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
uni.showLoading({ title: '加载中' })
|
||||
getWithdrawApplyWechatTransferInfo(id)
|
||||
.then((res) => {
|
||||
if (!res.data || !res.data.success) return
|
||||
|
||||
const info = res.data.result || {}
|
||||
const mchId = info.mchId
|
||||
const wechatPackage = info.wechatPackage
|
||||
let appId = info.appId
|
||||
|
||||
if (typeof wx !== 'undefined' && wx.getAccountInfoSync) {
|
||||
try {
|
||||
const accountInfo = wx.getAccountInfoSync()
|
||||
const mpAppId = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.appId
|
||||
if (mpAppId) appId = mpAppId
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (!mchId || !appId || !wechatPackage) {
|
||||
uni.showToast({ title: '微信确认参数缺失', duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const openResultToast = (errMsg?: string) => {
|
||||
if (errMsg === 'requestMerchantTransfer:ok') {
|
||||
uni.showToast({ title: '已唤起确认页面', duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (errMsg === 'requestMerchantTransfer:cancel') {
|
||||
uni.showToast({ title: '已取消', duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.showToast({
|
||||
title: errMsg ? `唤起失败:${errMsg}` : '唤起失败',
|
||||
duration: 2500,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof wx !== 'undefined' && wx.getSystemInfoSync) {
|
||||
try {
|
||||
const sys = wx.getSystemInfoSync()
|
||||
if (sys && sys.platform === 'devtools') {
|
||||
uni.showToast({
|
||||
title: '开发者工具可能不支持,请真机测试',
|
||||
duration: 2500,
|
||||
icon: 'none',
|
||||
})
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (typeof wx !== 'undefined' && wx.canIUse && wx.canIUse('requestMerchantTransfer')) {
|
||||
wx.requestMerchantTransfer({
|
||||
mchId,
|
||||
appId,
|
||||
package: wechatPackage,
|
||||
success: (r: any) => openResultToast(r && (r.errMsg || r.err_msg)),
|
||||
fail: (r: any) => openResultToast(r && (r.errMsg || r.err_msg)),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof WeixinJSBridge !== 'undefined' && WeixinJSBridge.invoke) {
|
||||
WeixinJSBridge.invoke(
|
||||
'requestMerchantTransfer',
|
||||
{ mchId, appId, package: wechatPackage },
|
||||
(r: any) => openResultToast(r && (r.errMsg || r.err_msg))
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
uni.showToast({ title: '请在微信内打开确认收款', duration: 2000, icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
uni.hideLoading()
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.view-item {
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.view-item-change {
|
||||
text-align: right;
|
||||
> .-money {
|
||||
font-size: 36rpx;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
}
|
||||
> .-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
> .confirm-btn {
|
||||
display: inline-block;
|
||||
margin-top: 12rpx;
|
||||
padding: 12rpx 20rpx;
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
background: $main-color;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
.view-item-detail {
|
||||
line-height: 1.75;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
> .-title {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
> .-number {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.wap {
|
||||
width: 100%;
|
||||
height: calc(100vh - 44px);
|
||||
}
|
||||
|
||||
.scroll-v {
|
||||
height: calc(100vh - 88rpx);
|
||||
}
|
||||
</style>
|
||||
@@ -1,78 +1,329 @@
|
||||
<template>
|
||||
<view>
|
||||
<view class="-list">
|
||||
<view class="title">提现金额</view>
|
||||
<view class="content">
|
||||
<view class="price">
|
||||
<span> ¥</span>
|
||||
<u-input v-model="price" placeholder="" type="number" />
|
||||
</view>
|
||||
|
||||
<view class="all">
|
||||
<view @click="handleAll" :style="{ color: $mainColor }">全部</view>
|
||||
<view style="font-size: 24rpx; color: #999">可提现金额<span>{{ walletNum | unitPrice }}</span>元</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="tips">
|
||||
最低提现金额为1.00元
|
||||
</view>
|
||||
<view :style="themeStyle" class="page">
|
||||
<view class="form-card">
|
||||
<view class="field-label">提现类型</view>
|
||||
<view class="readonly-value">{{ typeLabel }}</view>
|
||||
</view>
|
||||
|
||||
<view class="form-card">
|
||||
<view class="field-label">提现金额</view>
|
||||
<view class="amount-main">
|
||||
<view class="amount-left">
|
||||
<text class="currency">¥</text>
|
||||
<u-input
|
||||
v-model="price"
|
||||
type="digit"
|
||||
border="none"
|
||||
placeholder="请输入提现金额"
|
||||
input-align="left"
|
||||
class="amount-input"
|
||||
/>
|
||||
</view>
|
||||
<view class="amount-extra">
|
||||
<text class="all-btn" @click="handleAll">全部</text>
|
||||
<text class="balance-tip">可提现金额 {{ unitPrice(walletNum) }} 元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">最低提现金额为 {{ minPrice }} 元</view>
|
||||
</view>
|
||||
|
||||
<template v-if="type === 'ALI'">
|
||||
<view class="form-card">
|
||||
<view class="field-row">
|
||||
<text class="field-label">真实姓名</text>
|
||||
<u-input
|
||||
v-model="realName"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入真实姓名"
|
||||
class="field-input"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-card">
|
||||
<view class="field-row">
|
||||
<text class="field-label">第三方登录账号</text>
|
||||
<u-input
|
||||
v-model="connectNumber"
|
||||
border="none"
|
||||
input-align="right"
|
||||
placeholder="请输入支付宝账号"
|
||||
class="field-input"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view class="submit" @click="cashd">提现</view>
|
||||
|
||||
<!-- 提现规则说明(满足微信小程序审核要求) -->
|
||||
<view class="rule-card">
|
||||
<view class="rule-title">
|
||||
<text class="rule-icon"></text>
|
||||
<text>提现规则说明</text>
|
||||
</view>
|
||||
<view class="rule-content">
|
||||
<text class="rule-text">{{ displayRule }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { getUserWallet, withdrawalApply } from "@/api/members";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
price: 0,
|
||||
walletNum: 0,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
let result = await getUserWallet(); //预存款
|
||||
this.walletNum = result.data.result.memberWallet;
|
||||
},
|
||||
|
||||
methods: {
|
||||
cashd() {
|
||||
this.price = this.price + "";
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, getCurrentInstance, onMounted } from 'vue'
|
||||
import { getUserWallet, withdrawalApply, withdrawalSettingVO } from '@/api/members'
|
||||
import { unitPrice } from '@/utils/filters.js'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
if (this.$u.test.amount(parseInt(this.price))) {
|
||||
withdrawalApply({ price: this.price }).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.showToast({
|
||||
title: "提现成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "请输入正确金额",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
},
|
||||
handleAll() {
|
||||
this.price = this.walletNum;
|
||||
},
|
||||
},
|
||||
};
|
||||
const { proxy } = getCurrentInstance()!
|
||||
|
||||
const price = ref('')
|
||||
const walletNum = ref(0)
|
||||
const minPrice = ref(0)
|
||||
const type = ref('')
|
||||
const connectNumber = ref('')
|
||||
const realName = ref('')
|
||||
const withdrawRule = ref('')
|
||||
|
||||
const typeLabel = computed(() => {
|
||||
if (type.value === 'ALI') return '支付宝'
|
||||
if (type.value) return '微信'
|
||||
return '--'
|
||||
})
|
||||
|
||||
const defaultRule = computed(() => {
|
||||
const minText = minPrice.value ? `单笔最低提现金额为 ${minPrice.value} 元` : '单笔最低提现金额以页面提示为准'
|
||||
const channelText = typeLabel.value === '支付宝' ? '支付宝账户' : '微信零钱账户'
|
||||
return `1. 提现额度:${minText},单日累计最高提现 5000 元;
|
||||
2. 提现频次:每个用户每日最多可申请提现 1 次;
|
||||
3. 提现时间:支持全天 24 小时随时发起提现申请;
|
||||
4. 到账时间:提交申请后预计 1~3 个工作日内审核并转账至您的${channelText};
|
||||
5. 手续费说明:提现免收手续费;
|
||||
6. 如有其他疑问或遇到提现未到账,请联系在线客服协助处理。`
|
||||
})
|
||||
|
||||
const displayRule = computed(() => {
|
||||
return withdrawRule.value ? withdrawRule.value : defaultRule.value
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
const result = await getUserWallet()
|
||||
const res = await withdrawalSettingVO()
|
||||
walletNum.value = result.data.result.memberWallet
|
||||
minPrice.value = res.data.result.minPrice
|
||||
type.value = res.data.result.type
|
||||
withdrawRule.value = res.data.result.withdrawRule || ''
|
||||
})
|
||||
|
||||
function cashd() {
|
||||
const amount = Number(price.value)
|
||||
if (!proxy.$u.test.amount(parseInt(String(amount)))) {
|
||||
uni.showToast({ title: '请输入正确金额', duration: 2000, icon: 'none' })
|
||||
return
|
||||
}
|
||||
|
||||
const params: Record<string, any> = { price: amount }
|
||||
if (type.value === 'ALI') {
|
||||
if (!connectNumber.value || !realName.value) {
|
||||
uni.showToast({
|
||||
title: '请输入真实姓名和第三方登录账号',
|
||||
duration: 2000,
|
||||
icon: 'none',
|
||||
})
|
||||
return
|
||||
}
|
||||
params.connectNumber = connectNumber.value
|
||||
params.realName = realName.value
|
||||
}
|
||||
|
||||
withdrawalApply(params).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.showToast({ title: '提现成功!', duration: 2000, icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack({ delta: 1 })
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function handleAll() {
|
||||
price.value = String(walletNum.value || '')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "./style.scss";
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding: 24rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 30rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.readonly-value {
|
||||
margin-top: 20rpx;
|
||||
font-size: 32rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.amount-main {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-top: 20rpx;
|
||||
padding-bottom: 16rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.amount-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.currency {
|
||||
flex-shrink: 0;
|
||||
margin-right: 12rpx;
|
||||
font-size: 56rpx;
|
||||
font-weight: 600;
|
||||
line-height: 1;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.amount-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.amount-extra {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
margin-left: 24rpx;
|
||||
padding-top: 8rpx;
|
||||
}
|
||||
|
||||
.all-btn {
|
||||
font-size: 28rpx;
|
||||
color: $main-color;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.balance-tip {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.field-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
margin-left: 24rpx;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input),
|
||||
:deep(.field-input .u-input) {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content),
|
||||
:deep(.field-input .u-input__content) {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content__field-wrapper__field) {
|
||||
height: 72rpx !important;
|
||||
min-height: 72rpx !important;
|
||||
font-size: 56rpx !important;
|
||||
font-weight: 600 !important;
|
||||
color: #333 !important;
|
||||
}
|
||||
|
||||
:deep(.amount-input .u-input__content__field-wrapper__field--placeholder) {
|
||||
font-size: 32rpx !important;
|
||||
font-weight: 400 !important;
|
||||
color: #c0c4cc !important;
|
||||
}
|
||||
|
||||
:deep(.field-input .u-input__content__field-wrapper__field) {
|
||||
font-size: 28rpx !important;
|
||||
color: #666 !important;
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: 56rpx;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
background: $light-color;
|
||||
color: #fff;
|
||||
border-radius: 44rpx;
|
||||
text-align: center;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.rule-card {
|
||||
margin-top: 48rpx;
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.rule-title {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.rule-icon {
|
||||
width: 6rpx;
|
||||
height: 26rpx;
|
||||
background: $main-color;
|
||||
border-radius: 4rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.rule-content {
|
||||
.rule-text {
|
||||
font-size: 24rpx;
|
||||
color: #888;
|
||||
line-height: 1.8;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,11 +1,568 @@
|
||||
<template>
|
||||
|
||||
<view class="page" :style="themeStyle">
|
||||
<view v-if="!pageReady" class="state-wrap">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view class="filter-bar">
|
||||
<scroll-view scroll-x class="filter-scroll" :show-scrollbar="false">
|
||||
<view class="filter-tabs">
|
||||
<view
|
||||
v-for="item in rangeTabs"
|
||||
:key="item.value"
|
||||
class="filter-tab"
|
||||
:class="{ active: activeRange === item.value }"
|
||||
@click="changeRange(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="custom-time" :class="{ active: activeRange === 'CUSTOM' }" @click="openCustomPicker">
|
||||
<text>自定义时间</text>
|
||||
<u-icon name="arrow-down" size="12" :color="activeRange === 'CUSTOM' ? '#ff8f3f' : '#999'" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="state-wrap">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view v-else class="content">
|
||||
<view class="summary-card">
|
||||
<view class="summary-label-row">
|
||||
<text class="summary-label">累计收益</text>
|
||||
<view class="info-icon" @click="showGuide = true">
|
||||
<u-icon name="info-circle" size="14" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="summary-amount">
|
||||
<text class="amount-value">{{ formatMoney(stats.totalEarnings) }}</text>
|
||||
<text class="amount-unit">元</text>
|
||||
</view>
|
||||
<view class="summary-pending">含待结算 {{ formatMoney(stats.pendingEarnings) }}元</view>
|
||||
</view>
|
||||
|
||||
<view class="earn-grid">
|
||||
<view class="earn-item">
|
||||
<view class="earn-title">已结算收益</view>
|
||||
<view class="earn-value">{{ formatMoney(stats.settledEarnings) }}<text class="unit">元</text></view>
|
||||
</view>
|
||||
<view class="earn-item">
|
||||
<view class="earn-title">商品佣金</view>
|
||||
<view class="earn-value">{{ formatMoney(stats.directCommission) }}<text class="unit">元</text></view>
|
||||
<view class="earn-sub">含待结算: {{ formatMoney(stats.directPending) }}元</view>
|
||||
</view>
|
||||
<view class="earn-item">
|
||||
<view class="earn-title">邀请奖励</view>
|
||||
<view class="earn-value">{{ formatMoney(stats.inviteReward) }}<text class="unit">元</text></view>
|
||||
<view class="earn-sub">含待结算: {{ formatMoney(stats.invitePending) }}元</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="stats-grid">
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">累计销售额(元)</view>
|
||||
<view class="stats-value">{{ formatMoney(stats.totalSales) }}</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">累计订单(笔)</view>
|
||||
<view class="stats-value">{{ stats.totalOrders || 0 }}</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">累计客户(人)</view>
|
||||
<view class="stats-value">{{ stats.totalCustomers || 0 }}</view>
|
||||
</view>
|
||||
<view class="stats-item">
|
||||
<view class="stats-label">累计邀请(人)</view>
|
||||
<view class="stats-value">{{ stats.totalInvites || 0 }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="guide-link" @click="showGuide = true">查看业绩指标说明</view>
|
||||
</view>
|
||||
|
||||
<u-popup v-model:show="customVisible" mode="bottom" round="16">
|
||||
<view class="custom-popup">
|
||||
<view class="popup-title">自定义时间</view>
|
||||
<view class="popup-row">
|
||||
<text class="popup-label">开始日期</text>
|
||||
<picker mode="date" :value="customStartDate" @change="onStartDateChange">
|
||||
<view class="picker-value">{{ customStartDate || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="popup-row">
|
||||
<text class="popup-label">结束日期</text>
|
||||
<picker mode="date" :value="customEndDate" @change="onEndDateChange">
|
||||
<view class="picker-value">{{ customEndDate || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="popup-actions">
|
||||
<view class="popup-btn ghost" @click="customVisible = false">取消</view>
|
||||
<view class="popup-btn primary" @click="applyCustomRange">确定</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
|
||||
<u-popup v-model:show="showGuide" mode="center" round="16" :safe-area-inset-bottom="false">
|
||||
<view class="guide-popup">
|
||||
<view class="guide-title">业绩指标说明</view>
|
||||
<view class="guide-item">
|
||||
<view class="guide-term">销售额</view>
|
||||
<view class="guide-desc">仅统计推广订单的销售金额总和</view>
|
||||
</view>
|
||||
<view class="guide-item">
|
||||
<view class="guide-term">客户</view>
|
||||
<view class="guide-desc">与分销员建立绑定关系的买家</view>
|
||||
</view>
|
||||
<view class="guide-item">
|
||||
<view class="guide-term">邀请</view>
|
||||
<view class="guide-desc">分销员成功邀请的下级分销员</view>
|
||||
</view>
|
||||
<view class="guide-item">
|
||||
<view class="guide-term">商品佣金</view>
|
||||
<view class="guide-desc">分销员推荐客户购买商品后获得的佣金</view>
|
||||
</view>
|
||||
<view class="guide-item">
|
||||
<view class="guide-term">邀请奖励</view>
|
||||
<view class="guide-desc">下级分销员推广商品后,上级获得的奖励</view>
|
||||
</view>
|
||||
<view class="guide-btn" @click="showGuide = false">知道了</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onShow, onLoad } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import { getDistributionPerformance } from '@/api/distribution'
|
||||
import { assertActiveDistributor } from '@/utils/distributionStatus'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const rangeTabs = [
|
||||
{ label: '全部', value: 'ALL' },
|
||||
{ label: '今日', value: 'TODAY' },
|
||||
{ label: '昨日', value: 'YESTERDAY' },
|
||||
{ label: '近七日', value: 'LAST_7_DAYS' },
|
||||
]
|
||||
|
||||
const pageReady = ref(false)
|
||||
const activeRange = ref('ALL')
|
||||
const loading = ref(false)
|
||||
const customVisible = ref(false)
|
||||
const showGuide = ref(false)
|
||||
const customStartDate = ref('')
|
||||
const customEndDate = ref('')
|
||||
|
||||
const stats = ref({
|
||||
totalEarnings: 0,
|
||||
pendingEarnings: 0,
|
||||
settledEarnings: 0,
|
||||
directCommission: 0,
|
||||
directPending: 0,
|
||||
inviteReward: 0,
|
||||
invitePending: 0,
|
||||
totalSales: 0,
|
||||
totalOrders: 0,
|
||||
totalCustomers: 0,
|
||||
totalInvites: 0,
|
||||
})
|
||||
|
||||
const RANGE_VALUES = ['ALL', 'TODAY', 'YESTERDAY', 'LAST_7_DAYS', 'CUSTOM']
|
||||
|
||||
onLoad((options: Record<string, string>) => {
|
||||
if (options?.rangeType && RANGE_VALUES.includes(options.rangeType)) {
|
||||
activeRange.value = options.rangeType
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
assertActiveDistributor().then((d) => {
|
||||
if (!d) {
|
||||
return
|
||||
}
|
||||
pageReady.value = true
|
||||
loadPerformance()
|
||||
})
|
||||
})
|
||||
|
||||
function formatMoney(val: number | string) {
|
||||
return Number(val || 0).toFixed(2)
|
||||
}
|
||||
|
||||
function buildParams() {
|
||||
const params: Record<string, string> = { rangeType: activeRange.value }
|
||||
if (activeRange.value === 'CUSTOM') {
|
||||
if (customStartDate.value) {
|
||||
params.startTime = `${customStartDate.value} 00:00:00`
|
||||
}
|
||||
if (customEndDate.value) {
|
||||
params.endTime = `${customEndDate.value} 23:59:59`
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
function loadPerformance() {
|
||||
loading.value = true
|
||||
getDistributionPerformance(buildParams())
|
||||
.then((res) => {
|
||||
const data = res.data?.result || {}
|
||||
stats.value = {
|
||||
totalEarnings: Number(data.totalEarnings || 0),
|
||||
pendingEarnings: Number(data.pendingEarnings || 0),
|
||||
settledEarnings: Number(data.settledEarnings || 0),
|
||||
directCommission: Number(data.directCommission || 0),
|
||||
directPending: Number(data.directPending || 0),
|
||||
inviteReward: Number(data.inviteReward || 0),
|
||||
invitePending: Number(data.invitePending || 0),
|
||||
totalSales: Number(data.totalSales || 0),
|
||||
totalOrders: Number(data.totalOrders || 0),
|
||||
totalCustomers: Number(data.totalCustomers || 0),
|
||||
totalInvites: Number(data.totalInvites || 0),
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function changeRange(value: string) {
|
||||
if (activeRange.value === value) return
|
||||
activeRange.value = value
|
||||
loadPerformance()
|
||||
}
|
||||
|
||||
function openCustomPicker() {
|
||||
customVisible.value = true
|
||||
}
|
||||
|
||||
function onStartDateChange(e: { detail: { value: string } }) {
|
||||
customStartDate.value = e.detail.value
|
||||
}
|
||||
|
||||
function onEndDateChange(e: { detail: { value: string } }) {
|
||||
customEndDate.value = e.detail.value
|
||||
}
|
||||
|
||||
function applyCustomRange() {
|
||||
if (!customStartDate.value || !customEndDate.value) {
|
||||
uni.showToast({ title: '请选择开始和结束日期', icon: 'none' })
|
||||
return
|
||||
}
|
||||
activeRange.value = 'CUSTOM'
|
||||
customVisible.value = false
|
||||
loadPerformance()
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx 24rpx;
|
||||
background: #fff;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.filter-scroll {
|
||||
flex: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.filter-tab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 88rpx;
|
||||
height: 56rpx;
|
||||
padding: 0 20rpx;
|
||||
border-radius: 28rpx;
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
color: #ff8f3f;
|
||||
background: #fff3ea;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.custom-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6rpx;
|
||||
margin-left: 16rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.custom-time.active {
|
||||
color: #ff8f3f;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.state-wrap {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-text {
|
||||
font-size: 28rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.summary-card,
|
||||
.earn-grid,
|
||||
.stats-grid {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
padding: 32rpx 28rpx 28rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.summary-label-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.summary-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.summary-amount {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.amount-value {
|
||||
font-size: 64rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.amount-unit {
|
||||
margin-left: 8rpx;
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.summary-pending {
|
||||
margin-top: 12rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.earn-grid {
|
||||
display: flex;
|
||||
padding: 28rpx 0;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.earn-item {
|
||||
flex: 1;
|
||||
padding: 0 20rpx;
|
||||
text-align: center;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.earn-item:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.earn-title {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.earn-value {
|
||||
margin-top: 12rpx;
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.earn-value .unit {
|
||||
font-size: 22rpx;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.earn-sub {
|
||||
margin-top: 8rpx;
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
padding: 32rpx 28rpx;
|
||||
border-right: 1rpx solid #f0f0f0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.stats-item:nth-child(2n) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.stats-item:nth-last-child(-n + 2) {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.stats-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
margin-top: 16rpx;
|
||||
font-size: 40rpx;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.guide-link {
|
||||
margin-top: 40rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.custom-popup {
|
||||
padding: 32rpx 32rpx calc(32rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.popup-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.popup-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.picker-value {
|
||||
font-size: 28rpx;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.popup-btn {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
border-radius: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.popup-btn.ghost {
|
||||
background: #f5f6f8;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.popup-btn.primary {
|
||||
background: linear-gradient(135deg, #ff9f43, #ff7f27);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.guide-popup {
|
||||
width: 620rpx;
|
||||
padding: 40rpx 36rpx 32rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.guide-title {
|
||||
font-size: 34rpx;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
color: #222;
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.guide-item {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.guide-term {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #222;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.guide-desc {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.guide-btn {
|
||||
margin-top: 12rpx;
|
||||
height: 84rpx;
|
||||
border-radius: 42rpx;
|
||||
background: linear-gradient(135deg, #ff9f43, #ff7f27);
|
||||
color: #fff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,188 +1,49 @@
|
||||
<template>
|
||||
<view class="wrapper">
|
||||
<view>
|
||||
<h4>实名认证(请上传真实的个人信息,认证通过后将无法修改)</h4>
|
||||
<view>
|
||||
<u-form :model="ruleForm" label-width="200rpx" ref="uForm">
|
||||
<u-form-item label="姓名" prop="name">
|
||||
<u-input v-model="ruleForm.name" placeholder="请输入您的真实姓名" />
|
||||
</u-form-item>
|
||||
<u-form-item label="身份证" prop="idNumber">
|
||||
<u-input v-model="ruleForm.idNumber" placeholder="请输入身份证号码" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银行开户行" prop="settlementBankBranchName">
|
||||
<u-input v-model="ruleForm.settlementBankBranchName" type="text" placeholder="请输入银行开户行" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银行开户名" prop="settlementBankAccountName">
|
||||
<u-input v-model="ruleForm.settlementBankAccountName" type="text" placeholder="请输入银行开户名" />
|
||||
</u-form-item>
|
||||
<u-form-item label="银行账号" prop="settlementBankAccountNum">
|
||||
<u-input v-model="ruleForm.settlementBankAccountNum" type="text" placeholder="请输入银行账号" />
|
||||
</u-form-item>
|
||||
<!-- <u-form-item label="身份证正面照" prop="name">
|
||||
<u-upload></u-upload>
|
||||
</u-form-item>
|
||||
<u-form-item label="身份证反面照" prop="name">
|
||||
<u-upload></u-upload>
|
||||
</u-form-item>
|
||||
<u-form-item label="手持身份证照" prop="name">
|
||||
<u-upload></u-upload>
|
||||
</u-form-item> -->
|
||||
</u-form>
|
||||
<u-button :customStyle="{'background':$lightColor,'color':'#fff' }" @click="submit">提交</u-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tips">
|
||||
<view>您提交的信息正在审核</view>
|
||||
<view>提交认证申请后,工作人员将在三个工作日进行核对完成审核</view>
|
||||
<view>分销员申请已改为招募流程</view>
|
||||
<view>请前往招募页面提交申请</view>
|
||||
</view>
|
||||
<u-button :customStyle="{ background: lightColor, color: '#fff', marginTop: '40rpx' }" @click="goJoin">
|
||||
前往招募页
|
||||
</u-button>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { applyDistribution } from "@/api/goods";
|
||||
import { checkBankno } from "@/utils/Foundation";
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ruleForm: {
|
||||
name: "",
|
||||
idNumber: "",
|
||||
settlementBankBranchName: "", // 银行开户行
|
||||
settlementBankAccountName: "", //银行开户名
|
||||
settlementBankAccountNum: "", //银行账号
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入姓名",
|
||||
// 可以单个或者同时写两个触发验证方式
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
// 自定义验证函数,见上说明
|
||||
validator: (rule, value, callback) => {
|
||||
// 上面有说,返回true表示校验通过,返回false表示不通过
|
||||
// this.$u.test.mobile()就是返回true或者false的
|
||||
return this.$u.test.chinese(value);
|
||||
},
|
||||
message: "姓名输入不正确",
|
||||
// 触发器可以同时用blur和change
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
// 银行开户行
|
||||
settlementBankBranchName: [
|
||||
{
|
||||
required: true,
|
||||
message: "银行开户行",
|
||||
// 可以单个或者同时写两个触发验证方式
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
// 上面有说,返回true表示校验通过,返回false表示不通过
|
||||
// this.$u.test.mobile()就是返回true或者false的
|
||||
return this.$u.test.chinese(value);
|
||||
},
|
||||
message: "银行开户行不正确",
|
||||
// 触发器可以同时用blur和change
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
], //银行开户名
|
||||
settlementBankAccountName: [
|
||||
{
|
||||
required: true,
|
||||
message: "银行开户名",
|
||||
// 可以单个或者同时写两个触发验证方式
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
//银行账号
|
||||
settlementBankAccountNum: [
|
||||
{
|
||||
required: true,
|
||||
message: "银行账号不正确",
|
||||
// 可以单个或者同时写两个触发验证方式
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
// 上面有说,返回true表示校验通过,返回false表示不通过
|
||||
// this.$u.test.mobile()就是返回true或者false的
|
||||
return checkBankno(value);
|
||||
},
|
||||
message: "银行账号不正确",
|
||||
},
|
||||
],
|
||||
idNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: "请输入身份证",
|
||||
// 可以单个或者同时写两个触发验证方式
|
||||
trigger: "blur",
|
||||
},
|
||||
{
|
||||
// 自定义验证函数,见上说明
|
||||
validator: (rule, value, callback) => {
|
||||
// 上面有说,返回true表示校验通过,返回false表示不通过
|
||||
// this.$u.test.mobile()就是返回true或者false的
|
||||
return this.$u.test.idCard(value);
|
||||
},
|
||||
message: "身份证号码不正确",
|
||||
// 触发器可以同时用blur和change
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submit() {
|
||||
this.$refs.uForm.validate((valid) => {
|
||||
if (valid) {
|
||||
applyDistribution(this.ruleForm).then((res) => {
|
||||
if (res.data.success) {
|
||||
uni.showToast({
|
||||
title: "认证提交成功!",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 500);
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: res.data.message,
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: "请填写有效信息",
|
||||
duration: 2000,
|
||||
icon: "none",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
onReady() {
|
||||
this.$refs.uForm.setRules(this.rules);
|
||||
},
|
||||
};
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from '@/store'
|
||||
import {
|
||||
checkSelfRecruitEntry,
|
||||
DISTRIBUTION_CLOSE_MESSAGE,
|
||||
DISTRIBUTION_ONLINE_ENTRY_CLOSED_MESSAGE,
|
||||
} from '@/utils/distributionStatus'
|
||||
|
||||
const store = useStore()
|
||||
const lightColor = computed(() => store.getters.lightColor)
|
||||
|
||||
function goJoin() {
|
||||
checkSelfRecruitEntry().then((gate) => {
|
||||
if (gate === 'distribution_closed') {
|
||||
uni.showToast({ title: DISTRIBUTION_CLOSE_MESSAGE, icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (gate === 'entry_closed') {
|
||||
uni.showToast({ title: DISTRIBUTION_ONLINE_ENTRY_CLOSED_MESSAGE, icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.redirectTo({ url: '/pages/mine/distribution/join' })
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.wrapper {
|
||||
padding: 32rpx;
|
||||
}
|
||||
.tips {
|
||||
margin-top: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
391
pages/mine/distribution/bank-accounts.vue
Normal file
391
pages/mine/distribution/bank-accounts.vue
Normal file
@@ -0,0 +1,391 @@
|
||||
<template>
|
||||
<view class="page" :style="themeStyle">
|
||||
<view class="content">
|
||||
<!-- 加载中 -->
|
||||
<view v-if="loading" class="state-wrap">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<!-- 账户列表 -->
|
||||
<view v-else-if="pageReady && accounts.length" class="account-list">
|
||||
<view
|
||||
class="account-item"
|
||||
v-for="item in accounts"
|
||||
:key="item.id"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<view class="item-left">
|
||||
<view v-if="isSelectMode" class="radio-circle" :class="{ active: item.id === selectedId }">
|
||||
<view class="radio-dot" v-if="item.id === selectedId" />
|
||||
</view>
|
||||
<view class="item-info">
|
||||
<text class="item-name">{{ item.holderName }}</text>
|
||||
<view class="item-meta">
|
||||
<text class="item-bank">{{ item.bankName }}</text>
|
||||
<text class="item-card">{{ maskCard(item.cardNo) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="!isSelectMode && item.isDefault" class="default-tag">默认</view>
|
||||
</view>
|
||||
<view class="item-actions">
|
||||
<view v-if="!isSelectMode && !item.isDefault" class="set-default-btn" @click.stop="handleSetDefault(item.id)">
|
||||
<text class="set-default-text">设为默认</text>
|
||||
</view>
|
||||
<view class="item-delete" @click.stop="handleDelete(item.id)">
|
||||
<u-icon name="trash" color="#ccc" size="18" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-else-if="pageReady" class="empty-wrap">
|
||||
<text class="empty-text">暂无银行卡,请添加</text>
|
||||
</view>
|
||||
|
||||
<!-- 添加账户按钮 -->
|
||||
<view v-if="pageReady" class="add-btn-wrap">
|
||||
<view class="add-btn" @click="openAddPopup">+ 添加账户</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 添加账户 Popup -->
|
||||
<u-popup v-model:show="addPopupVisible" mode="bottom" round="16">
|
||||
<view class="popup-wrap">
|
||||
<view class="popup-title">添加到账账户</view>
|
||||
|
||||
<view class="form-item">
|
||||
<text class="form-label">姓名</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.holderName"
|
||||
placeholder="请输入收款人姓名"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-divider" />
|
||||
<view class="form-item">
|
||||
<text class="form-label">银行</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.bankName"
|
||||
placeholder="如:中国工商银行"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
<view class="form-divider" />
|
||||
<view class="form-item">
|
||||
<text class="form-label">卡号</text>
|
||||
<input
|
||||
class="form-input"
|
||||
v-model="form.cardNo"
|
||||
type="number"
|
||||
placeholder="请输入银行卡号"
|
||||
placeholder-class="form-placeholder"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="popup-actions">
|
||||
<view class="popup-btn ghost" @click="addPopupVisible = false">取消</view>
|
||||
<view class="popup-btn primary" :class="{ disabled: saveLoading }" @click="saveAccount">
|
||||
{{ saveLoading ? '保存中...' : '保存' }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad, onShow } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import { getBankCards, addBankCard, deleteBankCard, setDefaultBankCard } from '@/api/distribution'
|
||||
import { assertActiveDistributor } from '@/utils/distributionStatus'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
// 从 getStorageSync 读取上次选择的默认卡 id(仅选择模式使用)
|
||||
const SELECTED_KEY = 'dist_selected_account_id'
|
||||
|
||||
const isSelectMode = ref(false)
|
||||
|
||||
const accounts = ref<any[]>([])
|
||||
const selectedId = ref('')
|
||||
const pageReady = ref(false)
|
||||
const loading = ref(true)
|
||||
const addPopupVisible = ref(false)
|
||||
const saveLoading = ref(false)
|
||||
const form = ref({ holderName: '', bankName: '', cardNo: '' })
|
||||
|
||||
onLoad((options: any) => {
|
||||
isSelectMode.value = options?.mode === 'select'
|
||||
uni.setNavigationBarTitle({ title: isSelectMode.value ? '选择账户' : '到账账户' })
|
||||
if (isSelectMode.value) {
|
||||
selectedId.value = uni.getStorageSync(SELECTED_KEY) || ''
|
||||
}
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
loading.value = true
|
||||
assertActiveDistributor().then((d) => {
|
||||
if (!d) {
|
||||
return
|
||||
}
|
||||
pageReady.value = true
|
||||
loadAccounts()
|
||||
})
|
||||
})
|
||||
|
||||
function loadAccounts() {
|
||||
loading.value = true
|
||||
getBankCards()
|
||||
.then((res: any) => {
|
||||
accounts.value = res?.data?.result || []
|
||||
// 如果选择模式没有已选卡,默认选中 isDefault=true 的卡
|
||||
if (isSelectMode.value && !selectedId.value && accounts.value.length) {
|
||||
const def = accounts.value.find((a: any) => a.isDefault)
|
||||
selectedId.value = def ? def.id : accounts.value[0].id
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function handleSelect(item: any) {
|
||||
if (!isSelectMode.value) return
|
||||
selectedId.value = item.id
|
||||
uni.setStorageSync(SELECTED_KEY, item.id)
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
function handleDelete(id: string) {
|
||||
uni.showModal({
|
||||
title: '删除账户',
|
||||
content: '确认删除该银行卡?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
deleteBankCard(id)
|
||||
.then(() => {
|
||||
uni.showToast({ title: '已删除', icon: 'success' })
|
||||
loadAccounts()
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const msg = err?.data?.message || '删除失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleSetDefault(id: string) {
|
||||
setDefaultBankCard(id)
|
||||
.then(() => {
|
||||
uni.showToast({ title: '已设为默认', icon: 'success' })
|
||||
loadAccounts()
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const msg = err?.data?.message || '操作失败'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
})
|
||||
}
|
||||
|
||||
function openAddPopup() {
|
||||
form.value = { holderName: '', bankName: '', cardNo: '' }
|
||||
addPopupVisible.value = true
|
||||
}
|
||||
|
||||
function saveAccount() {
|
||||
if (!form.value.holderName.trim()) {
|
||||
uni.showToast({ title: '请输入姓名', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (!form.value.bankName.trim()) {
|
||||
uni.showToast({ title: '请输入银行名称', icon: 'none' })
|
||||
return
|
||||
}
|
||||
if (form.value.cardNo.trim().length === 0) {
|
||||
uni.showToast({ title: '请输入银行卡号', icon: 'none' })
|
||||
return
|
||||
}
|
||||
saveLoading.value = true
|
||||
addBankCard({
|
||||
holderName: form.value.holderName.trim(),
|
||||
bankName: form.value.bankName.trim(),
|
||||
cardNo: form.value.cardNo.trim(),
|
||||
})
|
||||
.then((res: any) => {
|
||||
const newCard = res?.data?.result
|
||||
addPopupVisible.value = false
|
||||
uni.showToast({ title: '添加成功', icon: 'success' })
|
||||
loadAccounts()
|
||||
// 如果是选择模式,自动选中刚添加的卡
|
||||
if (isSelectMode.value && newCard?.id) {
|
||||
selectedId.value = newCard.id
|
||||
uni.setStorageSync(SELECTED_KEY, newCard.id)
|
||||
}
|
||||
})
|
||||
.catch((err: any) => {
|
||||
const msg = err?.data?.message || '添加失败,请重试'
|
||||
uni.showToast({ title: msg, icon: 'none' })
|
||||
})
|
||||
.finally(() => {
|
||||
saveLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function maskCard(cardNo: string) {
|
||||
if (!cardNo || cardNo.length < 4) return cardNo
|
||||
return '****' + cardNo.slice(-4)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
}
|
||||
|
||||
.content { padding: 24rpx; }
|
||||
|
||||
/* ── 状态 ──────────────────────────────────── */
|
||||
.state-wrap {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
.state-text { color: #999; font-size: 26rpx; }
|
||||
|
||||
/* ── 账户列表 ──────────────────────────────── */
|
||||
.account-list { margin-bottom: 24rpx; }
|
||||
|
||||
.account-item {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx 24rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.item-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20rpx;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.radio-circle {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #ddd;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
&.active { border-color: var(--theme-primary); }
|
||||
}
|
||||
|
||||
.radio-dot {
|
||||
width: 22rpx;
|
||||
height: 22rpx;
|
||||
border-radius: 50%;
|
||||
background: var(--theme-primary);
|
||||
}
|
||||
|
||||
.item-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8rpx;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.item-name { font-size: 28rpx; font-weight: 500; color: #333; }
|
||||
|
||||
.item-meta { display: flex; align-items: center; gap: 12rpx; }
|
||||
|
||||
.item-bank { font-size: 24rpx; color: #666; }
|
||||
.item-card { font-size: 24rpx; color: #999; }
|
||||
|
||||
.default-tag {
|
||||
font-size: 20rpx;
|
||||
color: var(--theme-primary);
|
||||
border: 1rpx solid var(--theme-primary);
|
||||
border-radius: 6rpx;
|
||||
padding: 2rpx 10rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.item-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.set-default-btn { padding: 4rpx 0; }
|
||||
.set-default-text { font-size: 22rpx; color: #999; }
|
||||
|
||||
.item-delete { padding: 10rpx; }
|
||||
|
||||
/* ── 空状态 ────────────────────────────────── */
|
||||
.empty-wrap { padding: 120rpx 0; text-align: center; }
|
||||
.empty-text { font-size: 26rpx; color: #ccc; }
|
||||
|
||||
/* ── 添加按钮 ──────────────────────────────── */
|
||||
.add-btn-wrap { padding-bottom: 40rpx; }
|
||||
|
||||
.add-btn {
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
border-radius: 16rpx;
|
||||
border: 2rpx solid var(--theme-primary);
|
||||
color: var(--theme-primary);
|
||||
font-size: 30rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ── 弹窗 ──────────────────────────────────── */
|
||||
.popup-wrap { padding: 32rpx 28rpx 48rpx; }
|
||||
|
||||
.popup-title {
|
||||
text-align: center;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.form-item { display: flex; align-items: center; padding: 18rpx 0; }
|
||||
|
||||
.form-label { font-size: 28rpx; color: #333; width: 100rpx; flex-shrink: 0; }
|
||||
|
||||
.form-input { flex: 1; font-size: 28rpx; color: #333; height: 64rpx; }
|
||||
|
||||
.form-placeholder { color: #ccc; font-size: 26rpx; }
|
||||
|
||||
.form-divider { height: 1rpx; background: #f5f5f5; }
|
||||
|
||||
.popup-actions { display: flex; gap: 20rpx; margin-top: 32rpx; }
|
||||
|
||||
.popup-btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
text-align: center;
|
||||
border-radius: 999rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.popup-btn.ghost { color: #666; background: #f5f5f5; }
|
||||
.popup-btn.primary { color: #fff; background: var(--theme-primary); }
|
||||
.popup-btn.disabled { opacity: 0.5; }
|
||||
</style>
|
||||
32
pages/mine/distribution/bind.vue
Normal file
32
pages/mine/distribution/bind.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<view class="bind-page"></view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import {
|
||||
resolveDistributionIdFromScene,
|
||||
tryBindDistribution,
|
||||
} from '@/utils/distributionBind.js'
|
||||
|
||||
onLoad(async (options) => {
|
||||
let distributionId = (options?.distributionId as string) || ''
|
||||
|
||||
if (options?.scene) {
|
||||
distributionId = await resolveDistributionIdFromScene(String(options.scene))
|
||||
}
|
||||
|
||||
if (distributionId) {
|
||||
await tryBindDistribution(distributionId)
|
||||
}
|
||||
|
||||
uni.switchTab({ url: '/pages/tabbar/home/index' })
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.bind-page {
|
||||
min-height: 100vh;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
210
pages/mine/distribution/cash-history.vue
Normal file
210
pages/mine/distribution/cash-history.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<template>
|
||||
<view class="page" :style="themeStyle">
|
||||
<view v-if="cashLoading && !cashList.length" class="state-wrap">
|
||||
<text class="state-text">加载中...</text>
|
||||
</view>
|
||||
<view v-else-if="!cashList.length" class="state-wrap">
|
||||
<text class="state-text">暂无提现记录</text>
|
||||
</view>
|
||||
<view v-else class="log-list">
|
||||
<view class="log-item" v-for="item in cashList" :key="item.id">
|
||||
<view class="log-left">
|
||||
<view class="log-sn">单号:{{ item.sn || item.id }}</view>
|
||||
<view class="log-time">{{ formatTime(item.createTime) }}</view>
|
||||
</view>
|
||||
<view class="log-right">
|
||||
<text class="log-amount log-amount-out">-{{ formatMoney(item.price) }}</text>
|
||||
<view class="cash-status-tag" :class="cashStatusClass(item.distributionCashStatus)">
|
||||
{{ cashStatusLabel(item.distributionCashStatus) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list-footer">
|
||||
<text v-if="cashFinished && cashList.length" class="footer-text">没有更多数据了</text>
|
||||
<text v-else-if="cashLoadingMore" class="footer-text">加载中...</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { useStore } from '@/store'
|
||||
import { getThemeStyle } from '@/utils/theme'
|
||||
import { cashLog } from '@/api/distribution'
|
||||
import { assertActiveDistributor } from '@/utils/distributionStatus'
|
||||
|
||||
const store = useStore()
|
||||
const themeStyle = computed(() => getThemeStyle(store.state.theme))
|
||||
|
||||
const cashList = ref<any[]>([])
|
||||
const cashPage = ref(1)
|
||||
const cashTotal = ref(0)
|
||||
const cashLoading = ref(true)
|
||||
const cashLoadingMore = ref(false)
|
||||
const cashFinished = ref(false)
|
||||
const PAGE_SIZE = 15
|
||||
|
||||
onLoad(() => {
|
||||
assertActiveDistributor().then((d) => {
|
||||
if (!d) {
|
||||
return
|
||||
}
|
||||
loadCashHistory(true)
|
||||
})
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
loadCashHistory(false)
|
||||
})
|
||||
|
||||
function loadCashHistory(reset: boolean) {
|
||||
if (!reset && (cashLoading.value || cashLoadingMore.value || cashFinished.value)) return
|
||||
if (reset) {
|
||||
cashLoading.value = true
|
||||
cashPage.value = 1
|
||||
cashFinished.value = false
|
||||
} else {
|
||||
cashLoadingMore.value = true
|
||||
}
|
||||
cashLog({ pageNumber: cashPage.value, pageSize: PAGE_SIZE, sort: 'createTime', order: 'desc' })
|
||||
.then((res: any) => {
|
||||
const result = res?.data?.result || {}
|
||||
const records: any[] = result.records || []
|
||||
cashTotal.value = Number(result.total || 0)
|
||||
cashList.value = reset ? records : [...cashList.value, ...records]
|
||||
if (records.length < PAGE_SIZE || cashList.value.length >= cashTotal.value) {
|
||||
cashFinished.value = true
|
||||
} else {
|
||||
cashPage.value += 1
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
cashLoading.value = false
|
||||
cashLoadingMore.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function formatMoney(val: number | string) {
|
||||
return Number(val || 0).toFixed(2)
|
||||
}
|
||||
|
||||
function formatTime(value?: string) {
|
||||
if (!value) return '-'
|
||||
return String(value).replace('T', ' ').slice(0, 19)
|
||||
}
|
||||
|
||||
const CASH_STATUS_MAP: Record<string, { label: string; cls: string }> = {
|
||||
APPLY: { label: '审核中', cls: 'status-pending' },
|
||||
VIA_AUDITING: { label: '已通过', cls: 'status-success' },
|
||||
FAIL_AUDITING: { label: '已驳回', cls: 'status-fail' },
|
||||
}
|
||||
|
||||
function cashStatusLabel(status: string) {
|
||||
return CASH_STATUS_MAP[status]?.label || status
|
||||
}
|
||||
|
||||
function cashStatusClass(status: string) {
|
||||
return CASH_STATUS_MAP[status]?.cls || ''
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f6f8;
|
||||
padding: 16rpx 24rpx 40rpx;
|
||||
}
|
||||
|
||||
.state-wrap {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.state-text {
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.log-list {
|
||||
padding-top: 16rpx;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.log-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.log-sn {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
max-width: 360rpx;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.log-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.log-amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.log-amount-out {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.cash-status-tag {
|
||||
font-size: 22rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
background: #fff8e6;
|
||||
color: #e6a23c;
|
||||
}
|
||||
|
||||
.status-success {
|
||||
background: #e8f8ec;
|
||||
color: #3aaa52;
|
||||
}
|
||||
|
||||
.status-fail {
|
||||
background: #fef0f0;
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.list-footer {
|
||||
padding: 12rpx 0 32rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.footer-text {
|
||||
color: #ccc;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user