Compare commits

..

191 Commits

Author SHA1 Message Date
5e10552d41 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-09 10:55:16 +08:00
0f6725262b bugfix: 保存按钮错乱问题 2026-06-09 10:55:15 +08:00
李志鹏
006fa85306 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-09 10:37:20 +08:00
李志鹏
eb153825c3 111 2026-06-09 10:37:18 +08:00
73dbd34e0a bugfix: 截图框占满容器 2026-06-08 11:39:49 +08:00
7814e870f3 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-08 10:12:17 +08:00
46d1b0e381 chore: 更新依赖,解决头像裁剪弹窗图片位置漂移 2026-06-08 10:12:15 +08:00
X1627315083@163.com
35c3c3aed4 fix 2026-06-04 15:53:41 +08:00
c9a848a5be Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-04 15:37:31 +08:00
4747489d84 feat: 发票未生成提示信息 2026-06-04 15:37:29 +08:00
李志鹏
5e39f5d2af 更换支付按钮icon 2026-06-04 15:36:52 +08:00
李志鹏
e3607e43ac 支付页面去掉发票 2026-06-04 15:24:42 +08:00
6e879123ab bugfix: 保存时头像链接被清空 2026-06-04 14:35:41 +08:00
437b191b12 feat: 头像更细 2026-06-04 14:20:44 +08:00
aa672194ea feat: 头像显示 2026-06-04 14:16:19 +08:00
8ab14e88f1 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-04 09:54:40 +08:00
275d4ad0d0 feat: 完成付款&再次购买 2026-06-04 09:54:38 +08:00
李志鹏
d789edcc80 401提示多语言 2026-06-04 09:41:14 +08:00
李志鹏
8cc646f4b8 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-03 13:18:57 +08:00
李志鹏
e64dbd8925 支付设置最小高度 2026-06-03 13:18:56 +08:00
bcb04aedd7 feat: 修改语言 2026-06-03 13:14:12 +08:00
1c80ba9fbc feat: 设置页监听语言变化重新获取信息 2026-06-03 09:53:23 +08:00
6b214d2abc Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-02 17:34:15 +08:00
fb767218c4 feat: 头像上传弹窗 2026-06-02 17:34:13 +08:00
29400bb23b feat: 头像上传 2026-06-02 17:33:57 +08:00
李志鹏
aa7f6724b6 购物车标题溢出隐藏 2026-06-02 17:21:29 +08:00
李志鹏
4aba2fc0a4 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-02 15:39:23 +08:00
李志鹏
33d126bac9 店铺icon 2026-06-02 15:39:19 +08:00
X1627315083@163.com
e68fb8afc7 fix 2026-06-02 15:06:15 +08:00
李志鹏
2c5ef21edb 默认图 2026-06-02 14:44:06 +08:00
X1627315083@163.com
15dd98a555 图片添加空状态 2026-06-02 14:31:02 +08:00
X1627315083@163.com
792db5bff9 fix 2026-06-02 14:27:07 +08:00
李志鹏
310e514206 主页item图片,底部url 2026-06-02 11:35:06 +08:00
087f8f1096 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 16:57:28 +08:00
480c1baa4a bugfix: 我的资产列表 2026-06-01 16:57:27 +08:00
X1627315083@163.com
cf0791dec7 增加添加购物车校验,状态有已下单未支付 已支付 2026-06-01 16:37:38 +08:00
4f0d17ea29 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 16:17:06 +08:00
240b006a8f feat: 设置页密码提示框 2026-06-01 16:17:04 +08:00
李志鹏
d3da32d4a6 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-01 16:07:15 +08:00
李志鹏
1c26f6ae28 登录密码校验 2026-06-01 16:07:14 +08:00
X1627315083@163.com
7e0abc7d16 去掉购物车的背景图,商品名字过长变为省略号 2026-06-01 15:52:32 +08:00
李志鹏
ff36a56002 支付跳转隐私政策 2026-06-01 15:34:01 +08:00
44dea76108 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 15:22:17 +08:00
8321871e18 bugfix: sellerId 2026-06-01 15:22:14 +08:00
李志鹏
40132cebce 1 2026-06-01 15:05:46 +08:00
李志鹏
183e7e551e 111 2026-06-01 14:36:23 +08:00
X1627315083@163.com
7f08c52a98 页脚添加跳转地址 2026-06-01 14:16:13 +08:00
X1627315083@163.com
c004308f77 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 14:12:43 +08:00
X1627315083@163.com
c32bd02efe fix 2026-06-01 14:12:40 +08:00
李志鹏
277457d42a Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-01 11:39:45 +08:00
李志鹏
f52594efdd 支付 2026-06-01 11:39:23 +08:00
16347514b5 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 11:21:54 +08:00
8b0bcdc8ac feat: 筛选组件修改 2026-06-01 11:21:49 +08:00
李志鹏
edb8e7a4bf Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-01 11:18:36 +08:00
李志鹏
bf5c27d857 优化 2026-06-01 11:18:35 +08:00
X1627315083@163.com
a4494831a1 fix 2026-06-01 11:03:14 +08:00
ad0f4f65e5 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-06-01 10:44:34 +08:00
052764166f feat: hero背景&通知时间 2026-06-01 10:44:32 +08:00
X1627315083@163.com
fdc5f9d095 fix 2026-06-01 10:40:11 +08:00
李志鹏
965721bbdd Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-06-01 10:10:23 +08:00
李志鹏
88a5618976 支付 2026-06-01 10:10:22 +08:00
6c4ebc26d1 feat: ws连接 2026-06-01 09:35:11 +08:00
6ac5eb9664 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-31 09:51:59 +08:00
74b6045220 feat: 消息通知 2026-05-31 09:51:57 +08:00
X1627315083@163.com
d90b788155 fix 2026-05-29 16:35:14 +08:00
X1627315083@163.com
cc100caa38 fix 2026-05-29 15:31:14 +08:00
李志鹏
b66b8df8a3 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-29 15:30:14 +08:00
李志鹏
a0b31f9374 支付接口 2026-05-29 15:30:13 +08:00
1cc85f1c46 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-29 14:59:16 +08:00
484d15f91d feat: 重新付款 2026-05-29 14:59:14 +08:00
李志鹏
8209a1d678 11 2026-05-29 14:59:03 +08:00
李志鹏
b7250c51bf 123123 2026-05-29 14:55:54 +08:00
李志鹏
fe04ee3d1a 11 2026-05-29 14:25:24 +08:00
李志鹏
c3aebf85c1 支付 2026-05-29 13:52:28 +08:00
李志鹏
2f0f875080 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-28 15:15:21 +08:00
李志鹏
83cf98c8b0 1 2026-05-28 15:15:05 +08:00
a28636d25c Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-28 15:01:24 +08:00
b47f0b8296 feat: 下载接口超时时间设置为10分钟 2026-05-28 15:01:22 +08:00
李志鹏
2c29d2ad38 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-28 14:57:00 +08:00
李志鹏
fa73f949d5 支付页面 2026-05-28 14:56:59 +08:00
9948b18c26 bugfix: 设置页邮箱&资源下载 2026-05-28 14:20:23 +08:00
f203d6146c feat: 语言切换 2026-05-28 13:45:59 +08:00
e45fd1135c bugfix: 重复key 2026-05-28 13:10:06 +08:00
4e49584c60 bugfix: 传参 2026-05-28 13:08:01 +08:00
bc8e436c94 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-28 11:46:25 +08:00
881f25ac2b feat: security部分改为二次编辑 2026-05-28 11:46:23 +08:00
李志鹏
242c1ae8e8 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-28 11:43:52 +08:00
李志鹏
a55e6b3ea9 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-28 11:43:46 +08:00
X1627315083@163.com
7057a6c8f7 fix 2026-05-28 11:43:43 +08:00
李志鹏
7df8b38966 111 2026-05-28 11:43:24 +08:00
X1627315083@163.com
729755efdf fix 2026-05-28 11:34:07 +08:00
李志鹏
b8243128d6 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-28 11:31:38 +08:00
李志鹏
5ada85d05c 123 2026-05-28 11:31:37 +08:00
8f9511966e merge 2026-05-28 11:29:09 +08:00
248bf6b1eb Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-28 11:28:53 +08:00
e4f1c535a7 feat: 邮箱密码修改 2026-05-28 11:27:39 +08:00
X1627315083@163.com
8f23b4c387 fix 2026-05-28 11:07:21 +08:00
a4861da21a feat: 资料更新 2026-05-27 17:29:35 +08:00
X1627315083@163.com
d0224e5c6d fix 2026-05-27 16:33:25 +08:00
X1627315083@163.com
91889102ea detail布局调整 2026-05-27 16:31:08 +08:00
李志鹏
3da18cb246 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-27 13:06:28 +08:00
李志鹏
0b014f2a3b 语言适配 2026-05-27 13:06:08 +08:00
X1627315083@163.com
8a99048639 调整digital item布局 2026-05-27 11:49:10 +08:00
3e64912804 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-27 11:30:56 +08:00
dfe8099945 feat: order跳转卖家主页 2026-05-27 11:29:37 +08:00
X1627315083@163.com
fc5a361bba fix 2026-05-27 11:25:06 +08:00
X1627315083@163.com
95556b1a1f fix 2026-05-27 11:22:01 +08:00
李志鹏
3c39959ce3 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-27 11:16:42 +08:00
李志鹏
22ebbba451 aaa 2026-05-27 11:16:40 +08:00
X1627315083@163.com
92c625fb89 fix 2026-05-27 11:16:15 +08:00
李志鹏
a0fa8cd523 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-27 11:06:02 +08:00
c263f1cffc feat: 下载按钮显示 2026-05-27 11:05:30 +08:00
李志鹏
1ab121a703 语言适配 2026-05-27 11:04:51 +08:00
b8662fd379 bugfix: 订单状态 2026-05-27 10:53:37 +08:00
X1627315083@163.com
bcbaa47926 fix 2026-05-27 10:49:29 +08:00
李志鹏
a4441c6107 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-27 09:29:48 +08:00
李志鹏
d0dc9b2af0 交互 2026-05-27 09:29:47 +08:00
ec4c3bc81f feat: 我的衣柜筛选 2026-05-26 15:24:10 +08:00
dc6658e1dd Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-26 10:55:32 +08:00
0d10954251 feat: order页面空状态 2026-05-26 10:55:28 +08:00
X1627315083@163.com
f402c7b2aa fix 2026-05-26 10:22:06 +08:00
X1627315083@163.com
0e28f089dc digitallItem语言适配 2026-05-26 10:19:01 +08:00
X1627315083@163.com
ab556a17e7 footer collectionstory语言适配 2026-05-26 10:01:07 +08:00
X1627315083@163.com
9b86a9f65e ifx 2026-05-22 14:35:43 +08:00
李志鹏
49cec0ee28 退出登录 2026-05-22 13:53:07 +08:00
李志鹏
ecf53c8353 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-22 13:36:53 +08:00
李志鹏
c8fa11aa25 111 2026-05-22 13:36:51 +08:00
c576d050db Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-22 13:20:18 +08:00
2a64226f35 feat: 筛选时获取数据 2026-05-22 13:20:17 +08:00
X1627315083@163.com
f6f62fde8e 增加卖家默认头像默认背景 2026-05-22 13:04:33 +08:00
99f4515e9d Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-22 11:39:21 +08:00
4c22311e08 feat: wardrobe assets 2026-05-22 11:39:17 +08:00
X1627315083@163.com
3ae774bc6b digital item页面 2026-05-22 11:11:38 +08:00
李志鹏
2d95c3c976 1 2026-05-22 10:19:22 +08:00
李志鹏
3de38d2856 更改loading样式 2026-05-21 15:13:18 +08:00
李志鹏
2f1cc8d55d 1 2026-05-21 14:58:52 +08:00
李志鹏
5d0de56c1a Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-21 14:58:08 +08:00
李志鹏
1b32002af5 创建订单 2026-05-21 14:58:07 +08:00
X1627315083@163.com
59803cf2ea fix 2026-05-21 14:56:37 +08:00
d772cae6bc feat: 创建订单接口 2026-05-21 14:55:31 +08:00
X1627315083@163.com
8c080d3e22 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-21 14:36:33 +08:00
李志鹏
4be692bb29 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-21 14:36:30 +08:00
X1627315083@163.com
7bfae7d024 fix 2026-05-21 14:36:29 +08:00
李志鹏
e7957532e8 购物车 2026-05-21 14:36:29 +08:00
338ee24da2 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-21 13:44:58 +08:00
81b907562e feat: 服装分类 2026-05-21 13:44:57 +08:00
李志鹏
31de24cc2b 购物车 2026-05-21 11:46:04 +08:00
李志鹏
c18b424f99 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-21 11:36:15 +08:00
李志鹏
b9be27ab85 aaa 2026-05-21 11:36:14 +08:00
5476a1f69d Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-21 11:33:32 +08:00
9f620ab9d5 feat: 我的衣柜接口 2026-05-21 11:32:52 +08:00
X1627315083@163.com
e418bf80ad brand对接接口 2026-05-21 11:25:11 +08:00
李志鹏
2346e079a1 登录、退出登录 2026-05-21 11:04:12 +08:00
李志鹏
6772bf0e90 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-21 10:48:55 +08:00
李志鹏
26dfbd9bb5 接口配置 2026-05-21 10:48:53 +08:00
X1627315083@163.com
8c8ec7846d 调整添加购物车逻辑 2026-05-12 17:02:49 +08:00
X1627315083@163.com
8d441766c5 fix 2026-05-12 14:13:56 +08:00
X1627315083@163.com
bf907a1378 digital空状态 2026-05-12 13:35:53 +08:00
李志鹏
de6295f2af Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-12 13:32:48 +08:00
李志鹏
3e0a7b8928 导航栏激活 2026-05-12 13:32:47 +08:00
X1627315083@163.com
09909552bc digital 过滤调整 2026-05-12 13:27:30 +08:00
李志鹏
baf57515c0 登录还原 2026-05-12 11:10:42 +08:00
X1627315083@163.com
b8c844363c brand页面交互调整 2026-05-11 16:16:59 +08:00
李志鹏
87b071c319 22 2026-05-11 14:34:37 +08:00
李志鹏
bdc824e1f6 11 2026-05-11 14:22:52 +08:00
李志鹏
1c7b2d32a6 空列表 2026-05-11 14:21:42 +08:00
李志鹏
cffd554365 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-11 13:56:12 +08:00
李志鹏
33043eedf1 home修改 2026-05-11 13:56:10 +08:00
ce35f788ca feat: 商品item组件 2026-05-11 13:15:31 +08:00
5bbdeb6236 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-05-11 11:29:00 +08:00
93813f7b56 chore: setting页面拆分 2026-05-11 11:28:59 +08:00
李志鹏
51e6933f9f Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-05-11 10:51:10 +08:00
李志鹏
c0b4742966 11 2026-05-11 10:51:00 +08:00
f76c9ed828 feat: 设置页去掉社媒填写 2026-05-11 10:39:34 +08:00
d50e3781db feat: 设置页i18n 2026-05-05 09:59:44 +08:00
e48e369ef1 feat: 验证码输入弹窗 2026-05-04 17:06:04 +08:00
bb8344b27a feat: account页面 2026-05-04 15:53:13 +08:00
b538853800 feat: 页脚 2026-04-24 14:50:28 +08:00
f772e3d250 style: 订单子列表样式 2026-04-24 14:36:52 +08:00
f5a7ad51f0 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-04-24 14:13:15 +08:00
8df8618f40 feat: wardrobe orders 2026-04-24 14:04:01 +08:00
李志鹏
f355835819 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-04-24 09:38:25 +08:00
李志鹏
42c2817c2f 分离 2026-04-24 09:38:24 +08:00
9bf0211e78 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-04-23 17:29:17 +08:00
281272c9f9 feat: wardrobe assets页面 2026-04-23 17:29:15 +08:00
李志鹏
71cfef996d 更改字节转换方法 2026-04-23 17:03:45 +08:00
李志鹏
09125378b6 gogogo 2026-04-23 16:35:07 +08:00
李志鹏
e8da956543 Merge branch 'main' of http://18.167.251.121:10003/aidlab/Aida_Purchaser_Front 2026-04-23 16:11:14 +08:00
李志鹏
ad81feaf81 购物车 2026-04-23 16:11:13 +08:00
254f61e524 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/Aida_Purchaser_Front 2026-04-23 15:20:46 +08:00
3512d9ae87 feat: wardrobe页面 2026-04-23 15:20:22 +08:00
151 changed files with 10569 additions and 2836 deletions

View File

@@ -1,3 +1,4 @@
# VITE_APP_URL = http://192.168.31.82:8771 # VITE_APP_URL = http://192.168.31.82:10094
# VITE_APP_URL = http://18.167.251.121:10095 VITE_APP_URL = https://www.develop-ms.api.aida.com.hk
VITE_APP_URL = https://www.lc-api.aida.com.hk # WebSocket 主机地址
VITE_WS_HOST = 18.167.251.121:10094

View File

@@ -1,2 +1,4 @@
VITE_APP_URL = https://www.lc-api.aida.com.hk VITE_APP_URL = http://192.168.31.82:10094
# VITE_APP_URL = http://18.167.251.121:10095 VITE_APP_URL = https://www.develop-ms.api.aida.com.hk
# WebSocket 主机地址
VITE_WS_HOST = www.develop-ms.api.aida.com.hk

View File

@@ -1,8 +1,10 @@
{ {
"$schema": "https://json.schemastore.org/prettierrc", "$schema": "https://json.schemastore.org/prettierrc",
"semi": false, "semi": false,
"tabWidth": 2, "tabWidth": 4,
"singleQuote": true, "singleQuote": true,
"printWidth": 100, "printWidth": 100,
"trailingComma": "none" "useTabs": true,
"trailingComma": "none",
"vueIndentScriptAndStyle": true
} }

View File

@@ -7,14 +7,14 @@
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> --> <!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> -->
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=0" />
<link rel="stylesheet" href="/fonts/index.css"> <link rel="stylesheet" href="/fonts/index.css">
<title>Lane Crawford</title> <title>Stylish Parade</title>
<!-- Open Graph / WhatsApp share metadata --> <!-- Open Graph / WhatsApp share metadata -->
<meta property="og:title" content="Lane Crawford" /> <meta property="og:title" content="Stylish Parade" />
<meta property="og:description" content="create and share looks from the Lane Crawford creation gallery." /> <meta property="og:description" content="create and share looks from the Stylish Parade creation gallery." />
<meta property="og:image" content="" /> <meta property="og:image" content="" />
<meta property="og:url" content="https://www.lc.aida.com.hk" /> <meta property="og:url" content="https://www.lc.aida.com.hk" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:site_name" content="Lane Crawford" /> <meta property="og:site_name" content="Stylish Parade" />
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
</head> </head>
<body> <body>

356
package-lock.json generated
View File

@@ -10,9 +10,11 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"axios": "^1.3.6", "axios": "^1.3.6",
"cropper-next-vue": "^0.3.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"element-plus": "^2.13.2", "element-plus": "^2.13.2",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"lodash-es": "^4.18.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"md5": "^2.3.0", "md5": "^2.3.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
@@ -20,7 +22,7 @@
"pinia-persistedstate-plugin": "^0.1.0", "pinia-persistedstate-plugin": "^0.1.0",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
"swiper": "^12.1.3", "swiper": "^12.1.3",
"vue": "^3.2.47", "vue": "^3.5.35",
"vue-i18n": "^11.2.8", "vue-i18n": "^11.2.8",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
@@ -57,30 +59,30 @@
"dev": true "dev": true
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.29.0", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.29.0" "@babel/types": "^7.29.7"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -90,13 +92,13 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.29.0", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.28.5" "@babel/helper-validator-identifier": "^7.29.7"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -1161,21 +1163,21 @@
} }
}, },
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz",
"integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.3",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"entities": "^7.0.0", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-core/node_modules/entities": { "node_modules/@vue/compiler-core/node_modules/entities": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
@@ -1186,40 +1188,40 @@
} }
}, },
"node_modules/@vue/compiler-dom": { "node_modules/@vue/compiler-dom": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz",
"integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-core": "3.5.27", "@vue/compiler-core": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"node_modules/@vue/compiler-sfc": { "node_modules/@vue/compiler-sfc": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz",
"integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.3",
"@vue/compiler-core": "3.5.27", "@vue/compiler-core": "3.5.35",
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/compiler-ssr": "3.5.27", "@vue/compiler-ssr": "3.5.35",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"postcss": "^8.5.6", "postcss": "^8.5.15",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"node_modules/@vue/compiler-ssr": { "node_modules/@vue/compiler-ssr": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz",
"integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"node_modules/@vue/devtools-api": { "node_modules/@vue/devtools-api": {
@@ -1266,53 +1268,53 @@
} }
}, },
"node_modules/@vue/reactivity": { "node_modules/@vue/reactivity": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz",
"integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"node_modules/@vue/runtime-core": { "node_modules/@vue/runtime-core": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz",
"integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.27", "@vue/reactivity": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"node_modules/@vue/runtime-dom": { "node_modules/@vue/runtime-dom": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz",
"integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/reactivity": "3.5.27", "@vue/reactivity": "3.5.35",
"@vue/runtime-core": "3.5.27", "@vue/runtime-core": "3.5.35",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"csstype": "^3.2.3" "csstype": "^3.2.3"
} }
}, },
"node_modules/@vue/server-renderer": { "node_modules/@vue/server-renderer": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz",
"integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-ssr": "3.5.27", "@vue/compiler-ssr": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
}, },
"peerDependencies": { "peerDependencies": {
"vue": "3.5.27" "vue": "3.5.35"
} }
}, },
"node_modules/@vue/shared": { "node_modules/@vue/shared": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz",
"integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@vue/tsconfig": { "node_modules/@vue/tsconfig": {
@@ -2152,6 +2154,18 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/cropper-next-vue": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cropper-next-vue/-/cropper-next-vue-0.3.1.tgz",
"integrity": "sha512-B80WRLPavJ/xAjDEofPWLEqnMy99yzXy+Kpak3wMFf+X/TqebvhvBL3fM0P5ZHLRq0v9k0qSAqFjukBhHc3XZA==",
"license": "ISC",
"engines": {
"node": ">=22.0.0"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -2312,7 +2326,7 @@
}, },
"node_modules/csstype": { "node_modules/csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"license": "MIT" "license": "MIT"
}, },
@@ -4790,9 +4804,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash-es": { "node_modules/lodash-es": {
"version": "4.17.23", "version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lodash-unified": { "node_modules/lodash-unified": {
@@ -5213,9 +5227,9 @@
"dev": true "dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.11", "version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -6038,9 +6052,9 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.5.6", "version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@@ -6057,7 +6071,7 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.3.11", "nanoid": "^3.3.12",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
@@ -8390,16 +8404,16 @@
"dev": true "dev": true
}, },
"node_modules/vue": { "node_modules/vue": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.27.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz",
"integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/compiler-sfc": "3.5.27", "@vue/compiler-sfc": "3.5.35",
"@vue/runtime-dom": "3.5.27", "@vue/runtime-dom": "3.5.35",
"@vue/server-renderer": "3.5.27", "@vue/server-renderer": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": "*" "typescript": "*"
@@ -8733,30 +8747,30 @@
"dev": true "dev": true
}, },
"@babel/helper-string-parser": { "@babel/helper-string-parser": {
"version": "7.27.1", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==" "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="
}, },
"@babel/helper-validator-identifier": { "@babel/helper-validator-identifier": {
"version": "7.28.5", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==" "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.29.0", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"requires": { "requires": {
"@babel/types": "^7.29.0" "@babel/types": "^7.29.7"
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.29.0", "version": "7.29.7",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.0.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"requires": { "requires": {
"@babel/helper-string-parser": "^7.27.1", "@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.28.5" "@babel/helper-validator-identifier": "^7.29.7"
} }
}, },
"@ctrl/tinycolor": { "@ctrl/tinycolor": {
@@ -9427,56 +9441,56 @@
} }
}, },
"@vue/compiler-core": { "@vue/compiler-core": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.35.tgz",
"integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", "integrity": "sha512-BUmHaR1J+O+CKZ9uJucdVTEr1LHsdyvv7vG3eNRhK3CczEHeMd/LtsHAuD7PbrxvI2envCY2v7HI1vC1aBRzKw==",
"requires": { "requires": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.3",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"entities": "^7.0.0", "entities": "^7.0.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
}, },
"dependencies": { "dependencies": {
"entities": { "entities": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==" "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="
} }
} }
}, },
"@vue/compiler-dom": { "@vue/compiler-dom": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.35.tgz",
"integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", "integrity": "sha512-k+bprkXxuqhVajgTx5mUHuir7TwQzUKOWR40ng1ncAqQRPnrLngGGgqVEEhOnTMlc8btHYVKmrP8s5Qyg0hvYA==",
"requires": { "requires": {
"@vue/compiler-core": "3.5.27", "@vue/compiler-core": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"@vue/compiler-sfc": { "@vue/compiler-sfc": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.35.tgz",
"integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", "integrity": "sha512-G5VPMcXTSywXBgtFOZOnHKBxKSrwXUcvY1iaF5/hRcy7t0J6CH/d8ha9F4nzi00Fax1eLV0QHM7v4mQu68jydw==",
"requires": { "requires": {
"@babel/parser": "^7.28.5", "@babel/parser": "^7.29.3",
"@vue/compiler-core": "3.5.27", "@vue/compiler-core": "3.5.35",
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/compiler-ssr": "3.5.27", "@vue/compiler-ssr": "3.5.35",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"magic-string": "^0.30.21", "magic-string": "^0.30.21",
"postcss": "^8.5.6", "postcss": "^8.5.15",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
}, },
"@vue/compiler-ssr": { "@vue/compiler-ssr": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.35.tgz",
"integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", "integrity": "sha512-rGhAeXgdM7/ffTJGXT69rCCdTmjDewnFuUZfBQQHTdcEBeWdT5HCGY60y2ytLJr9/Dsu7IntUi5z/w0h6Rjnzw==",
"requires": { "requires": {
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"@vue/devtools-api": { "@vue/devtools-api": {
@@ -9506,46 +9520,46 @@
} }
}, },
"@vue/reactivity": { "@vue/reactivity": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.35.tgz",
"integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", "integrity": "sha512-tVc+SsHConvh/Lz64qq1pP3rYArBmK42xonovEcxY74SQtvctZodG/zhq54P5dr38cVuw25d27cPNRdlMidpGQ==",
"requires": { "requires": {
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"@vue/runtime-core": { "@vue/runtime-core": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.35.tgz",
"integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", "integrity": "sha512-A/xFNX9loIcWDygeQuNCfKuh0CoYBzxhqEMNah5TSFg9Z53DrFYEN2qi5CU9necjM1OWYegYREUTHmXTmhfXtg==",
"requires": { "requires": {
"@vue/reactivity": "3.5.27", "@vue/reactivity": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"@vue/runtime-dom": { "@vue/runtime-dom": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.35.tgz",
"integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", "integrity": "sha512-odrJ1C391dbGnyDRh8U+rnP7J2amIEzfmRk5vXy7xi3aZhEXofTvpi0T4HJb6jlNqQZTNPR5MPHSB3RHNkIORA==",
"requires": { "requires": {
"@vue/reactivity": "3.5.27", "@vue/reactivity": "3.5.35",
"@vue/runtime-core": "3.5.27", "@vue/runtime-core": "3.5.35",
"@vue/shared": "3.5.27", "@vue/shared": "3.5.35",
"csstype": "^3.2.3" "csstype": "^3.2.3"
} }
}, },
"@vue/server-renderer": { "@vue/server-renderer": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.35.tgz",
"integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", "integrity": "sha512-NkebSOYdB97wi8OQcO3HqzZSlymJi/aWsN/7h74OSVhRTm6qGs3Jp3e0rCXynmWwSlKeRrnlIug+ilYoHBmQDA==",
"requires": { "requires": {
"@vue/compiler-ssr": "3.5.27", "@vue/compiler-ssr": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"@vue/shared": { "@vue/shared": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.27.tgz", "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.35.tgz",
"integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==" "integrity": "sha512-zSbjL7gRXwks2ZQLRGCajBtBXEOXW9Ddhn/HvSdrGkE2dqGnumzW8XtusRrxrE9LvqtiqDXQ+A60Hp6mvdYxfA=="
}, },
"@vue/tsconfig": { "@vue/tsconfig": {
"version": "0.1.3", "version": "0.1.3",
@@ -10147,6 +10161,12 @@
"vary": "^1" "vary": "^1"
} }
}, },
"cropper-next-vue": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/cropper-next-vue/-/cropper-next-vue-0.3.1.tgz",
"integrity": "sha512-B80WRLPavJ/xAjDEofPWLEqnMy99yzXy+Kpak3wMFf+X/TqebvhvBL3fM0P5ZHLRq0v9k0qSAqFjukBhHc3XZA==",
"requires": {}
},
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -10259,7 +10279,7 @@
}, },
"csstype": { "csstype": {
"version": "3.2.3", "version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="
}, },
"data-view-buffer": { "data-view-buffer": {
@@ -12111,9 +12131,9 @@
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==" "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
}, },
"lodash-es": { "lodash-es": {
"version": "4.17.23", "version": "4.18.1",
"resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==" "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="
}, },
"lodash-unified": { "lodash-unified": {
"version": "1.0.3", "version": "1.0.3",
@@ -12438,9 +12458,9 @@
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
"version": "3.3.11", "version": "3.3.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==" "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="
}, },
"nanomatch": { "nanomatch": {
"version": "1.2.13", "version": "1.2.13",
@@ -13050,11 +13070,11 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "8.5.6", "version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
"requires": { "requires": {
"nanoid": "^3.3.11", "nanoid": "^3.3.12",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"source-map-js": "^1.2.1" "source-map-js": "^1.2.1"
} }
@@ -14816,15 +14836,15 @@
} }
}, },
"vue": { "vue": {
"version": "3.5.27", "version": "3.5.35",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.27.tgz", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.35.tgz",
"integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", "integrity": "sha512-cx89fnr+0kVGHiNFG6y6s0bdjypJRFNZn6x3WPstNdQR1bi1mbB7h4v5IBGTsPJU3nK1+0Iqj3Zf+hZWMieR4Q==",
"requires": { "requires": {
"@vue/compiler-dom": "3.5.27", "@vue/compiler-dom": "3.5.35",
"@vue/compiler-sfc": "3.5.27", "@vue/compiler-sfc": "3.5.35",
"@vue/runtime-dom": "3.5.27", "@vue/runtime-dom": "3.5.35",
"@vue/server-renderer": "3.5.27", "@vue/server-renderer": "3.5.35",
"@vue/shared": "3.5.27" "@vue/shared": "3.5.35"
} }
}, },
"vue-eslint-parser": { "vue-eslint-parser": {

View File

@@ -14,9 +14,11 @@
}, },
"dependencies": { "dependencies": {
"axios": "^1.3.6", "axios": "^1.3.6",
"cropper-next-vue": "^0.3.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"element-plus": "^2.13.2", "element-plus": "^2.13.2",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"lodash-es": "^4.18.1",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"md5": "^2.3.0", "md5": "^2.3.0",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
@@ -24,7 +26,7 @@
"pinia-persistedstate-plugin": "^0.1.0", "pinia-persistedstate-plugin": "^0.1.0",
"pinia-plugin-persistedstate": "^3.1.0", "pinia-plugin-persistedstate": "^3.1.0",
"swiper": "^12.1.3", "swiper": "^12.1.3",
"vue": "^3.2.47", "vue": "^3.5.35",
"vue-i18n": "^11.2.8", "vue-i18n": "^11.2.8",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

View File

@@ -7,14 +7,18 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, ref, onBeforeUnmount } from 'vue' import { computed, onMounted, ref, onBeforeUnmount, watch } from 'vue'
import RouteCache from '@/components/RouteCache.vue' import RouteCache from '@/components/RouteCache.vue'
import MainHeader from '@/views/main-header.vue' import MainHeader from '@/views/main-header.vue'
import LoginDialog from '@/views/login/login-dialog.vue' import LoginDialog from '@/views/login/login-dialog.vue'
import { useGlobalStore } from '@/stores' import { useGlobalStore, useUserInfoStore } from '@/stores'
import ShoppingDrawer from '@/views/shopping-drawer.vue' import ShoppingDrawer from '@/views/shopping-drawer.vue'
import { wsManager } from '@/utils/websocket'
const globalStore = useGlobalStore() const globalStore = useGlobalStore()
const userInfoStore = useUserInfoStore()
const loading = computed(() => globalStore.state.loading) const loading = computed(() => globalStore.state.loading)
globalStore.setLoading(false)
const viewRef = ref() const viewRef = ref()
const viewStyle = ref({ const viewStyle = ref({
'--app-view-width': '', '--app-view-width': '',
@@ -25,13 +29,41 @@
viewStyle.value['--app-view-width'] = width + 'px' viewStyle.value['--app-view-width'] = width + 'px'
viewStyle.value['--app-view-height'] = height + 'px' viewStyle.value['--app-view-height'] = height + 'px'
}) })
// 监听 token 变化,建立或关闭 WebSocket 连接
watch(
() => userInfoStore.state.token,
(newToken, oldToken) => {
if (newToken && newToken !== oldToken) {
// 用户登录,建立 WebSocket 连接
console.log('用户已登录,建立 WebSocket 连接')
wsManager.connect(newToken)
} else if (!newToken && oldToken) {
// 用户退出登录,关闭 WebSocket 连接
console.log('用户已退出,关闭 WebSocket 连接')
wsManager.close()
}
},
{ immediate: true }
)
onMounted(() => { onMounted(() => {
observer.observe(viewRef.value) observer.observe(viewRef.value)
console.log('onMounted')
// 如果已经有 token立即建立连接
const token = userInfoStore.state.token
if (token) {
console.log('应用启动时检测到 token建立 WebSocket 连接')
wsManager.connect(token)
}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
observer.disconnect() observer.disconnect()
// 组件卸载时关闭 WebSocket 连接
wsManager.close()
}) })
window['onClickPrivacy'] = () => { window['onClickPrivacy'] = () => {
const e = window.event || event const e = window.event || event
e.stopPropagation() e.stopPropagation()
@@ -53,10 +85,9 @@
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
// background-color: rgba(0, 0, 0, 0.3); --el-mask-color: rgba(0, 0, 0, 0.3);
// display: flex; --el-color-primary: #007bff;
// align-items: center; --el-loading-spinner-size: 9rem;
// justify-content: center;
} }
</style> </style>
<style lang="less" scoped> <style lang="less" scoped>

136
src/api/account.ts Normal file
View File

@@ -0,0 +1,136 @@
import request from '@/utils/request'
/**
* 登录发送验证码
* @param data - 包含邮箱的参数
* @param data.email - 邮箱
* @param data.password - 密码
*/
export const AccountSendLoginCode = (data) => {
return request({
url: '/buyer/account/preLogin',
method: 'post',
data,
loading: true
})
}
/**
* 登录
* @param data - 包含邮箱的参数
* @param data.email - 邮箱
* @param data.password - 密码
* @param data.emailVerifyCode - 验证码
*/
export const AccountLogin = (data) => {
return request({
url: '/buyer/account/login',
method: 'post',
data,
loading: true
})
}
/**
* 注册
* @param data - 包含注册信息的参数
* @param data.email - 邮箱
* @param data.password - 密码
* @param data.username - 用户名
* @param data.emailVerifyCode - 验证码
*/
export const AccountRegister = (data) => {
return request({
url: '/buyer/account/register',
method: 'post',
data,
loading: true
})
}
/**
* 注册||忘记密码:发送邮箱验证码
* @param data - 包含邮箱的参数
* @param data.email - 邮箱
* @param data.operationType - 操作类型FORGET_PWD, REGISTER
*/
export const AccountSendVerifyCode = (data) => {
return request({
url: '/buyer/account/sendCode',
method: 'post',
data,
loading: true
})
}
/**
* 忘记密码:重置密码
* @param data - 包含邮箱的参数
* @param data.email - 邮箱
* @param data.password - 密码
* @param data.emailVerifyCode - 验证码
*/
export const AccountResetPassword = (data) => {
return request({
url: '/buyer/account/resetPassword',
method: 'post',
data,
loading: true
})
}
/**
* 通用验证码校验
* @param data - 包含邮箱的参数
* @param data.email - 邮箱
* @param data.emailVerifyCode - 验证码
* @param data.operationType - 操作类型FORGET_PWD, BIND_MAILBOX
*/
export const AccountVerifyCode = (data) => {
return request({
url: '/buyer/account/verifyCode',
method: 'post',
data,
loading: true
})
}
/**
* 变更邮箱:发送新邮箱验证码
* @param data - 包含邮箱的参数
* @param data.oldEmail - 旧邮箱
* @param data.newEmail - 新邮箱
*/
export const AccountSendEmailChangeCode = (data) => {
return request({
url: '/buyer/account/sendEmailChangeCode',
method: 'post',
data,
loading: true
})
}
/**
* 变更邮箱:绑定新邮箱
* @param data - 包含邮箱的参数
* @param data.oldEmail - 旧邮箱
* @param data.newEmail - 新邮箱
* @param data.emailVerifyCode - 验证码
*/
export const AccountBindEmail = (data) => {
return request({
url: '/buyer/account/bindEmail',
method: 'post',
data,
loading: true
})
}
/**
* 退出登录
* @param data - 参数
* @param data.userId - 用户ID
*/
export const AccountLogout = (data) => {
return request({
url: '/buyer/account/logout',
method: 'post',
data,
loading: true
})
}

35
src/api/brand.ts Normal file
View File

@@ -0,0 +1,35 @@
import request from '@/utils/request'
/**
* 获取店铺列表
* @param data 获取店铺列表的参数
* @param data.keyword 模糊查询店铺
* @returns 获取店铺列表
*/
export interface designerListData {
keyword: string
}
export const getDesignerList = (data:designerListData,loading?:boolean) => {
return request({
url: '/buyer/designer/search',
method: 'get',
params: data,
loading
})
}
/**
* 获取店铺详情
* @param data 获取店铺详情的参数
* @param data.sellerId 店铺 id
* @returns 获取店铺详情
*/
export interface designerDetailData {
sellerId?: string
}
export const getDesignerDetail = (data:designerDetailData,loading?:boolean) => {
return request({
url: `/buyer/designer/shop/${data.sellerId}`,
method: 'get',
loading
})
}

70
src/api/listing.ts Normal file
View File

@@ -0,0 +1,70 @@
import request from '@/utils/request'
/**
* 获取店铺商品列表
* @param data 获取店铺商品列表的参数
* @param data.sellerId 店铺id
* @param data.designFor 查询类型 female/male/all
* @param data.pageNum 页码
* @param data.pageSize 页面大小
* @returns 获取店铺商品列表
*/
export interface listingListData {
sellerId?: string
designFor?: string
pageNum?: number
pageSize?: number
}
export const getlistingListApi = (data:listingListData,loading?:boolean) => {
return request({
url: '/buyer/listing/shop/seller',
method: 'get',
params: data,
loading
})
}
/**
* 获取商品详情
* @param data 获取商品详情的参数
* @param data.id 商品 id
* @returns 获取商品详情
*/
export interface listingDetailData {
id?: string
}
export const getListingDetailApi = (data:listingDetailData,loading?:boolean) => {
return request({
url: `/buyer/listing/mall/detail`,
method: 'get',
params: data,
loading
})
}
/**
* 获取资产数字商品列表
* @param data 获取资产数字商品列表的参数
* @param data.designFor 查询类型 female/male/all
* @param data.categories 商品分类
* @param data.sortField 排序字段 price/salesVolume/updateTime/viewCount/createTime默认 updateTime
* @param data.sortOrder 排序顺序asc/desc默认 desc
* @param data.pageNum 页码
* @param data.pageSize 页面大小
* @returns 获取资产数字商品列表
*/
export interface listingMallData {
designFor: string,
categories: string[],
sortField: string,
sortOrder: string,
pageNum: number,
pageSize: number
}
export const getListingMallListApi = (data:listingMallData,loading?:boolean) => {
return request({
url: `/buyer/listing/mall`,
method: 'post',
data,
loading
})
}

View File

@@ -1,77 +0,0 @@
import request from '@/utils/request'
interface LoginParamsType {
name?: string // 姓名
email: string // 邮箱
password?: string // 密码
operationType: 'REGISTER' | 'LOGIN' | 'FORGET_PWD'
verifyCode?: string // 验证码
}
// 发送验证码
export const precheckEmail = (params: { email: string }): Promise<ApiResponse> => {
return request({
url: '/api/auth/precheckEmail',
method: 'get',
params
})
}
export const fetchRegisterOrLogin = (data: LoginParamsType): Promise<LoginResponse> => {
return request({
url: '/api/auth/registerOrLogin',
method: 'post',
data
})
}
export const resetPassword = (data: LoginParamsType): Promise<ApiResponse> => {
return request({
url: '/api/auth/forgotPwd',
method: 'post',
data
})
}
export const checkLoginStatus = (): Promise<ApiResponse<LoginResponse>> => {
return request({
url: '/api/auth/checkLoginStatus',
method: 'get',
meta: { responseAll: true }
})
}
export const LogOut = (): Promise<ApiResponse> => {
return request({
url: '/api/auth/logout',
method: 'get'
})
}
// Google登录/注册参数类型
interface GoogleAuthParamsType {
accessToken?: string // Google ID Token (用于One Tap登录)
}
export const googleAuth = (data: GoogleAuthParamsType): Promise<LoginResponse> => {
return request({
url: '/api/auth/parseGoogleAccessToken',
method: 'get',
params: data
})
}
/** 更改用户信息
* @param data 包含用户信息的对象
* @param data.username 用户名
* @param data.email 邮箱
* @param data.password 密码
* @returns 包含更新后的用户信息的对象
*/
export const updateUserInfo = (data: any) => {
return request({
url: '/api/auth/updateUserInfo',
method: 'post',
data
})
}

40
src/api/notification.ts Normal file
View File

@@ -0,0 +1,40 @@
import request from '@/utils/request'
interface Page {
page: number
size: number
type?: number
isRead?: 0 | 1 // 0未读1已读
keyword?: string // 关键词搜索标题Ï
}
export const fetchAllMessageList = (data) => {
return request({
url: '/buyer/buyer/message/page',
method: 'post',
data
})
}
// 获取所有未读消息数量
export const fetchAllUnreadMessage = () => {
return request({
url: '/buyer/buyer/message/unread-count',
method: 'get'
})
}
// 标记单条消息已读
export const markMessageAsRead = (id: number | string) => {
return request({
url: `/buyer/buyer/message/${id}/read`,
method: 'put'
})
}
// 标记全部消息已读
export const markAllMessagesAsRead = () => {
return request({
url: '/buyer/buyer/message/read-all',
method: 'put'
})
}

91
src/api/shoppingCart.ts Normal file
View File

@@ -0,0 +1,91 @@
import request from '@/utils/request'
/**
* 加入购物车
* @param data - 包含邮箱的参数
* @param data.listingId - 商品ID
* @param data.listingIds - 商品ID列表
*/
export const AddShoppingCart = (data, loading?: boolean) => {
return request({
url: '/buyer/buyer/cart/add',
method: 'post',
data,
loading
})
}
/**
* 清空购物车
*/
export const ClearShoppingCart = (loading?: boolean) => {
return request({
url: '/buyer/buyer/cart/clear',
method: 'delete',
loading
})
}
/**
* 获取购物车列表
* @param loading - 是否显示loading
* @returns 购物车列表数据
*/
export const GetShoppingCartList = (loading?: boolean) => {
return request({
url: '/buyer/buyer/cart/list',
method: 'get',
loading
})
}
/**
* 从购物车移除商品
* @param params - 包含邮箱的参数
* @param params.listingId - 商品ID
*/
export const RemoveShoppingCartItem = (params, loading?: boolean) => {
return request({
url: '/buyer/buyer/cart/remove',
method: 'delete',
params,
loading
})
}
/**
* 创建订单
* @param { Array } data - 商品id数组
* @param loading - 是否显示loading
* @returns
*/
export const CreateOrder = (data, loading?: boolean) => {
return request({
url: '/buyer/buyer/order/create',
method: 'post',
data,
loading
})
}
/**
* 获取订单状态
* @param { String } paymentId - 订单ID
* @param loading - 是否显示loading
* @returns
*/
export const GetOrderStatus = (paymentId: string) => {
return request({
url: `/buyer/buyer/payment/status/${paymentId}`,
method: 'get',
loading: true,
})
}
export const ORDER_STATUS = {
PENDING: 0,//待支付
SUCCESS: 1,//支付成功
FAILED: 2,//支付失败
CANCELED: 3,//已取消支付
PARTIAL_REFUND: 4,//已部分退款
FULL_REFUND: 5,//已全额退款
}

172
src/api/user.ts Normal file
View File

@@ -0,0 +1,172 @@
import request from '@/utils/request'
export interface AxiosProgressEvent {
loaded: number
total?: number
progress?: number
bytes: number
rate?: number
estimated?: number
upload?: boolean
download?: boolean
event?: any
}
export interface WardrobeItem {
buyerId: number
categories: string[]
designFor: 'female' | 'male' | 'all'
page: number
size: number
}
// 获取我的衣橱assets
export const fetchMyWardrobe = (data: WardrobeItem): Promise<ApiResponse> => {
return request({
url: '/buyer/buyer/order/assets/page',
method: 'post',
data
})
}
export interface OrderItem {
status?: number // 0未支付 1已支付 2已取消 不传查全部
page: number
size: number
}
export interface OrdersPageResponse {
content: any[]
}
// 获取我的衣橱 orders
export const fetchMyOrders = (data: OrderItem): Promise<OrdersPageResponse> => {
return request({
url: '/buyer/buyer/order/page',
method: 'get',
params: data
})
}
export interface Download {
ids: string[]
}
// 下载资源
export const fetchDownloadItemsByGet = (
params: Download,
onDownloadProgress?: (event: AxiosProgressEvent) => void
): Promise<ApiResponse> => {
return request({
url: '/buyer/listing/mall/main-product/download',
method: 'get',
responseType: 'blob',
timeout: 600000,
params,
paramsSerializer: (p: any) => {
const usp = new URLSearchParams()
if (p && p.ids && Array.isArray(p.ids)) {
p.ids.forEach((id: any) => usp.append('ids', String(id)))
} else if (p) {
Object.keys(p).forEach((k) => {
const v = (p as any)[k]
if (Array.isArray(v)) {
v.forEach((x) => usp.append(k, String(x)))
} else if (v !== undefined && v !== null) {
usp.append(k, String(v))
}
})
}
return usp.toString()
},
onDownloadProgress
})
}
// 获取用户信息
export const fetchUserProfile = (): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/getProfile',
method: 'post'
})
}
// 设置用户信息
export interface UserProfile {
firstName: string
lastName: string
username: string
roles: string[]
region: string
language: string
email: string
avatarUrl?: string
oldPassword?: string
newPassword?: string
verifyCode?: string
}
export const updateUserProfile = (data: UserProfile): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/setProfile',
method: 'post',
data
})
}
// 设置头像
interface AvatarData {
avatarUrl: string
}
export const updateUserAvatar = (data: AvatarData): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/setAvatar',
method: 'post',
data
})
}
// 获取设置页验证码
export const fetchVerifyCode = (): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/sendEmailChangeCode',
method: 'post'
})
}
// 验证设置页验证码
export const verifyEmailCode = (verifyCode: string): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/verifyEmailChangeCode',
method: 'post',
data: { verifyCode }
})
}
// 获取用户语言设置
export const getUserLanguage = (): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/getLanguage',
method: 'post'
})
}
// 设置语言
export const setUserLanguage = (
language: 'ENGLISH' | 'CHINESE_SIMPLIFIED'
): Promise<ApiResponse> => {
return request({
url: '/buyer/profile/setLanguage',
method: 'post',
data: { language }
})
}
// 文件上传
export const uploadFile = (file: File): Promise<ApiResponse> => {
const formData = new FormData()
formData.append('file', file)
return request({
url: '/buyer/file/upload',
method: 'post',
data: formData,
headers: {
'Content-Type': 'multipart/form-data'
},
timeout: 60000
})
}

View File

@@ -111,33 +111,48 @@ body,
--mosaic-bg-size: 1rem; --mosaic-bg-size: 1rem;
--mosaic-bg-color1: #efefef; --mosaic-bg-color1: #efefef;
--mosaic-bg-color2: #fff; --mosaic-bg-color2: #fff;
background-image: repeating-conic-gradient(var(--mosaic-bg-color1) 0% 25%, var(--mosaic-bg-color2) 0% 50%); background-image: repeating-conic-gradient(
var(--mosaic-bg-color1) 0% 25%,
var(--mosaic-bg-color2) 0% 50%
);
background-repeat: repeat; background-repeat: repeat;
background-position: 50% 50%; background-position: 50% 50%;
background-size: var(--mosaic-bg-size) var(--mosaic-bg-size); background-size: var(--mosaic-bg-size) var(--mosaic-bg-size);
} }
button[custom], button[custom],
button[custom="white"] { button[custom='white'] {
min-width: 19.4rem; min-width: 19.4rem;
height: 5rem; height: 5rem;
padding: 0 1rem; padding: 0 1rem;
border-radius: 0; border-radius: 0;
border: none;
font-family: KaiseiOpti-Bold; font-family: KaiseiOpti-Bold;
font-size: var(--button-font-size, 2rem); font-size: var(--button-font-size, 2rem);
color: var(--button-color, #232323); color: var(--button-color, #232323);
background: var(--button-bgcolor, #fff); background: var(--button-bgcolor, #fff);
border: var(--button-border, none);
cursor: pointer; cursor: pointer;
} }
button[custom]:active, button[custom]:active,
button[custom="white"]:active { button[custom='white']:active {
background: var(--button-click-bgcolor, #e4e4e4); background: var(--button-click-bgcolor, #e4e4e4);
color: var(--button-click-color, #232323); color: var(--button-click-color, #232323);
} }
button[custom="black"] { button[custom='black'] {
--button-bgcolor: #232323; --button-bgcolor: #232323;
--button-color: #fff; --button-color: #fff;
--button-click-bgcolor: #333; --button-click-bgcolor: #333;
--button-click-color: #fff; --button-click-color: #fff;
--button-font-size: 1.6rem; --button-font-size: 1.6rem;
} }
button[custom='black-box'] {
--button-bgcolor: transparent;
--button-color: #232323;
--button-border: 0.2rem solid #979797;
--button-click-bgcolor: #979797;
--button-click-color: #fff;
--button-font-size: 1.6rem;
}
.el-select-dropdown .el-select-dropdown__item {
padding: 0 2rem;
}

View File

@@ -149,11 +149,11 @@ button[custom="white"] {
height: 5rem; height: 5rem;
padding: 0 1rem; padding: 0 1rem;
border-radius: 0; border-radius: 0;
border: none;
font-family: KaiseiOpti-Bold; font-family: KaiseiOpti-Bold;
font-size: var(--button-font-size, 2rem); font-size: var(--button-font-size, 2rem);
color: var(--button-color, #232323); color: var(--button-color, #232323);
background: var(--button-bgcolor, #fff); background: var(--button-bgcolor, #fff);
border: var(--button-border, none);
cursor: pointer; cursor: pointer;
&:active { &:active {
@@ -169,3 +169,12 @@ button[custom="black"] {
--button-click-color: #fff; --button-click-color: #fff;
--button-font-size: 1.6rem; --button-font-size: 1.6rem;
} }
button[custom="black-box"] {
--button-bgcolor: transparent;
--button-color: #232323;
--button-border: 0.2rem solid #979797;
--button-click-bgcolor: #979797;
--button-click-color: #fff;
--button-font-size: 1.6rem;
}

View File

@@ -0,0 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.75 5.41675H6.25V6.25008H13.75V5.41675Z" fill="currentColor"/>
<path d="M13.75 8.75H6.25V9.58333H13.75V8.75Z" fill="currentColor"/>
<path d="M10.4167 12.0833H6.25V12.9166H10.4167V12.0833Z" fill="currentColor"/>
<path d="M2.5 18.3334L6.25 16.6667L10 18.3334L13.75 16.6667L17.5 18.3334V1.66675H2.5V18.3334ZM3.33333 2.50008H16.6667V17.0509L14.0883 15.9051L13.75 15.7547L13.4117 15.9051L10 17.4213L6.58833 15.9051L6.25 15.7547L5.91167 15.9051L3.33333 17.0509V2.50008Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 602 B

View File

@@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14.1113 5.26938H12.1387V3.63221C12.1387 2.87065 11.518 2.25 10.7565 2.25H6.73062C5.96906 2.25 5.34841 2.87065 5.34841 3.63221V5.26938H3.37575C3.16774 5.26938 3 5.43713 3 5.64513C3 5.85313 3.16774 6.02087 3.37575 6.02087H4.00646V14.3678C4.00646 15.1293 4.62711 15.75 5.38867 15.75H12.0984C12.86 15.75 13.4806 15.1293 13.4806 14.3678V6.02423H14.1113C14.3193 6.02423 14.4871 5.85649 14.4871 5.64848C14.4871 5.44048 14.3193 5.27274 14.1113 5.27274V5.26938ZM6.73062 3.00485H10.7565C11.102 3.00485 11.3872 3.28666 11.3872 3.63556V5.12848C11.3872 5.209 11.3234 5.27274 11.2429 5.27274H6.24751C6.167 5.27274 6.10325 5.209 6.10325 5.12848V3.63556C6.10325 3.29001 6.38506 3.00485 6.73397 3.00485H6.73062ZM12.0984 15.0019H5.38867C5.04312 15.0019 4.75795 14.7201 4.75795 14.3711V6.02423H12.7258V14.3711C12.7258 14.7167 12.444 15.0019 12.0951 15.0019H12.0984Z" fill="#979797"/>
<path d="M7.40098 7.95319C7.15943 7.95319 6.96484 8.14777 6.96484 8.38932V12.2977C6.96484 12.5393 7.15943 12.7339 7.40098 12.7339C7.64253 12.7339 7.83711 12.5393 7.83711 12.2977V8.38932C7.83711 8.14777 7.64253 7.95319 7.40098 7.95319Z" fill="#979797"/>
<path d="M10.0846 7.95319C9.84302 7.95319 9.64844 8.14777 9.64844 8.38932V12.2977C9.64844 12.5393 9.84302 12.7339 10.0846 12.7339C10.3261 12.7339 10.5207 12.5393 10.5207 12.2977V8.38932C10.5207 8.14777 10.3261 7.95319 10.0846 7.95319Z" fill="#979797"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.5 12.5C9.5 13.8805 10.6195 15 12 15C13.3805 15 14.5 13.8805 14.5 12.5C14.5 11.1195 13.3805 10 12 10C10.6195 10 9.5 11.1195 9.5 12.5ZM13.5 12.5C13.5 13.327 12.827 14 12 14C11.173 14 10.5 13.327 10.5 12.5C10.5 11.673 11.173 11 12 11C12.827 11 13.5 11.673 13.5 12.5Z" fill="#7B7B7B"/>
<path d="M3 12.5C3 13.8805 4.1195 15 5.5 15C6.8805 15 8 13.8805 8 12.5C8 11.1195 6.8805 10 5.5 10C4.1195 10 3 11.1195 3 12.5ZM7 12.5C7 13.327 6.327 14 5.5 14C4.673 14 4 13.327 4 12.5C4 11.673 4.673 11 5.5 11C6.327 11 7 11.673 7 12.5Z" fill="#7B7B7B"/>
<path d="M16 12.5C16 13.8805 17.1195 15 18.5 15C19.8805 15 21 13.8805 21 12.5C21 11.1195 19.8805 10 18.5 10C17.1195 10 16 11.1195 16 12.5ZM20 12.5C20 13.327 19.327 14 18.5 14C17.673 14 17 13.327 17 12.5C17 11.673 17.673 11 18.5 11C19.327 11 20 11.673 20 12.5Z" fill="#7B7B7B"/>
</svg>

After

Width:  |  Height:  |  Size: 927 B

View File

@@ -0,0 +1,4 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="13.8654" cy="13.8665" r="7.86667" stroke="#232323" stroke-width="1.33333"/>
<path d="M19.5586 19.5552L26.6697 26.6663" stroke="#232323" stroke-width="1.33333" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 299 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20C4.48583 20 0 15.5142 0 10C0 4.48583 4.48583 0 10 0C15.5142 0 20 4.48583 20 10C20 15.5142 15.5142 20 10 20ZM10 0.833333C4.94583 0.833333 0.833333 4.94583 0.833333 10C0.833333 15.0542 4.94583 19.1667 10 19.1667C15.0542 19.1667 19.1667 15.0542 19.1667 10C19.1667 4.94583 15.0542 0.833333 10 0.833333ZM12.3333 14.0833C12.5175 13.945 12.555 13.6842 12.4175 13.5L10.0008 10.2775V4.58333C10.0008 4.35333 9.81417 4.16667 9.58417 4.16667C9.35417 4.16667 9.1675 4.35333 9.1675 4.58333V10.4167C9.1675 10.5067 9.19667 10.5942 9.25083 10.6667L11.7508 14C11.8333 14.1092 11.9583 14.1667 12.0842 14.1667C12.1708 14.1667 12.2583 14.1392 12.3333 14.0833Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 773 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="16" viewBox="0 0 20 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 0H2C0.9 0 0 0.9 0 2V14C0 15.1 0.9 16 2 16H18C19.1 16 20 15.1 20 14V2C20 0.9 19.1 0 18 0ZM19 14C19 14.5515 18.5515 15 18 15H2C1.4485 15 1 14.5515 1 14V11H19V14ZM19 9H1V2C1 1.4485 1.4485 1 2 1H18C18.5515 1 19 1.4485 19 2V9Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1,5 @@
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.5">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.82684 8.7465L7.13386 6.43947L9.44089 8.7465C9.67278 8.97839 10.0474 8.97839 10.2793 8.7465C10.5112 8.51461 10.5112 8.14001 10.2793 7.90812L7.55008 5.17893C7.31819 4.94704 6.94359 4.94704 6.7117 5.17893L3.98251 7.90812C3.75062 8.14001 3.75062 8.51461 3.98251 8.7465C4.2144 8.97244 4.59495 8.97839 4.82684 8.7465Z" fill="black"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 506 B

View File

@@ -0,0 +1,3 @@
<svg width="26" height="20" viewBox="0 0 26 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 18.6667H25.3333V20H0V18.6667ZM18 8.39067L13.3333 13.0573V0H12V13.0573L7.33333 8.39067L6.39067 9.33333L12.6667 15.6093L18.9427 9.33333L18 8.39067Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 284 B

View File

@@ -0,0 +1,4 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.32243 7.42574L9.409 10.5123C9.43299 10.5363 9.46897 10.5443 9.50096 10.5323C9.53294 10.5203 9.55293 10.4883 9.55293 10.4563V2.94781C9.55293 2.69992 9.75284 2.50002 10.0007 2.50002C10.2486 2.50002 10.4485 2.69992 10.4485 2.94781V10.4563C10.4485 10.4923 10.4685 10.5203 10.5005 10.5323C10.5325 10.5443 10.5685 10.5403 10.5925 10.5123L13.679 7.42574C13.851 7.25782 14.1468 7.25782 14.3147 7.42574C14.3987 7.5097 14.4467 7.62565 14.4467 7.74559C14.4467 7.86554 14.3987 7.97749 14.3147 8.06545L10.1287 12.2515C10.0607 12.3195 9.93676 12.3195 9.86879 12.2515L5.68272 8.06545C5.59876 7.98148 5.55078 7.86554 5.55078 7.74559C5.55078 7.62565 5.59876 7.5137 5.68272 7.42574C5.85064 7.25782 6.1505 7.25782 6.31843 7.42574H6.32243Z" fill="currentColor"/>
<path d="M17.1966 12.6062C16.9487 12.6062 16.7488 12.8061 16.7488 13.054V16.2525C16.7488 16.4444 16.5929 16.6044 16.397 16.6044H3.60289C3.41098 16.6044 3.25106 16.4484 3.25106 16.2525V13.054C3.25106 12.8061 3.05115 12.6062 2.80326 12.6062C2.55538 12.6062 2.35547 12.8061 2.35547 13.054V16.2525C2.35547 16.9402 2.91521 17.4999 3.60289 17.4999H16.397C17.0847 17.4999 17.6444 16.9402 17.6444 16.2525V13.054C17.6444 12.8061 17.4445 12.6062 17.1966 12.6062Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,5 +1,34 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" standalone="no"?>
<path d="M20 6.4C20 5.3 19.1 4 18 4H7.5C7.4 4 7.2 4 7.1 4C5.9 4.2 5 5.2 5 6.4L4 9C4 10.4 5.1 11.5 6.5 11.5C7.9 11.5 8.1 11.1 8.5 10.5C8.9 11.1 9.7 11.5 10.5 11.5C11.3 11.5 12.1 11.1 12.5 10.5C12.9 11.1 13.7 11.5 14.5 11.5C15.3 11.5 16.1 11.1 16.5 10.5C16.9 11.1 17.7 11.5 18.5 11.5C19.9 11.5 21 10.4 21 9M5.9 6.4C5.9 5.6 6.5 5 7.2 4.9C7.2 4.9 7.3 4.9 7.4 4.9H17.9C18.5 4.9 19 5.8 19 6.4C19 7 20 9.1 20 9.1C20 10 19.3 10.7 18.4 10.7C17.5 10.7 16.8 10 16.8 9.1V7.6H15.9V9.1C15.9 10 15.2 10.7 14.3 10.7C13.4 10.7 12.7 10 12.7 9.1V7.6H11.8V9.1C11.8 10 11.1 10.7 10.2 10.7C9.3 10.7 8.6 10 8.6 9.1V7.6H7.7V9.1C7.7 10 7.50938 10.7 6.60938 10.7C5.48438 10.7 4.9 9.9 4.9 9L5.9 6.4Z" fill="#232323"/> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
<path d="M6 11.2V19.3C6 19.9 6.4 20.3 7 20.3H18C18.6 20.3 19 19.9 19 19.3V13.5" stroke="#232323" stroke-linecap="round"/> "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<path d="M17 15.5V13.5C17 13.2239 16.7761 13 16.5 13C16.2239 13 16 13.2239 16 13.5V15.5C16 15.7761 16.2239 16 16.5 16C16.7761 16 17 15.7761 17 15.5Z" fill="#232323"/> <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1466 4255 c-92 -26 -177 -75 -247 -145 -80 -80 -124 -165 -140 -271
-7 -49 -46 -167 -115 -349 -110 -291 -116 -319 -93 -420 31 -136 153 -289 276
-346 l62 -29 3 -885 3 -885 23 -47 c13 -26 43 -65 67 -87 82 -76 1 -72 1386
-69 l1234 3 55 30 c60 33 114 96 130 153 6 24 10 274 10 699 l0 662 -22 21
c-25 23 -56 26 -84 6 -18 -14 -19 -36 -24 -678 -6 -738 -3 -708 -74 -742 -31
-15 -145 -16 -1253 -16 -1204 0 -1218 0 -1251 20 -64 39 -62 3 -62 938 l0 849
108 5 c166 9 244 46 316 151 l37 54 57 -56 c99 -98 230 -151 374 -151 141 0
287 60 376 154 l49 51 57 -57 c103 -104 250 -156 409 -145 138 9 257 64 347
160 l40 43 63 -61 c98 -97 221 -145 367 -145 263 0 486 190 521 445 l11 80
-102 265 c-72 186 -107 290 -116 348 -39 236 -188 417 -373 452 -35 6 -467 10
-1215 9 -939 -1 -1170 -3 -1210 -14z m2388 -186 c97 -27 177 -151 202 -313 16
-108 55 -221 147 -426 52 -115 61 -143 60 -189 -2 -72 -49 -167 -109 -220 -70
-61 -140 -84 -244 -79 -75 3 -94 8 -150 39 -43 24 -78 52 -102 85 -63 82 -70
115 -75 337 l-5 197 -94 0 -94 0 0 -195 c0 -174 -2 -200 -21 -252 -27 -70 -95
-145 -164 -181 -72 -37 -197 -43 -277 -12 -75 28 -150 96 -185 168 -27 56 -28
61 -31 265 l-4 207 -93 0 -94 0 -3 -203 -3 -203 -33 -66 c-40 -81 -102 -139
-181 -169 -80 -30 -204 -24 -279 15 -64 33 -123 92 -154 154 -21 43 -23 63
-28 257 l-5 210 -95 0 -95 0 -5 -240 c-5 -223 -7 -243 -28 -290 -47 -101 -102
-130 -234 -123 -65 4 -96 11 -141 33 -107 53 -174 152 -184 270 -5 57 0 72 98
325 56 146 106 291 110 323 20 132 105 232 234 273 53 17 2299 20 2359 3z"/>
<path d="M3494 2343 c-12 -2 -34 -18 -50 -34 l-29 -30 -3 -211 c-2 -116 -1
-231 3 -256 14 -97 108 -135 177 -73 l33 29 3 241 c3 205 1 245 -13 274 -21
45 -71 70 -121 60z"/>
</g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 20C8.02219 20 6.08879 19.4135 4.4443 18.3147C2.79981 17.2159 1.51809 15.6541 0.761209 13.8268C0.00433286 11.9996 -0.193701 9.98891 0.192152 8.0491C0.578004 6.10929 1.53041 4.32746 2.92894 2.92894C4.32746 1.53041 6.10929 0.578004 8.0491 0.192152C9.98891 -0.193701 11.9996 0.00433286 13.8268 0.761209C15.6541 1.51809 17.2159 2.79981 18.3147 4.4443C19.4135 6.08879 20 8.02219 20 10C19.9971 12.6513 18.9426 15.1932 17.0679 17.0679C15.1932 18.9426 12.6513 19.9971 10 20ZM10 1.66667C8.35183 1.66667 6.74066 2.15541 5.37025 3.07109C3.99984 3.98677 2.93174 5.28826 2.30101 6.81098C1.67028 8.33369 1.50525 10.0092 1.82679 11.6258C2.14834 13.2423 2.94201 14.7271 4.10745 15.8926C5.27289 17.058 6.75774 17.8517 8.37425 18.1732C9.99076 18.4948 11.6663 18.3297 13.189 17.699C14.7118 17.0683 16.0132 16.0002 16.9289 14.6298C17.8446 13.2593 18.3333 11.6482 18.3333 10C18.3309 7.79061 17.4522 5.67241 15.8899 4.11013C14.3276 2.54785 12.2094 1.6691 10 1.66667Z" fill="#232323"/>
<path d="M11.6654 15.8333H9.9987V9.99998H8.33203V8.33331H9.9987C10.4407 8.33331 10.8646 8.50891 11.1772 8.82147C11.4898 9.13403 11.6654 9.55795 11.6654 9.99998V15.8333Z" fill="#232323"/>
<path d="M10 6.66666C10.6904 6.66666 11.25 6.10701 11.25 5.41666C11.25 4.7263 10.6904 4.16666 10 4.16666C9.30964 4.16666 8.75 4.7263 8.75 5.41666C8.75 6.10701 9.30964 6.66666 10 6.66666Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 6.4C20 5.3 19.1 4 18 4H7.5C7.4 4 7.2 4 7.1 4C5.9 4.2 5 5.2 5 6.4L4 9C4 10.4 5.1 11.5 6.5 11.5C7.9 11.5 8.1 11.1 8.5 10.5C8.9 11.1 9.7 11.5 10.5 11.5C11.3 11.5 12.1 11.1 12.5 10.5C12.9 11.1 13.7 11.5 14.5 11.5C15.3 11.5 16.1 11.1 16.5 10.5C16.9 11.1 17.7 11.5 18.5 11.5C19.9 11.5 21 10.4 21 9M5.9 6.4C5.9 5.6 6.5 5 7.2 4.9C7.2 4.9 7.3 4.9 7.4 4.9H17.9C18.5 4.9 19 5.8 19 6.4C19 7 20 9.1 20 9.1C20 10 19.3 10.7 18.4 10.7C17.5 10.7 16.8 10 16.8 9.1V7.6H15.9V9.1C15.9 10 15.2 10.7 14.3 10.7C13.4 10.7 12.7 10 12.7 9.1V7.6H11.8V9.1C11.8 10 11.1 10.7 10.2 10.7C9.3 10.7 8.6 10 8.6 9.1V7.6H7.7V9.1C7.7 10 7.50938 10.7 6.60938 10.7C5.48438 10.7 4.9 9.9 4.9 9L5.9 6.4Z" fill="#232323"/>
<path d="M6 11.2V19.3C6 19.9 6.4 20.3 7 20.3H18C18.6 20.3 19 19.9 19 19.3V13.5" stroke="#232323" stroke-width="0.75" stroke-linecap="round"/>
<path d="M17 15.5V13.5C17 13.2239 16.7761 13 16.5 13C16.2239 13 16 13.2239 16 13.5V15.5C16 15.7761 16.2239 16 16.5 16C16.7761 16 17 15.7761 17 15.5Z" fill="#232323"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.99326 1.66675C5.39812 1.66675 1.66406 5.40495 1.66406 10.0001C1.66406 14.5952 5.40226 18.3334 9.99326 18.3334C14.5842 18.3334 18.3224 14.5952 18.3224 10.0001C18.3224 5.40495 14.5884 1.66675 9.99326 1.66675ZM9.99326 17.402C5.91145 17.402 2.59137 14.0819 2.59137 10.0001C2.59137 5.91828 5.91145 2.59819 9.99326 2.59819C14.0751 2.59819 17.3951 5.91828 17.3951 10.0001C17.3951 14.0819 14.0751 17.402 9.99326 17.402Z" fill="#979797"/>
<path d="M10.5115 9.17225C10.5115 8.88646 10.2798 8.65479 9.99403 8.65479C9.70824 8.65479 9.47656 8.88646 9.47656 9.17225V14.14C9.47656 14.4258 9.70824 14.6574 9.99403 14.6574C10.2798 14.6574 10.5115 14.4258 10.5115 14.14V9.17225Z" fill="#979797"/>
<path d="M9.99592 6.68813C10.4513 6.68813 10.8239 6.31555 10.8239 5.86018C10.8239 5.4048 10.4513 5.03223 9.99592 5.03223C9.54055 5.03223 9.16797 5.4048 9.16797 5.86018C9.16797 6.31555 9.54055 6.68813 9.99592 6.68813Z" fill="#979797"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 713 KiB

After

Width:  |  Height:  |  Size: 597 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 808 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 851 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 500 B

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import { ref, onMounted, onUnmounted, reactive, toRefs } from 'vue'
const props = defineProps({ const props = defineProps({
url: { url: {
type: String, type: String,
default: '' default: ''
@@ -10,80 +10,110 @@ const props = defineProps({
default: 'aaa' default: 'aaa'
}, },
price: { price: {
type: String, type: [String, Number],
default: '111' default: ''
},
download: {
type: Boolean,
default: false
},
showPrice: {
type: Boolean,
default: true
} }
}) })
const emit = defineEmits([ const emit = defineEmits(['addShopping', 'openDetail', 'download'])
'addShopping', let data = reactive({})
'openDetail' const addShopping = () => {
]) if (props.download) {
let data = reactive({ emit('download')
}) } else {
const addShopping = () => {
emit('addShopping') emit('addShopping')
} }
const openDetail = () => { }
const openDetail = () => {
emit('openDetail') emit('openDetail')
} }
onMounted(()=>{ onMounted(() => {})
}) onUnmounted(() => {})
onUnmounted(()=>{ defineExpose({})
}) const {} = toRefs(data)
defineExpose({})
const {} = toRefs(data);
</script> </script>
<template> <template>
<div class="commodity-item"> <div class="commodity-item" :class="{ 'is-download': download }">
<img :src="props.url" alt="" @click="openDetail"> <img v-loadimg="props.url" alt="" @click="openDetail" />
<div class="detail"> <div class="detail">
<div class="text"> <div class="text">
<div class="name"> <div class="name">
{{ props.name }} {{ props.name }}
</div> </div>
<div class="price"> <div
{{ props.price }} class="price"
:class="{ 'is-download': download }"
v-if="props.showPrice && (props.price || props.price === 0)"
>
HK${{ props.price }}
</div> </div>
</div> </div>
<div class="btn" @click="addShopping"> <div class="btn" @click="addShopping">
<div class="text"> <div class="text">
<SvgIcon name="add" size="26"></SvgIcon> <SvgIcon
:name="download ? 'download' : 'add'"
size="26"
color="#232323"
></SvgIcon>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.commodity-item{ .commodity-item {
width: var(--commodity-width,100%); width: var(--commodity-width, 100%);
> img{ &.is-download {
img {
cursor: initial;
}
}
> img {
width: 100%; width: 100%;
cursor: pointer; cursor: pointer;
height: var(--commodity-height,auto); height: var(--commodity-height, auto);
margin-bottom: var(--commodity-marginBottom,1rem); aspect-ratio: 0.8/1;
object-fit: cover;
margin-bottom: var(--commodity-marginBottom, 1rem);
} }
> .detail{ > .detail {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
> .text{ > .text {
color: #232323; color: #232323;
> .name{ overflow: hidden;
font-family: "KaiseiOpti-Regular"; > .name {
font-family: 'KaiseiOpti-Regular';
font-weight: 700;
font-size: var(--commodity-name-fontSize, 2rem);
line-height: var(--commodity-name-lineHeight, 2.3rem);
margin-bottom: var(--commodity-name-marginBottom, 0rem);
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
> .price {
font-family: 'KaiseiOpti-Regular';
font-weight: 400; font-weight: 400;
font-size: var(--commodity-name-fontSize,1.6rem); font-size: var(--commodity-price-fontSize, 1.6rem);
line-height: var(--commodity-name-lineHeight,2.3rem); line-height: var(--commodity-price-lineHeight, 2.3rem);
margin-bottom: var(--commodity-name-marginBottom,0rem); margin-top: .8rem;
} &.is-download {
> .price{ color: #979797;
font-family: "KaiseiOpti-Regular";
font-weight: 400;
font-size: var(--commodity-price-fontSize,1.4rem);
line-height: var(--commodity-price-lineHeight,2.3rem);
} }
} }
} }
.btn{ }
.btn {
cursor: pointer; cursor: pointer;
} }
} }

View File

@@ -1,21 +1,26 @@
<template> <template>
<section class="section-footer"> <section class="section-footer">
<div class="footer"> <div class="footer">
<div class="left"> <div class="left" v-show="!isHome">
<div>About</div> <div @click="skip('about')">{{ $t('footer.About') }}</div>
<div>Privacy Policy</div> <div @click="skip('privacy-policy')">{{ $t('footer.PrivacyPolicy') }}</div>
<div>Terms of Use</div> <div @click="skip('terms-of-use')">{{ $t('footer.TermsOfUse') }}</div>
<div>Disclaimer</div> <div @click="skip('disclaimer')">{{ $t('footer.Disclaimer') }}</div>
<div>Site Map</div> <div @click="skip('site-map')">{{ $t('footer.SiteMap') }}</div>
</div>
<div class="left" v-show="isHome">
<div class="text" @click="skip('code-create')">© Code-Create 2026</div>
</div> </div>
<div class="right"> <div class="right">
<img src="@/assets/images/icons/xiaohongshu.png" /> <img src="@/assets/images/icons/xiaohongshu.png" @click="skip('xiaohongshu')" />
<img src="@/assets/images/icons/linkedin.png" /> <img src="@/assets/images/icons/linkedin.png" @click="skip('linkedin')" />
<img src="@/assets/images/icons/instagram.png" /> <img src="@/assets/images/icons/instagram.png" @click="skip('instagram')" />
<img src="@/assets/images/icons/facebook.png" /> <img src="@/assets/images/icons/facebook.png" @click="skip('facebook')" />
<img src="@/assets/images/icons/douyin.png" /> <img src="@/assets/images/icons/douyin.png" @click="skip('tiktok')" />
<img src="@/assets/images/icons/wechat.png" /> <img src="@/assets/images/icons/youtube.png" @click="skip('youtube')" />
<div class="text">© Code-Create 2026</div> <div v-show="!isHome" class="text" @click="skip('code-create')">
© Code-Create 2026
</div>
</div> </div>
</div> </div>
</section> </section>
@@ -23,6 +28,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
const props = defineProps({
isHome: {
type: Boolean,
default: false
}
})
let urlList = ref({
xiaohongshu: 'https://xhslink.com/m/5Ony2FapizV',
linkedin: 'https://www.linkedin.com/company/code-create-limited/posts?feedView=all',
instagram: 'https://www.instagram.com/aida_codecreate?igsh=MzRlODBiNWFlZA== ',
facebook: 'https://www.facebook.com/CodeCreateAI',
tiktok: 'https://www.tiktok.com/@aida_codecreate',
youtube: 'https://www.youtube.com/@Code-Create_AiDA',
'code-create': 'https://www.code-create.com/',
about: 'https://code-create.com.hk/about-us/ ',
'privacy-policy': 'https://code-create.com.hk/privacy-policy/',
'terms-of-use': 'https://code-create.com.hk/terms-of-use/',
disclaimer: 'https://code-create.com.hk/disclaimer/',
'site-map': 'https://code-create.com.hk/site-map/'
})
const skip = (name: string) => {
window.open(urlList.value[name], '_blank')
}
</script> </script>
<style lang="less"> <style lang="less">
@@ -41,6 +69,9 @@
color: #585858; color: #585858;
display: flex; display: flex;
gap: 4.5rem; gap: 4.5rem;
> div {
cursor: pointer;
}
} }
> .right { > .right {
display: flex; display: flex;
@@ -49,8 +80,9 @@
width: 2rem; width: 2rem;
height: 2rem; height: 2rem;
margin-left: 2rem; margin-left: 2rem;
cursor: pointer;
} }
.text{ .text {
margin-left: 4rem; margin-left: 4rem;
font-family: KaiseiOpti-Regular; font-family: KaiseiOpti-Regular;
font-size: 1.2rem; font-size: 1.2rem;

View File

@@ -3,7 +3,7 @@
<svg <svg
:class="svgClass" :class="svgClass"
v-bind="$attrs" v-bind="$attrs"
:style="{ color: color, fontSize: size/10 + 'rem' }" :style="{ color: color, fontSize: size / 10 + 'rem' }"
> >
<use :href="iconName"></use> <use :href="iconName"></use>
</svg> </svg>
@@ -11,40 +11,40 @@
</template> </template>
<script setup> <script setup>
import { computed } from "vue"; import { computed } from 'vue'
const props = defineProps({ const props = defineProps({
name: { name: {
type: String, type: String,
required: true, required: true
}, },
color: { color: {
type: String, type: String,
default: "", default: ''
}, },
size: { size: {
type: [Number, String], type: [Number, String],
default: 16, default: 16
}, }
}); })
const iconName = computed(() => `#icon-${props.name}`); const iconName = computed(() => `#icon-${props.name}`)
const svgClass = computed(() => { const svgClass = computed(() => {
if (props.name) return `svg-icon icon-${props.name}`; if (props.name) return `svg-icon icon-${props.name}`
return "svg-icon"; return 'svg-icon'
}); })
</script> </script>
<style scoped> <style scoped>
.svg-icon { .svg-icon {
width: 1em; width: var(--svg-icon-width, 1em);
height: 1em; height: var(--svg-icon-height, 1em);
fill: currentColor; fill: currentColor;
color: var(--svg-icon-color); color: var(--svg-icon-color);
} }
.c-svg { .c-svg {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
</style> </style>

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import { ref, watch, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
const props = defineProps({
list:{
type:Array,
default:()=>[]
},
selected:{
type:Array,
default:()=>[]
}
})
const emit = defineEmits([
'update:selected','change'
])
const checkList = ref([])
const checkAll = ref(false)
watch(()=>props.selected, (newVal, oldVal) => {
if(newVal[0] === 'all' && newVal.length === 1){
checkList.value = []
checkAll.value = true
}else{
checkList.value = [...newVal]
checkAll.value = false
}
},{immediate:true})
const handleChange = (val) => {
let data = val.filter(item => item !== 'all' && props.selected?.[0] != item)
emit('update:selected', data)
emit('change', data)
}
const handleCheckAllChange = (val) => {
let data = ['all']
emit('update:selected', data)
emit('change', data)
}
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="all">
<el-checkbox
v-model="checkAll"
@change="handleCheckAllChange"
>
{{ $t('checked.All') }}
</el-checkbox>
</div>
<el-checkbox-group v-model="checkList" @change="handleChange">
<el-checkbox
v-for="item in props.list"
:key="item.value"
:value="item.value"
>
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
</template>
<style lang="less" scoped>
.all{
margin-bottom: 1.2rem;
}
.el-checkbox-group{
display: flex;
flex-direction: column;
gap: 1.2rem;
}
label{
--el-checkbox-font-size: 1.6rem;
--el-checkbox-checked-text-color: #232323;
--el-checkbox-font-weight: 400;
--el-checkbox-height: 2rem;
--el-checkbox-checked-bg-color: #232323;
--el-checkbox-checked-input-border-color: #232323;
--el-checkbox-input-border: 1px solid #232323;
font-family: "KaiseiOpti-Regular";
line-height: 2rem;
.el-checkbox__label{
padding-left: 1.4rem;
}
}
</style>

View File

@@ -1,25 +1,52 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs, computed } from "vue"; import { ref, watch, onMounted, onUnmounted, reactive, toRefs, computed } from "vue";
const props = defineProps({ const props = defineProps({
list:{ list:{
type:Array, type:Array,
default:()=>[] default:()=>[]
}, },
selected:{ selected:{
type:String, type:Array,
default:()=>'' default:()=>[]
} }
}) })
const emit = defineEmits([ const emit = defineEmits([
'update:selected' 'update:selected','change'
]) ])
const checkList = computed(()=>{ const checkList = ref([])
return [props.selected] const checkAll = ref(false)
})
const handleChange = (val) => { watch(()=>props.selected, (newVal, oldVal) => {
if (val.length > 1) { if(newVal[0] === 'all' && newVal.length === 1){
emit('update:selected', val[val.length - 1]) checkList.value = []
checkAll.value = true
}else{
checkList.value = [...newVal]
checkAll.value = false
} }
},{immediate:true})
const handleChange = (val) => {
let data = val.filter(item => item !== 'all')
if(data.length == props.list.length || data.length == 0){
data = ['all']
}else{
data = [...val]
}
emit('update:selected', data)
emit('change', data)
}
const handleCheckAllChange = (val) => {
let data = []
if(val && props.selected[0] !== 'all'){
data = ['all']
// data = props.list.map(item => item.value)
emit('update:selected', data)
emit('change', data)
}else{
data = []
}
} }
let data = reactive({ let data = reactive({
}) })
@@ -31,6 +58,14 @@ defineExpose({})
const {} = toRefs(data); const {} = toRefs(data);
</script> </script>
<template> <template>
<div class="all">
<el-checkbox
v-model="checkAll"
@change="handleCheckAllChange"
>
{{ $t('checked.All') }}
</el-checkbox>
</div>
<el-checkbox-group v-model="checkList" @change="handleChange"> <el-checkbox-group v-model="checkList" @change="handleChange">
<el-checkbox <el-checkbox
v-for="item in props.list" v-for="item in props.list"
@@ -42,6 +77,9 @@ const {} = toRefs(data);
</el-checkbox-group> </el-checkbox-group>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.all{
margin-bottom: 1.2rem;
}
.el-checkbox-group{ .el-checkbox-group{
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -0,0 +1,27 @@
// 加载图片
import avatar from '@/assets/images/avatar.png'
export default {
name: 'avatarLoad',
mounted(el, binding) {
loadImage(el, binding.value)
},
updated(el, binding) {
// 当 binding.value 发生变化时重新加载
if (binding.oldValue !== binding.value) {
loadImage(el, binding.value)
}
},
};
function loadImage(el, src) {
if (!src) return
if (el.src === src) return
const img = new Image()
img.src = src
img.onload = () => {
el.src = src
}
img.onerror = () => {
el.src = avatar // 默认头像
console.log('图片加载失败:', src)
}
}

View File

@@ -1,6 +1,9 @@
export default { export default {
install(app) { install(app) {
const directivesList = import.meta.glob('./*.js', { eager: true }); const directivesList1 = import.meta.glob('./*.js', { eager: true });
const directivesList2 = import.meta.glob('./*.ts', { eager: true });
const directivesList = { ...directivesList1, ...directivesList2 };
// 遍历指令文件实现自动注册 // 遍历指令文件实现自动注册
Object.keys(directivesList).forEach(key => { Object.keys(directivesList).forEach(key => {
app.directive(directivesList[key].default.name, directivesList[key].default); app.directive(directivesList[key].default.name, directivesList[key].default);

View File

@@ -1,6 +1,10 @@
import img from '@/assets/images/shopping-cart-null.png'
// 加载图片 // 加载图片
export default { export default {
name: 'loadimg', name: 'loadimg',
beforeMount(el, binding) {
if (!el.src) el.src = img
},
mounted(el, binding) { mounted(el, binding) {
const src = binding.value const src = binding.value
if (el.src === src) return if (el.src === src) return

View File

@@ -1,9 +1,12 @@
import { add } from "lodash-es";
export default { export default {
Login: { Login: {
signup: 'Sign up',
login: 'Log in', login: 'Log in',
logoff: 'Log off',
register: 'Register', register: 'Register',
loginTo: 'Log on to <span>FiDA</span>', loginTip: 'Platform integrated with AiDA.<br />AiDA account login required.',
loginTitle: 'A multi-agent canvas for rapid, trend driven design iteration.',
name: 'Name', name: 'Name',
email: 'Email', email: 'Email',
password: 'Password', password: 'Password',
@@ -12,11 +15,12 @@ export default {
enterEmail: 'Enter your email', enterEmail: 'Enter your email',
enterPassword: 'Enter your password', enterPassword: 'Enter your password',
enterPasswordAgain: 'Enter your password again', enterPasswordAgain: 'Enter your password again',
passwordTip: 'You must satisfy ALL password conditions to register.',
forgotPassword: 'Forget password?', forgotPassword: 'Forget password?',
pleaseInputName: 'Please input the name', pleaseInputName: 'Please input the name',
nameLengthError: 'Name length must be between {min} and {max} characters', nameLengthError: 'Name length must be between {min} and {max} characters',
passwordSpecial: 'Must contain special characters', passwordSpecial: 'Must contain special characters',
passwordCase: 'Mix of uppercase, lowercase and numbers', passwordCase: 'A combination of numbers and letters',
pleaseInputEmail: 'Please input the email', pleaseInputEmail: 'Please input the email',
emailFormatError: 'Please input the email again', emailFormatError: 'Please input the email again',
pleaseInputPassword: 'Please input the password', pleaseInputPassword: 'Please input the password',
@@ -38,6 +42,383 @@ export default {
wechatLogin: 'Sign in with Wechat', wechatLogin: 'Sign in with Wechat',
indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.', indexTip: 'A multi-agent canvas for rapid, trend driven design iteration.',
sendCodeError: 'Send code error', sendCodeError: 'Send code error',
retrievePassword: 'Retrieve password' retrievePassword: 'Retrieve password',
emailVerification: 'Email Verification',
retrievePasswordTitle: 'Please enter your email address below to verify your identity.',
submit: 'Submit',
enterNewPassword: 'Enter a new password for<br/><span>{email}</span>',
passwordsDoNotMatch: 'Passwords do not match',
logOffTip: 'Are you sure to log off?',
pleaseLogTip: 'Please log in and try again.'
}, },
RegisterSuccess: {
title1: 'Welcome to Stylish Parade!',
title2: 'Please switch to the Login tab to log in.',
title3: 'What awaits you in Stylish Parade',
item1title: 'Behind the design',
item1tip:
'Discover how designers bring ideas to life with AiDA — from first sketch to final look.',
item2title: 'Creative digital works',
item2tip:
'Unlock a growing library of inspiring digital works to refresh your creative mind.',
item3title: 'A fashion community',
item3tip:
'Join a space where fashion speaks — exchange ideas and connect with creators worldwide.'
},
Settings: {
title: 'Settings',
slogan: 'Manage your account settings and preferences',
profile: {
title: 'Profile',
description: 'Update your display name, avatar, social links and account security.',
firstName: 'FIRST NAME',
lastName: 'LAST NAME',
firstNamePlaceholder: 'First Name',
lastNamePlaceholder: 'Last Name',
username: 'USERNAME',
usernamePlaceholder: 'Username',
usernameTip: 'Your public username on Stylish Parade.',
role: 'ROLE',
roleTip: 'Select up to 2 labels that suit you.'
},
avatarCrop: {
title: 'Crop Avatar',
confirm: 'Confirm',
processing: 'Processing...'
},
security: {
title: 'Security',
description: 'Manage your login email and password.',
email: 'EMAIL',
newEmail: 'NEW EMAIL ADDRESS',
newEmailPlaceholder: 'Enter new email',
verify: 'Verify',
verified: 'Verified',
verifiedTip: 'Your new email has been verified and is ready to save.',
password: 'PASSWORD',
newPassword: 'NEW PASSWORD',
newPasswordPlaceholder: 'Enter new password',
passwordTip: 'You must satisfy ALL password conditions to register.',
currentPassword: 'CURRENT PASSWORD',
currentPasswordPlaceholder: 'Confirm with your password'
},
region: {
title: 'Language & Region',
description: 'Set your preferred language, region and currency display.',
displayLanguage: 'DISPLAY LANGUAGE',
selectLanguage: 'Select language',
region: 'REGION',
selectRegion: 'Select region'
},
buttons: {
cancel: 'CANCEL',
discard: 'DISCARD',
edit: 'EDIT',
saveChange: 'SAVE CHANGE',
verifyEmail: 'VERIFY EMAIL',
saving: 'SAVING...'
},
dialog: {
title: 'Check your new email',
subtitle: 'Enter the 6-digit code sent to',
submit: 'Submit',
resendCode: 'Resend Code',
resendCodeIn: 'Resend Code in {time}'
},
messages: {
enterNewEmailFirst: 'Please enter your email address first',
invalidEmail: 'Please enter a valid email address',
sameEmail: 'Please enter a different email address',
alreadyVerified: 'This email has already been verified',
verificationCodeSent: 'Verification code sent',
enterVerificationCode: 'Please enter the 6-digit verification code',
verificationCompleted: 'Email verification completed',
verifyEmailBeforeSave: 'Please verify your new email before saving',
currentPasswordRequired: 'Please enter your current password',
passwordLengthError: 'Password length must be between {min} and {max} characters',
passwordSpecial: 'Password must contain special characters',
passwordCase: 'Password must include upper/lowercase letters and numbers',
passwordNotSameAsOld: 'New password cannot be the same as current password',
settingsUpdated: 'Settings updated',
avatarTooLarge: 'Image is too large. Max 5MB.',
avatarCropFailed: 'Failed to crop avatar',
avatarUploadUrlMissing: 'Failed to get uploaded file URL',
avatarUpdated: 'Avatar updated',
avatarUploadFailed: 'Upload failed'
},
roles: {
fashionEnthusiast: 'Fashion Enthusiast',
contentCreator: 'Content Creator',
student: 'Student',
retailBuyer: 'Retail / Buyer',
fashionDesigner: 'Fashion Designer',
brandBusiness: 'Brand / Business',
prCommunications: 'PR & Communications',
stylist: 'Stylist',
graphicDesigner: 'Graphic Designer',
artist3d: '3D Artist',
other: 'Other'
},
languages: {
ENGLISH: 'English',
CHINESE_SIMPLIFIED: 'Chinese Simplified'
},
regions: {
hongKongSar: 'Hong Kong SAR',
mainlandChina: 'Mainland China',
singapore: 'Singapore',
unitedKingdom: 'United Kingdom'
}
},
Wardrobe: {
title: 'My Wardrobe',
subtitle: 'Your digital pieces, all in one place',
common: {
all: 'All'
},
tabs: {
ariaLabel: 'Wardrobe tabs',
assets: 'Assets',
orders: 'Orders'
},
sort: {
label: 'Sort by',
placeholder: 'Select',
dateAdded: 'Date Added',
selectedFirst: 'Selected First'
},
assets: {
filters: 'Filters',
clear: 'Clear',
categories: 'Categories',
gender: 'Gender',
selectedCount: '{count} Selected',
selectAll: 'Select All',
deselectAll: 'Deselect All',
downloadSelected: 'Download Selected',
genders: {
male: 'Male',
female: 'Female'
}
},
orders: {
moreItems: '+{count} more',
statuses: {
all: 'All',
paid: 'Paid',
unpaid: 'Unpaid',
cancelled: 'Canceled'
},
statusBadges: {
paid: 'PAID',
unpaid: 'UNPAID',
cancelled: 'CANCELED'
},
actions: {
invoice: 'Invoice',
completePayment: 'Complete Payment',
buyAgain: 'Buy Again'
},
invoiceMessage: 'Invoice is being prepared. Please refresh later.'
},
empty: {
title: 'Nothing in Wardrobe yet',
description: 'Explore the digital item and add pieces to your collection.',
action: 'Explore Digital Items'
}
},
ClothesCategories: {
blouse: 'Blouse',
dress: 'Dress',
trousers: 'Trousers',
skirt: 'Skirt',
tops: 'Tops',
bottoms: 'Bottoms',
outwear: 'Outwear',
others: 'Others'
},
collectionStory: {
back: 'Back to Home',
title: 'Were Seeking',
description: 'Fashion Voice Worth Featuring.',
button: 'Contact Us if Interested',
joinUs: {
title: 'Join Our Designer Community',
info: 'Join our community of visionaries and publish your collection story.',
info2: 'We are currently seeking collections that deeply integrate the AiDA creative workflow, specifically those that resonate through powerful core concepts and evocative inspiration. ',
info3: 'This architecture is designed to elevate your exposure through profound "propositional expression," ensuring that soulful, story-driven designs achieve higher market premiums and superior sales conversion.'
}
},
footer: {
About: 'About',
PrivacyPolicy: 'Privacy Policy',
TermsOfUse: 'Terms of Use',
Disclaimer: 'Disclaimer',
SiteMap: 'Site Map'
},
brand: {
title: 'Brand',
description: "Every brand, every story — discover who's behind the collections.",
search: 'Enter a brand name...',
noFound: 'Brand No Found',
noFoundTip: 'Try using another keywords.',
searchHistory: 'Searching History',
brandItem: {
viewProfile: 'View Profile'
}
},
brandDetail: {
addShoppingTip: 'Please log in first.',
merchantInfo: {
Contact: 'Contact',
About: 'About'
},
All: 'All'
},
digitalItem: {
BestSelling: 'Best Selling',
Price: 'Price: Low to High',
SelectedFirst: 'Selected First',
DateAdded: 'Date Added',
NewestFirst: 'Newest First',
title: 'Digital Item',
info: 'Virtual fashion creations collected in your personal archive',
sortBy: 'Sort By',
noData: 'Nothing in Digital Item',
noDataTip: 'Try adjusting your filters or refreshing the page.',
MerchantInfo: {
Filters: 'Filters',
Clear: 'Clear',
Categories: 'Categories',
Gender: 'Gender'
}
},
checked: {
All: 'All'
},
MainHeader: {
Home: 'Home',
CollectionStory: 'Collection Story',
Brand: 'Brand',
DigitalItem: 'Digital Item',
HiName: 'Hi, {name}',
MyWardrobe: 'My Wardrobe',
Notifications: 'Notifications',
Settings: 'Settings'
},
ShoppingCart: {
title: 'Shopping Cart',
listNullTitle: 'Your Cart is empty',
listNullTip: 'Discover new fashion assets and add them to your cart.',
dateTimeFormat: 'SM D, YYYY, h:mm A',
noLongerAvailable: 'No Longer Available',
delistedFromMarketplace: 'Delisted from marketplace',
remove: 'Remove',
removeTip: 'Are you sure to remove this item?',
total: 'Total',
digitalAssets: 'Digital assets. Creator retains copyright.',
checkout: 'Checkout',
exploreDigitalItems: 'Explore Digital Items',
orderSummary: 'Order Summary',
selected: 'Selected',
brands: 'Brands',
item: 'item',
checkoutSelected: 'Checkout Selected'
},
digitalDetail: {
Sketch: 'Sketch',
Illustration: 'Illustration',
Product: 'Product Image',
EditorialVisual: 'Editorial Visual',
Back: 'Back',
ReleaseIn: 'Release in',
CopyrightLicenseNotice: 'Copyright & License Notice',
LicenseIncludedInAsset: 'License Included in Asset',
LicenseIncludedInAssetInfo:
'All products on this platform are digital assets, not physical goods. Purchase grants a usage license only; copyright and intellectual property rights remain with the original creator, unless otherwise stated.',
BuyNow: 'Buy Now',
AddToCart: 'Add to Cart'
},
Home: {
IndexTitle:
'Were Seeking<br /><span>Fashion Voice</span><br /><span class="small">Worth Featuring.</span>',
IndexTip:
'Discover collections through the stories behind their creation. A curated space connecting designers, narratives, and fashion commerce.',
DesignerTitle: 'Designer Community',
DesignerTip:
'Discover the designers shaping AiDAs creative landscape. <br />Each month, we will showcase a curated selection of their most distinguished works.',
SearchBrands: 'Search Brands',
AidaTitle: 'Design with AiDA',
AidaTip:
'Each garment on this platform is where designer vision blooms through AiDA. A tool that nurtures your creativity, never overshadows it. Let your ideas flourish.',
TryNow: 'Try Now',
DigitalItems: 'Digital Items',
DigitalItemsTip1:
'AiDA captures your boldest thoughts and transforms them into vivid<br/>digital visions—a virtual realm where creativity collides and evolves.',
DigitalItemsTip2:
'AiDA accelerates style innovation, shaping daily pieces that keep<br/>your wardrobe in sync with modern fashions rhythm.',
FooterTip:
"Stylish Parade is a commerce platform for designers, serving as AiDA's commercial extension.",
FooterAidaTip: 'Bloom your Creativity with AiDA!',
Help: 'Help',
FAQ: 'FAQ',
MyAccount: 'My Account',
MyOrders: 'My Orders',
PaymentInvoices: 'Payment and Invoices',
CopyrightLicense: 'Copyright Licence',
Polices: 'Policies',
Legal: 'Legal',
PrivacyPolicy: 'Privacy Policy',
CookiesSettings: 'Cookies Settings',
PurchaseConditions: 'Purchase Conditions',
Company: 'Company',
AboutUs: 'About Us',
Offices: 'Offices',
JoinWithUs: 'Join with Us'
},
addShoppingCart: {
title: 'Added to your Shopping Cart',
statement: 'Digital Assets Only. No physical product included.',
button: 'See Shopping Cart',
status1: 'The product has been purchased. You can view it in your orders.',
status0: 'The order has been placed but not yet paid for.'
},
area: {
chinaMainland: 'China Mainland',
hongKongSar: 'Hong Kong SAR',
macauSar: 'Macau SAR',
taiwan: 'Taiwan',
japan: 'Japan',
southKorea: 'South Korea',
singapore: 'Singapore',
unitedStates: 'United States',
unitedKingdom: 'United Kingdom',
france: 'France',
italy: 'Italy',
germany: 'Germany',
australia: 'Australia',
canada: 'Canada'
},
Pay: {
OrderSummary: 'Order Summary',
PaymentDetails: 'Payment Details',
CreditDebitCard: 'Credit / Debit Card',
AgreementText:
'I agree to the <span onclick="{onTermsClick}">Terms & Conditions</span> and <span onclick="{onPrivacyClick}">Privacy Policy</span>. All digital item sales are final and non-refundable.',
PayWithStripe: 'Pay with Stripe',
PayWith: 'Pay with',
Cancel: 'Cancel',
IHaveCompletedPayment: 'I Have Completed payment',
Back: 'Back',
PayTip1:
"You'll be redirected to a Stripe popup to log in and confirm. No card details are shared with Stylish Parade — Stripe handles all payment security.",
PayTip2:
'Please keep the window open until the payment is completed. If you are to open the payment window, please check your browser settings to see if pop-ups are being blocked. Points may be delayed after successful payment. Please wait 1-3 minutes and click the credits refresh button.',
PurchaseSuccessful: 'Purchase Successful',
PurchaseSuccessfulTip:
'Your digital items are now available and have been saved in Personal Center → My Wardrobe.',
DownloadAllAssets: 'download all Assets',
ExportInvoice: 'Export Invoice',
ContinueShopping: 'Continue Shopping'
}
} }

View File

@@ -1,10 +1,10 @@
export default { export default {
Login: { Login: {
signup: '注册',
login: '登录', login: '登录',
logoff: '退出登录',
register: '注册', register: '注册',
signUp: '注册', loginTip: '与 AiDA 集成的平台。<br />需要登录 AiDA 账户。',
loginTo: '登录到 <span>FiDA</span',
loginTitle: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
name: '姓名', name: '姓名',
email: '邮箱', email: '邮箱',
password: '密码', password: '密码',
@@ -13,11 +13,12 @@ export default {
enterEmail: '请输入邮箱', enterEmail: '请输入邮箱',
enterPassword: '请输入密码', enterPassword: '请输入密码',
enterPasswordAgain: '请输入密码确认', enterPasswordAgain: '请输入密码确认',
passwordTip: '你必须满足所有密码条件才能注册。',
forgotPassword: '忘记密码?', forgotPassword: '忘记密码?',
pleaseInputName: '请输入姓名', pleaseInputName: '请输入姓名',
nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间', nameLengthError: '姓名长度必须在 {min} 到 {max} 个字符之间',
passwordSpecial: '必须包含特殊符号', passwordSpecial: '必须包含特殊符号',
passwordCase: '大小写字母数字混合组合', passwordCase: '字母数字组合',
pleaseInputEmail: '请输入邮箱', pleaseInputEmail: '请输入邮箱',
emailFormatError: '请输入正确的邮箱', emailFormatError: '请输入正确的邮箱',
pleaseInputPassword: '请输入密码', pleaseInputPassword: '请输入密码',
@@ -39,6 +40,376 @@ export default {
wechatLogin: '使用微信登录', wechatLogin: '使用微信登录',
indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。', indexTip: '一个多智能体画布,用于快速、趋势驱动的设计迭代。',
sendCodeError: '发送验证码失败', sendCodeError: '发送验证码失败',
retrievePassword: '找回密码' retrievePassword: '找回密码',
emailVerification: '邮箱验证',
retrievePasswordTitle: '请输入您的邮箱地址以验证您的身份。',
submit: '提交',
enterNewPassword: '请输入新密码<br/><span>{email}</span>',
passwordsDoNotMatch: '两次输入密码不一致',
logOffTip: '确定退出登录吗?',
pleaseLogTip: '请重新登录并重试。'
}, },
RegisterSuccess: {
title1: '欢迎来到 Stylish Parade',
title2: '请切换到登录选项卡以登录。',
title3: '在 Stylish Parade 中等待你的发现',
item1title: '设计灵感',
item1tip: '了解设计师是如何借助 AiDA 将创意变为现实的——从最初的草图到最终的成品。',
item2title: '创意数字作品',
item2tip: '解锁一个增长的数字作品库,刷新你的创意。',
item3title: '时尚社区',
item3tip: '加入一个全球的时尚社区,与设计师分享创意。'
},
Settings: {
title: '设置',
slogan: '管理你的账户设置和偏好',
profile: {
title: '个人资料',
description: '更新你的显示名称、头像、社交链接和账户安全信息。',
firstName: '名字',
lastName: '姓氏',
firstNamePlaceholder: '请输入名字',
lastNamePlaceholder: '请输入姓氏',
username: '用户名',
usernamePlaceholder: '请输入用户名',
usernameTip: '这是你在 Stylish Parade 上公开显示的用户名。',
role: '身份标签',
roleTip: '最多选择 2 个符合你的标签。'
},
avatarCrop: {
title: '裁剪头像',
confirm: '确认',
processing: '处理中...'
},
security: {
title: '安全',
description: '管理你的登录邮箱和密码。',
email: '邮箱',
newEmail: '新邮箱地址',
newEmailPlaceholder: '请输入新邮箱',
verify: '验证',
verified: '已验证',
verifiedTip: '你的新邮箱已验证成功,可以保存。',
password: '密码',
newPassword: '新密码',
newPasswordPlaceholder: '请输入新密码',
passwordTip: '你必须满足所有密码条件才能注册。',
currentPassword: '当前密码',
currentPasswordPlaceholder: '请输入当前密码确认'
},
region: {
title: '语言与地区',
description: '设置你偏好的语言、地区和货币显示方式。',
displayLanguage: '显示语言',
selectLanguage: '请选择语言',
region: '地区',
selectRegion: '请选择地区'
},
buttons: {
cancel: '取消',
discard: '放弃',
edit: '编辑',
saveChange: '保存更改',
verifyEmail: '验证邮箱',
saving: '保存中...'
},
dialog: {
title: '检查你的新邮箱',
subtitle: '请输入发送到以下邮箱的 6 位验证码',
submit: '提交',
resendCode: '重新发送验证码',
resendCodeIn: '{time} 后可重新发送验证码'
},
messages: {
enterNewEmailFirst: '请先输入新的邮箱地址',
invalidEmail: '请输入有效的邮箱地址',
sameEmail: '请输入不同的邮箱地址',
alreadyVerified: '该邮箱已完成验证',
verificationCodeSent: '验证码已发送',
enterVerificationCode: '请输入 6 位验证码',
verificationCompleted: '邮箱验证完成',
verifyEmailBeforeSave: '请先完成新邮箱验证再保存',
currentPasswordRequired: '请输入当前密码',
passwordLengthError: '密码长度必须在 {min} 到 {max} 个字符之间',
passwordSpecial: '密码必须包含特殊符号',
passwordCase: '密码必须包含大小写字母和数字',
passwordNotSameAsOld: '新密码不能与旧密码相同',
settingsUpdated: '设置已更新',
avatarTooLarge: '图片过大,最大 5MB。',
avatarCropFailed: '头像裁剪失败',
avatarUploadUrlMissing: '未获取到上传后的文件地址',
avatarUpdated: '头像已更新',
avatarUploadFailed: '上传失败'
},
roles: {
fashionEnthusiast: '时尚爱好者',
contentCreator: '内容创作者',
student: '学生',
retailBuyer: '零售 / 买手',
fashionDesigner: '服装设计师',
brandBusiness: '品牌 / 商业',
prCommunications: '公关与传播',
stylist: '造型师',
graphicDesigner: '平面设计师',
artist3d: '3D 艺术家',
other: '其他'
},
languages: {
ENGLISH: '英文',
CHINESE_SIMPLIFIED: '简体中文'
},
regions: {
hongKongSar: '中国香港特别行政区',
mainlandChina: '中国大陆',
singapore: '新加坡',
unitedKingdom: '英国'
}
},
Wardrobe: {
title: '我的衣橱',
subtitle: '你的数字单品尽在此处',
common: {
all: '全部'
},
tabs: {
ariaLabel: '衣橱标签页',
assets: '资产',
orders: '订单'
},
sort: {
label: '排序',
placeholder: '请选择',
dateAdded: '添加日期',
selectedFirst: '已选优先'
},
assets: {
filters: '筛选',
clear: '清除',
categories: '类别',
gender: '性别',
selectedCount: '已选择 {count} 件',
selectAll: '全选',
deselectAll: '取消全选',
downloadSelected: '下载已选',
genders: {
male: '男',
female: '女'
}
},
orders: {
moreItems: '还有 {count} 件',
statuses: {
all: '全部',
paid: '已支付',
unpaid: '待支付',
cancelled: '已取消'
},
statusBadges: {
paid: '已支付',
unpaid: '待支付',
cancelled: '已取消'
},
actions: {
invoice: '发票',
completePayment: '完成付款',
buyAgain: '再次购买'
},
invoiceMessage: '发票生成中,请稍后刷新页面查看。'
},
empty: {
title: '衣橱暂无内容',
description: '探索数字单品,并添加到你的收藏。',
action: '探索数字单品'
}
},
ClothesCategories: {
blouse: '衬衫',
dress: '连衣裙',
trousers: '裤子',
skirt: '短裙',
tops: '上装',
bottoms: '下装',
outwear: '外套',
others: '其他'
},
collectionStory: {
back: '返回首页',
title: '我们在寻找',
description: '值得被听见的时尚之声',
button: '如有兴趣,请联系我们',
joinUs: {
title: '加入我们的设计师社区,',
info: '加入我们的远见者社区,发表你的系列故事。',
info2: '我们目前正在寻找深度整合 AiDA 创意工作流程的系列作品,特别是那些通过强大的核心理念和富有感染力的灵感而产生共鸣的作品。',
info3: '这一架构旨在通过深刻的‘命题式表达’提升你的曝光度,确保那些有灵魂、由故事驱动的设计能获得更高的市场溢价和卓越的销售转化率。'
}
},
footer: {
About: '关于我们',
PrivacyPolicy: '隐私政策',
TermsOfUse: '条款与条件',
Disclaimer: '免责声明',
SiteMap: '地图'
},
brand: {
title: '品牌',
description: '每一个品牌,每一个故事 — 发现系列作品背后的缔造者。',
search: '输入一个品牌名字',
noFound: '未找到品牌',
noFoundTip: '请尝试使用其他关键词。',
searchHistory: '搜索历史',
brandItem: {
viewProfile: '查看简介'
}
},
brandDetail: {
addShoppingTip: '请先登录。',
merchantInfo: {
Contact: '联系方式',
About: '关于我们'
},
All: '全部'
},
digitalItem: {
BestSelling: '畅销优先',
Price: '价格:从低到高',
SelectedFirst: '已选优先',
DateAdded: '添加日期',
NewestFirst: '最新优先',
title: '数字藏品',
info: '收藏于个人档案中的虚拟时装作品',
sortBy: '排序方式',
noData: '暂无数字藏品',
noDataTip: '请尝试调整筛选条件或刷新页面。',
MerchantInfo: {
Filters: '筛选',
Clear: '清空',
Categories: '分类',
Gender: '适用性别'
}
},
checked: {
All: '全部'
},
MainHeader: {
Home: '首页',
CollectionStory: '系列故事',
Brand: '品牌',
DigitalItem: '数字藏品',
HiName: '你好,{name}',
MyWardrobe: '我的衣橱',
Notifications: '通知',
Settings: '设置'
},
ShoppingCart: {
title: '购物车',
listNullTitle: '你的购物车为空',
listNullTip: '发现新的时尚资产并将其添加到你的购物车。',
dateTimeFormat: 'YYYY-MM-DD HH:mm',
noLongerAvailable: '已下架',
delistedFromMarketplace: '已从市场移除',
remove: '删除',
removeTip: '确认删除吗?',
total: '总金额',
digitalAssets: '数字资产。创作者保留版权。',
checkout: '结账',
exploreDigitalItems: '探索数字单品',
orderSummary: '订单信息',
selected: '已选',
brands: '品牌',
item: '数字藏品',
checkoutSelected: '结账'
},
digitalDetail: {
Sketch: '草图',
Illustration: '插画',
Product: '产品',
EditorialVisual: '编辑视觉',
Back: '返回',
ReleaseIn: '发布于',
CopyrightLicenseNotice: '版权与许可声明',
LicenseIncludedInAsset: '资产包含许可',
LicenseIncludedInAssetInfo:
'本平台所有产品均为数字资产,非实物商品。购买仅授予使用许可;版权及知识产权仍归原作者所有,除非另有说明。',
BuyNow: '立即购买',
AddToCart: '加入购物车'
},
addShoppingCart: {
title: '已添加到您的购物车',
statement: '仅限数字资产。不包含实体产品。',
button: '去购物车',
status1: '商品已经购买,可在订单中看到',
status0: '商品已下单尚未付款'
},
area: {
chinaMainland: '中国大陆',
hongKongSar: '中国香港特别行政区',
macauSar: '中国澳门特别行政区',
taiwan: '中国台湾',
japan: '日本',
southKorea: '韩国',
singapore: '新加坡',
unitedStates: '美国',
unitedKingdom: '英国',
france: '法国',
italy: '意大利',
germany: '德国',
australia: '澳大利亚',
canada: '加拿大'
},
Home: {
IndexTitle: '我们正在寻<br />找值得推广<br />的时尚之声。',
IndexTip:
'通过了解每件作品背后的故事来探索其收藏品。这是一个精心打造的场所,将设计师、故事和时尚商业紧密相连。',
DesignerTitle: '设计师社区',
DesignerTip: '发现 AiDA 创意社区的设计师。<br />每月我们都会展示他们最杰出的作品。',
SearchBrands: '搜索品牌',
AidaTitle: '使用 AiDA 设计',
AidaTip:
'在这个平台上,每一件服装都是设计师创意在 AiDA 软件中绽放的成果。AiDA 是一款能激发您的创造力的工具,它从不掩盖您的才华。让您的创意尽情绽放吧。',
TryNow: '立即试用',
DigitalItems: '数字藏品',
DigitalItemsTip1:
'AiDA 能够捕捉您最激昂的想法,并将其转化为生动的形象。<br />“数字愿景”一个充满创造力碰撞与发展的虚拟世界。',
DigitalItemsTip2:
'AiDA 风格的创新,打造出实用性强的日常服饰,这些服饰能够经久耐用。<br/>你的衣橱与现代时尚的节奏保持同步。',
FooterTip: 'Stylish Parade 是一个为设计师提供的商业平台,它作为 AiDA 商业的商业扩展。',
FooterAidaTip: '使用 AiDA 设计',
Help: '帮助',
FAQ: '常见问题',
MyAccount: '我的账户',
MyOrders: '我的订单',
PaymentInvoices: '支付发票',
CopyrightLicense: '版权与许可声明',
Polices: '政策',
Legal: '法律',
PrivacyPolicy: '隐私政策',
CookiesSettings: 'Cookie 设置',
PurchaseConditions: '购买条件',
Company: '公司',
AboutUs: '关于我们',
Offices: '办公室',
JoinWithUs: '加入我们'
},
Pay: {
OrderSummary: '订单信息',
PaymentDetails: '支付详情',
CreditDebitCard: '信用卡/借记卡',
AgreementText:
'我同意 <span onclick="{onTermsClick}">使用条款与条件</span> 和 <span onclick="{onPrivacyClick}">隐私政策</span>。所有数字资产销售均为最终销售,不可退款。',
PayWithStripe: '使用 Stripe 支付',
PayWith: '支付',
Cancel: '取消',
IHaveCompletedPayment: '我已完成支付',
Back: '返回',
PayTip1:
'您将被重定向到 Stripe 弹窗以登录并确认支付。您的信用卡信息不会被 Stylish Parade 收集。Stripe 处理所有支付安全。',
PayTip2:
'请保持窗口打开,直到支付完成。如果您打开支付窗口,请检查浏览器设置以查看是否已阻止弹窗。支付完成后,积分可能会有延迟。请等待 1-3 分钟并点击积分刷新按钮。',
PurchaseSuccessful: '购买成功',
PurchaseSuccessfulTip: '您的数字资产已保存在个人中心的我的衣橱中。',
DownloadAllAssets: '下载所有资产',
ExportInvoice: '导出发票',
ContinueShopping: '继续购物'
}
} }

View File

@@ -1,10 +1,27 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import { useUserInfoStore } from '@/stores/userInfo'
import { useGlobalStore } from '@/stores/global'
import { getUserLanguage, fetchUserProfile } from '@/api/user'
import { fetchAllUnreadMessage } from '@/api/notification'
import i18n from '@/lang/index'
import myEvent from '@/utils/myEvent'
// 语言映射:后端格式 -> i18n 格式
const backendToI18nLanguage: Record<string, string> = {
en: 'ENGLISH',
'zh-CN': 'CHINESE_SIMPLIFIED'
}
// 语言同步状态缓存(避免每次路由切换都请求)
let languageSynced = false
/** /**
* 路由缓存机制: * 路由缓存机制:
* 1. 设置路由的meta属性为{ cache: true },表示需要缓存 * 1. 设置路由的meta属性为{ cache: true },表示需要缓存
* 2. App.vue中使用RouteCache组件通过路由的name来进行匹配 * 2. App.vue中使用RouteCache组件通过路由的name来进行匹配
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置 * 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
*
* 需要登录路由: meta={ login:true }
*/ */
const router = createRouter({ const router = createRouter({
routes: [ routes: [
@@ -23,6 +40,11 @@ const router = createRouter({
name: 'brand', name: 'brand',
component: () => import('../views/brand/index.vue') component: () => import('../views/brand/index.vue')
}, },
{
path: '/brand/:id',
name: 'brandDetail',
component: () => import('../views/brandDetail/index.vue')
},
{ {
path: '/digitalItem', path: '/digitalItem',
name: 'digitalItem', name: 'digitalItem',
@@ -32,7 +54,7 @@ const router = createRouter({
{ {
path: '/digitalItem/:id', path: '/digitalItem/:id',
name: 'digitalItemDetail', name: 'digitalItemDetail',
component: () => import('../views/digitalDetail/index.vue'), component: () => import('../views/digitalDetail/index.vue')
}, },
{ {
path: '/settings', path: '/settings',
@@ -41,15 +63,31 @@ const router = createRouter({
meta: { cache: true } meta: { cache: true }
}, },
{ {
path: '/shoppingCart',// 购物车 path: '/shoppingCart', // 购物车
name: 'shoppingCart', name: 'shoppingCart',
component: () => import('@/views/shoppingCart/index.vue') component: () => import('@/views/shoppingCart/index.vue'),
meta: { login: true }
}, },
{ {
path: '/notifications', path: '/notifications',
name: 'notifications', name: 'notifications',
component: () => import('@/views/notifications/index.vue') component: () => import('@/views/notifications/index.vue')
}, },
{
path: '/wardrobe',
name: 'wardrobe',
component: () => import('@/views/wardrobe/index.vue')
},
{
path: '/account',
name: 'account',
component: () => import('@/views/account/index.vue')
},
{
path: '/pay',
name: 'pay',
component: () => import('@/views/pay/index.vue')
},
{ {
path: '/:pathMatch(.*)', path: '/:pathMatch(.*)',
name: '404', name: '404',
@@ -60,9 +98,71 @@ const router = createRouter({
}) })
router.beforeEach((to, from, next) => { router.beforeEach((to, from, next) => {
if (to.meta?.login && !useUserInfoStore().state.token) {
myEvent.emit('openLoginDialog')
}
next() next()
}) })
router.afterEach(() => {}) router.afterEach(async () => {
const userInfoStore = useUserInfoStore()
const token = userInfoStore.state.token
if (!token) {
languageSynced = false
return
}
const avatarUrl = userInfoStore.state.userInfo.avatarUrl
if (!avatarUrl) {
fetchUserProfile().then((res) => {
const profile = res as any
if (profile.avatarUrl) {
userInfoStore.setAvatarUrl(profile.avatarUrl)
}
})
}
const globalStore = useGlobalStore()
fetchAllUnreadMessage().then((res) => {
globalStore.setUnredCount(res.totalUnread)
})
if (languageSynced) {
return
}
try {
// 获取用户语言设置
const response = await getUserLanguage()
const userLanguage = (response as any)?.language // 后端返回 'en' 或 'zh-CN'
if (!userLanguage) {
return
}
// 转换为 i18n 格式
const i18nLanguage = backendToI18nLanguage[userLanguage]
if (!i18nLanguage) {
return
}
// 获取当前 i18n 语言
const currentLocale = i18n.global.locale.value
// 如果用户语言和本地 i18n 不一致,更新 i18n
if (i18nLanguage !== currentLocale) {
i18n.global.locale.value = i18nLanguage as 'ENGLISH' | 'CHINESE_SIMPLIFIED'
localStorage.setItem('language', i18nLanguage)
}
// 标记已同步
languageSynced = true
} catch (error) {
// 静默失败,不影响页面正常加载
console.warn('Failed to sync user language:', error)
}
})
export default router export default router

View File

@@ -2,13 +2,20 @@ import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
export const useGlobalStore = defineStore('global', () => { export const useGlobalStore = defineStore('global', () => {
const state = ref({ const state = ref({
loading: false loading: false,
unReadMessageCount: 0
}) })
const setLoading = (v: boolean) => { state.value.loading = v } const setLoading = (v: boolean) => {
state.value.loading = v
}
const setUnredCount = (number: number) => {
state.value.unReadMessageCount = number
}
return { return {
state, state,
setLoading, setLoading,
setUnredCount
} }
}) })

View File

@@ -1,25 +1,27 @@
// 每一个存储的模块命名规则use开头store结尾 // 每一个存储的模块命名规则use开头store结尾
import { AccountLogout } from '@/api/account'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { removeLocal, setLocal } from '@/utils/local' import { removeLocal, setLocal } from '@/utils/local'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
import router from '@/router'
export const useUserInfoStore = defineStore('userInfo', () => { export const useUserInfoStore = defineStore('userInfo', () => {
const state = ref({ const state = ref({
userInfo: {}, userInfo: {
token: '', userId: "",
generateParams: { email: "",
stylist: '', username: "",
sex: '', accessToken: "",
stylistImage: '' expiresIn: "",
} avatarUrl: "",
},
token: ''
}) })
// getters
const getUserInfo = computed(() => state.value.userInfo)
// actions // actions
const setUserInfo = (data: any) => { const setUserInfo = (data: any) => {
state.value.userInfo = data state.value.userInfo = data
setToken(data.accessToken)
} }
const setToken = (data: string) => { const setToken = (data: string) => {
@@ -27,44 +29,33 @@ export const useUserInfoStore = defineStore('userInfo', () => {
setLocal(data, 'token') setLocal(data, 'token')
} }
const getGenerateParams = () => { const setAvatarUrl = (url: string) => {
return state.value.generateParams state.value.userInfo.avatarUrl = url
} }
const setGenerateParams = (data: any) => { const logout = async (reload: boolean = false) => {
state.value.generateParams = data
}
const resetGenerateParams = () => {
state.value.generateParams = {
stylist: '',
sex: '',
stylistImage: ''
}
}
const logOut = () => {
// 处理退出登录的一些逻辑 // 处理退出登录的一些逻辑
return new Promise((resolve) => { const userId = state.value.userInfo.userId
if (userId) await AccountLogout({ userId })
state.value.userInfo = {
userId: "",
email: "",
username: "",
accessToken: "",
expiresIn: "",
avatarUrl: "",
}
state.value.token = '' state.value.token = ''
state.value.userInfo = {}
removeLocal('token') removeLocal('token')
resetGenerateParams() if (reload) router.go(0)
MyEvent.emit('clear-generate-state')
MyEvent.emit('clear-client-state')
MyEvent.emit('clearAllCache')
resolve('')
})
} }
return { return {
state, state,
getUserInfo,
setToken, setToken,
setUserInfo, setUserInfo,
setGenerateParams, setAvatarUrl,
getGenerateParams, logout
resetGenerateParams,
logOut
} }
}) })

View File

@@ -0,0 +1,71 @@
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import i18n from '@/lang'
type Translate = (key: string) => string
const clothesCategoryConfigs = [
{
key: 'blouse',
value: 'blouse'
},
{
key: 'dress',
value: 'dress'
},
{
key: 'trousers',
value: 'trousers'
},
{
key: 'skirt',
value: 'skirt'
},
{
key: 'tops',
value: 'tops'
},
{
key: 'bottoms',
value: 'bottoms'
},
{
key: 'outwear',
value: 'outwear'
},
{
key: 'others',
value: 'others'
}
] as const
export type ClothesCategoryValue = (typeof clothesCategoryConfigs)[number]['value']
export interface ClothesCategory {
name: string
label: string
value: ClothesCategoryValue
}
export const createClothesCategories = (t: Translate = i18n.global.t): ClothesCategory[] =>
clothesCategoryConfigs.map(({ key, value }) => ({
name: t(`ClothesCategories.${key}`),
label: t(`ClothesCategories.${key}`),
value
}))
export const ClothesCategories: ClothesCategory[] = clothesCategoryConfigs.map(({ key, value }) => ({
get name() {
return i18n.global.t(`ClothesCategories.${key}`)
},
get label() {
return i18n.global.t(`ClothesCategories.${key}`)
},
value
}))
export const useClothesCategories = () => {
const { t } = useI18n({ useScope: 'global' })
return computed(() => createClothesCategories(t))
}

12
src/utils/UrlList.js Normal file
View File

@@ -0,0 +1,12 @@
export const UrlList = {
aida: 'https://www.aida.com.hk/',
codeCreate: 'https://www.code-create.com/',
terms: 'https://www.code-create.com.hk/terms-of-use/',
privacy: 'https://www.code-create.com.hk/privacy-policy/',
faq: 'https://code-create.com.hk/help-centre/',
aboutUs: 'https://code-create.com.hk/about-us/',
joinWithUs: 'https://code-create.com.hk/contact-us/',
}
export const openView = (url) => {
window.open(url, '_blank')
}

86
src/utils/area.ts Normal file
View File

@@ -0,0 +1,86 @@
export default [
{
key: 'chinaMainland',
name: 'China Mainland',
label: 'China Mainland',
value: 'China Mainland'
},
{
key: 'hongKongSar',
name: 'Hong Kong SAR',
label: 'Hong Kong SAR',
value: 'Hong Kong SAR'
},
{
key: 'macauSar',
name: 'Macau SAR',
label: 'Macau SAR',
value: 'Macau SAR'
},
{
key: 'taiwan',
name: 'Taiwan',
label: 'Taiwan',
value: 'Taiwan'
},
{
key: 'japan',
name: 'Japan',
label: 'Japan',
value: 'Japan'
},
{
key: 'southKorea',
name: 'South Korea',
label: 'South Korea',
value: 'South Korea'
},
{
key: 'singapore',
name: 'Singapore',
label: 'Singapore',
value: 'Singapore'
},
{
key: 'unitedStates',
name: 'United States',
label: 'United States',
value: 'United States'
},
{
key: 'unitedKingdom',
name: 'United Kingdom',
label: 'United Kingdom',
value: 'United Kingdom'
},
{
key: 'france',
name: 'France',
label: 'France',
value: 'France'
},
{
key: 'italy',
name: 'Italy',
label: 'Italy',
value: 'Italy'
},
{
key: 'germany',
name: 'Germany',
label: 'Germany',
value: 'Germany'
},
{
key: 'australia',
name: 'Australia',
label: 'Australia',
value: 'Australia'
},
{
key: 'canada',
name: 'Canada',
label: 'Canada',
value: 'Canada'
}
]

View File

@@ -2,6 +2,8 @@ import axios from 'axios'
import router from '@/router/index' import router from '@/router/index'
import { useGlobalStore, useUserInfoStore } from '@/stores' import { useGlobalStore, useUserInfoStore } from '@/stores'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import i18n from '@/lang'
const t = i18n.global.t
// 扩展 AxiosRequestConfig 接口 // 扩展 AxiosRequestConfig 接口
declare module 'axios' { declare module 'axios' {
@@ -41,7 +43,7 @@ service.interceptors.request.use(
// Do something before request is sent // Do something before request is sent
const token = useUserInfoStore().state.token const token = useUserInfoStore().state.token
if (token) { if (token) {
config.headers.Authorization = 'Bearer ' + token // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改 config.headers.Authorization = token // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
// config.headers['X-Token'] = getLocal('token') // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改 // config.headers['X-Token'] = getLocal('token') // 让每个请求携带token--['X-Token']为自定义key 请根据实际情况自行修改
} }
return config return config
@@ -65,6 +67,16 @@ service.interceptors.response.use(
if (response.config.url.includes('llm/streamChat')) { if (response.config.url.includes('llm/streamChat')) {
return response return response
} }
// 如果是二进制下载blob/arraybuffer直接返回原始 response 以便调用方处理文件
if (
response.config.responseType === 'blob' ||
response.config.responseType === 'arraybuffer' ||
response.headers['content-type'] === 'application/octet-stream'
) {
removePending(response.config)
if (response.config.loading) closeLoading()
return response
}
// 已完成请求的删除请求中数组 // 已完成请求的删除请求中数组
removePending(response.config) removePending(response.config)
@@ -75,9 +87,10 @@ service.interceptors.response.use(
const res = response.data const res = response.data
// 处理异常的情况 // 处理异常的情况
// console.log(res) // console.log(res)
if (res.code != 200) { if (res.errCode != 0) {
ElMessage.error(res.message) let msg = res.errMsg || res.message || 'error'
return Promise.reject(new Error(res.errMsg || res.message || 'error')) ElMessage.error(msg)
return Promise.reject(new Error(msg))
} else { } else {
// 默认只返回data不返回状态码和message // 默认只返回data不返回状态码和message
// 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码message和data) // 通过 meta 中的 responseAll 配置来取决后台是否返回所有数据(包括状态码message和data)
@@ -106,11 +119,10 @@ service.interceptors.response.use(
// }) // })
ElMessage({ ElMessage({
type: 'error', type: 'error',
message: 'Please log in and try again.', message: t("Login.pleaseLogTip"),
duration: 5000 duration: 5000
}) })
router.push('/login') useUserInfoStore().logout()
useUserInfoStore().logOut(false)
return Promise.reject(false) return Promise.reject(false)
} }
error.config && removePending(error.config) error.config && removePending(error.config)

View File

@@ -197,14 +197,16 @@ export function CountDown(time: number) {
/** /**
* 字节转换为可读格式 * 字节转换为可读格式
* @param {number} bytes - 字节数 * @param {number} bytes - 字节数
* @param {number} decimals - 保留小数位数默认2位 * @param {number} options - 选项对象
* @param {number} options.decimals - 保留小数位数默认2位
* @param {boolean} options.unitBig - 是否使用大写单位默认false
* @returns {string} 格式化后的字符串 * @returns {string} 格式化后的字符串
*/ */
export function FormatBytes(bytes, decimals = 2) { export function FormatBytes(bytes, options: { decimals?: number, unitBig?: boolean } = {}) {
if (bytes === 0) return '0 B'; const { decimals = 2, unitBig = false } = options;
if (!bytes || isNaN(bytes)) return '0 B'; if (!bytes || isNaN(bytes)) return unitBig ? '0 B' : '0 b';
const k = 1024; const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; const sizes = unitBig ? ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['b', 'kb', 'mb', 'gb', 'tb', 'pb', 'eb', 'zb', 'yb'];
const i = Math.floor(Math.log(bytes) / Math.log(k)); const i = Math.floor(Math.log(bytes) / Math.log(k));
const value = bytes / Math.pow(k, i); const value = bytes / Math.pow(k, i);
return `${Number(value.toFixed(decimals))} ${sizes[i]}`; return `${Number(value.toFixed(decimals))} ${sizes[i]}`;

252
src/utils/websocket.ts Normal file
View File

@@ -0,0 +1,252 @@
/**
* WebSocket 管理类
*/
import { useGlobalStore } from '@/stores'
class WebSocketManager {
private ws: WebSocket | null = null
private url: string = ''
private reconnectTimer: number | null = null
private reconnectAttempts: number = 0
private maxReconnectAttempts: number = 5
private reconnectInterval: number = 3000
private heartbeatTimer: number | null = null
private heartbeatInterval: number = 30000
private isManualClose: boolean = false
/**
* 连接 WebSocket
* @param token 用户 token
*/
connect(token: string) {
if (!token) {
console.warn('WebSocket: token 为空,无法建立连接')
return
}
// 如果已经有连接在进行中,先关闭
if (this.ws) {
if (this.ws.readyState === WebSocket.CONNECTING) {
console.log('WebSocket 正在连接中,跳过重复连接')
return
}
if (this.ws.readyState === WebSocket.OPEN) {
console.log('WebSocket 已连接,跳过重复连接')
return
}
}
// 根据当前页面协议自动选择 ws 或 wss
// const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const protocol = 'wss:'
// 从环境变量获取 WebSocket 主机地址
// const wsHost = import.meta.env.VITE_WS_HOST || '18.167.251.121:10094'
const wsHost = 'www.develop-ms.api.aida.com.hk'
this.url = `${protocol}//${wsHost}/ws?token=${token}`
this.isManualClose = false
console.log('WebSocket 开始连接:', this.url.replace(/token=.+/, 'token=***'))
try {
this.ws = new WebSocket(this.url)
this.initEventHandlers()
} catch (error) {
console.error('WebSocket 连接失败:', error)
this.reconnect(token)
}
}
/**
* 初始化事件处理器
*/
private initEventHandlers() {
if (!this.ws) return
this.ws.onopen = () => {
console.log('✅ WebSocket 连接成功')
this.reconnectAttempts = 0
this.startHeartbeat()
}
this.ws.onmessage = (event) => {
console.log('📨 WebSocket 收到消息:', event.data)
try {
const data = JSON.parse(event.data)
this.handleMessage(data)
} catch (error) {
console.error('WebSocket 消息解析失败:', error)
}
}
this.ws.onerror = (error) => {
console.error('❌ WebSocket 错误:', error)
console.error('可能的原因:')
console.error('1. 服务器未运行或地址错误')
console.error('2. Token 无效或已过期')
console.error('3. 网络连接问题')
console.error('4. 服务器拒绝连接')
}
this.ws.onclose = (event) => {
const closeReasons: Record<number, string> = {
1000: '正常关闭',
1001: '端点离开',
1002: '协议错误',
1003: '不支持的数据类型',
1006: '连接异常关闭(可能是网络问题或服务器未响应)',
1007: '数据格式错误',
1008: '违反策略',
1009: '消息过大',
1010: '扩展协商失败',
1011: '服务器错误',
1015: 'TLS 握手失败'
}
const reason = closeReasons[event.code] || '未知原因'
console.log(`🔌 WebSocket 连接关闭 [代码: ${event.code}] ${reason}`)
if (event.reason) {
console.log('关闭原因:', event.reason)
}
this.stopHeartbeat()
// 如果是异常关闭(非正常关闭码),才尝试重连
if (!this.isManualClose && event.code !== 1000) {
const token = this.extractTokenFromUrl()
if (token) {
this.reconnect(token)
}
} else if (event.code === 1000) {
console.log('连接正常关闭,不进行重连')
}
}
}
/**
* 处理接收到的消息
* @param data 消息数据
*/
private handleMessage(data: any) {
// 这里可以根据消息类型进行不同的处理
// 例如:通知、消息推送等
if (data.type === 'unread') {
// 处理通知消息
const info = data.data
// console.log('收到通知-----:', info)
useGlobalStore().setUnredCount(info.unreadCount)
// 可以触发自定义事件或调用回调函数
}
}
/**
* 发送消息
* @param data 要发送的数据
*/
send(data: any) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const message = typeof data === 'string' ? data : JSON.stringify(data)
this.ws.send(message)
} else {
console.warn('WebSocket 未连接,无法发送消息')
}
}
/**
* 重连
* @param token 用户 token
*/
private reconnect(token: string) {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('❌ WebSocket 重连次数已达上限,停止重连')
console.error('请检查:')
console.error('1. 服务器是否正常运行')
console.error('2. Token 是否有效')
console.error('3. 网络连接是否正常')
return
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
}
this.reconnectAttempts++
const delay = this.reconnectInterval * this.reconnectAttempts // 递增延迟
console.log(
`🔄 WebSocket 将在 ${delay / 1000} 秒后尝试重连 (${this.reconnectAttempts}/${
this.maxReconnectAttempts
})`
)
this.reconnectTimer = window.setTimeout(() => {
this.connect(token)
}, delay)
}
/**
* 开始心跳
*/
private startHeartbeat() {
this.stopHeartbeat()
this.heartbeatTimer = window.setInterval(() => {
this.send({ type: 'heartbeat', timestamp: Date.now() })
}, this.heartbeatInterval)
}
/**
* 停止心跳
*/
private stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer)
this.heartbeatTimer = null
}
}
/**
* 从 URL 中提取 token
*/
private extractTokenFromUrl(): string | null {
if (!this.url) return null
const match = this.url.match(/token=([^&]+)/)
return match ? match[1] : null
}
/**
* 关闭连接
*/
close() {
this.isManualClose = true
this.stopHeartbeat()
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
if (this.ws) {
this.ws.close()
this.ws = null
}
console.log('WebSocket 已手动关闭')
}
/**
* 获取连接状态
*/
getReadyState(): number {
return this.ws ? this.ws.readyState : WebSocket.CLOSED
}
/**
* 是否已连接
*/
isConnected(): boolean {
return this.ws !== null && this.ws.readyState === WebSocket.OPEN
}
}
// 导出单例
export const wsManager = new WebSocketManager()

505
src/views/account/index.vue Normal file
View File

@@ -0,0 +1,505 @@
<template>
<div class="account-container">
<img src="@/assets/images/account/account-bg.png" alt="" class="banner" />
<div class="account-main">
<aside class="designer-panel">
<div class="designer-avatar">
<img :src="designerPortrait" alt="Lian Su" />
</div>
<div class="designer-content">
<section class="designer-heading">
<div class="designer-name">Lian Su</div>
<h1 class="designer-title">Roaming Clouds</h1>
</section>
<section class="designer-section">
<h2 class="section-title">Contact</h2>
<div class="contact-list">
<div class="contact-item">
<SvgIcon name="brand-email" size="24" />
<span>lian.su.studio@mail.com</span>
</div>
<div class="contact-item">
<SvgIcon name="brand-call" size="24" />
<span>+86 139 4829 7710</span>
</div>
<div class="contact-item">
<SvgIcon name="brand-link" size="24" />
<span>746312432</span>
</div>
<div class="contact-item">
<SvgIcon name="brand-link" size="24" />
<span>https://urieworweoo.com</span>
</div>
</div>
</section>
<section class="designer-section">
<h2 class="section-title">About</h2>
<p class="designer-about">
Lian Su's work weaves understated ethnic influences into contemporary minimalism.
She explores materials and silhouettes that bridge heritage and modern sensibilities.
Her designs reflect a quiet dialogue between cultural memory and forward-looking innovation.
</p>
</section>
</div>
</aside>
<section class="items-section">
<header class="items-header">
<h2 class="items-title">Items</h2>
<div class="items-tabs" role="tablist" aria-label="Item gender filters">
<button
v-for="tab in tabs"
:key="tab"
type="button"
class="items-tab"
:class="{ active: activeTab === tab }"
:aria-selected="activeTab === tab"
role="tab"
@click="activeTab = tab"
>
{{ tab }}
</button>
</div>
</header>
<div class="items-grid">
<article v-for="item in visibleItems" :key="item.id" class="product-card">
<div class="product-image">
<img
:src="item.url"
:alt="item.title"
:style="{ transform: `translateY(${item.imageOffset})` }"
loading="lazy"
/>
</div>
<div class="product-info">
<div class="product-copy">
<h3 class="product-name">{{ item.title }}</h3>
<div class="product-price">{{ item.price }}</div>
</div>
<button
type="button"
class="add-button"
:aria-label="`Add ${item.title} to cart`"
@click="addShopping(item)"
>
<SvgIcon name="add" size="24" color="#232323" />
</button>
</div>
</article>
</div>
</section>
</div>
<Footer />
</div>
</template>
<script setup lang="ts">
import { computed, shallowRef } from 'vue'
import myEvent from '@/utils/myEvent'
import designerPortrait from '@/assets/images/account/designer-lian-su.png'
import item01 from '@/assets/images/account/item-01.png'
import item02 from '@/assets/images/account/item-02.png'
import item03 from '@/assets/images/account/item-03.png'
import item04 from '@/assets/images/account/item-04.png'
import item05 from '@/assets/images/account/item-05.png'
import item06 from '@/assets/images/account/item-06.png'
import item07 from '@/assets/images/account/item-07.png'
import item08 from '@/assets/images/account/item-08.png'
import item09 from '@/assets/images/account/item-09.png'
type Gender = 'Male' | 'Female'
type Tab = 'All' | Gender
interface AccountItem {
id: number
title: string
price: string
url: string
gender: Gender
imageOffset: string
}
const tabs: Tab[] = ['All', 'Male', 'Female']
const activeTab = shallowRef<Tab>('All')
const items: AccountItem[] = [
{ id: 1, title: 'Item Name', price: '$430', url: item01, gender: 'Female', imageOffset: '0rem' },
{ id: 2, title: 'Item Name', price: '$392', url: item02, gender: 'Female', imageOffset: '0rem' },
{ id: 3, title: 'Item Name', price: '$211', url: item03, gender: 'Male', imageOffset: '0rem' },
{ id: 4, title: 'Item Name', price: '$187', url: item04, gender: 'Female', imageOffset: '0rem' },
{ id: 5, title: 'Item Name', price: '$325', url: item05, gender: 'Male', imageOffset: '-1.4rem' },
{ id: 6, title: 'Item Name', price: '$458', url: item06, gender: 'Female', imageOffset: '-0.4rem' },
{ id: 7, title: 'Item Name', price: '$192', url: item07, gender: 'Male', imageOffset: '-3.6rem' },
{ id: 8, title: 'Item Name', price: '$93', url: item08, gender: 'Female', imageOffset: '0rem' },
{ id: 9, title: 'Item Name', price: '$198', url: item09, gender: 'Male', imageOffset: '-3.8rem' }
]
const visibleItems = computed(() => {
if (activeTab.value === 'All') return items
return items.filter((item) => item.gender === activeTab.value)
})
const addShopping = (item: AccountItem) => {
myEvent.emit('addShopping', item)
}
</script>
<style lang="less" scoped>
.account-container {
width: 100%;
height: 100%;
position: relative;
overflow-y: auto;
background-color: #ffffff;
.banner {
display: block;
width: 100%;
height: 27.6rem;
object-fit: cover;
}
}
.account-main {
display: flex;
align-items: flex-start;
margin: 0 9rem;
min-height: 170.3rem;
border-top: 0.5px solid #585858;
}
.designer-panel {
position: sticky;
top: 0;
width: 29.7rem;
height: var(--app-view-height);
padding-top: 4rem;
overflow-y: auto;
flex: 0 0 29.7rem;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
.designer-avatar {
width: 20rem;
height: 20rem;
margin-left: 5.5rem;
border: 0.1rem solid #d2d2d7;
> img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
}
.designer-content {
display: flex;
flex-direction: column;
gap: 6rem;
width: 24.6rem;
margin-top: 4rem;
margin-left: 3.7rem;
}
.designer-heading,
.designer-section {
width: 100%;
}
.designer-name {
margin-bottom: 0.8rem;
font-family: KaiseiOpti-Medium;
font-size: 1.8rem;
font-weight: 500;
line-height: 1;
color: #232323;
}
.designer-title,
.section-title {
margin: 0;
font-family: KaiseiOpti-Bold;
font-weight: 700;
color: #121212;
}
.designer-title {
width: 23.1rem;
font-size: 3.4rem;
line-height: 3.6rem;
}
.section-title {
margin-bottom: 2rem;
font-size: 3.4rem;
line-height: 3.1rem;
}
.contact-list {
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.contact-item {
display: flex;
align-items: center;
gap: 2rem;
min-width: 0;
font-family: KaiseiOpti-Regular;
font-size: 1.4rem;
font-weight: 400;
line-height: 2.4rem;
color: #585858;
> :first-child {
width: 2.4rem;
height: 2.4rem;
flex: 0 0 2.4rem;
}
> span {
min-width: 0;
overflow-wrap: anywhere;
}
}
.designer-about {
width: 22rem;
margin: 0;
font-family: KaiseiOpti-Regular;
font-size: 1.6rem;
font-weight: 400;
line-height: 2.3rem;
color: #585858;
}
.items-section {
flex: 1;
min-height: 170.3rem;
border-left: 0.5px solid #585858;
border-right: 0.5px solid #585858;
}
.items-header {
position: sticky;
top: 0;
z-index: 2;
background-color: #ffffff;
}
.items-title {
margin: 0;
padding: 4rem 0 3.6rem 1.2rem;
font-family: KaiseiOpti-Bold;
font-size: 3.6rem;
font-weight: 700;
line-height: 6rem;
color: #121212;
}
.items-tabs {
display: flex;
gap: 2rem;
padding: 0 1.2rem;
margin-bottom: 5.9rem;
}
.items-tab {
position: relative;
min-width: 6rem;
padding: 0;
border: none;
background: transparent;
font-family: KaiseiOpti-Regular;
font-size: 1.988rem;
font-weight: 400;
line-height: 2.6rem;
text-align: center;
color: #7b7b7b;
cursor: pointer;
&::after {
content: '';
position: absolute;
left: 0;
bottom: -0.1rem;
display: none;
width: 100%;
border-bottom: 0.1rem solid #232323;
}
&.active {
font-family: KaiseiOpti-Bold;
font-weight: 700;
color: #232323;
&::after {
display: block;
}
}
}
.items-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
align-content: start;
border-top: 0.5px solid #585858;
}
.product-card {
position: relative;
min-height: 44.7rem;
padding: 1.2rem;
&::before,
&::after {
content: '';
position: absolute;
z-index: 1;
pointer-events: none;
}
&::before {
top: 0;
right: 0;
height: 100%;
border-right: 0.5px solid #585858;
}
&::after {
left: 0;
bottom: 0;
width: 100%;
border-bottom: 0.5px solid #585858;
}
&:nth-child(3n)::before {
display: none;
}
}
.product-image {
height: 37.5rem;
margin-bottom: 0.8rem;
overflow: hidden;
background-color: #f7f7f7;
> img {
display: block;
width: 100%;
height: auto;
will-change: transform;
}
}
.product-info {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1.2rem;
}
.product-copy {
min-width: 0;
}
.product-name {
margin: 0;
font-family: KaiseiOpti-Regular;
font-size: 1.6rem;
font-weight: 400;
line-height: 2.3rem;
color: #232323;
}
.product-price {
font-family: KaiseiOpti-Regular;
font-size: 1.4rem;
font-weight: 400;
line-height: 2.3rem;
color: #585858;
}
.add-button {
width: 2.4rem;
height: 2.4rem;
padding: 0;
margin-top: 1rem;
border: none;
background: transparent;
cursor: pointer;
flex: 0 0 2.4rem;
}
@media (max-width: 900px) {
.account-main {
display: block;
margin: 0 2.4rem;
min-height: auto;
}
.designer-panel {
position: static;
width: 100%;
height: auto;
padding: 4rem 0;
overflow: visible;
}
.designer-avatar {
margin-left: 0;
}
.designer-content {
width: 100%;
}
.items-section {
min-height: auto;
border-left: none;
border-right: none;
}
.items-header {
position: static;
}
.items-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.product-card:nth-child(3n)::before {
display: block;
}
.product-card:nth-child(2n)::before {
display: none;
}
}
@media (max-width: 600px) {
.account-main {
margin: 0 1.6rem;
}
.items-grid {
grid-template-columns: 1fr;
}
.product-card::before {
display: none;
}
}
</style>

View File

@@ -0,0 +1,127 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
const props = defineProps({
item:{
type:Object,
default:()=>{},
},
})
const emit = defineEmits([
'viewProfile',
])
const viewProfile = (item) => {
emit('viewProfile', item)
}
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="item">
<div class="left">
<div class="portrait">
<img v-avatarLoad="item.avatar" alt="">
</div>
<div class="info">
<div class="name">{{ item.shopName }}</div>
<div class="collection">
{{ item.ownerName }} |
{{ item?.listingTotal || 0 }} Collections
</div>
<div class="view-profile" @click="viewProfile(item)">{{ $t('brand.brandItem.viewProfile') }}</div>
</div>
</div>
<div class="right">
<div class="img-list">
<div class="img-item" v-for="itemImg in item?.covers" :key="itemImg">
<img :src="itemImg" alt="">
</div>
</div>
<div class="more">
<div class="icon" v-show="item?.covers?.length == 5">
<svgIcon name="brand-more" size="24" />
</div>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.item{
display: flex;
width: 100%;
height: 14rem;
justify-content: space-between;
border-bottom: 0.5px solid #C4C4C4;
> .left{
display: flex;
.portrait{
width: 8rem;
height: 8rem;
margin-right: 2.4rem;
> img{
width: 100%;
height: 100%;
object-fit: cover;
}
}
.info{
display: flex;
flex-direction: column;
align-items: flex-start;
> .name{
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 2rem;
line-height: 100%;
}
> .collection{
color: #7B7B7B;
font-family: "KaiseiOpti-Regular";
font-weight: 400;
font-size: 1.2rem;
line-height: 2.3rem;
margin-top: .4rem;
}
> .view-profile{
margin-top: 2.4rem;
border-bottom: 2px solid #585858;
background: #F9F9F9;
font-family: "KaiseiOpti-Regular";
font-weight: 400;
font-size: 1.4rem;
letter-spacing: -0.4px;
line-height: 3.4rem;
padding: 0 2.55rem;
cursor: pointer;
}
}
}
> .right{
display: flex;
> .img-list{
display: flex;
gap: 3.2rem;
> .img-item{
width: 9.2rem;
height: 12rem;
img{
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
> .more{
width: 8rem;
display: flex;
justify-content: center;
align-items: center;
}
}
}
</style>

View File

@@ -1,169 +0,0 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import img from "@/assets/images/collectionStory/Rectangle.png";
//const props = defineProps({
//})
const emit = defineEmits([
'addShopping'
])
let data = reactive({
})
const list = ref([
{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},
])
const type = ref('All')
const addShopping = (item) => {
emit('addShopping', item)
}
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="commodityList">
<div class="header">
<div class="title">
Items
</div>
<div class="menu">
<div :class="{'active': type === 'All'}" @click="type = 'All'">All</div>
<div :class="{'active': type === 'Male'}" @click="type = 'Male'">Male</div>
<div :class="{'active': type === 'Female'}" @click="type = 'Female'">Female</div>
</div>
</div>
<div class="list">
<div class="item" v-for="item in list" :key="item.url">
<CommodityItem :url="item.url" :name="item.title" :price="item.price" @addShopping="addShopping(item)"></CommodityItem>
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.commodityList{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
.header{
position: sticky;
top: 0;
z-index: 2;
background-color: #fff;
.title{
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 3.6rem;
line-height: 6rem;
color: #121212;
padding: 4rem 0 3.6rem 1.2rem;
}
.menu{
padding: 0 1.2rem;
display: flex;
gap: 2rem;
margin-bottom: 6rem;
> div{
min-width: 6rem;
text-align: center;
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 1.98rem;
line-height: 100%;
position: relative;
color: #7B7B7B;
cursor: pointer;
&::after{
content: '';
position: absolute;
bottom: -5px;
left: 0;
display: block;
width: 100%;
border-bottom: 1px solid #232323;
display: none;
}
&.active{
color: #232323;
&::after{
display: block;
}
}
}
}
}
.list{
border-top: 0.5px solid #585858;
width: 100%;
flex: 1;
display: grid;
align-content: start;
grid-template-columns: repeat(3, 1fr);
overflow-y: auto;
/* 垂直线(右边框) */
.item{
position: relative;
padding: 1.2rem;
}
.item::before {
content: '';
position: absolute;
right: 0;
top: 0;
height: 100%;
border-right: 0.5px solid #585858;
z-index: 1;
}
/* 水平线(下边框) */
.item::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
border-bottom: 0.5px solid #585858;
z-index: 1;
}
/* 移除最后一列的右边框 */
.item:nth-child(3n)::before {
display: none;
}
}
}
</style>

View File

@@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import CommodityList from "./commodity-list.vue";
import MerchantInfo from "./merchant-info.vue";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import myEvent from '@/utils/myEvent' import myEvent from '@/utils/myEvent'
import scListNull from '@/views/shoppingCart/sc-list-null.vue'
import brandItem from '@/views/brand/brand-item.vue'
import { getDesignerList } from '@/api/brand'
//const props = defineProps({ //const props = defineProps({
//}) //})
//const emit = defineEmits([ //const emit = defineEmits([
@@ -11,13 +13,109 @@ import myEvent from '@/utils/myEvent'
const router = useRouter() const router = useRouter()
let data = reactive({ let data = reactive({
}) })
const addShopping = (item) => {
myEvent.emit('addShopping', item) const searchBrand = ref('')
const merchantList = ref([
])
const getMerchantData = reactive({
pageSize: 10,
pageNum: 1,
isShowMark:false,
isNoData:false,
})
const searchHistory = ref([
])
let changeSearchBrandTime = null
const changeSearchBrand = () => {
if(!searchBrand.value)return
clearTimeout(changeSearchBrandTime)
changeSearchBrandTime = setTimeout(()=>{
merchantList.value = []
getDesignerList({
keyword: searchBrand.value,
}).then((res)=>{
merchantList.value.push(...res)
})
},500)
// changeSearchBrandTime = setTimeout(()=>{
// getMerchantData.pageNum = 1
// getMerchantData.isShowMark = false
// getMerchantData.isNoData = false
// },300)
} }
const openDetail = (item) => {
router.push({name: 'digitalDetail', params: {id: item.id}}) // const getBrandList = async () => {
// if(getMerchantData.isShowMark && !getMerchantData.isNoData)return
// getMerchantData.isShowMark = true
// let value = {
// pageSize: getMerchantData.pageSize,
// pageNum: getMerchantData.pageNum,
// status: 1,
// }
// setTimeout(()=>{
// if(merchantList.value.length >= 5){
// getMerchantData.isNoData = true
// merchantList.value = []
// return
// }
// getMerchantData.pageNum += 1
// merchantList.value.push({
// name:'Roaming Clouds',
// portrait: img,
// collectionsName:'by Lian Su ',
// collections:[
// img,img,img,
// ],
// })
// getMerchantData.isShowMark = false
// },1000)
// // await getPublishList(value).then((res)=>{
// // if(res.content.length == 0)getMerchantData.isNoData = true
// // getMerchantData.pageNum += 1
// // list.value.push(...res.content)
// // })
// }
// const vObserve = {
// mounted (el,binding) {
// getMerchantData.isShowMark = false
// getMerchantData.isNoData = false
// new IntersectionObserver(
// (entries, observer) => {
// // 如果不是相交,则直接返回
// // console.log(entries[0]);
// if (!entries[0].intersectionRatio) return;
// getMerchantData.pageNum += 1
// binding.value()
// },
// // { root:worksPage }
// ).observe(el);
// }
// }
const deleteHistory = (item) => {
searchHistory.value = searchHistory.value.filter((i) => i != item)
localStorage.setItem('brandSearchHistory', JSON.stringify(searchHistory.value));
} }
const viewProfile = (item) => {
if(!searchHistory.value.includes(searchBrand.value))searchHistory.value.push(searchBrand.value)
localStorage.setItem('brandSearchHistory', JSON.stringify(searchHistory.value));
router.push({
path:'/brand/'+item.sellerId,
})
}
const setSearchHistory = (item) => {
searchBrand.value = item
changeSearchBrand()
}
onMounted(()=>{ onMounted(()=>{
const value = localStorage.getItem('brandSearchHistory');
if(value)searchHistory.value = JSON.parse(value)
}) })
onUnmounted(()=>{ onUnmounted(()=>{
}) })
@@ -26,15 +124,52 @@ const {} = toRefs(data);
</script> </script>
<template> <template>
<div class="brand"> <div class="brand">
<div class="header-img"> <div class="header-img" :class="{'active': searchBrand.length > 0}">
<img src="@/assets/images/brand/brandBg.png" alt=""> <img src="@/assets/images/brand/brandBg.png" alt="">
<div class="text-box">
<div class="title">{{ $t('brand.title') }}</div>
<span>{{ $t('brand.description') }}</span>
</div>
</div> </div>
<div class="content"> <div class="content">
<div class="merchant-info"> <div class="input">
<MerchantInfo></MerchantInfo> <input type="text" v-model="searchBrand" @input="changeSearchBrand" :placeholder="$t('brand.search')">
<div class="icon" @click="changeSearchBrand">
<SvgIcon name="brand-search" size="32" />
</div>
</div>
<div class="merchantList" v-if="searchBrand.length > 0">
<brand-item v-for="item in merchantList" :key="item.sellerId" :item="item" @viewProfile="viewProfile"></brand-item>
<div class="end" v-show="!getMerchantData.isNoData && !getMerchantData.isShowMark">- The End-</div>
<!-- <div v-show="!getMerchantData.isNoData" class="material_content_list_loding">
<span class="page_loading" v-show="!getMerchantData.isShowMark" v-observe="getBrandList"></span>
<img v-if="getMerchantData.isShowMark" src="@/assets/images/brand/brandLoading.gif" alt="">
</div> -->
<div class="merchantListNull" v-if="getMerchantData.isNoData && searchBrand.length > 0">
<sc-list-null
nullImage="brand"
:showButton="false"
:title="$t('brand.noFound')"
:tip="$t('brand.noFoundTip')"
/>
</div>
</div>
<div class="null-input" v-if="searchBrand.length == 0">
<div class="title">
<div class="icon">
<SvgIcon name="brand-time" size="20" />
</div>
<span>{{ $t('brand.searchHistory') }}</span>
</div>
<div class="history">
<div v-for="item in searchHistory" :key="item" @click.stop="setSearchHistory(item)" class="item">
<span>{{item}}</span>
<div class="icon" @click.stop="deleteHistory(item)">
<SvgIcon name="brand-delete" size="18" />
</div>
</div>
</div> </div>
<div class="commodity-list">
<CommodityList @addShopping="addShopping" @openDetail="openDetail"></CommodityList>
</div> </div>
</div> </div>
<Footer></Footer> <Footer></Footer>
@@ -46,34 +181,155 @@ const {} = toRefs(data);
height: 100%; height: 100%;
position: relative; position: relative;
overflow-y: auto; overflow-y: auto;
display: flex;
flex-direction: column;
.header-img{ .header-img{
width: 100%; width: 100%;
position: relative;
height: 34.4rem;
transition: all .3s;
flex-shrink: 0;
&.active{
height: 14.7rem;
}
>img{ >img{
width: 100%; width: 100%;
position: absolute;
bottom: 0;
}
> .text-box{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
> .title{
font-family: 'KaiseiOpti-Bold';
font-weight: 700;
font-size: 4rem;
line-height: 2.3rem;
letter-spacing: 0%;
color: #000;
}
> span{
display: block;
margin-top: 2.4rem;
font-family: 'KaiseiOpti-Regular';
font-weight: 400;
font-size: 1.6rem;
line-height: 140%;
} }
} }
.content{ }
>.content{
display: flex; display: flex;
height: auto; height: auto;
align-items: flex-start; align-items: flex-start;
.merchant-info{ flex-direction: column;
width: 40rem; align-items: center;
padding-left: 12.7rem;
padding-right: 2.7rem;
height: var(--app-view-height);
overflow-y: auto;
position: sticky;
top: 0;
&::-webkit-scrollbar{
width: 0;
height: 0;
}
}
.commodity-list{
flex: 1; flex: 1;
border-left: 0.5px solid #585858; // overflow: hidden;
border-right: 0.5px solid #585858; > .input{
margin-right: 9rem; width: 66.6rem;
display: flex;
border-bottom: 2px solid #232323;
padding: 1.4rem 1.4rem 1.4rem 2.4rem;
background-color: #f9f9f9;
display: flex;
margin-top: 8rem;
> input{
width: 57.2rem;
line-height: 3.2rem;
padding: 0;
border: none;
font-family: 'KaiseiOpti-Regular';
font-weight: 400;
font-size: 1.6rem;
line-height: 100%;
margin-right: 2.4rem;
background-color: transparent;
outline: none;
}
> .icon{
cursor: pointer;
}
}
> .null-input{
margin-top: 8rem;
.title{
display: flex;
justify-content: center;
.icon{
margin-right: 2rem;
}
}
.history{
margin-top: 4rem;
width: 59rem;
display: flex;
flex-wrap: wrap;
justify-content: center;
column-gap: 3.2rem;
row-gap: 1.2rem;
.item{
cursor: pointer;
display: flex;
> span{
font-family: 'KaiseiOpti-Regular';
font-weight: 400;
font-size: 1.4rem;
line-height: 100%;
margin-right: .4rem;
}
}
}
}
> .merchantList{
width: 121.8rem;
margin-top: 6rem;
// flex: 1;
// overflow-y: auto;
display: flex;
flex-direction: column;
::-webkit-scrollbar{
display: none;
}
:deep(.item){
margin-top: 2.1rem;
padding-bottom: 2.1rem;
}
.end{
font-family: 'KaiseiOpti-Regular';
font-weight: 400;
font-size: 1.2rem;
line-height: 140%;
height: 7rem;
display: flex;
align-items: center;
justify-content: center;
}
> .material_content_list_loding{
width: 100%;
height: 5rem;
aspect-ratio: 1/1;
overflow: hidden;
display: flex;
flex-shrink: 0;
justify-content: center;
> .page_loading{
width: 5rem;
height: 5rem;
}
> img{
width: 5rem;
height: 5rem;
object-fit: contain;
}
}
> .merchantListNull{
flex: 1;
width: 100%;
}
} }
} }
} }

View File

@@ -0,0 +1,189 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import { getlistingListApi } from "@/api/listing";
const props = defineProps({
id: {
type: String,
default: ''
}
})
const emit = defineEmits([
'addShopping','openDetail'
])
const getMerchantData = reactive({
pageSize: 10,
pageNum: 1,
isShowMark:false,
isNoData:false,
})
const listingList = ref([
])
const type = ref('all')
const addShopping = (item) => {
emit('addShopping', item)
}
const setType = (val) => {
type.value = val
listingList.value = []
getMerchantData.pageNum = 1
getMerchantData.isShowMark = false
getMerchantData.isNoData = false
}
const vObserve = {
mounted (el,binding) {
getMerchantData.isShowMark = false
getMerchantData.isNoData = false
new IntersectionObserver(
(entries, observer) => {
// 如果不是相交,则直接返回
// console.log(entries[0]);
if (!entries[0].intersectionRatio) return;
binding.value()
getMerchantData.pageNum += 1
},
// { root:worksPage }
).observe(el);
}
}
const getListingList = () => {
getMerchantData.isShowMark = true
getMerchantData.isNoData = false
getlistingListApi({
sellerId: props.id,
designFor: type.value,
pageNum: getMerchantData.pageNum,
pageSize: getMerchantData.pageSize,
}).then((res)=>{
if(res.content.length == 0)getMerchantData.isNoData = true
listingList.value.push(...res.content)
getMerchantData.isShowMark = false
})
}
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
</script>
<template>
<div class="commodityList">
<div class="header">
<div class="title">
Items
</div>
<div class="menu">
<div :class="{'active': type === 'all'}" @click="setType('all')">{{ $t('brandDetail.All') }}</div>
<div :class="{'active': type === 'male'}" @click="setType('male')">{{ $t('Wardrobe.assets.genders.male') }}</div>
<div :class="{'active': type === 'female'}" @click="setType('female')">{{ $t('Wardrobe.assets.genders.female') }}</div>
</div>
</div>
<div class="list">
<div class="item" v-for="item in listingList" :key="item.url">
<CommodityItem :url="item.cover" :name="item.title" :price="item.price" @addShopping="addShopping(item)" @openDetail="$emit('openDetail', item)"></CommodityItem>
</div>
<div v-show="!getMerchantData.isNoData" class="material_content_list_loding">
<span class="page_loading" v-show="!getMerchantData.isShowMark" v-observe="getListingList"></span>
<img v-if="getMerchantData.isShowMark" src="@/assets/images/brand/brandLoading.gif" alt="">
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.commodityList{
width: 100%;
height: 100%;
position: relative;
display: flex;
flex-direction: column;
.header{
// position: sticky;
// top: 0;
z-index: 2;
background-color: #fff;
.title{
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 3.6rem;
line-height: 6rem;
color: #121212;
padding: 4rem 0 3.6rem 1.2rem;
}
.menu{
padding: 0 1.2rem;
display: flex;
gap: 2rem;
margin-bottom: 6rem;
> div{
min-width: 6rem;
text-align: center;
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 1.98rem;
line-height: 100%;
position: relative;
color: #7B7B7B;
cursor: pointer;
&::after{
content: '';
position: absolute;
bottom: -5px;
left: 0;
display: block;
width: 100%;
border-bottom: 1px solid #232323;
display: none;
}
&.active{
color: #232323;
&::after{
display: block;
}
}
}
}
}
.list{
width: 100%;
flex: 1;
// display: grid;
// align-content: start;
// grid-template-columns: repeat(3, 1fr);
overflow: hidden;
display: grid;
align-content: start;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 28rem), 1fr));
border-top: 0.5px solid #585858;
padding: .5px 0 0 .5px;
/* 垂直线(右边框) */
.item{
position: relative;
padding: 1.2rem;
border-bottom: 0.5px solid #585858;
border-right: 0.5px solid #585858;
margin-right: -1px;
margin-bottom: -1px;
min-width: 0;
}
> .material_content_list_loding{
width: 100%;
height: 5rem;
aspect-ratio: 1/1;
grid-column: 1 / -1;
overflow: hidden;
display: flex;
flex-shrink: 0;
justify-content: center;
> .page_loading{
width: 5rem;
height: 5rem;
}
> img{
width: 5rem;
height: 5rem;
object-fit: contain;
}
}
}
}
</style>

View File

@@ -0,0 +1,117 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import CommodityList from "./commodity-list.vue";
import MerchantInfo from "./merchant-info.vue";
import { useRouter, useRoute } from "vue-router";
import myEvent from '@/utils/myEvent'
import { ElMessage } from 'element-plus'
import { getDesignerDetail } from '@/api/brand'
import { AddShoppingCart } from '@/api/shoppingCart'
import brandDetailBg from '@/assets/images/brand/brandDetailBg.png'
import { useI18n } from 'vue-i18n'
//const props = defineProps({
//})
//const emit = defineEmits([
//])
const router = useRouter()
const route = useRoute()
const { t, locale } = useI18n()
const designerDetail = ref({
avatar: '',
brandBanner: '',
description: '',
email: '',
mobile: '',
ownerName: '',
shopName: '',
productStatus: 2,
socialLinks: '[]',
})
const addShopping = (item) => {
if(!item.price) return ElMessage.warning(t('brandDetail.addShoppingTip'))
if(item.productStatus == 0) return ElMessage.warning(t('addShoppingCart.status0'))
if(item.productStatus == 1) return ElMessage.warning(t('addShoppingCart.status1'))
AddShoppingCart({listingIds:[item.id]}).then((res)=>{
item.shopName = designerDetail.value.shopName
myEvent.emit('addShopping', item)
})
}
const openDetail = (item) => {
router.push({name: 'digitalItemDetail', params: {id: item.id}})
}
const getDetail = ()=>{
let data = {
sellerId: route.params.id,
}
getDesignerDetail(data,true).then((res)=>{
designerDetail.value = res
})
}
onMounted(()=>{
getDetail()
})
onUnmounted(()=>{
})
defineExpose({})
</script>
<template>
<div class="brand">
<div class="header-img">
<img :src="designerDetail.brandBanner || brandDetailBg" alt="">
</div>
<div class="content">
<div class="merchant-info">
<MerchantInfo :designerDetail="designerDetail"></MerchantInfo>
</div>
<div class="commodity-list">
<CommodityList :id="route.params.id" @addShopping="addShopping" @openDetail="openDetail"></CommodityList>
</div>
</div>
<Footer></Footer>
</div>
</template>
<style lang="less" scoped>
.brand{
width: 100%;
height: 100%;
position: relative;
overflow-y: auto;
.header-img{
width: 100%;
border-bottom: 1px solid #585858;
>img{
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
}
.content{
display: flex;
height: auto;
// align-items: flex-start;
.merchant-info{
width: 40rem;
padding-left: 12.7rem;
padding-right: 2.7rem;
height: var(--app-view-height);
overflow-y: auto;
position: sticky;
top: 0;
&::-webkit-scrollbar{
width: 0;
height: 0;
}
}
.commodity-list{
flex: 1;
border-left: 0.5px solid #585858;
border-right: 0.5px solid #585858;
margin-right: 9rem;
}
}
}
</style>

View File

@@ -1,7 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
//const props = defineProps({ const props = defineProps({
//}) designerDetail: {
type: Object,
default: () => {
return {
avatar: '',
brandBanner: '',
description: '',
email: '',
mobile: '',
ownerName: '',
shopName: '',
socialLinks: '[]',
}
},
},
})
//const emit = defineEmits([ //const emit = defineEmits([
//]) //])
let data = reactive({ let data = reactive({
@@ -16,43 +31,39 @@ const {} = toRefs(data);
<template> <template>
<div class="merchantInfo"> <div class="merchantInfo">
<div class="profile"> <div class="profile">
<img src="@/assets/images/collectionStory/Rectangle.png" alt=""> <img v-avatarLoad="designerDetail.avatar" alt="">
</div> </div>
<div class="info"> <div class="info">
<div class="detail"> <div class="detail">
<div class="name">Lian Su</div> <div class="name">{{ designerDetail.ownerName }}</div>
<div class="title">Roaming Clouds</div> <div class="title">{{ designerDetail.shopName }}</div>
</div> </div>
<div class="contact"> <div class="contact">
<div class="title">Contact</div> <div class="title">{{ $t('brandDetail.merchantInfo.Contact') }}</div>
<div class="email label"> <div class="email label">
<div class="icon"> <div class="icon">
<svg-icon name="brand-email" size="24" /> <svg-icon name="brand-email" size="24" />
</div> </div>
<div>lian.su@urieworweoo.com</div> <div class="text">{{ designerDetail.email }}</div>
</div> </div>
<div class="phone label"> <div class="phone label">
<div class="icon"> <div class="icon">
<svg-icon name="brand-call" size="24" /> <svg-icon name="brand-call" size="24" />
</div> </div>
<div>+86 139 4829 7710</div> <div class="text">{{ designerDetail.mobile }}</div>
</div> </div>
<div class="address label"> <div class="address label" v-for="value in JSON.parse(designerDetail.socialLinks)">
<div class="icon"> <div class="icon">
<svg-icon name="brand-link" size="24" /> <svg-icon name="brand-link" size="24" />
</div> </div>
<div>746312432</div> <div class="text">{{value}}</div>
</div>
<div class="website label">
<div class="icon">
<svg-icon name="brand-link" size="24" />
</div>
<div>https://urieworweoo.com</div>
</div> </div>
</div> </div>
<div class="about"> <div class="about">
<div class="title">About</div> <div class="title">{{ $t('brandDetail.merchantInfo.About') }}</div>
<div class="content">Lian Sus work weaves understated ethnic influences into contemporary minimalism. She explores materials and silhouettes that bridge heritage and modern sensibilities. Her designs reflect a quiet dialogue between cultural memory and forward-looking innovation.</div> <div class="content">
{{ designerDetail.description }}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -107,9 +118,20 @@ const {} = toRefs(data);
font-size: 1.4rem; font-size: 1.4rem;
line-height: 100%; line-height: 100%;
color: #585858; color: #585858;
align-items: center;
&:last-child{ &:last-child{
margin-bottom: 0; margin-bottom: 0;
} }
> .text{
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
// word-break: break-word;
// white-space: normal;
}
} }
} }
> .about{ > .about{

View File

@@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue"; import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
import img from "@/assets/images/collectionStory/Rectangle.png"; import img from "@/assets/images/example.png";
import coreConcept from "./coreConcept.vue"; import coreConcept from "./coreConcept.vue";
import inspiration from "./inspiration.vue"; import inspiration from "./inspiration.vue";
import feelingWithAiDA from "./feelingWithAiDA.vue"; import feelingWithAiDA from "./feelingWithAiDA.vue";
import joinUs from "./join-us.vue";
import CommodityItem from "@/components/CommodityItem.vue"; import CommodityItem from "@/components/CommodityItem.vue";
//const props = defineProps({ //const props = defineProps({
//}) //})
@@ -13,23 +14,19 @@ const emit = defineEmits([
let data = reactive({ let data = reactive({
}) })
const list = ref([ const list = ref([
{ // {
url: img, // url: img,
title: "Windswept Burden", // title: "Item sold here",
price: "$100.00", // price: "$?",
},{ // },
url: img,
title: "Windswept Burden",
price: "$100.00",
},{
url: img,
title: "Windswept Burden",
price: "$100.00",
},
]) ])
const addShopping = (item) => { const addShopping = (item) => {
emit('addShopping', item) emit('addShopping', item)
} }
const openCodeCreate = () => {
window.open('https://code-create.com.hk/', '_blank')
}
onMounted(()=>{ onMounted(()=>{
}) })
onUnmounted(()=>{ onUnmounted(()=>{
@@ -41,9 +38,9 @@ const {} = toRefs(data);
<div class="detail"> <div class="detail">
<div class="left"> <div class="left">
<div class="personal"> <div class="personal">
<img :src="img" alt=""> <img src="@/assets/images/collectionStory/code-create.png" alt="">
<div class="name"> <div class="name" @click="openCodeCreate">
<span>Lian Su</span> <span>Code-Create</span>
<div class="icon"> <div class="icon">
<SvgIcon name="share" size="24" /> <SvgIcon name="share" size="24" />
</div> </div>
@@ -51,11 +48,12 @@ const {} = toRefs(data);
</div> </div>
</div> </div>
<div class="center"> <div class="center">
<coreConcept ></coreConcept> <joinUs></joinUs>
<!-- <coreConcept ></coreConcept>
<div class="line"></div> <div class="line"></div>
<inspiration ></inspiration> <inspiration ></inspiration>
<div class="line"></div> <div class="line"></div>
<feelingWithAiDA ></feelingWithAiDA> <feelingWithAiDA ></feelingWithAiDA> -->
</div> </div>
<div class="right"> <div class="right">
<div class="item" v-for="item in list" :key="item.url"> <div class="item" v-for="item in list" :key="item.url">
@@ -67,10 +65,11 @@ const {} = toRefs(data);
<style lang="less" scoped> <style lang="less" scoped>
.detail{ .detail{
width: 100%; width: 100%;
height: auto;
position: relative; position: relative;
display: flex; display: flex;
height: calc(100vh - var(--header-height) - var(--footer-height));
align-items: flex-start; align-items: flex-start;
overflow: hidden;
> div{ > div{
// height: 100%; // height: 100%;
} }
@@ -93,6 +92,9 @@ const {} = toRefs(data);
display: flex; display: flex;
gap: .4rem; gap: .4rem;
align-items: center; align-items: center;
cursor: pointer;
> .icon{
}
> span{ > span{
font-family: 'KaiseiOpti-Bold'; font-family: 'KaiseiOpti-Bold';
font-weight: 700; font-weight: 700;
@@ -110,8 +112,9 @@ const {} = toRefs(data);
border-right: 0.5px solid #585858; border-right: 0.5px solid #585858;
// overflow-y: auto; // overflow-y: auto;
overflow: hidden; overflow: hidden;
// height: 100%; height: 100%;
height: auto; // height: auto;
position: relative;
.line{ .line{
border: 0.5px solid #58585899; border: 0.5px solid #58585899;
width: 100%; width: 100%;
@@ -123,14 +126,15 @@ const {} = toRefs(data);
> .right{ > .right{
width: 25.4rem; width: 25.4rem;
padding-top: 6rem; padding-top: 6rem;
position: sticky; // position: sticky;
top: 0; top: 0;
height: calc(100vh - var(--header-height)); // height: calc(100vh - var(--header-height));
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
gap: 4rem; gap: 4rem;
overflow-y: auto; overflow-y: auto;
height: 100%;
&::-webkit-scrollbar{ &::-webkit-scrollbar{
display: none; display: none;
} }

View File

@@ -0,0 +1,59 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted, reactive, toRefs } from "vue";
//const props = defineProps({
//})
//const emit = defineEmits([
//])
let data = reactive({
})
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({})
const {} = toRefs(data);
</script>
<template>
<div class="joinUs">
<div class="title">{{ $t('collectionStory.joinUs.title') }}</div>
<div class="info">
<div>
{{ $t('collectionStory.joinUs.info') }}
</div>
<div>
{{ $t('collectionStory.joinUs.info2') }}
</div>
<br />
<div>
{{ $t('collectionStory.joinUs.info3') }}
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.joinUs{
width: 100%;
height: auto;
position: relative;
padding: 6rem 0;
> .title{
padding: 0 2.4rem;
font-family: "KaiseiOpti-Bold";
font-weight: 700;
font-size: 3rem;
line-height: 100%;
margin-bottom: 6rem;
}
> .info{
width: 70.7rem;
margin: 0 auto;
margin-bottom: 4rem;
> div{
font-family: "KaiseiOpti-Regular";
font-weight: 400;
font-size: 1.6rem;
line-height: 2.3rem;
}
}
}
</style>

View File

@@ -22,30 +22,23 @@ const {} = toRefs(data);
<div class="collectionStory"> <div class="collectionStory">
<div class="first-screen"> <div class="first-screen">
<img class="banner" src="@/assets/images/collectionStory/collection_story_banner.png" alt=""> <img class="banner" src="@/assets/images/collectionStory/collection_story_banner.png" alt="">
<div class="back"> <!-- <div class="back">
<SvgIcon name="collectionStory-back" size="20" /> <SvgIcon name="collectionStory-back" size="20" />
<div class="text">Back to Home</div> <div class="text">{{ $t('collectionStory.back') }}</div>
</div> </div> -->
<div class="title-content"> <div class="title-content">
<div class="title-box"> <div class="title-box">
<div class="left">
<div class="title"> <div class="title">
Windswept Burden {{ $t('collectionStory.title') }}
</div> </div>
<div class="info"> <div class="info">
Publish Date: 24th Nov 2025 {{ $t('collectionStory.description') }}
</div> </div>
</div> </div>
<div class="right"> <div class="button">
<div class="info"> <a href="mailto:info@code-create.com.hk">
We are spiritual nomads carrying what wind cannot take. <br /> {{ $t('collectionStory.button') }}
Inspired by those who knew home is not a place, but what you wear. </a>
</div>
</div>
</div>
<div class="scrolling-learn-more">
<div>Scrolling Learn More</div>
<SvgIcon name="collectionStory-scrollingLearnMore" size="48" />
</div> </div>
</div> </div>
</div> </div>
@@ -66,6 +59,7 @@ const {} = toRefs(data);
height: auto; height: auto;
display: flex; display: flex;
overflow: hidden; overflow: hidden;
align-items: center;
> .back{ > .back{
position: absolute; position: absolute;
top: 2.4rem; top: 2.4rem;
@@ -73,7 +67,7 @@ const {} = toRefs(data);
display: flex; display: flex;
align-items: center; align-items: center;
gap: .8rem; gap: .8rem;
color: #fff; color: #000;
cursor: pointer; cursor: pointer;
> .text{ > .text{
font-size: 2rem; font-size: 2rem;
@@ -83,73 +77,46 @@ const {} = toRefs(data);
} }
> .title-content{ > .title-content{
width: 100%; width: 100%;
height: 63.2rem;
margin-top: 24.8rem;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 38.37%, rgba(0, 0, 0, 0.192) 90.74%);
padding: 0 4rem;
> .title-box{
margin-top: 36.7rem;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
> .left{
font-family: 'KaiseiOpti-Bold';
font-weight: 700;
> .title{
font-size: 6rem;
line-height: 6rem;
}
> .info{
margin-top: 1.7rem;
font-size: 1.8rem;
line-height: 100%;
vertical-align: bottom;
}
}
> .right{
> .info{
font-weight: 500;
font-size: 1.8rem;
line-height: 100%;
text-align: right;
}
}
}
}
.scrolling-learn-more{
position: absolute; position: absolute;
bottom: 2.1rem; padding: 0 6.7rem;
left: 50%; // margin-top: 11.5rem;
transform: translateX(-50%);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: #fff; align-items: flex-start;
animation: scroll 3s linear infinite; > .title-box{
@keyframes scroll { display: flex;
0% { flex-direction: column;
transform: translateY(0); > .title{
font-size: 6.5rem;
line-height: 1.3;
font-weight: 500;
color: #585858;
} }
50% { > .info{
transform: translateY(-20%); font-size: 3rem;
} font-weight: 500;
100% {
transform: translateY(0%);
}
}
> div{
font-family: 'KaiseiOpti-Regular';
font-weight: 400;
font-size: 1.4rem;
line-height: 100%; line-height: 100%;
text-align: center; color: #585858;
margin-bottom: 1.5rem; }
white-space: nowrap; }
> .button{
padding: 0 4.5rem;
line-height: 5.1rem;
background-color: #1B1B1B;
color: #fff;
margin-top: 4rem;
font-weight: 700;
font-size: 2rem;
letter-spacing: -0.4px;
cursor: pointer;
> a{
color: #fff;
text-decoration: none;
}
} }
} }
.banner{ .banner{
width: 100%; width: 100%;
position: absolute;
z-index: -1; z-index: -1;
} }
} }

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