Compare commits

...

147 Commits

Author SHA1 Message Date
df5cfc5eba feat: 修改请求时机
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-02-27 15:02:08 +08:00
8100459c4e feat: dressfor页面&生成outfit逻辑修改
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-02-27 13:44:19 +08:00
10ee247b8d bugfix: 图片引入
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-29 14:23:57 +08:00
7f34ce80b9 feat: 测试部署
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-29 14:04:32 +08:00
57359d1067 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-29 13:13:04 +08:00
9101116430 chore: 更换设计师图片 2026-01-29 13:11:36 +08:00
X1627315083
7a87c6cd11 Merge branches 'main' and 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-19 13:40:54 +08:00
X1627315083
c8dc6cf8d1 对话页面指定标识字体加粗 2026-01-19 13:39:51 +08:00
f092a76162 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-14 09:49:25 +08:00
48a32a60a1 bugfix: customer页面选择顾客列表 2026-01-14 09:41:51 +08:00
X1627315083
f157c6ead3 调整设计师名字
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-13 16:09:16 +08:00
X1627315083
592792d071 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-13 14:48:56 +08:00
X1627315083
aace73d5c4 修改设计师名字最后两个改为sera、edi
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-13 14:39:38 +08:00
X1627315083
02dcfba4ba fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2026-01-07 11:48:31 +08:00
X1627315083
465fa5e6ae fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-31 10:12:46 +08:00
X1627315083
0ec9e4dc46 调整outfit流程
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-31 10:07:08 +08:00
X1627315083
e230b4c83f Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 15:57:20 +08:00
X1627315083
cf15e371ab 修改product页面标题颜色 2025-12-30 15:53:27 +08:00
李志鹏
ef314931eb 1
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 15:36:49 +08:00
李志鹏
71a3946aae 1
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 15:35:49 +08:00
李志鹏
38f027d3ad Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 14:58:22 +08:00
李志鹏
d497471e09 调整 2025-12-30 14:58:15 +08:00
92c571b5f9 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 14:51:39 +08:00
6156c88429 style: 样式修改 2025-12-30 14:51:19 +08:00
X1627315083
319130dc6a 调整product的history标签位置
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 14:18:10 +08:00
李志鹏
6a2b456bf3 selectStyle
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 13:31:45 +08:00
X1627315083
135adb3907 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 13:10:53 +08:00
089a266c35 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 13:05:10 +08:00
a9732a659e feat: 对话加载动画 2025-12-30 11:54:13 +08:00
X1627315083
24e221902e 反推新增参数 2025-12-30 11:40:39 +08:00
李志鹏
8b92d360c9 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 11:25:37 +08:00
李志鹏
f10039e9dc 1 2025-12-30 11:25:30 +08:00
9d0b29ab95 feat: 切换用户之后返回主页
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 11:16:48 +08:00
433dec935a Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 10:14:01 +08:00
dfdfce2076 bugfix: 聊天界面菜单&customer界面间距 2025-12-30 10:12:25 +08:00
李志鹏
eebe7b5b40 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-30 10:03:02 +08:00
李志鹏
fc1c212eca 添加分享按钮 2025-12-30 10:02:55 +08:00
X1627315083
9f338a772d 修复魔改页面无法跳转到outfit页面 2025-12-30 09:45:26 +08:00
8e50f3d8a5 style: continue按钮背景样式
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 16:53:44 +08:00
f5bfad7973 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 16:46:24 +08:00
27bb0394f8 style: continue按钮背景 2025-12-29 16:46:09 +08:00
李志鹏
73ae6744ab Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 16:35:02 +08:00
李志鹏
2b42ef6ab7 Outfit收藏 2025-12-29 16:34:55 +08:00
X1627315083
0794749f9a Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 16:04:35 +08:00
X1627315083
5f84eb496c 调整不同流程下style生成数量 2025-12-29 16:04:29 +08:00
10fc7f3e30 style: dressfor页面样式
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:47:37 +08:00
6b19672f1c style: re-try字体颜色
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:37:54 +08:00
32245b8931 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:35:54 +08:00
25852e7c5a style: 文字颜色 2025-12-29 15:35:38 +08:00
X1627315083
5d89d06531 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:25:53 +08:00
X1627315083
279776c4f7 调整product不同流程的标题 2025-12-29 15:25:25 +08:00
李志鹏
94cb354483 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:21:04 +08:00
李志鹏
88abe91538 customize更改历史图标 2025-12-29 15:20:58 +08:00
X1627315083
55ed8474be fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 15:20:03 +08:00
李志鹏
fab733c7c7 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 14:52:24 +08:00
李志鹏
3826e553b1 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 14:50:33 +08:00
李志鹏
8cb77593f8 更改viewtype方式 2025-12-29 14:48:54 +08:00
X1627315083
891fb3f4b0 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 14:43:28 +08:00
X1627315083
c2b800eb35 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 14:43:03 +08:00
X1627315083
15a4220fbc 选择风格变为生成四个,调整渐变按钮样式 2025-12-29 14:42:50 +08:00
9da2cd9dc2 chore: retry图标
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 13:04:26 +08:00
8af7092f56 style: 选择顾客弹窗
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 11:24:01 +08:00
f0ff9ebcbe Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 10:51:09 +08:00
9c41bf4711 style: 样式修改.
bugfix: customer选择页面弹窗触发错误.
2025-12-29 10:46:00 +08:00
李志鹏
59c864fef3 更改字体
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-29 10:20:46 +08:00
李志鹏
28f67cf228 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-24 12:06:49 +08:00
李志鹏
1be879fbe4 修改账号信息 2025-12-24 12:04:11 +08:00
8c2f4ca6e6 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-24 11:50:04 +08:00
53b9a83b80 feat: 右上角图标随个人中心弹窗展示而变化 2025-12-24 11:48:31 +08:00
李志鹏
9dd3bed850 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-24 11:34:10 +08:00
a5cf919ded Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-24 10:38:46 +08:00
42a7194156 bugfix: 文案错误 2025-12-24 10:36:54 +08:00
李志鹏
b206cae025 1
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-24 10:09:25 +08:00
李志鹏
1a52eaa3c3 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 17:29:56 +08:00
李志鹏
70cf624d88 按钮样式 2025-12-23 17:29:49 +08:00
a629a08e66 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 16:51:39 +08:00
73e8662895 style: 文案&样式修改 2025-12-23 16:51:23 +08:00
X1627315083
9a51259b11 修改style生成文案
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 16:31:30 +08:00
X1627315083
e6d542fb78 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 16:29:46 +08:00
33659049ce Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 15:52:35 +08:00
9310761a5d style: 文案和样式修改 2025-12-23 15:52:20 +08:00
李志鹏
321c0bdb92 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 15:10:56 +08:00
李志鹏
30e46dff15 添加下拉加载分页 2025-12-23 15:10:50 +08:00
X1627315083
6b255728af 调整loding动画细节 2025-12-23 15:05:15 +08:00
8e73c90746 bugfix: 聊天界面白屏
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 14:45:23 +08:00
65617baa96 feat: 切换customer
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 14:41:09 +08:00
e8a72ec0aa Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 14:22:02 +08:00
c356b1b503 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front 2025-12-23 14:21:21 +08:00
X1627315083
1b7694cee0 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 14:20:11 +08:00
b17637b2bc feat: 获取顾客列表&新建顾客 2025-12-23 14:19:00 +08:00
X1627315083
665194f18e tryon 反推style 2025-12-23 14:00:30 +08:00
李志鹏
c6a9d91159 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-23 09:30:33 +08:00
李志鹏
51336fff77 添加历史流程数据状态
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 16:34:43 +08:00
李志鹏
d45fd93c4f Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 15:52:13 +08:00
李志鹏
bf605ea297 fix 2025-12-22 15:51:57 +08:00
b7abd2dbf9 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 15:04:09 +08:00
69e6408d55 feat: customer页面新增customer&profile弹窗切换customer 2025-12-22 15:01:58 +08:00
X1627315083
e2f1e6fa5b Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 13:51:21 +08:00
X1627315083
1bf3038b29 fix 2025-12-22 13:51:13 +08:00
李志鹏
4aac607f6a Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 13:45:04 +08:00
李志鹏
53264e209b fix 2025-12-22 13:42:55 +08:00
X1627315083
f266bb4213 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 13:39:43 +08:00
X1627315083
3dde004166 style product页面设置跳转携带参数 2025-12-22 13:39:34 +08:00
李志鹏
9ba6804d85 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 13:03:03 +08:00
李志鹏
41e605a8bf view 2025-12-22 13:02:53 +08:00
X1627315083
d0ada4e6f0 Merge branches 'main' and 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-22 11:33:17 +08:00
X1627315083
48890121f6 调整homeNav导航跳转页面 2025-12-22 11:29:38 +08:00
e14ac0f15f feat: customer页面 & dressfor页面
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 17:27:26 +08:00
X1627315083
60b0f4bbec 调整home跳转路径
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 15:32:04 +08:00
李志鹏
33a73643b4 fix
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 15:17:20 +08:00
李志鹏
bd36a237ec fix 2025-12-19 14:55:08 +08:00
X1627315083
ea84256cf6 新增主页home页面作为选择主流程或者从历史流程继续
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 13:46:59 +08:00
X1627315083
67702b36b2 调整customer路由位置,吧登陆后的路由放在workshop路由下
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 13:40:15 +08:00
李志鹏
c157c0a14a Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-19 13:26:58 +08:00
李志鹏
c04b99a9aa 更改头部标题和底部导航 2025-12-19 13:25:58 +08:00
58ef526542 style: 性别选择按钮样式
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-18 09:50:35 +08:00
543ac89f9b style: 毛玻璃效果
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-17 10:36:07 +08:00
5b3eb52841 style: ai标签颜色
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-10 14:33:49 +08:00
21751700f6 style: ai标签颜色
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-10 14:14:34 +08:00
662f0e2115 style: 样式填充
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-10 13:32:22 +08:00
927e07bf79 bugfix: 背景图片尺寸错误
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-10 11:23:02 +08:00
1424e3025d style: contnue按钮高度
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-09 16:36:57 +08:00
2929f13e93 style: selectStylecontinue样式 2025-12-09 16:21:01 +08:00
94796110c4 style: 样式修改
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-09 15:12:25 +08:00
3f6652c2ec bugfix: 对话时的性别选择
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 15:05:06 +08:00
2db23b8d05 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 15:00:24 +08:00
6dbef599bb bugfix: 重复执行返回 2025-12-01 14:58:42 +08:00
X1627315083
2259edb1a1 Merge branch 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 13:17:45 +08:00
X1627315083
1a8cda5d5d 取消公共按钮样式, 2025-12-01 13:15:43 +08:00
2487e4e045 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 11:32:05 +08:00
81df571ddf style: 样式修改 2025-12-01 11:30:31 +08:00
X1627315083
ed5c3232b9 统一selectStyle和toproduct按钮
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 10:24:33 +08:00
X1627315083
5d6ad32c80 Merge branches 'main' and 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-12-01 09:39:32 +08:00
X1627315083
c4e8b0daea 合并绑定客户页面,去掉导航icon,对话生产首次发送弹出提示 2025-12-01 09:38:16 +08:00
75ebc649be 更新 .gitea/workflows/main_build_commit.yaml
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-11-28 16:06:06 +08:00
71bfd347b7 更新 .gitea/workflows/main_build_commit.yaml
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-11-28 16:05:09 +08:00
4d031341bc 上传文件至「.gitea/workflows」
All checks were successful
git提交控制 AiDA WEB-Node.js main 分支构建部署 / build (20.19.0) (push) Has been skipped
2025-11-28 16:04:33 +08:00
73064d6896 上传文件至「.gitea/workflows」 2025-11-28 15:33:13 +08:00
李志鹏
281359eaf9 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front 2025-11-28 11:39:09 +08:00
578cc153f4 test 2025-11-27 16:51:54 +08:00
391b71e8be test 2025-11-27 16:47:14 +08:00
a2ae6caf94 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front 2025-11-27 16:42:45 +08:00
72f270a9d5 feta: 测试whatsapp自身分享 2025-11-27 16:42:32 +08:00
X1627315083
81a762a6f4 fix 2025-11-27 16:28:09 +08:00
李志鹏
e91f1071b3 Merge branch 'main' of http://18.167.251.121:10003/aidlab/lanecarford_front 2025-11-21 16:49:20 +08:00
李志鹏
2cd94da7ba fix不显示密码 2025-11-21 16:49:14 +08:00
李志鹏
2c09500134 fix 2025-11-21 14:47:06 +08:00
83 changed files with 4022 additions and 2146 deletions

View File

@@ -0,0 +1,56 @@
name: git提交控制 AiDA WEB-Node.js main 分支构建部署
on:
workflow_dispatch:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
if: "contains(github.event.head_commit.message, '[run build]')"
strategy:
matrix:
node-version: [ 20.19.0 ]
env:
REMOTE_DEPLOY_PATH: /workspace/workspace_lanecrawford/front
steps:
- name: 1.检出代码
uses: actions/checkout@v4
- name: 2.设置 Node.js 环境
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- run: ls -l
- name: 3.同步文件到远程服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "./dist/*"
target: ${{ env.REMOTE_DEPLOY_PATH }}
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
strip_components: 0
- name: 4. 远程重载 Nginx 配置
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
# 核心:执行 Nginx 重载命令
script: |
echo "尝试重载 Nginx 服务..."
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
# 否则工作流可能会因为权限不足而失败。
sudo systemctl reload nginx
echo "Nginx 重载命令已发送。"

View File

@@ -0,0 +1,51 @@
name: 手动 AiDA WEB-Node.js main 分支构建部署
on:
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20.19.0 ]
env:
REMOTE_DEPLOY_PATH: /workspace/workspace_lanecrawford/front
steps:
- name: 1.检出代码
uses: actions/checkout@v4
- name: 2.设置 Node.js 环境
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- run: ls -l
- name: 3.同步文件到远程服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "./dist/*"
target: ${{ env.REMOTE_DEPLOY_PATH }}
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
strip_components: 0
- name: 4. 远程重载 Nginx 配置
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
# 核心:执行 Nginx 重载命令
script: |
echo "尝试重载 Nginx 服务..."
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
# 否则工作流可能会因为权限不足而失败。
sudo systemctl reload nginx
echo "Nginx 重载命令已发送。"

View File

@@ -0,0 +1,54 @@
name: 定时 AiDA WEB-Node.js main 分支构建部署
on:
schedule:
# cron为UTC时区构建时间=部署时间-8小时 {*分 (-8)时 *日 *月 *周} ---
# 示例: 1月1日22点22分触发构建 cron写作 - '22 14 1 1 *'
- cron: '22 14 1 1 *'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [ 20.19.0 ]
env:
REMOTE_DEPLOY_PATH: /workspace/workspace_lanecrawford/front
steps:
- name: 1.检出代码
uses: actions/checkout@v4
- name: 2.设置 Node.js 环境
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- run: ls -l
- name: 3.同步文件到远程服务器
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
source: "./dist/*"
target: ${{ env.REMOTE_DEPLOY_PATH }}
ssh_options: "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"
strip_components: 0
- name: 4. 远程重载 Nginx 配置
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_KEY }}
# 核心:执行 Nginx 重载命令
script: |
echo "尝试重载 Nginx 服务..."
# 💡 注意:执行此命令需要服务器用户具有 sudo 权限,并且配置了 NOPASSWD。
# 否则工作流可能会因为权限不足而失败。
sudo systemctl reload nginx
echo "Nginx 重载命令已发送。"

View File

@@ -34,6 +34,13 @@
src: url("./Roboto/Roboto-Bold.ttf") format('woff2'); src: url("./Roboto/Roboto-Bold.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */ /* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
} }
@font-face {
font-family: 'robotoRegular';
font-style: italic;
font-weight: 700;
src: url("./Roboto/Roboto-Regular.ttf") format('woff2');
/* unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; */
}
@font-face { @font-face {
font-family: 'boskaRegular'; font-family: 'boskaRegular';
font-style: italic; font-style: italic;

View File

@@ -59,3 +59,19 @@ export const googleAuth = (data: GoogleAuthParamsType): Promise<LoginResponse> =
params: data 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
})
}

View File

@@ -8,12 +8,12 @@ import request from '@/utils/request'
* @param data.stylist 提示词 * @param data.stylist 提示词
* @param data.gender 原始试穿效果id * @param data.gender 原始试穿效果id
* @param data.num 是否重新生成 0-否1-是 * @param data.num 是否重新生成 0-否1-是
*/ */
export function generateRequestOutfit(data: Object) { export function generateRequestOutfit(data: Object) {
return request({ return request({
url: '/api/style/requestOutfit', url: '/api/style/requestOutfit',
method: 'post', method: 'post',
data, data
}) })
} }
@@ -21,12 +21,12 @@ export function generateRequestOutfit(data: Object) {
* 获取传达风格 * 获取传达风格
* @param data 获取传达风格数据 * @param data 获取传达风格数据
* @param data.requestIDs 获取生成结果的taskId * @param data.requestIDs 获取生成结果的taskId
*/ */
export function getRequestOutfit(data: Object) { export function getRequestOutfit(data: Object) {
return request({ return request({
url: '/api/style/getOutfitResult', url: '/api/style/getOutfitResult',
method: 'get', method: 'get',
params:data params: data
}) })
} }
@@ -41,12 +41,12 @@ export function getRequestOutfit(data: Object) {
* @param data.prompt 提示词 * @param data.prompt 提示词
* @param data.originalTryOnId 原始试穿效果id * @param data.originalTryOnId 原始试穿效果id
* @param data.isRegenerated 是否重新生成 0-否1-是 * @param data.isRegenerated 是否重新生成 0-否1-是
*/ */
export function generateTryOnEffect(data: Object) { export function generateTryOnEffect(data: Object) {
return request({ return request({
url: '/api/try-on-effects/generate', url: '/api/try-on-effects/generate',
method: 'post', method: 'post',
data, data
}) })
} }
/** /**
@@ -55,12 +55,12 @@ export function generateTryOnEffect(data: Object) {
* @param data.customerPhotoId 顾客照片id * @param data.customerPhotoId 顾客照片id
* @param data.prompt 提示词 * @param data.prompt 提示词
* @param data.tryonUrl AI魔改url * @param data.tryonUrl AI魔改url
*/ */
export function generateTryOnEffectDemo(data: Object) { export function generateTryOnEffectDemo(data: Object) {
return request({ return request({
url: '/api/try-on-effects/reFace', url: '/api/try-on-effects/reFace',
method: 'post', method: 'post',
data, data
}) })
} }
@@ -75,29 +75,29 @@ export function uploadCustomerPhoto(data: FormData) {
url: '/api/customer-photos/upload', url: '/api/customer-photos/upload',
method: 'post', method: 'post',
data, data,
loading: true, loading: true
}) })
} }
/** /**
* 设置喜欢 * 设置喜欢
* @param tryOnId 试穿效果id * @param tryOnId 试穿效果id
*/ */
export function setTryOnEffectFavorite(tryOnId: string | number) { export function setTryOnEffectFavorite(tryOnId: string | number) {
if (!tryOnId) return Promise.reject('试穿效果id不能为空'); if (!tryOnId) return Promise.reject('试穿效果id不能为空')
return request({ return request({
url: `/api/try-on-effects/set-favorite/${tryOnId}`, url: `/api/try-on-effects/set-favorite/${tryOnId}`,
method: 'post', method: 'post'
}) })
} }
/** /**
* 取消喜欢 * 取消喜欢
* @param tryOnId 试穿效果id * @param tryOnId 试穿效果id
*/ */
export function cancelTryOnEffectFavorite(tryOnId: string | number) { export function cancelTryOnEffectFavorite(tryOnId: string | number) {
if (!tryOnId) return Promise.reject('试穿效果id不能为空'); if (!tryOnId) return Promise.reject('试穿效果id不能为空')
return request({ return request({
url: `/api/try-on-effects/cancel-favorite/${tryOnId}`, url: `/api/try-on-effects/cancel-favorite/${tryOnId}`,
method: 'post', method: 'post'
}) })
} }
@@ -105,20 +105,20 @@ export function cancelTryOnEffectFavorite(tryOnId: string | number) {
* @param customerId 客户id * @param customerId 客户id
*/ */
export function getCustomerPhotos(customerId: string | number) { export function getCustomerPhotos(customerId: string | number) {
if (!customerId) return Promise.reject('客户id不能为空'); if (!customerId) return Promise.reject('客户id不能为空')
return request({ return request({
url: `/api/visit-records/customer/${customerId}`, url: `/api/visit-records/customer/${customerId}`,
method: 'get', method: 'get'
}) })
} }
/** 删除进店记录-library /** 删除进店记录-library
* @param visitRecordId 进店记录id * @param visitRecordId 进店记录id
*/ */
export function deleteCustomerPhoto(visitRecordId: string | number) { export function deleteCustomerPhoto(visitRecordId: string | number) {
if (!visitRecordId) return Promise.reject('进店记录id不能为空'); if (!visitRecordId) return Promise.reject('进店记录id不能为空')
return request({ return request({
url: `/api/visit-records/${visitRecordId}`, url: `/api/visit-records/${visitRecordId}`,
method: 'delete', method: 'delete'
// loading: true, // loading: true,
}) })
} }
@@ -126,23 +126,92 @@ export function deleteCustomerPhoto(visitRecordId: string | number) {
* @param visitRecordId 进店记录id * @param visitRecordId 进店记录id
*/ */
export function getTryOnEffectFavoriteList(visitRecordId: string | number) { export function getTryOnEffectFavoriteList(visitRecordId: string | number) {
if (!visitRecordId) return Promise.reject('进店记录id不能为空'); if (!visitRecordId) return Promise.reject('进店记录id不能为空')
return request({ return request({
url: `/api/try-on-effects/favorites/${visitRecordId}`, url: `/api/try-on-effects/favorites/${visitRecordId}`,
method: 'get', method: 'get'
}) })
} }
/** 查询某套试穿效果列表 /** 查询某套试穿效果列表
* @param styleId 服装id * @param styleId 服装id
*/ */
export function getTryOnEffectStyleList(styleId: string | number) { export function getTryOnEffectStyleList(styleId: string | number) {
if (!styleId) return Promise.reject('服装id不能为空'); if (!styleId) return Promise.reject('服装id不能为空')
return request({ return request({
url: `/api/try-on-effects/style/${styleId}`, url: `/api/try-on-effects/style/${styleId}`,
method: 'get', method: 'get'
}) })
} }
// 获取当前Sales名下的customers列表
export interface CustomerListParams {
current: number
size: number
desc: boolean
sortField?: 'createTime'
sortOrder?: 'DESC' | 'ASC'
keyword?: string
startTime?: string
endTime?: string
status?: number
offset?: number
limit?: number
}
export const getCustomerList = (data: CustomerListParams) => {
return request({
url: '/api/customers/getAllCustomer',
method: 'post',
data
})
}
// 创建顾客
export interface CreateCustomerParams {
nickname: string
vipId: string
}
export const createCustomer = (data: CreateCustomerParams) => {
return request({
url: '/api/customers/createCustomer',
method: 'get',
params: data
})
}
// /** 查询收藏列表
// * @param visitRecordId 进店记录id
// */
// export function getTryOnEffectFavoriteList(visitRecordId: string | number) {
// if (!visitRecordId) return Promise.reject('进店记录id不能为空');
// return request({
// url: `/api/try-on-effects/favorites/${visitRecordId}`,
// method: 'get',
// })
// }
// /** 查询某套试穿效果列表
// * @param styleId 服装id
// */
// export function getTryOnEffectStyleList(styleId: string | number) {
// if (!styleId) return Promise.reject('服装id不能为空');
// return request({
// url: `/api/try-on-effects/style/${styleId}`,
// method: 'get',
// })
// }
/** 获取历史生成记录
* @param params 获取历史生成记录参数
* @param params.visitRecordId 进店记录id
* @param params.type 类型
* @param params.isLibrary 是否是收藏
*/
export function getGenerateHistoricals(params: Object) {
if (!params) return Promise.reject('参数不能为空');
return request({
url: `/api/try-on-effects/getHistoricals`,
method: 'get',
params,
})
}
// 选择顾客 // 选择顾客
interface CustomerInfo { interface CustomerInfo {
@@ -152,7 +221,7 @@ export const customerCheckin = (data: CustomerInfo) => {
return request({ return request({
url: '/api/customers/checkIn', url: '/api/customers/checkIn',
method: 'get', method: 'get',
params: data, params: data
}) })
} }
@@ -171,7 +240,7 @@ export const streamChatAddress = '/api/llm/streamChat'
* @param data.visitRecordId 进店记录id * @param data.visitRecordId 进店记录id
* @param data.customerId 顾客id * @param data.customerId 顾客id
* @param data.suggestion 意见和建议 * @param data.suggestion 意见和建议
*/ */
export function addTryOnEffectComment(data: Object) { export function addTryOnEffectComment(data: Object) {
return request({ return request({
url: '/api/try-on-effects/add-comment', url: '/api/try-on-effects/add-comment',
@@ -179,3 +248,38 @@ export function addTryOnEffectComment(data: Object) {
data, data,
}) })
} }
/**
* like outfit接口
* @param styleId 设置like的styleid
*/
export function setStyleFavorite(styleId: Object) {
return request({
url: `/api/style/set-favorite/${styleId}`,
method: 'post',
})
}
/**
* 取消like outfit接口
* @param styleId 取消like的styleid
*/
export function cancelStyleFavorite(styleId: Object) {
return request({
url: `/api/style/cancel-favorite/${styleId}`,
method: 'post',
})
}
/**
* try on 返推outfitId
* @param tryOnEffectsId tryOnId
*/
export function retrieveAndRegenerate(data: Object) {
return request({
url: `/api/style/retrieveAndRegenerate`,
method: 'get',
params: data,
})
}

View File

@@ -1,9 +1,11 @@
button.sandblasted-blurred { button.sandblasted-blurred {
box-sizing: content-box; width: 35rem;
border: 0.4rem solid #fff; height: 8.3rem;
box-sizing: border-box;
border: 0.25rem solid #fff;
font-family: satoshiMedium; font-family: satoshiMedium;
font-weight: 500; font-weight: 500;
font-size: 5.5rem; font-size: 4rem;
color: #fff; color: #fff;
background-color: transparent; background-color: transparent;
position: relative; position: relative;
@@ -71,3 +73,24 @@ html:root {
padding: 0 2rem; padding: 0 2rem;
min-height: fit-content; min-height: fit-content;
} }
button.general,
.general_button {
border-radius: 0.7rem;
border: 3px solid #000;
background-color: #000;
text-align: center;
color: #fff;
font-family: satoshiMedium;
}
button.general.smail,
.general_button.smail {
font-size: 3.6rem;
width: 24.6rem;
line-height: 6.7rem;
}
button.general.big,
.general_button.big {
font-size: 3.8rem;
line-height: 7.4rem;
width: 3.4rem;
}

View File

@@ -1,9 +1,11 @@
button.sandblasted-blurred { button.sandblasted-blurred {
box-sizing: content-box; width: 35rem;
border: 0.4rem solid #fff; height: 8.3rem;
box-sizing: border-box;
border: 0.25rem solid #fff;
font-family: satoshiMedium; font-family: satoshiMedium;
font-weight: 500; font-weight: 500;
font-size: 5.5rem; font-size: 4rem;
color: #fff; color: #fff;
background-color: transparent; background-color: transparent;
position: relative; position: relative;
@@ -41,7 +43,9 @@ button.sandblasted-blurred {
//只使用浅色模式 //只使用浅色模式
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root, body {
:root,
body {
background: white !important; background: white !important;
color: black !important; color: black !important;
} }
@@ -71,10 +75,11 @@ html:root {
--van-toast-default-width: 88rem; --van-toast-default-width: 88rem;
} }
.van-toast{ .van-toast {
min-height: fit-content; min-height: fit-content;
max-width: none; max-width: none;
} }
.van-toast__text { .van-toast__text {
font-size: 4rem; font-size: 4rem;
height: 5rem; height: 5rem;
@@ -82,3 +87,25 @@ html:root {
padding: 0 2rem; padding: 0 2rem;
min-height: fit-content; min-height: fit-content;
} }
button.general,
.general_button {
border-radius: .7rem;
border: 3px solid #000;
background-color: #000;
text-align: center;
color: #fff;
font-family: satoshiMedium;
&.smail {
font-size: 3.6rem;
width: 24.6rem;
line-height: 6.7rem;
}
&.big {
font-size: 3.8rem;
line-height: 7.4rem;
width: 3.4rem;
}
}

View File

@@ -1 +1,4 @@
<svg t="1760424451063" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13798" width="200" height="200"><path d="M842 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 140.3-113.7 254-254 254S258 594.3 258 454c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8 0 168.7 126.6 307.9 290 327.6V884H326.7c-13.7 0-24.7 14.3-24.7 32v36c0 4.4 2.8 8 6.2 8h407.6c3.4 0 6.2-3.6 6.2-8v-36c0-17.7-11-32-24.7-32H548V782.1c165.3-18 294-158 294-328.1z" p-id="13799" fill="#6d6868"></path><path d="M512 624c93.9 0 170-75.2 170-168V232c0-92.8-76.1-168-170-168s-170 75.2-170 168v224c0 92.8 76.1 168 170 168z m-94-392c0-50.6 41.9-92 94-92s94 41.4 94 92v224c0 50.6-41.9 92-94 92s-94-41.4-94-92V232z" p-id="13800" fill="#6d6868"></path></svg> <svg width="37" height="57" viewBox="0 0 37 57" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28.4992 12.9C28.4992 7.15624 23.843 2.5 18.0992 2.5C12.3555 2.5 7.69922 7.15624 7.69922 12.9V25.9C7.69922 31.6438 12.3555 36.3 18.0992 36.3C23.843 36.3 28.4992 31.6438 28.4992 25.9V12.9Z" stroke="#6D6868" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M33.7 39.6592C31.7501 41.8748 29.35 43.6489 26.6598 44.8631C23.9697 46.0772 21.0514 46.7035 18.1 46.7M18.1 46.7C15.1485 46.7035 12.2303 46.0772 9.54017 44.8631C6.85003 43.6489 4.44988 41.8748 2.5 39.6592M18.1 46.7V54.5M12.9 54.5H23.3" stroke="#6D6868" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 743 B

After

Width:  |  Height:  |  Size: 711 B

View File

@@ -0,0 +1,6 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M36.876 6.10352e-05C37.278 8.15739e-05 37.7109 0.0606485 38.082 0.210999C38.4531 0.361329 38.7933 0.601565 39.1025 0.872131C39.3809 1.14276 39.5983 1.50348 39.7529 1.86432C39.9385 2.22508 40 2.6163 40 3.03717C40 3.42815 39.9076 3.81982 39.7529 4.18073C39.5983 4.54161 39.3503 4.87217 39.041 5.17291L23.7295 20.0606L26.6367 22.8575L39.041 34.9473C39.5978 35.5188 39.9069 36.2708 39.876 37.0528C39.8759 37.8347 39.5671 38.5566 38.9795 39.128C38.4227 39.6693 37.649 40.0001 36.8447 40.0001C36.0405 40 35.2674 39.7289 34.6797 39.1876L17.1719 22.1651C16.8935 21.8944 16.6761 21.5641 16.5215 21.2032C16.3669 20.8424 16.2745 20.4514 16.2744 20.0606C16.2744 19.6696 16.3668 19.2778 16.5215 18.8868C16.6761 18.526 16.8935 18.1955 17.1719 17.9249L34.6797 0.902405C34.958 0.631804 35.2979 0.390671 35.6689 0.240295C36.0711 0.0899197 36.4738 6.10352e-05 36.876 6.10352e-05Z" fill="#A2A2A2"/>
<path d="M36.876 6.10352e-05C37.278 8.15739e-05 37.7109 0.0606485 38.082 0.210999C38.4531 0.361329 38.7933 0.601565 39.1025 0.872131C39.3809 1.14276 39.5983 1.50348 39.7529 1.86432C39.9385 2.22508 40 2.6163 40 3.03717C40 3.42815 39.9076 3.81982 39.7529 4.18073C39.5983 4.54161 39.3503 4.87217 39.041 5.17291L23.7295 20.0606L26.6367 22.8575L39.041 34.9473C39.5978 35.5188 39.9069 36.2708 39.876 37.0528C39.8759 37.8347 39.5671 38.5566 38.9795 39.128C38.4227 39.6693 37.649 40.0001 36.8447 40.0001C36.0405 40 35.2674 39.7289 34.6797 39.1876L17.1719 22.1651C16.8935 21.8944 16.6761 21.5641 16.5215 21.2032C16.3669 20.8424 16.2745 20.4514 16.2744 20.0606C16.2744 19.6696 16.3668 19.2778 16.5215 18.8868C16.6761 18.526 16.8935 18.1955 17.1719 17.9249L34.6797 0.902405C34.958 0.631804 35.2979 0.390671 35.6689 0.240295C36.0711 0.0899197 36.4738 6.10352e-05 36.876 6.10352e-05Z" stroke="#A2A2A2"/>
<path d="M3.12402 39.9999C2.72195 39.9999 2.2891 39.9393 1.91797 39.789C1.5469 39.6387 1.20671 39.3984 0.897461 39.1279C0.619115 38.8572 0.40173 38.4965 0.24707 38.1357C0.0615446 37.7749 4.72544e-05 37.3837 2.62463e-07 36.9628C2.9625e-07 36.5719 0.0924056 36.1802 0.247071 35.8193C0.401736 35.4584 0.649669 35.1278 0.958985 34.8271L16.2705 19.9394L13.3633 17.1425L0.958987 5.05267C0.402194 4.48124 0.0930932 3.72915 0.124027 2.9472C0.124068 2.16533 0.432862 1.44339 1.02051 0.872007C1.5773 0.330652 2.35102 -6.29255e-05 3.15528 -6.28544e-05C3.95949 -3.75358e-05 4.73262 0.271107 5.32032 0.812437L22.8281 17.8349C23.1065 18.1056 23.3239 18.4359 23.4785 18.7968C23.6331 19.1576 23.7255 19.5486 23.7256 19.9394C23.7256 20.3304 23.6332 20.7222 23.4785 21.1132C23.3239 21.474 23.1065 21.8045 22.8281 22.0751L5.32031 39.0976C5.04199 39.3682 4.70214 39.6093 4.33105 39.7597C3.92893 39.9101 3.52615 39.9999 3.12402 39.9999Z" fill="#A2A2A2"/>
<path d="M3.12402 39.9999C2.72195 39.9999 2.2891 39.9393 1.91797 39.789C1.5469 39.6387 1.20671 39.3984 0.897461 39.1279C0.619115 38.8572 0.40173 38.4965 0.24707 38.1357C0.0615446 37.7749 4.72544e-05 37.3837 2.62463e-07 36.9628C2.9625e-07 36.5719 0.0924056 36.1802 0.247071 35.8193C0.401736 35.4584 0.649669 35.1278 0.958985 34.8271L16.2705 19.9394L13.3633 17.1425L0.958987 5.05267C0.402194 4.48124 0.0930932 3.72915 0.124027 2.9472C0.124068 2.16533 0.432862 1.44339 1.02051 0.872007C1.5773 0.330652 2.35102 -6.29255e-05 3.15528 -6.28544e-05C3.95949 -3.75358e-05 4.73262 0.271107 5.32032 0.812437L22.8281 17.8349C23.1065 18.1056 23.3239 18.4359 23.4785 18.7968C23.6331 19.1576 23.7255 19.5486 23.7256 19.9394C23.7256 20.3304 23.6332 20.7222 23.4785 21.1132C23.3239 21.474 23.1065 21.8045 22.8281 22.0751L5.32031 39.0976C5.04199 39.3682 4.70214 39.6093 4.33105 39.7597C3.92893 39.9101 3.52615 39.9999 3.12402 39.9999Z" stroke="#A2A2A2"/>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -1,4 +1,4 @@
<svg width="34" height="33" viewBox="0 0 34 33" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.96387 16.8115C1.96387 19.7782 2.8436 22.6783 4.49182 25.1451C6.14004 27.6118 8.48272 29.5344 11.2236 30.6697C13.9645 31.805 16.9805 32.1021 19.8902 31.5233C22.7999 30.9445 25.4727 29.5159 27.5705 27.4181C29.6682 25.3203 31.0969 22.6476 31.6756 19.7379C32.2544 16.8282 31.9574 13.8122 30.8221 11.0713C29.6867 8.33038 27.7641 5.9877 25.2974 4.33948C22.8307 2.69126 19.9306 1.81152 16.9639 1.81152C12.7705 1.8273 8.74548 3.46356 5.73053 6.37819L1.96387 10.1449" stroke="black" stroke-width="2.28788" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1 21C1 24.9556 2.17298 28.8224 4.37061 32.1114C6.56823 35.4004 9.69181 37.9638 13.3463 39.4776C17.0008 40.9913 21.0222 41.3874 24.9018 40.6157C28.7814 39.844 32.3451 37.9392 35.1421 35.1421C37.9392 32.3451 39.844 28.7814 40.6157 24.9018C41.3874 21.0222 40.9913 17.0008 39.4776 13.3463C37.9638 9.69181 35.4004 6.56823 32.1114 4.37061C28.8224 2.17298 24.9556 1 21 1C15.4088 1.02103 10.0422 3.20272 6.02222 7.08889L1 12.1111" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.96387 1.81152V10.1449H10.2972M16.9639 8.47819V16.8115L23.6305 20.1448" stroke="black" stroke-width="2.28788" stroke-linecap="round" stroke-linejoin="round"/> <path d="M1 1V12.1111H12.1111M21 9.88889V21L29.8889 25.4444" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 759 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Outline" viewBox="0 0 24 24" width="512" height="512"><path d="M10.6,12.71a1,1,0,0,1,0-1.42l4.59-4.58a1,1,0,0,0,0-1.42,1,1,0,0,0-1.41,0L9.19,9.88a3,3,0,0,0,0,4.24l4.59,4.59a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.42Z"/></svg>

After

Width:  |  Height:  |  Size: 317 B

View File

@@ -1,9 +1,10 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="60" height="60" fill="url(#pattern0_1_2934)"/> <circle cx="32.5" cy="30.5" r="18.5" fill="white"/>
<rect width="60" height="60" fill="url(#pattern0_2099_1006)"/>
<defs> <defs>
<pattern id="pattern0_1_2934" patternContentUnits="objectBoundingBox" width="1" height="1"> <pattern id="pattern0_2099_1006" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_1_2934" transform="scale(0.0111111)"/> <use xlink:href="#image0_2099_1006" transform="scale(0.0111111)"/>
</pattern> </pattern>
<image id="image0_1_2934" width="90" height="90" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAAA4qEECAAAACXBIWXMAAAsTAAALEwEAmpwYAAAETUlEQVR4nO2dS2jVRxSHv9a0SbGtm4JGhVpS10KtqypKSCsBS+3CFtIH9UnBglYRdSG4M2YlAbsSuuo2TZuFortuFGtJE+HSLrRdtBbNy66aQuKRgRMIITfexzzO/3/ng98m3DuZ+THMzDnzuJDJZDKZTCaTyWQymZpZBWwDDgIDwDAwDtwDpoH/VdP6t3H9zEX9zttaRmYZNgLHgRHgX0Ca1GPgR+AYsIEW5yXgM+AGMO/B3GqaA64DnwIdtBAva0/7O6C51fQIOA+socS8AJwCphIYvFSuDie1TqViB3DXgMFL9RvQQwlwY+Jl4IkBU6vJ1W0QaKegbAJuGTBSatQvwJsUjB5PyzSJLLcs7KYgfAj8Z8A0aVAuGPoY4xwJvCaWSHJtOIxR9mpwkNok8Wj2RxjDjWuzBsyRAMPIexhhc0EnPqljguxKbXK7Louk5LqTep192YAJEkkuqEkWVluO+MSzXFt3xja5DRgz0HiJrLuxE1GnDDRaEunrmPnkSQMNlkSaVA+Cc9pAYyWxXC47eNrzgYGGSmL9o1txwfjcQCPFiPpCGn3DQAPFiK6GPBJQhszcrM4z61SnNadRbzkugdYZwujjJTG5d5m2nWmwvK9CGD1iwChpQq7Xvl+lbWsbLHPIt8nuiNVMSU12rG+w3Bnfx8+2GTBLPA8XiznbRPlbfRp9sMQm9za5abHfp9EDBkwTz8OFY7eHjeQLPo3+wYBxYtBk7xNikVKisxGGi8Ua9Wn0nwYMFGM9eUH3fRpt4QSoGDTZacKn0Y2EqK1gsugQZNLon4AvNXz9uYBjclCjfe2ofAc8t2Tv8UqBTfY+dPzhqVKblin7+QbNtmCy98nQx/JuHnixSvn1mm3FZO/Lu2FPlepe4X/UarYlk70HLL5C8N+B15ow25rJTv1Wk0oV3dmohpssvzG2hFtJX1hOk47V2bMt9uQFvWU98V+psWdb7clBEv/o3WpJ0LO3GO3JQbay0GvFksBsqyY7HSUA6wPeUak8YxixNFwsaK6BOtfM9YAVr9RR8dQmBz1Agz77IInN3m3A5OBHwjoiPP9QWcFsKyYHP+QY6xD6mB5qWcweQ1fsThCBVyLtuDwEzmnk9a2hc38uLbqaSJw00GBJJLfMjYZL2P9qoNFS9stCju35+ls8Bg0YIJF0iYS06/VdKblur7A7FI0uvZguJdUM8AZG2GUkkBDPcinadzHGByV8GGUfRjlsKLCQJuQ6zCGMs7fgw8isxSd+qtFd0AlyRuebQvE6cNOAeVKj7lh40qeZdfag8QjyiQYjydfJvsL1MQOmLpWr0zuUjDZ9VGTCgMETmoVzdSotq7WRfyUw+KE+1P0qLUQH8AlwLXCgM6cbqX2t9vT8cnTqDYDv9dcomjV3Wss6GvJIQNFZpefZDuhJzSE9g3xPt9AWfh5kSv82qp/p11us7rv550EymUwmk8lkMplMhhp5CqaIor9P1Mi+AAAAAElFTkSuQmCC"/> <image id="image0_2099_1006" width="90" height="90" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAABaCAYAAAA4qEECAAAACXBIWXMAAAsTAAALEwEAmpwYAAAETUlEQVR4nO2dS2jVRxSHv9a0SbGtm4JGhVpS10KtqypKSCsBS+3CFtIH9UnBglYRdSG4M2YlAbsSuuo2TZuFortuFGtJE+HSLrRdtBbNy66aQuKRgRMIITfexzzO/3/ng98m3DuZ+THMzDnzuJDJZDKZTCaTyWQymZpZBWwDDgIDwDAwDtwDpoH/VdP6t3H9zEX9zttaRmYZNgLHgRHgX0Ca1GPgR+AYsIEW5yXgM+AGMO/B3GqaA64DnwIdtBAva0/7O6C51fQIOA+socS8AJwCphIYvFSuDie1TqViB3DXgMFL9RvQQwlwY+Jl4IkBU6vJ1W0QaKegbAJuGTBSatQvwJsUjB5PyzSJLLcs7KYgfAj8Z8A0aVAuGPoY4xwJvCaWSHJtOIxR9mpwkNok8Wj2RxjDjWuzBsyRAMPIexhhc0EnPqljguxKbXK7Louk5LqTep192YAJEkkuqEkWVluO+MSzXFt3xja5DRgz0HiJrLuxE1GnDDRaEunrmPnkSQMNlkSaVA+Cc9pAYyWxXC47eNrzgYGGSmL9o1txwfjcQCPFiPpCGn3DQAPFiK6GPBJQhszcrM4z61SnNadRbzkugdYZwujjJTG5d5m2nWmwvK9CGD1iwChpQq7Xvl+lbWsbLHPIt8nuiNVMSU12rG+w3Bnfx8+2GTBLPA8XiznbRPlbfRp9sMQm9za5abHfp9EDBkwTz8OFY7eHjeQLPo3+wYBxYtBk7xNikVKisxGGi8Ua9Wn0nwYMFGM9eUH3fRpt4QSoGDTZacKn0Y2EqK1gsugQZNLon4AvNXz9uYBjclCjfe2ofAc8t2Tv8UqBTfY+dPzhqVKblin7+QbNtmCy98nQx/JuHnixSvn1mm3FZO/Lu2FPlepe4X/UarYlk70HLL5C8N+B15ow25rJTv1Wk0oV3dmohpssvzG2hFtJX1hOk47V2bMt9uQFvWU98V+psWdb7clBEv/o3WpJ0LO3GO3JQbay0GvFksBsqyY7HSUA6wPeUak8YxixNFwsaK6BOtfM9YAVr9RR8dQmBz1Agz77IInN3m3A5OBHwjoiPP9QWcFsKyYHP+QY6xD6mB5qWcweQ1fsThCBVyLtuDwEzmnk9a2hc38uLbqaSJw00GBJJLfMjYZL2P9qoNFS9stCju35+ls8Bg0YIJF0iYS06/VdKblur7A7FI0uvZguJdUM8AZG2GUkkBDPcinadzHGByV8GGUfRjlsKLCQJuQ6zCGMs7fgw8isxSd+qtFd0AlyRuebQvE6cNOAeVKj7lh40qeZdfag8QjyiQYjydfJvsL1MQOmLpWr0zuUjDZ9VGTCgMETmoVzdSotq7WRfyUw+KE+1P0qLUQH8AlwLXCgM6cbqX2t9vT8cnTqDYDv9dcomjV3Wss6GvJIQNFZpefZDuhJzSE9g3xPt9AWfh5kSv82qp/p11us7rv550EymUwmk8lkMplMhhp5CqaIor9P1Mi+AAAAAElFTkSuQmCC"/>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_图层_1" data-name="图层 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 880 860.38">
<path d="M0,778.02v-31c5.85-.83,5.72-6.77,7.59-9.91,8.69-14.56,35.82-36.92,48.91-50.09,132.41-133.33,265.22-266.72,399-399,24.48-24.21,50.22-64.36,90.41-43.4,11.6,6.05,52.53,47.35,62.07,58.93,36.58,44.38-10.81,75.6-38.98,103.98-134.47,135.49-269.91,271.36-406,406-16.26,16.09-36.44,43.61-60.47,46.53-29.22,3.55-48.2-22.96-66.04-41.03-9.24-9.36-21.12-18.28-28.34-29.66-2.17-3.42-2.85-10.44-8.15-11.35ZM411.35,365.37c-2.17,2.21-15.35,15.62-15.83,16.67-.89,1.93-.9,4.04,0,5.97,13.11,12.89,26.45,25.54,39.5,38.49,9.43,9.36,18.54,18.95,28.02,27.98,3.81,3.63,7.06,10.05,13.42,10.57l111.05-110.01c8.1-9.65,12.08-18.21,5.33-30.36-3.97-7.14-45.89-48.98-53.82-55.18-15.22-11.91-27.19-3.82-38.92,7.13-29.56,27.59-60.23,59.7-88.75,88.75ZM45.35,731.37c-11.89,12.23-27.69,25.33-17.21,44.01,4.11,7.33,43.34,46.29,51.36,52.64,12.24,9.71,22.88,9.89,35.03.02l338.5-338.5c2.02-1.93,3.87-4.47,2.24-7.24l-76.71-77.33c-6.81-3.73-7.97,2-11.53,5.57-106.81,107.19-216.25,212.35-321.68,320.82Z"/>
<path d="M880,334.02v17c-5.37-.47-7.34,5.21-11.28,7.22-8.66,4.42-23.83,6.25-34.09,10.91-26.75,12.14-29.6,29.14-37.9,54.1-3.19,9.58-7.95,22.23-20.19,21.83-17.04-.55-18.67-24.85-23.04-37.04-6.38-17.79-17.31-31.49-34.79-39.21-12.9-5.7-50.73-9.57-44.24-30.86,4.1-13.47,23.9-15.02,35.08-18.92,23.2-8.09,37.12-22.61,45.17-45.83,3.07-8.85,4.36-25.17,12.91-31.09,13.68-9.47,24.05,3.61,28.11,15.65,5.67,16.79,7.2,30.82,20.37,44.63,16.19,16.99,35.71,15.07,53.62,24.38,3.37,1.75,6.17,6.34,10.28,7.22ZM776.74,289.16c-4.56.64-5.93,9.79-7.87,13.23-4.92,8.72-16.76,21.22-24.93,27.07-3.39,2.43-27.4,12.25-17.91,17.04,1.57.79,3.84.48,5.69,1.3,14.82,6.58,27.91,19.11,36.1,32.9,2.41,4.06,4.1,13.24,9.67,13.25,5.06.01,6.38-9.04,8.68-13.25,7.33-13.41,22.11-26.66,36.1-32.9,2.84-1.26,8.25-.22,7.76-5.23-.43-4.4-10.52-7.04-13.94-9.15-10.29-6.37-25.31-20.31-30.97-31.03-1.68-3.19-3.2-13.95-8.4-13.22Z"/>
<path d="M372.75.26c17.02-2.7,21.7,16.54,25.74,28.78.66,2,.35,4.18.97,6.03,13.37,39.65,24.62,56.65,65.57,70.43,12.7,4.27,34.46,6.35,40.83,19.17,3.04,6.11.84,15.15-3.82,19.9-7.28,7.4-39.68,13.7-51.41,18.59-18.8,7.84-36.26,25.05-44.04,43.96-4.84,11.74-11.29,45.73-19.62,52.38-9.65,7.7-21.8,4.05-27.82-6.11-7.31-12.34-9.82-36.44-16.95-51.05-15.65-32.09-39.88-37.6-70.19-47.81-6.54-2.2-20.02-5.97-23.84-11.16-7.42-10.08-4.71-18.67,3.87-26.8,29-11.22,57.77-13.95,78.99-39.01,16.65-19.67,17.52-40.92,26.48-63.52,2.43-6.14,8.52-12.7,15.25-13.77ZM373.63,45.09c-3.49,2.24-2.14,6.63-3.08,9.98-8.8,31.51-36.03,59.55-66.76,70.24-3.39,1.18-14.11,1.7-14.73,5.33-.78,4.51,19.66,9.4,23.63,11.19,23.74,10.71,47.33,35.36,55.8,60.2,1.49,4.36,1.94,11.23,3.41,14.59.55,1.24.82,2.54,2.58,2.47,1.5-.08,2.17-1.03,2.94-2.13,3.14-4.52,5.53-17.08,8.16-22.84,9.96-21.83,30.88-42.46,52.72-52.28,5.99-2.69,14.22-4.36,20.1-6.9,13.11-5.67-3.32-7.16-6.66-8.15-28.02-8.37-54.24-31.06-66.23-57.77-1.97-4.39-6.53-21.22-8.95-23.05-.95-.72-1.67-1.23-2.94-.86Z"/>
<path d="M636.71,457.23c20.94-4.69,20.95,15.78,25.59,28.99,5.11,14.55,14.97,26.74,29.53,32.47,9.09,3.58,25.95,5.73,31.6,13.4,9.34,12.66-1.53,22.37-13.25,26.61-18.27,6.61-33.98,7.98-43.9,28.1-6.28,12.74-8.15,46.13-30.23,38.68-10.99-3.71-14.09-28.07-18.87-38.13-5.97-12.57-17.69-20.98-30.64-25.36-3.51-1.19-7.43-1.16-10.72-2.28-6.67-2.26-17.88-7.24-18.84-15.15-2.53-20.97,22.31-20.38,35.96-26.09,8.39-3.51,21.6-16.31,25.26-24.74,4.28-9.87,6.48-33.79,18.51-36.49ZM676.76,545.79c5.98-5.71-8.51-11.82-11.7-14.33-6.14-4.82-12.27-10.92-16.47-17.53-1.39-2.19-3.02-9.96-6.08-9.97-2.5,0-11.59,15.35-14.51,18.56-5.69,6.22-13.07,9.73-19.02,14.98-8.94,7.88,1.05,8.3,5.39,10.67,4.81,2.63,14.75,12.4,18.08,16.92,2.63,3.58,6.01,13.02,10.06,13.03s3.7-4.8,5.7-8.4c2.93-5.27,16.66-19.78,21.87-22.13,1.99-.9,5.35-.52,6.69-1.81Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -1,3 +1,3 @@
<svg width="50" height="43" viewBox="0 0 50 43" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="47" height="47" viewBox="0 0 47 47" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M44.0137 16.3477C48.4427 18.0966 48.4427 24.3653 44.0137 26.1143L9.67871 39.6719C5.26568 41.4142 0.98905 36.8733 2.99219 32.5723L8.12695 21.5478C8.22042 21.3471 8.22043 21.1148 8.12695 20.9141L2.99219 9.88965C0.989068 5.58859 5.26567 1.04766 9.67871 2.79004L44.0137 16.3477Z" stroke="black" stroke-width="4.5" stroke-linejoin="round"/> <path d="M44.6152 23.0769C44.6152 23.6595 44.2866 24.1922 43.7656 24.4529L3.76562 44.4529C3.20141 44.735 2.52193 44.6439 2.05176 44.2234C1.58157 43.8028 1.41503 43.1377 1.63281 42.5457L8.79394 23.0769L1.63281 3.60815C1.41504 3.01608 1.58158 2.35103 2.05176 1.93042C2.52194 1.5099 3.20142 1.41882 3.76563 1.70093L43.7656 21.7009L43.8604 21.7527C44.3261 22.028 44.6152 22.5305 44.6152 23.0769Z" stroke="#6D6868" stroke-width="3.07692" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 448 B

After

Width:  |  Height:  |  Size: 562 B

View File

@@ -0,0 +1,3 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M30.5 16C30.5 16.5682 30.1791 17.0877 29.6709 17.3418L3.6709 30.3418C3.12081 30.6168 2.45842 30.5281 2 30.1182C1.54166 29.7082 1.37965 29.0596 1.5918 28.4824L6.18457 16L1.5918 3.51758C1.37965 2.94039 1.54166 2.29185 2 1.88183C2.45842 1.47186 3.12081 1.38316 3.6709 1.6582L29.6709 14.6582L29.8525 14.7656C30.2544 15.043 30.5 15.5029 30.5 16Z" stroke="#6D6868" stroke-width="3" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 514 B

View File

@@ -1 +1,4 @@
<svg t="1760605228863" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10717" width="200" height="200"><path d="M512 341.333333a170.666667 170.666667 0 1 1 0 341.333334 170.666667 170.666667 0 0 1 0-341.333334z m0 64a106.666667 106.666667 0 1 0 0 213.333334 106.666667 106.666667 0 0 0 0-213.333334z" fill="currentColor" p-id="10718"></path><path d="M538.496 42.666667a85.333333 85.333333 0 0 1 78.506667 51.968l2.432 6.4 16.554666 49.578666c7.765333 23.338667 25.514667 41.685333 47.018667 53.632l9.898667 5.76c18.773333 11.264 40.448 17.408 61.952 15.189334l8.064-1.28 51.285333-10.453334a85.333333 85.333333 0 0 1 87.338667 35.157334l3.669333 5.76 26.453333 45.909333a85.333333 85.333333 0 0 1-5.418666 93.610667l-4.608 5.674666-34.773334 39.210667c-16.298667 18.346667-23.338667 42.88-22.954666 67.413333v11.605334c-0.341333 21.802667 5.205333 43.605333 17.877333 61.056l5.12 6.4 34.730667 39.168a85.333333 85.333333 0 0 1 13.44 92.842666l-3.413334 6.442667-26.453333 45.909333a85.333333 85.333333 0 0 1-84.266667 42.026667l-6.741333-1.109333-51.285333-10.496c-24.106667-4.906667-48.896 1.28-69.973334 13.952l-9.941333 5.76c-19.114667 10.666667-35.242667 26.282667-44.074667 46.037333l-2.986666 7.594667-16.512 49.578666a85.333333 85.333333 0 0 1-74.112 58.112l-6.826667 0.256h-52.992a85.333333 85.333333 0 0 1-78.506667-51.968l-2.432-6.4-16.554666-49.578666c-7.765333-23.338667-25.514667-41.685333-47.018667-53.632l-9.898667-5.76c-18.773333-11.264-40.448-17.408-61.952-15.189334l-8.106666 1.28-51.2 10.453334a85.333333 85.333333 0 0 1-87.381334-35.157334l-3.669333-5.76-26.453333-45.909333a85.333333 85.333333 0 0 1 5.418666-93.610667l4.608-5.674666 34.773334-39.210667c16.298667-18.346667 23.338667-42.88 22.954666-67.413333v-11.605334c0.341333-21.802667-5.205333-43.605333-17.877333-61.098666l-5.12-6.314667-34.730667-39.253333a85.333333 85.333333 0 0 1-13.44-92.8l3.413334-6.442667 26.453333-45.909333a85.333333 85.333333 0 0 1 84.266667-42.026667l6.741333 1.109333 51.285333 10.496c24.106667 4.906667 48.896-1.28 69.973334-13.952l9.941333-5.76c19.114667-10.666667 35.242667-26.282667 44.074667-46.037333l2.986666-7.594667 16.512-49.578666a85.333333 85.333333 0 0 1 74.112-58.112l6.826667-0.256h52.992z m0 64h-52.992a21.333333 21.333333 0 0 0-20.224 14.592l-16.554667 49.621333c-12.842667 38.570667-39.936 66.816-69.205333 84.949333l-15.488 9.045334c-31.530667 18.901333-72.533333 30.634667-115.797333 21.76l-51.242667-10.496a21.333333 21.333333 0 0 0-22.784 10.24l-26.453333 45.909333a21.333333 21.333333 0 0 0 2.474666 24.789333l34.773334 39.253334c26.965333 30.378667 37.930667 67.84 38.997333 102.357333l0.085333 18.090667c0.554667 36.736-9.898667 77.994667-39.082666 110.933333l-34.773334 39.168a21.333333 21.333333 0 0 0-2.517333 24.832l26.496 45.909333a21.333333 21.333333 0 0 0 22.784 10.24l51.242667-10.496c39.936-8.192 77.952 1.152 108.373333 17.536l15.530667 8.96c32.042667 17.834667 62.634667 47.445333 76.586666 89.258667l16.554667 49.621333a21.333333 21.333333 0 0 0 20.224 14.592h52.992a21.333333 21.333333 0 0 0 20.224-14.592l16.554667-49.621333c12.885333-38.570667 39.936-66.816 69.205333-84.949333l15.488-9.045334c31.530667-18.901333 72.533333-30.634667 115.797333-21.76l51.285334 10.496a21.333333 21.333333 0 0 0 22.741333-10.24l26.453333-45.909333a21.333333 21.333333 0 0 0-2.474666-24.832l-34.773334-39.210667c-26.965333-30.378667-37.930667-67.84-38.997333-102.357333l-0.085333-18.090667c-0.554667-36.736 9.898667-77.994667 39.082666-110.933333l34.773334-39.210667a21.333333 21.333333 0 0 0 2.517333-24.789333l-26.496-45.909333a21.333333 21.333333 0 0 0-22.741333-10.24l-51.285334 10.496c-39.936 8.192-77.952-1.194667-108.373333-17.536l-15.530667-8.96c-32.042667-17.834667-62.634667-47.445333-76.586666-89.258667l-16.554667-49.621333a21.333333 21.333333 0 0 0-20.224-14.592z" fill="currentColor" p-id="10719"></path></svg> <svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.1478 8.61658C20.2626 7.40895 20.8235 6.2875 21.7209 5.47132C22.6184 4.65515 23.7879 4.20288 25.0009 4.20288C26.214 4.20288 27.3835 4.65515 28.2809 5.47132C29.1784 6.2875 29.7393 7.40895 29.8541 8.61658C29.9231 9.39669 30.179 10.1487 30.6002 10.8089C31.0214 11.4692 31.5954 12.0183 32.2738 12.4097C32.9521 12.801 33.7148 13.0232 34.4972 13.0575C35.2796 13.0917 36.0587 12.9369 36.7687 12.6062C37.871 12.1057 39.1201 12.0333 40.2728 12.403C41.4256 12.7727 42.3995 13.5581 43.0052 14.6064C43.6108 15.6546 43.8047 16.8907 43.5492 18.074C43.2936 19.2573 42.607 20.3033 41.6228 21.0082C40.9819 21.4579 40.4588 22.0553 40.0976 22.75C39.7365 23.4446 39.5479 24.216 39.5479 24.9989C39.5479 25.7818 39.7365 26.5532 40.0976 27.2478C40.4588 27.9424 40.9819 28.5398 41.6228 28.9895C42.607 29.6945 43.2936 30.7404 43.5492 31.9237C43.8047 33.107 43.6108 34.3431 43.0052 35.3914C42.3995 36.4396 41.4256 37.225 40.2728 37.5947C39.1201 37.9645 37.871 37.892 36.7687 37.3916C36.0587 37.0609 35.2796 36.9061 34.4972 36.9403C33.7148 36.9745 32.9521 37.1967 32.2738 37.5881C31.5954 37.9795 31.0214 38.5285 30.6002 39.1888C30.179 39.849 29.9231 40.601 29.8541 41.3812C29.7393 42.5888 29.1784 43.7102 28.2809 44.5264C27.3835 45.3426 26.214 45.7948 25.0009 45.7948C23.7879 45.7948 22.6184 45.3426 21.7209 44.5264C20.8235 43.7102 20.2626 42.5888 20.1478 41.3812C20.079 40.6008 19.823 39.8485 19.4017 39.188C18.9804 38.5275 18.4061 37.9783 17.7275 37.5868C17.0488 37.1954 16.2859 36.9733 15.5032 36.9393C14.7205 36.9053 13.9412 37.0604 13.2312 37.3916C12.1288 37.892 10.8797 37.9645 9.72699 37.5947C8.57422 37.225 7.60026 36.4396 6.99466 35.3914C6.38906 34.3431 6.19514 33.107 6.45065 31.9237C6.70616 30.7404 7.39282 29.6945 8.37699 28.9895C9.01786 28.5398 9.54101 27.9424 9.90217 27.2478C10.2633 26.5532 10.4519 25.7818 10.4519 24.9989C10.4519 24.216 10.2633 23.4446 9.90217 22.75C9.54101 22.0553 9.01786 21.4579 8.37699 21.0082C7.3942 20.3029 6.70876 19.2574 6.45386 18.0749C6.19897 16.8923 6.39284 15.6573 6.99779 14.6097C7.60274 13.5622 8.57555 12.7769 9.72717 12.4067C10.8788 12.0364 12.127 12.1075 13.2291 12.6062C13.939 12.9369 14.7181 13.0917 15.5005 13.0575C16.2829 13.0232 17.0456 12.801 17.7239 12.4097C18.4023 12.0183 18.9763 11.4692 19.3975 10.8089C19.8187 10.1487 20.0747 9.39669 20.1437 8.61658" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M25 31.25C28.4518 31.25 31.25 28.4518 31.25 25C31.25 21.5482 28.4518 18.75 25 18.75C21.5482 18.75 18.75 21.5482 18.75 25C18.75 28.4518 21.5482 31.25 25 31.25Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,9 @@
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<rect width="25.338" height="25.338" fill="url(#pattern0_27_28523)"/>
<defs>
<pattern id="pattern0_27_28523" patternContentUnits="objectBoundingBox" width="1" height="1">
<use xlink:href="#image0_27_28523" transform="scale(0.0078125)"/>
</pattern>
<image id="image0_27_28523" width="128" height="128" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAACxQAAAsUBidZ/7wAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAq5SURBVHic7Z17sJdFGcc/PzjG4aKeIxCVIY4CR9SkAkVGQsWcGq+jlJdK05pm7I/SssuYTCn+UWOXyVS0sbEmYqzGSisHUAyUSqCRwhsdTTIvJy4RIBcFOuf0x3N+44+f57fPvvvuLkd+z2fmmWFY2Gd/77Pv++67++x3Kxi5GQmcBswEjgUmAIcA7cAuYAfwItAJrAIeBp7eHw014jEIuAD4HbAX6C1ofweuB0bnbrhRnouAtRQPen+2E/gu0Jb1FxhBjAMeJE7g6209cHG+n2IU5Tzgv6QJfq3dBbRm+k2GJ58Fukkf/KotAw7N8cMMnavJF/haWwmMCGlwJeQ/Gf1yCbAAGfH70g2sQ97pG4FhwLuBIyh+Vy8Gzu6r08jMJOT73feOXQRcSePPuhbgdOAWYGuBem+K/ssMlRbgr/gF6DHgAwXrHwV8H9jtUX83cEqpX2MU5hr8gn8zxV4P9cwANnj4WYN0SiMDBwObcQekB/h0JH/jgGcVf73AFZH8GQpfRg/GNyL7PAbYovjspNzTxvCgAjyHOxC/Jc2X1rmK317gjAR+jRqm4w7AHmSlLxWLFf8/TujbAObiDsC8xP7fi4wvGvn/NzbPk5RHcXeAkzK0YbXShmO1CmygEM57HGWvAH/J0Ib7lXJXGwHrAKG8A/ea/DLkDkzNUqW8Q6vAOkAYWmbOy1laoftRM4isA4Shrbytz9IK6ML9pDlEq8A6QBoGZ/TjGun3aBVYBwhjh1L+ziyt0P1s1yqwDhBGl1I+NksrJG/Ahfoqsg4QxmYk568Rs8jzGjhTKe/M0IamRZuKnZmhDVq6+dEZ2tC0XI/74v8ssf+Ziv8XEvtveo7DHYBu4H2JfFeAFYr/2xP5NvoYg74uvxw4KIHvTyl+e4GTE/g1kIHzVejBT7UqOA14TfH5eGSfRh+TkcROn8DX2teJszQ7mTdm/1x2YQRfRg3DgBvwy8xtZD/vqyeUc4BtHn6WY3kAUTkf2a8fGvhaWwdcSrEATQTuxZ38UbXdeCwBG34cAdxHnMDX25PAHBoHqx34BBL4PQXq/Xycn97ctADXUmynTxnbhSSXLgeeQLaIhdQzH3v0l2Y68DfCgvg15K4NUQApawtJ87nZNLQDdxK2rXshcFRNXbORDpEr+L8AhsS8GM3Gx5FVs6IXvguRf+mPKcQbODay/yGfl7agF8gEYAlhF/5W9O3bbcCP8Bu9F7VnyLPYdEAyBNmupc2m9WePA1ML+puOSL3FCHwX8AXgbUV/tCHMQtbIi174VxH1jzJr/FORcYa2mbTeupEOdAUJ9ICa5bNhDPAdZJRelF8hwX8lUlsGI52hKhTZgbxOhiIB3wK8hOwCXomkmLuSTwwHFeByYBPF7/qXaYI59dRPgA5E6mRK358PR3r7ICRhcRuigvk08AjwZ2TmKwYnII/c6QX/315EjPEm5HPOKMho4DpkpFr0rtsO3E3xoNUyHPg2YRMyy4HjS/huag5DtGx2EmfEu5TiejfnIalQRX1tRlQ8mmVMFJ3LCJ/DdlkP8i2tfXOPBX4TWP9PMBHmYIYDPyV+4Ovtn8h++HpagC8ir46ida5FZNuNQEaiJybGtJ1IUkSVafjLs9XaLmQp1iZTStCOLF3mCn7VdiPf8vMIW7hZjOXKl6YV+BNhd14noqyxkHJr30WtC5FyNSJwO/4Xfkvfvz+TxuvVExC5tZDHuWbdwG2YmnY0zsHvwr8OfA8ZJ/hSQQ5B+IenD81Wk0enp2kYiiQ1+jxuy1z4VmRbVWjgdwJfxeRSo+OjivkkMt1blgrwTQ9/9fZrRG7diEwrojvnuvgbEC3bmMxXfFbtBfb9RDQicwn6YKuoDLoPreiDw+eRCSkjIQ/gDsKChL5PV3x3k0+SpSlpxZ0+tYd9s2JTsNDhvxf4TGL/TY12By7K0IbZShvmZ2jDAYlPSvFkpVyTK43BYmRuoRFTMrThgMSnA0xUyh+O0RCFHUh+XCOOIp823wGFTwcYo5TnkkV9yVE2BDtPNwifDuCSRd1Kvrw5TZvv4CytOMAou60oZwqVpWslwKcDuGRRDyXfJMy7lHJVFtV4Mz4dYINSHmPu3weX/OpuZOnZKIhPB9DkRj8YoyEKI3CvMD6PhzK28WZ8OsAapfz8GA1R+DDufXGrM7ShaWnFLXiQYyp4kcO/TQVn4Pe4A3BPQt9nKL5tMSgDF+MOQg9wagK/Q5FXkC0H72da0RUqNwJHRvRZQZaZLSFkgHAteiCeIk5KVgX4loe/erOUsIQMRR63WhC6kF07oQxHBBKLBr9qlhSakLPwC8Ju4BZgVIG6K8DHkH2AocGvNUsLT8QP8A/CNuAO4EM03ovXgdyx2mAvxGxjSAKGoB+c3J+9hkii1m4NC5FuCTHbGhaZNtLcsZq9jgg72ubQAcBhhG0SDbXtyBikim0PHwAMQzR9Ugd/HSL6VI8JRAwQLiVMb1ezHkTpSzsE2SRiBgBtiDpXyN3Yny2h+MlXJhI1ABgFfAXZKFo0EFuBuyg/kWQycZ6k7vFHI/q8U5H08rFI8uZByBzBFkQS9SlEEnUFosYdAxOKNEpLxc7O3+S8NMs7byCJRbcAJ/KGWPREZPzUigxKtyF7IDqBVciT8T+RfDc9+1MufhoyvvE9cbT2S2UZclzs0BL+jT5yHxgxA5G9jfFltB74EnZGUBRSHxnTjswxxAh8vXVik1jRSHFo1EnIezxF8KvWDczFNsVGIeaxcR8l7PUSavdir4RolD048jL2z8GRi7DFrWjkPjp2JzIZ9iiSIxG6prKA5vmsz0LKw6PXIE+M4xr4bkPS435JscOjr4nz041aYh4f/xwycCxyp45Hjof1PT6+v2VzoyTDgBuQCxwa/HsoN5FzFjJLqPn5I/YqSMZk4DGKB39OJP8nIFPSmr+PRPJn9MMg4Cr8p3Rvi+z/RPTTyVdjT4HkjEHvBI+QZoPKJxW/vZQ7es/w4HjcAehG11gMpYIcsunyPy+Rb6OPObgDkFq9dIbi/1+J/Tc9D+IOQAr19Hq001nHaxWUlYlrZlzytJuQR3Rq7lPK+ztrcR+sA4QxEtkY04g/IGOA1CxRyju0CqwDhKFpFuaSz31RKddkfq0DBOKSzwVd1jYWpeVzrQOkIZdmYXV9oBFqfK0DhOGSz4V8qmWH457xe1WrwDpAGBuV8lw6RZpM7yatAusAYWxAtrE14jTyzMXPUso1mV+jBJpKSpn9jb5o+giTMrShabkR98W/M7H/9yv+u7AVwaScjDsAe/GYiCnBQ4r/uxP6NpC761ncQXiANHfhBYrfXuS4PyMxPuqpcyP7nISeGrYWG+BnYQSyc9cVjB7iydkfiSSWap3u8kj+DA+uRg9IL/BDymUHnYLfnoEnSvoxCjIY2TXs0wlWUXxT52hkM6rP/oBuLBVsv3AMxcSxHkJEqd7eoL4W5KCMW/FLBa/ajUUbbt+J8bgIyf0vMvjqQVTNupDZxeHINPI4ih+EuRA4lzx5CEYDPof/3RrTVmCnpgwYriTvDuGlmBr6gONs9M/DGHYHpg0wYBmLvJdTBL4L2/71luFCRAgzRuC3Azdjj/y3HIOQbeb3U2y/f9WeAa6j2BE8XthnYH7akTMWT0WEIsf3/V0bsulzO5Lt2wmsRFLM16ZqzP8B52laKFMhCbMAAAAASUVORK5CYII="/>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 849 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

BIN
src/assets/images/mini.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 655 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 220 KiB

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 244 KiB

After

Width:  |  Height:  |  Size: 242 KiB

View File

@@ -25,15 +25,15 @@
}) })
.then(() => { .then(() => {
MyEvent.emit('clear-generate-state') MyEvent.emit('clear-generate-state')
MyEvent.emit('clearAllCache') // MyEvent.emit('clearAllCache')
nav.path && router.push(nav.path) nav.path && router.push(nav.path)
}) })
.catch(() => {}) .catch(() => {})
} }
const navs = [ const navs = [
{ label: 'Home', icon: 'home', size: 73, path: '/homeNav' }, { label: 'Home', icon: 'home', size: 73, path: '/workshop/home', on: onHome },
{ label: 'Library', icon: 'library', size: 53, path: '/workshop/library' }, { label: 'Library', icon: 'library', size: 53, path: '/workshop/library' },
{ label: 'Profile', icon: 'profile', size: 55, path: '/workshop/profile' } // { label: 'Profile', icon: 'profile', size: 55, path: '/workshop/profile' }
] ]
const onNavClick = (nav) => { const onNavClick = (nav) => {
if (currentRoute.value !== nav.path) nav.on ? nav.on(nav) : nav.path && router.push(nav.path) if (currentRoute.value !== nav.path) nav.on ? nav.on(nav) : nav.path && router.push(nav.path)
@@ -49,10 +49,10 @@
@click="onNavClick(nav)" @click="onNavClick(nav)"
> >
<SvgIcon :name="`${nav.icon}_${currentRoute === nav.path ? '1' : '0'}`" size="55" /> <SvgIcon :name="`${nav.icon}_${currentRoute === nav.path ? '1' : '0'}`" size="55" />
<span class="label">{{ nav.label }}</span> <!-- <span class="label">{{ nav.label }}</span> -->
</div> </div>
</div> </div>
<div class="footer-navigation placeholder" v-show="isPlaceholder"></div> <!-- <div class="footer-navigation placeholder" v-show="isPlaceholder"></div> -->
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -61,9 +61,9 @@
height: var(--footer-navigation-height, 14.9rem); height: var(--footer-navigation-height, 14.9rem);
} }
.footer-navigation.main { .footer-navigation.main {
position: fixed; // position: fixed;
bottom: 0; // bottom: 0;
z-index: var(--footer-navigation-z-index, 999); // z-index: var(--footer-navigation-z-index, 999);
background-color: var(--footer-navigation-background, #fff); background-color: var(--footer-navigation-background, #fff);
box-shadow: -2.6rem -1.4rem 3.47rem 0 rgba(0, 0, 0, 0.05); box-shadow: -2.6rem -1.4rem 3.47rem 0 rgba(0, 0, 0, 0.05);
display: flex; display: flex;

View File

@@ -1,55 +1,49 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import MyEvent from '@/utils/myEvent'
const router = useRouter() const router = useRouter()
defineProps({ defineProps({
title: { type: String, default: 'AI STYLING ASSISTANT' }, title: { type: String, default: 'STYLING ASSISTANT' }
hasSetting: { type: Boolean, default: false },
styleType: { type: String, default: '1' },//1低 2高 3-12rem
isPlaceholder: { type: Boolean, default: true },
}) })
defineEmits(['clickReturn']) const emit = defineEmits(['clickReturn', 'clickProfile'])
const profileVisible = ref(false)
const handleProfileVisibleChange = (visible) => {
profileVisible.value = visible
}
MyEvent.add('change-profile-visible', handleProfileVisibleChange)
const handleClickReturn = () => { const handleClickReturn = () => {
router.back() router.back()
// emit('clickReturn') // emit('clickReturn')
} }
const handleClickProfile = () => {
// router.push('/workshop/profile')
emit('clickProfile')
}
</script> </script>
<template> <template>
<div class="header-title" :style-type="styleType"> <div class="header-title">
<div class="main">
<div class="return" @click="handleClickReturn"><SvgIcon name="return" size="34" /></div> <div class="return" @click="handleClickReturn"><SvgIcon name="return" size="34" /></div>
<span class="title">{{ title }}</span> <span class="title">{{ title }}</span>
<div class="setting" v-if="hasSetting"><SvgIcon name="setting" size="44" /></div> <div class="profile" @click="handleClickProfile">
<SvgIcon :name="profileVisible ? 'profileFilledBlack' : 'profile_white'" size="45" />
</div> </div>
<div class="placeholder" v-if="isPlaceholder"></div>
</div> </div>
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
.header-title { .header-title {
width: 100%; width: 100%;
&[style-type="2"] { height: var(--header-title-height, 14.6rem);
--header-title-height: 14.5rem;
--header-title-border-bottom-width: 0.2rem;
--header-title-return-left: 7.8rem;
}
&[style-type="3"] {
--header-title-height: 12rem;
--header-title-border-bottom-width: 0.2rem;
}
> div {
width: 100%;
height: var(--header-title-height, 9.9rem);
border-bottom-width: var(--header-title-border-bottom-width, 0);
border-bottom-style: solid; border-bottom-style: solid;
border-bottom-color: var(--header-title-border-bottom-color, #D5D5D5); border-bottom-width: var(--header-title-border-bottom-width, 0.2rem);
} border-bottom-color: var(--header-title-border-bottom-color, #d5d5d5);
> .main {
position: fixed;
top: 0;
z-index: var(--header-title-z-index, 999); z-index: var(--header-title-z-index, 999);
background-color: var(--header-title-background, #fff); background-color: var(--header-title-background, #fff);
display: flex; display: flex;
@@ -57,23 +51,22 @@
justify-content: center; justify-content: center;
> .return { > .return {
color: #2c2c2c;
position: absolute; position: absolute;
left: var(--header-title-return-left, 3.2rem); left: 7rem;
display: inline-block; display: inline-block;
--svg-icon-color: var(--header-title-color, #000);
} }
> .title { > .title {
font-size: 4.8rem; color: #2c2c2c;
color: var(--header-title-color, #000); font-family: 'satoshiRegular';
font-family: 'boskaRegular'; font-size: 4rem;
letter-spacing: 0.3rem;
} }
> .setting { > .profile {
position: absolute; position: absolute;
right: 3.2rem; right: 7rem;
display: inline-block; display: inline-block;
color: var(--header-title-color, #000); color: #333;
} }
} }
}
</style> </style>

View File

@@ -3,6 +3,7 @@
const props = defineProps({ const props = defineProps({
loading: { default: false, type: Boolean }, loading: { default: false, type: Boolean },
finish: { default: false, type: Boolean }, finish: { default: false, type: Boolean },
empty: { default: false, type: Boolean },
pel: { default: () => {}, type: Function } pel: { default: () => {}, type: Function }
}) })
const emit = defineEmits(['load']) const emit = defineEmits(['load'])
@@ -23,12 +24,13 @@
}) })
</script> </script>
<template> <template>
<div class="my-list" ref="el"> <div class="my-list" ref="el" :class="{ empty: !loading && empty }">
<slot></slot> <slot></slot>
<div class="footer"> <div class="footer">
<p v-show="!loading" class="placeholder" ref="placeholder"></p> <p v-show="!loading" class="placeholder" ref="placeholder"></p>
<span class="loading" v-show="loading">Loading...</span> <span class="loading" v-if="loading">Loading...</span>
<span class="nomore" v-show="finish">No more</span> <span class="empty" v-else-if="empty">Nothing Here</span>
<span class="nomore" v-else-if="finish">No more</span>
</div> </div>
</div> </div>
</template> </template>
@@ -40,12 +42,28 @@
> .footer { > .footer {
width: 100%; width: 100%;
font-size: 3rem; font-size: 3rem;
color: #000; color: #a1a1a1;
text-align: center; text-align: center;
margin: var(--my-list-footer-margin, 0); margin: var(--my-list-footer-margin, 0);
> .placeholder { > .placeholder {
height: 1px; height: 1px;
} }
> .empty {
font-size: 4rem;
}
}
&.empty {
> .footer {
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
padding: 0;
margin: 0;
> .empty {
margin-bottom: 5rem;
}
}
} }
} }
</style> </style>

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="routeCache" :view-type="viewType"> <div class="routeCache" :view-type="viewType">
<router-view v-slot="{ Component, route }" @view-type="changeViewType"> <router-view v-slot="{ Component, route }">
<keep-alive :include="cachedViews"> <keep-alive :include="cachedViews">
<component :is="Component" :key="route.name" /> <component :is="Component" :key="route.name" />
</keep-alive> </keep-alive>
@@ -14,6 +14,13 @@ import { useRoute } from 'vue-router'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
const props = defineProps({
viewType: {
type: String,
default: ''
}
})
const route = useRoute() const route = useRoute()
// 缓存的组件名称列表 // 缓存的组件名称列表
@@ -59,17 +66,6 @@ onMounted(() => {
MyEvent.add('clearAllCache', clearCache) MyEvent.add('clearAllCache', clearCache)
}) })
//根据viewType设置布局风格样式
const viewType = ref(0)
const changeViewType = (v: number) => {
viewType.value = v
}
const router = useRouter()
watch(
() => router.currentRoute.value,
() => (viewType.value = 0)
)
// 暴露方法供外部使用 // 暴露方法供外部使用
defineExpose({ defineExpose({
clearCache, clearCache,

View File

@@ -0,0 +1,78 @@
<template>
<div class="loading-container" :style="containerStyle">
<div class="dot"></div>
<div class="dot"></div>
<div class="dot"></div>
</div>
</template>
<script setup>
import { computed } from 'vue'
import { defineProps } from 'vue'
const props = defineProps({
color: {
type: String,
default: '#000' // 默认颜色
},
size: {
type: Number,
default: 10 // 默认圆点大小px
},
spacing: {
type: Number,
default: 3 // 默认间距px调整为合适小尺寸
},
amplitude: {
type: Number,
default: 8 // 默认浮动幅度px调整为合适比例
},
duration: {
type: Number,
default: 1.2 // 默认动画持续时间s
}
})
const containerStyle = computed(() => ({
'--color': props.color,
'--size': `${props.size}px`,
'--spacing': `${props.spacing}px`,
'--amplitude': `${props.amplitude}px`,
'--duration': `${props.duration}s`
}))
</script>
<style scoped>
.loading-container {
display: flex;
justify-content: center;
align-items: center;
/* 可以根据需要调整容器高度或移除 */
}
.dot {
width: var(--size);
height: var(--size);
background-color: var(--color);
border-radius: 50%;
margin: 0 var(--spacing);
animation: wave var(--duration) infinite ease-in-out;
}
.dot:nth-child(2) {
animation-delay: 0.2s;
}
.dot:nth-child(3) {
animation-delay: 0.4s;
}
@keyframes wave {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(calc(-1 * var(--amplitude)));
}
}
</style>

View File

@@ -0,0 +1,58 @@
<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="gradientButton">
<div class="bg">
<slot name="content">
</slot>
</div>
</div>
</template>
<style lang="less" scoped>
.gradientButton{
position: relative;
width: 100%;
height: 100%;
--gradientButtonBorderRadius: var(--borderRadius,1rem);
--gradientButtonBorderWidth: var(--borderWidth,2px);
> .bg{
position: absolute;
inset: var(--gradientButtonBorderWidth);
display: flex;
background-color: #fff;
align-items: center;
justify-content: center;
border-radius: var(--gradientButtonBorderRadius);
overflow: hidden;
}
&::before{
content: '';
position: absolute;
width: calc(100% + 0.2rem);
height: calc(100% + 0.2rem);
top: 50%;
left: 50%;
border-radius: var(--gradientButtonBorderRadius);
transform: translate(-50%, -50%);
background: linear-gradient(156deg,
#d3d3d3 0%,
#8a8682 40%,
#8a8682 65%,
#ebebeb 100%);
}
}
</style>

View File

@@ -82,22 +82,11 @@ const {} = toRefs(data);
</div> </div>
<div class="mask" v-if="item.id == select?.oldId"></div> <div class="mask" v-if="item.id == select?.oldId"></div>
</div> </div>
<div class="btn"> <!-- <div class="btn">
<!-- <div>
<SvgIcon v-if="!item.isLike" @click.stop="setLike(item,'like')" name="noLike" size="30" />
<SvgIcon v-else name="like" @click.stop="setLike(item,'noLike')" color="#FF4949" size="30" />
</div> -->
<div> <div>
<SvgIcon @click.stop="updateStyle(item,index)" name="update" size="30" /> <SvgIcon @click.stop="updateStyle(item,index)" name="update" size="30" />
</div> </div>
<!-- <div>
<SvgIcon v-if="!item.isAdd" @click.stop="addLibrary(item,'add')" name="add" size="30" />
<SvgIcon v-else @click.stop="addLibrary(item,'delete')" name="confirmation" size="30" />
</div> --> </div> -->
<!-- <div>
<SvgIcon @click.stop="deleteStyle(index)" name="delete" size="30" />
</div> -->
</div>
</div> </div>
</div> </div>
@@ -109,7 +98,7 @@ const {} = toRefs(data);
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
> .item{ > .item{
width: calc(50% - 3.1rem / 2); width: calc(50% - 3.5rem / 2);
position: relative; position: relative;
// margin-bottom: 3.3rem; // margin-bottom: 3.3rem;
display: flex; display: flex;
@@ -126,7 +115,7 @@ const {} = toRefs(data);
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
height: 45rem; height: 45rem;
margin: 2.4rem 0; margin: 2.3rem 0;
background-color: #fff; background-color: #fff;
justify-content: center; justify-content: center;
border: .6px solid #acacac; border: .6px solid #acacac;
@@ -170,26 +159,26 @@ const {} = toRefs(data);
// max-height: 50%; // max-height: 50%;
} }
} }
> .btn{ // > .btn{
display: flex; // display: flex;
align-items: center; // align-items: center;
justify-content: flex-end; // justify-content: flex-end;
> div{ // > div{
color: #000; // color: #000;
margin-right: 1.2rem; // margin-right: 1.2rem;
border-radius: 50%; // border-radius: 50%;
width: 5.2rem; // width: 5.2rem;
height: 5.2rem; // height: 5.2rem;
padding: 1rem; // padding: 1rem;
background-color: #fff; // background-color: #fff;
&:last-child{ // &:last-child{
margin-right: 0rem; // margin-right: 0rem;
} // }
&:hover{ // &:hover{
color: #000; // color: #000;
} // }
} // }
} // }
} }
} }
</style> </style>

112
src/hooks/useStreamChat.ts Normal file
View File

@@ -0,0 +1,112 @@
import { ref } from 'vue'
import { showToast } from 'vant'
import { streamChatAddress } from '@/api/workshop'
import { useUserInfoStore } from '@/stores'
/**
* 流式对话 Hook
* @param onSuccess - 成功时的回调(流式响应时调用)
* @returns { fetchMessage, isGenerating }
*/
export function useStreamChat(onSuccess?: () => void) {
const userInfoStore = useUserInfoStore()
const isGenerating = ref(false)
const fetchMessage = (message: string, sessionId: string): Promise<void> => {
isGenerating.value = true
const params = {
message,
sessionId,
gender: userInfoStore.state.generateParams.sex
}
// 直接使用 fetch 进行流式请求
const token = userInfoStore.state.token
const baseURL = import.meta.env.MODE === 'development' ? '' : import.meta.env.VITE_APP_URL
// 构建查询参数
const queryParams = new URLSearchParams()
Object.entries(params).forEach(([key, value]) => {
queryParams.append(key, String(value))
})
const url = `${baseURL}${streamChatAddress}?${queryParams.toString()}`
return fetch(url, {
method: 'GET',
headers: {
Authorization: token,
'Content-Type': 'application/json'
},
credentials: 'include'
})
.then(async (response) => {
// 检查响应内容类型,判断是否为流式响应
const contentType = response.headers.get('content-type') || ''
const isStreamResponse =
contentType.includes('text/event-stream') || contentType.includes('stream')
if (!response.ok) {
// 非流式错误响应,使用 text() 读取错误信息
const errorText = await response.text()
console.error('请求错误:', errorText)
showToast({
message: `failed to fetch: ${response.status}`,
position: 'top',
icon: 'none'
})
throw new Error(`发起对话错误--- ${response.status}: ${errorText}`)
}
// 不是流式响应,使用 text()读取错误信息
if (!isStreamResponse) {
const text = await response.text()
try {
const errorData = JSON.parse(text)
if (errorData.message || errorData.error) {
showToast({
message: errorData.message || errorData.error || 'network error',
position: 'top',
icon: 'none'
})
}
} catch (e) {
// 如果不是 JSON直接显示文本内容
showToast({
message: text || 'network error',
position: 'top',
icon: 'none'
})
throw new Error(text || 'network error')
}
return
}
// 流式响应处理
const reader = response.body?.getReader()
if (!reader) throw new Error('无法获取流读取器')
const decoder = new TextDecoder()
// 流式响应时调用成功回调
onSuccess?.()
})
.catch((error) => {
console.error('fetch请求失败:', error)
showToast({
message: error.message || 'network error'
})
throw error
})
.finally(() => {
isGenerating.value = false
})
}
return {
fetchMessage,
isGenerating
}
}

View File

@@ -8,9 +8,9 @@ const VerifyIDs = (num: number) => {
!!useGenerateStore().styleId, !!useGenerateStore().styleId,
// !!useGenerateStore().modelPhotoId, // !!useGenerateStore().modelPhotoId,
true, true,
!!useGenerateStore().originalTryOnId, !!useGenerateStore().originalTryOnId
]; ]
return ids.splice(0, num).every(id => id) ? true : "/stylist/customer"; return ids.splice(0, num).every((id) => id) ? true : '/stylist/customer'
} }
/** /**
@@ -48,8 +48,8 @@ const router = createRouter({
component: () => import('@/views/login/LoginPage.vue') component: () => import('@/views/login/LoginPage.vue')
}, },
{ {
path:'/reset', path: '/reset',
name:'ResetPage', name: 'ResetPage',
component: () => import('@/views/login/ResetPage.vue') component: () => import('@/views/login/ResetPage.vue')
}, },
{ {
@@ -63,128 +63,133 @@ const router = createRouter({
component: () => import('@/views/login/WelcomePage.vue') component: () => import('@/views/login/WelcomePage.vue')
}, },
{ {
path: '/homeNav', path: '/customer',
name: 'HomeNav',
component: () => import('@/views/Workshop/home.vue')
},
{
path: '/stylist',
name: 'StylistPage',
redirect: '/stylist/index',
component: () => import('@/views/stylist/container.vue'),
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/stylist/index.vue'),
meta: { verify: ()=> VerifyIDs(2) }
},
{
path: 'sex',
name: 'sex',
component: () => import('@/views/stylist/sex.vue'),
meta: { verify: ()=> VerifyIDs(2) }
},
{
path: 'dressfor',
name: 'dressfor',
component: () => import('@/views/stylist/dressfor.vue'),
meta: { verify: ()=> VerifyIDs(2) }
},
{
path: 'customer',
name: 'customer', name: 'customer',
component: () => import('@/views/stylist/customer.vue'), component: () => import('@/views/login/customer.vue')
}
]
}, },
{ {
path: '/asistant', path: '/asistant',
name: 'asistant', name: 'asistant',
component: () => import('../views/asistant/index.vue'), component: () => import('../views/asistant/index.vue'),
meta: { cache: true, verify: ()=> VerifyIDs(2) } meta: { cache: true, verify: () => VerifyIDs(2) }
}, },
{ {
path: '/workshop', path: '/workshop',
name: 'Workshop', name: 'Workshop',
component: () => import('../views/Workshop/index.vue'), component: () => import('../views/Workshop/index.vue'),
children: [ children: [
// {
// path: '/workshop',
// redirect: '/workshop/selectStyle'
// },
{ {
path: '/workshop', path: '/workshop/home',
redirect: '/workshop/selectStyle' name: 'Home',
component: () => import('@/views/Workshop/home.vue')
},
{
path: '/workshop/homeNav',
name: 'HomeNav',
component: () => import('@/views/Workshop/homeNav.vue')
},
{
path: '/workshop/stylist',
name: 'StylistPage',
redirect: '/workshop/stylist/index',
component: () => import('@/views/stylist/container.vue'),
children: [
{
path: 'index',
name: 'index',
component: () => import('@/views/stylist/index.vue'),
meta: { verify: () => VerifyIDs(2) }
},
{
path: 'sex',
name: 'sex',
component: () => import('@/views/stylist/sex.vue'),
meta: { verify: () => VerifyIDs(2) }
},
{
path: 'dressfor',
name: 'dressfor',
component: () => import('@/views/stylist/dressfor.vue'),
meta: { verify: () => VerifyIDs(2) }
}
]
}, },
{ {
path: '/workshop/selectStyle', path: '/workshop/selectStyle',
name: 'SelectStyle', name: 'SelectStyle',
component: () => import('../views/Workshop/selectStyle.vue'), component: () => import('../views/Workshop/selectStyle/index.vue'),
meta: { verify: ()=> VerifyIDs(2) } meta: { verify: () => VerifyIDs(2) }
}, },
{ {
path: '/workshop/selectModel', path: '/workshop/selectModel',
name: 'SelectModel', name: 'SelectModel',
component: () => import('../views/Workshop/selectModel.vue'), component: () => import('../views/Workshop/selectModel.vue'),
meta: { verify: ()=> VerifyIDs(3) } meta: { verify: () => VerifyIDs(3) }
}, },
{ {
path: '/workshop/product', path: '/workshop/product',
name: 'Product', name: 'product',
component: () => import('../views/Workshop/product.vue'), component: () => import('../views/Workshop/product.vue'),
meta: { verify: ()=> VerifyIDs(4) } meta: { verify: () => VerifyIDs(4) }
}, },
{ {
// 推荐try on // 推荐try on
path: '/workshop/recommended', path: '/workshop/recommended',
name: 'recommended', name: 'recommended',
component: () => import('../views/Workshop/recommended.vue'), component: () => import('../views/Workshop/recommended.vue'),
meta: { verify: ()=> VerifyIDs(5) } meta: { verify: () => VerifyIDs(5) }
}, },
{ {
// 上传照片1 // 上传照片1
path: '/workshop/uploadFace', path: '/workshop/uploadFace',
name: 'uploadFace', name: 'uploadFace',
component: () => import('../views/Workshop/uploadFace1.vue'), component: () => import('../views/Workshop/uploadFace1.vue'),
meta: { verify: ()=> VerifyIDs(5) } meta: { verify: () => VerifyIDs(5) }
}, },
{ {
// 上传照片2 // 上传照片2
path: '/workshop/uploadFace2', path: '/workshop/uploadFace2',
name: 'uploadFace2', name: 'uploadFace2',
component: () => import('../views/Workshop/uploadFace2.vue'), component: () => import('../views/Workshop/uploadFace2.vue'),
meta: { verify: ()=> VerifyIDs(5) } meta: { verify: () => VerifyIDs(5) }
}, },
{ {
// 自定义创作 // 自定义创作
path: '/workshop/customize', path: '/workshop/customize',
name: 'customize', name: 'customize',
component: () => import('../views/Workshop/customize.vue'), component: () => import('../views/Workshop/customize.vue'),
meta: { verify: ()=> VerifyIDs(5) } meta: { verify: () => VerifyIDs(5) }
}, },
{ {
// library // library
path: '/workshop/library', path: '/workshop/library',
name: 'library', name: 'library',
component: () => import('../views/Workshop/library.vue'), component: () => import('../views/Workshop/library.vue'),
meta: { verify: ()=> VerifyIDs(2) } meta: { verify: () => VerifyIDs(2) }
}, },
{ {
path: '/workshop/profile', path: '/workshop/profile',
name: 'profile', name: 'profile',
component: () => import('../views/Workshop/profile.vue'), component: () => import('../views/Workshop/profile.vue'),
meta: { verify: ()=> VerifyIDs(1) } meta: { verify: () => VerifyIDs(1) }
}, },
{ {
// creation // creation
path: '/workshop/creation', path: '/workshop/creation',
name: 'creation', name: 'creation',
component: () => import('../views/Workshop/creation/index.vue'), component: () => import('../views/Workshop/creation/index.vue'),
meta: { verify: ()=> VerifyIDs(2) } meta: { verify: () => VerifyIDs(2) }
}, },
{ {
// 完成创建 // 完成创建
path: '/workshop/end', path: '/workshop/end',
name: 'end', name: 'end',
component: () => import('../views/Workshop/end.vue'), component: () => import('../views/Workshop/end.vue'),
meta: { verify: ()=> VerifyIDs(2) } meta: { verify: () => VerifyIDs(2) }
} }
] ]
} }

View File

@@ -5,6 +5,7 @@ const store = createPinia()
// 使用持久化插件(全局持久化) // 使用持久化插件(全局持久化)
store.use(createPersistedState()) store.use(createPersistedState())
export default store export default store
export * from './modules/h_generate'
export * from './modules/generate' export * from './modules/generate'
export * from './modules/overall' export * from './modules/overall'
export * from './modules/userInfo' export * from './modules/userInfo'

View File

@@ -2,6 +2,7 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import MyEvent from '@/utils/myEvent' import MyEvent from '@/utils/myEvent'
MyEvent.add('clear-generate-state', () => useGenerateStore().clearGenerateData()) MyEvent.add('clear-generate-state', () => useGenerateStore().clearGenerateData())
MyEvent.add('clear-client-state', () => useGenerateStore().clearCustomerInfo())
export const useGenerateStore = defineStore({ export const useGenerateStore = defineStore({
id: 'generate', // 必须指明唯一的pinia仓库的id id: 'generate', // 必须指明唯一的pinia仓库的id
@@ -9,9 +10,17 @@ export const useGenerateStore = defineStore({
return { return {
style: { style: {
id: '', id: '',
oldId: '' //表示从生成页面返回回来需要调整的样式id path: '',
taskId:'',
isLike: false, //是否喜欢
status: ''
}, },
styleList: [{}, {}, {}, {}], styleList: [
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
],
model: { model: {
id: '' id: ''
}, },
@@ -68,7 +77,7 @@ export const useGenerateStore = defineStore({
/** 进店记录id */ /** 进店记录id */
visitRecordId: (state) => state.customerInfo.visitRecordId, visitRecordId: (state) => state.customerInfo.visitRecordId,
/** 服装id */ /** 服装id */
styleId: (state) => state.style.id || state.style.oldId, styleId: (state) => state.style.id,
/** 模特照片id */ /** 模特照片id */
modelPhotoId: (state) => state.model.id, modelPhotoId: (state) => state.model.id,
/** 原始试穿id不包含魔改id */ /** 原始试穿id不包含魔改id */
@@ -82,45 +91,39 @@ export const useGenerateStore = defineStore({
}, },
actions: { actions: {
selectStyle(data: any) { selectStyle(data: any) {
this.style.id = data.id this.style = {
}, ...data,
//生成后去掉id 设置oldId来修改样式
useStyleGenerate() {
if (!this.style.id) return
this.style.oldId = this.style.id
// this.style.id = ''
},
updateStyle(data) {
if (data.id == this.style.oldId) {
this.style.oldId = ''
} }
if(data.id == this.style.id) {
this.style.id = ''
}
console.log(this.style)
}, },
//模特相关 //模特相关
selectModel(data: any) { selectModel(data: any) {
this.model.id = data.id this.model.id = data.id
}, },
setIsGenerate(isGenerate: boolean) {
this.isGenerate = isGenerate
},
clearProductData() { clearProductData() {
this.styleList = [{}, {}, {}, {}] this.styleList = [
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
{id:'',taskId:'',status:'',path:''},
]
this.style = { this.style = {
id: '', id: '',
oldId: '' path: '',
isLike: false,
taskId:'',
status: ''
} }
this.model = { this.model = {
id: '' id: ''
} }
this.clearTryOn()
},
clearTryOn() {
this.originalTryOn = { this.originalTryOn = {
id: '', id: '',
isLike: false, isLike: false,
tryOnUrl: '' tryOnUrl: ''
} }
this.isGenerate = false
}, },
/** 更新顾客照片信息 */ /** 更新顾客照片信息 */
updatePhotoInfo(data: any) { updatePhotoInfo(data: any) {
@@ -174,7 +177,7 @@ export const useGenerateStore = defineStore({
this.updatePhotoInfo({}) this.updatePhotoInfo({})
this.clearCustomizeInfo() this.clearCustomizeInfo()
this.clearCustomizeInfoDemo() this.clearCustomizeInfoDemo()
this.clearCustomerInfo() // this.clearCustomerInfo()
this.setSessionId('') this.setSessionId('')
}, },
setCustomerInfo(data: any) { setCustomerInfo(data: any) {

View File

@@ -0,0 +1,72 @@
// 每一个存储的模块命名规则use开头store结尾
import { defineStore } from 'pinia'
import MyEvent from '@/utils/myEvent'
MyEvent.add('clear-generate-state', () => useHGenerateStore().clearGenerateData())
export const useHGenerateStore = defineStore({
id: 'h_generate', // 必须指明唯一的pinia仓库的id
state: () => {
return {
style: {
id: '',
url: ''
},
originalTryOn: {
//生成穿好衣服的回参
id: '',
isLike: false, //是否喜欢
tryOnUrl: ''
},
/** AI魔改信息 */
customizeInfo: {
inputText: '',
count: 0,
oldInputText: '',
oldTryOnId: '',
tryOnId: '',
tryOnUrl: '',
styleUrl: '',
isRegenerated: '',
isFavorite: false
},
}
},
getters: {
/** 服装id */
styleId: (state) => state.style.id,
/** 原始试穿id不包含魔改id */
originalTryOnIdNoRein: (state) => state.originalTryOn.id,
/** 原始试穿id-优先AI魔改 */
originalTryOnId: (state) => state.customizeInfo.tryOnId || state.originalTryOn.id,
},
actions: {
/** 清空 AI魔改信息 */
clearCustomizeInfo() {
this.customizeInfo.inputText = ''
this.customizeInfo.count = 0
this.customizeInfo.oldInputText = ''
this.customizeInfo.oldTryOnId = ''
this.customizeInfo.tryOnId = ''
this.customizeInfo.tryOnUrl = ''
this.customizeInfo.styleUrl = ''
this.customizeInfo.isRegenerated = ''
this.customizeInfo.isFavorite = false
},
/** 上传服装 */
uploadStyle(data: object) {
for (const key in data) {
this.style[key] = data[key]
}
},
uploadCustomizeInfo(data: object) {
for (const key in data) {
this.customizeInfo[key] = data[key]
}
},
//设置默认数据
clearGenerateData() {
this.clearCustomizeInfo()
},
}
})

View File

@@ -9,7 +9,8 @@ export const useUserInfoStore = defineStore('userInfo', () => {
token: '', token: '',
generateParams: { generateParams: {
stylist: '', stylist: '',
sex: '' sex: '',
stylistImage: ''
} }
}) })
@@ -37,7 +38,8 @@ export const useUserInfoStore = defineStore('userInfo', () => {
const resetGenerateParams = () => { const resetGenerateParams = () => {
state.value.generateParams = { state.value.generateParams = {
stylist: '', stylist: '',
sex: '' sex: '',
stylistImage: ''
} }
} }
@@ -49,6 +51,7 @@ export const useUserInfoStore = defineStore('userInfo', () => {
removeLocal('token') removeLocal('token')
resetGenerateParams() resetGenerateParams()
MyEvent.emit('clear-generate-state') MyEvent.emit('clear-generate-state')
MyEvent.emit('clear-client-state')
MyEvent.emit('clearAllCache') MyEvent.emit('clearAllCache')
resolve('') resolve('')
}) })

18
src/types/enum.ts Normal file
View File

@@ -0,0 +1,18 @@
/** 流程类型 */
export const FlowType = {
/** 主流程 */
MAIN: 'main',
/** 历史流程 */
HISTORY: 'history',
/** 历史流程-Outfit */
H_OUTFIT: 'history-outfit',
/** 历史流程-Tryon */
H_TRYON: 'history-tryon',
/** 历史流程-AI */
H_AI: 'history-ai',
}
/** 是否是历史流程 */
export const IsHistoryFlow = (flowType: any) => {
const arr = [FlowType.HISTORY, FlowType.H_OUTFIT, FlowType.H_TRYON, FlowType.H_AI]
return arr.some((v) => v === flowType)
}

View File

@@ -80,7 +80,7 @@ service.interceptors.response.use(
} }
const res = response.data const res = response.data
// 处理异常的情况 // 处理异常的情况
console.log(res) // console.log(res)
if (res.code != 0) { if (res.code != 0) {
showToast({ showToast({
message: res.errMsg || res.message, message: res.errMsg || res.message,

View File

@@ -1,209 +0,0 @@
/**
* 分享工具函数
* 支持移动浏览器原生分享 API 和 WhatsApp 分享
*/
interface ShareData {
title?: string
text?: string
url?: string
files?: File[]
}
/**
* 检查是否支持 Web Share API
*/
export function isWebShareSupported(): boolean {
return typeof navigator !== 'undefined' && 'share' in navigator
}
/**
* 使用 Web Share API 进行分享(移动浏览器原生分享)
* @param data 分享数据
*/
async function shareWithWebAPI(data: ShareData): Promise<void> {
if (!isWebShareSupported()) {
throw new Error('Web Share API is not supported')
}
try {
// Web Share API 只支持 title, text, url 和 files
const shareData: ShareData = {}
if (data.title) shareData.title = data.title
if (data.text) shareData.text = data.text
if (data.url) shareData.url = data.url
if (data.files && data.files.length > 0) shareData.files = data.files
await navigator.share(shareData)
} catch (error: any) {
// 用户取消分享时,会抛出 AbortError这是正常情况
if (error.name !== 'AbortError') {
console.error('分享失败:', error)
throw error
}
}
}
/**
* 分享到 WhatsApp回退方案
* @param text 分享的文本内容
* @param url 分享的链接(可选)
*/
export function shareToWhatsApp(text: string, url?: string): void {
const shareText = url ? `${text} ${url}` : text
const encodedText = encodeURIComponent(shareText)
const whatsappScheme = `whatsapp://send?text=${encodedText}`
const whatsappWebUrl = `https://wa.me/?text=${encodedText}`
const isMobile = typeof navigator !== 'undefined' && /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent)
if (typeof window === 'undefined') return
if (isMobile) {
// 优先尝试调用已安装的 WhatsApp 应用,失败后回退到 Web 版本
let didFallback = false
const fallbackTimer = window.setTimeout(() => {
didFallback = true
window.location.href = whatsappWebUrl
}, 1500)
try {
window.location.href = whatsappScheme
} catch (error) {
window.clearTimeout(fallbackTimer)
window.location.href = whatsappWebUrl
}
// 某些浏览器会在成功唤起 App 时终止脚本,无需额外处理
return
}
// 桌面端或不支持 scheme 的环境直接打开 WhatsApp Web
window.open(whatsappWebUrl, '_blank')
}
/**
* 通用分享函数(优先使用 Web Share API不支持则回退到 WhatsApp
* @param options 分享选项
*/
export async function share(options: {
title?: string
text?: string
url?: string
files?: File[]
fallbackToWhatsApp?: boolean
}): Promise<void> {
const { title, text, url, files, fallbackToWhatsApp = true } = options
// 如果支持 Web Share API优先使用
if (isWebShareSupported() && !files) {
try {
await shareWithWebAPI({ title, text, url })
return
} catch (error) {
// 如果分享失败且允许回退,则使用 WhatsApp
if (fallbackToWhatsApp) {
const shareText = [title, text, url].filter(Boolean).join('\n\n')
shareToWhatsApp(shareText)
return
}
throw error
}
}
// 如果不支持 Web Share API 或需要分享文件,使用 WhatsApp
if (fallbackToWhatsApp) {
const shareText = [title, text, url].filter(Boolean).join('\n\n')
shareToWhatsApp(shareText)
} else {
throw new Error('Web Share API is not supported and fallback is disabled')
}
}
/**
* 分享当前页面(优先使用原生分享,不支持则回退到 WhatsApp
* @param title 分享的标题
* @param description 分享的描述(可选)
*/
export async function shareCurrentPage(title: string, description?: string): Promise<void> {
const currentUrl = window.location.href
await share({
title,
text: description,
url: currentUrl,
fallbackToWhatsApp: true
})
}
/**
* 分享图片(优先使用原生分享,不支持则回退到 WhatsApp
* @param imageUrl 图片链接
* @param title 分享的标题
* @param description 分享的描述(可选)
*/
export async function shareImage(imageUrl: string, title: string, description?: string): Promise<void> {
const currentUrl = window.location.href
const text = description
? `${description}\n\n查看图片: ${imageUrl}`
: `查看图片: ${imageUrl}`
await share({
title,
text,
url: currentUrl,
fallbackToWhatsApp: true
})
}
/**
* 分享图片文件(使用 Web Share API支持直接分享图片文件
* @param imageFile 图片文件
* @param title 分享的标题(可选)
* @param text 分享的文本(可选)
*/
export async function shareImageFile(imageFile: File, title?: string, text?: string): Promise<void> {
if (!isWebShareSupported()) {
throw new Error('WEB_SHARE_UNSUPPORTED')
}
if (typeof navigator.canShare === 'function' && !navigator.canShare({ files: [imageFile] })) {
throw new Error('WEB_SHARE_FILE_UNSUPPORTED')
}
await shareWithWebAPI({
title,
text,
files: [imageFile]
})
}
/**
* 将远程图片转换为 File 对象,便于提前缓存后再触发分享
* @param imageUrl 图片链接
* @param fileName 文件名(可选)
*/
export async function createImageFileFromUrl(imageUrl: string, fileName?: string): Promise<File> {
const response = await fetch(imageUrl)
const blob = await response.blob()
const name = fileName || imageUrl.split('/').pop() || 'share-image.jpg'
return new File([blob], name, { type: blob.type || 'image/jpeg' })
}
/**
* 分享当前页面到 WhatsApp兼容旧版本推荐使用 shareCurrentPage
* @param title 分享的标题
* @param description 分享的描述(可选)
*/
export async function shareCurrentPageToWhatsApp(title: string, description?: string): Promise<void> {
await shareCurrentPage(title, description)
}
/**
* 分享图片到 WhatsApp兼容旧版本推荐使用 shareImage
* @param imageUrl 图片链接
* @param title 分享的标题
* @param description 分享的描述(可选)
*/
export async function shareImageToWhatsApp(imageUrl: string, title: string, description?: string): Promise<void> {
await shareImage(imageUrl, title, description)
}

View File

@@ -157,3 +157,30 @@ export async function DownloadImages(list: Array<{ url: string, name?: string }>
export function encryptPassword(password: string): string { export function encryptPassword(password: string): string {
return CryptoJS.MD5(password).toString() return CryptoJS.MD5(password).toString()
} }
/**
* 图片分享到WhatsApp
* @param url 图片URL
* @returns 无
*/
export async function shareImageToWhatsapp (url: string){
// 把图片 URL 转为 Blob
const blob = await fetch(url).then((res) => res.blob())
// 创建文件对象
const file = new File([blob], 'image.jpg', { type: 'image/jpeg' })
// 判断浏览器是否支持文件分享
if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({
files: [file]
})
} else {
// 你可以附加一些自定义文本
const message = 'share image ' + url
// 构造WhatsApp链接
const whatsappLink = `https://api.whatsapp.com/send/?text=${encodeURIComponent(message)}`
window.open(whatsappLink, '_blank')
}
}

View File

@@ -1,77 +1,129 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, computed } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import MyList from '@/components/MyList.vue' import MyList from '@/components/MyList.vue'
import { DownloadImages } from '@/utils/tools' import { DownloadImages } from '@/utils/tools'
import { import { showToast } from 'vant'
getTryOnEffectFavoriteList, import { FlowType } from '@/types/enum'
getTryOnEffectStyleList, import {
getGenerateHistoricals,
setTryOnEffectFavorite, setTryOnEffectFavorite,
cancelTryOnEffectFavorite cancelTryOnEffectFavorite,
} from '@/api/workshop' cancelStyleFavorite,
setStyleFavorite
} from '@/api/workshop'
import { useRouter } from 'vue-router'
import MyEvent from '@/utils/myEvent'
const router = useRouter()
const query = computed(() => router.currentRoute.value.query)
const visitRecordId = computed(() => query.value.visitRecordId) // 访问记录ID
import { useGenerateStore, useHGenerateStore } from '@/stores'
const generateStore = useGenerateStore()
const hGenerateStore = useHGenerateStore()
const props = defineProps({
// 是否单选模式
isChooseOne: { type: Boolean, default: false }
})
const list = reactive([])
const size = ref(10)
const page = computed(() => Math.ceil(list.length / size.value) + 1)
const loading = ref(false)
const finish = ref(false)
const selectCount = computed(() => list.filter((v) => v.selected).length)
const maxSelectCount = 10
const isChooseSave = ref(false) //是否选择保存模式
import { useRouter } from 'vue-router' const navLst = [
const router = useRouter() { label: 'Outfit', value: 'Outfit', flowType: FlowType.H_OUTFIT },
const emit = defineEmits(['view-type']) { label: 'Try-on', value: 'Try-on', flowType: FlowType.H_TRYON },
const query = computed(() => router.currentRoute.value.query) { label: 'Gen-AI', value: 'Gen-AI', flowType: FlowType.H_AI }
const visitRecordId = computed(() => query.value.visitRecordId) // 访问记录ID ]
import { useGenerateStore } from '@/stores' const navActive = ref('Outfit')
const generateStore = useGenerateStore() navLst.forEach((v) => {
if (v.flowType === query.value.flowType) navActive.value = v.value
})
navLst.forEach((v) => {
if (v.flowType === query.value.active) navActive.value = v.value
})
onMounted(() => { const clickNav = (v) => {
emit('view-type', 1) if (v.value === navActive.value || loading.value) return
}) navActive.value = v.value
const list = reactive([]) onBackChooseSave()
const loading = ref(false) onLoad('reload')
const finish = ref(false) }
const selectCount = computed(() => list.filter((v) => v.selected).length) const onLoad = (type?: 'reload') => {
const maxSelectCount = 10 if (type === 'reload') {
const isChooseSave = ref(false) //是否选择保存模式 finish.value = false
list.splice(0, list.length)
const onLoad = () => { }
loading.value = true loading.value = true
const http = visitRecordId.value ? getTryOnEffectFavoriteList : getTryOnEffectStyleList const params = {
const id = visitRecordId.value || generateStore.styleId customerId: generateStore.customerId,
http(id) type: navActive.value,
.then((data) => { isLibrary: false,
data?.forEach((v) => { pageNum: page.value,
pageSize: size.value
}
if (props.isChooseOne) {
params['visitRecordId'] = ''
} else if (visitRecordId.value) {
params['visitRecordId'] = visitRecordId.value
params.isLibrary = true
} else {
params['visitRecordId'] = generateStore.visitRecordId
}
getGenerateHistoricals(params)
.then((data: any) => {
data.records?.forEach((v) => {
const obj = { const obj = {
tryOnId: v.tryOnId, // tryOnId: v.tryOnId,
tryOnUrl: v.tryOnUrl, tryOnUrl: v.tryOnUrl,
styleUrl: v.styleUrl, styleUrl: v.styleUrl,
isFavorite: !!v.isFavorite, isFavorite: !!v.isFavorite,
isRegenerated: !!v.isRegenerated, isRegenerated: !!v.isRegenerated,
id: v.id,
url: v.url,
selected: list.length < maxSelectCount, selected: false,
loading: false, loading: false,
downloaded: false downloaded: false
} }
list.push(obj) list.push(obj)
}) })
loading.value = false loading.value = false
finish.value = true finish.value = !data.hasNext
}) })
.catch((err) => { .catch((err) => {
console.error(err) console.error(err)
loading.value = false loading.value = false
finish.value = true finish.value = true
}) })
} }
const onItem = (v) => { const onItem = (v) => {
if (props.isChooseOne) {
onSelectItem(v)
} else {
isChooseSave.value ? onSelectItem(v) : onDetailsItem(v) isChooseSave.value ? onSelectItem(v) : onDetailsItem(v)
} }
// 详情页 }
const onDetailsItem = (v) => { // 详情页
if (v.isRegenerated) return const onDetailsItem = (v) => {
if (v.isRegenerated || !v.styleUrl) return
router.push({ query: { ...query.value, styleUrl: v.styleUrl } }) router.push({ query: { ...query.value, styleUrl: v.styleUrl } })
} }
// 喜欢 // 喜欢
const isLoveLoading = ref(false) const isLoveLoading = ref(false)
const onLoveItem = (v) => { const onLoveItem = (v) => {
if (isLoveLoading.value) return if (isLoveLoading.value) return
const http = v.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite var http
if (navActive.value === 'Outfit') {
http = v.isFavorite ? cancelStyleFavorite : setStyleFavorite
} else {
http = v.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
}
isLoveLoading.value = true isLoveLoading.value = true
v.isFavorite = !v.isFavorite v.isFavorite = !v.isFavorite
http(v.tryOnId) http(v.id)
.then(() => { .then(() => {
isLoveLoading.value = false isLoveLoading.value = false
}) })
@@ -79,9 +131,9 @@ const onLoveItem = (v) => {
console.error(err) console.error(err)
isLoveLoading.value = false isLoveLoading.value = false
}) })
} }
const shareImageToWhatsapp = async (url) => { const shareImageToWhatsapp = async (url) => {
// 把图片 URL 转为 Blob // 把图片 URL 转为 Blob
const blob = await fetch(url).then((res) => res.blob()) const blob = await fetch(url).then((res) => res.blob())
@@ -91,23 +143,29 @@ const shareImageToWhatsapp = async (url) => {
// 判断浏览器是否支持文件分享 // 判断浏览器是否支持文件分享
if (navigator.canShare && navigator.canShare({ files: [file] })) { if (navigator.canShare && navigator.canShare({ files: [file] })) {
await navigator.share({ await navigator.share({
files: [file], files: [file]
title: '分享图片',
text: '给你一张图片'
}) })
} else { } else {
alert('当前浏览器不支持分享图片,请使用安卓 Chrome 浏览器') // 你可以附加一些自定义文本
} const message = 'share image ' + url
}
const isShare = ref(false) // 构造WhatsApp链接
const handleOpenShare = () => { const whatsappLink = `https://api.whatsapp.com/send/?text=${encodeURIComponent(message)}`
window.open(whatsappLink, '_blank')
}
}
const isShare = ref(false)
const handleOpenShare = () => {
isShare.value = !isShare.value isShare.value = !isShare.value
alert(`现在${isShare.value ? '可以' : '不可以'}分享`) alert(`现在${isShare.value ? '可以' : '不可以'}分享`)
} }
const onShareItem = (v) => {
const onDownloadItem = async (v) => { const url = v.tryOnUrl || v.url
if (url) shareImageToWhatsapp(url)
}
const onDownloadItem = async (v) => {
if (isShare.value) { if (isShare.value) {
await shareImageToWhatsapp(v.tryOnUrl) await shareImageToWhatsapp(v.tryOnUrl)
return return
@@ -119,19 +177,30 @@ const onDownloadItem = async (v) => {
v.loading = false v.loading = false
v.downloaded = true v.downloaded = true
}) })
} }
const onSelectItem = (v) => { const onSelectItem = (v) => {
if (props.isChooseOne) {
list.forEach((v) => (v.selected = false))
v.selected = true
} else {
if (selectCount.value >= maxSelectCount && !v.selected) return if (selectCount.value >= maxSelectCount && !v.selected) return
v.selected = !v.selected v.selected = !v.selected
} }
const onChooseSave = () => { }
const onChooseSave = () => {
isChooseSave.value = true isChooseSave.value = true
} if (selectCount.value < maxSelectCount) {
const onBackChooseSave = () => { list.forEach((v) => {
if (!v.selected && !v.downloaded && !v.loading && selectCount.value < maxSelectCount)
v.selected = true
})
}
}
const onBackChooseSave = () => {
isChooseSave.value = false isChooseSave.value = false
} }
// 下载选中项 // 下载选中项
const onConfirm = () => { const onConfirm = () => {
const downloadList = [] const downloadList = []
if (selectCount.value > 0) { if (selectCount.value > 0) {
list.forEach((v, i) => { list.forEach((v, i) => {
@@ -145,12 +214,7 @@ const onConfirm = () => {
} }
}) })
} }
if (selectCount.value < maxSelectCount) { onChooseSave()
list.forEach((v) => {
if (!v.selected && !v.downloaded && !v.loading && selectCount.value < maxSelectCount)
v.selected = true
})
}
if (downloadList.length > 0) { if (downloadList.length > 0) {
DownloadImages( DownloadImages(
downloadList, downloadList,
@@ -168,41 +232,92 @@ const onConfirm = () => {
} }
) )
} }
} }
const onContinue = () => { const onContinue = () => {
if (props.isChooseOne) {
const selectedItem = list.find((v) => v.selected)
const nav = navLst.find((v) => v.value === navActive.value)
if (!selectedItem || !nav) return showToast({ message: 'Please select one only.' })
if (nav.flowType !== FlowType.H_OUTFIT) {
hGenerateStore.originalTryOn.id = selectedItem.id
hGenerateStore.originalTryOn.tryOnUrl = selectedItem.tryOnUrl
hGenerateStore.originalTryOn.isLike = selectedItem.isFavorite
hGenerateStore.style.id = ''
hGenerateStore.style.url = selectedItem.styleUrl
} else {
// style
hGenerateStore.style.id = selectedItem.id
hGenerateStore.style.url = selectedItem.url
// selectedItem.isFavorite
}
MyEvent.emit('clear-generate-state')
router.push({ name: 'HomeNav', query: { flowType: nav.flowType } })
} else {
router.push({ name: 'end' }) router.push({ name: 'end' })
} }
}
</script> </script>
<template> <template>
<div class="creation-list"> <div class="creation-list">
<div class="title" @click="handleOpenShare">Your Creation</div> <div class="title" v-if="isChooseOne">Choose One to Start.</div>
<div class="title" v-else>Your Creation</div>
<div class="nav">
<div
class="item"
v-for="(v, i) in navLst"
:key="i"
@click="clickNav(v)"
:class="{ active: v.value === navActive }"
>
{{ v.label }}
</div>
</div>
<div class="list"> <div class="list">
<my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad"> <my-list
<div class="item" v-for="(v, i) in list" :key="i" @click="onItem(v)"> v-model:loading="loading"
<img v-lazy="v.tryOnUrl" /> v-model:finish="finish"
<div class="corner"> @load="onLoad"
:empty="list.length === 0"
>
<div
class="item"
v-for="(v, i) in list"
:key="i"
@click="onItem(v)"
:type="navActive"
:class="{ active: (isChooseSave || isChooseOne) && v.selected }"
>
<img v-lazy="v.tryOnUrl || v.url" />
<!-- <div class="corner">
<div class="ai" v-if="v.isRegenerated">Gen-AI</div> <div class="ai" v-if="v.isRegenerated">Gen-AI</div>
<div class="tryon" v-else>Try-on</div> <div class="tryon" v-else>Try-on</div>
</div> </div> -->
<div class="icons"> <div class="icons" v-if="!isChooseOne">
<div @click.stop="onLoveItem(v)"> <div @click.stop="onLoveItem(v)">
<SvgIcon :name="`love_${v.isFavorite ? '1' : '0'}`" size="27" /> <SvgIcon :name="`love_${v.isFavorite ? '1' : '0'}`" size="27" />
</div> </div>
<div @click.stop="onDownloadItem(v)"> <!-- <div @click.stop="onDownloadItem(v)">
<SvgIcon name="download" size="27" v-show="!v.loading" /> <SvgIcon name="download" size="27" v-show="!v.loading" />
<van-loading color="#000" size="3rem" v-show="v.loading" /> <van-loading color="#000" size="3rem" v-show="v.loading" />
</div> -->
<div @click.stop="onShareItem(v)">
<SvgIcon name="share" size="27" />
</div> </div>
</div> </div>
<div class="icon-selected" v-show="isChooseSave && v.selected"> <div class="icon-selected" v-show="(isChooseSave || isChooseOne) && v.selected">
<SvgIcon name="modelSelected" size="50" /> <SvgIcon name="modelSelected" size="50" />
</div> </div>
<div class="download-state" v-show="isChooseSave && v.loading">Downloading...</div> <div class="download-state" v-show="isChooseSave && v.loading">Downloading...</div>
<div class="download-state" v-show="isChooseSave && v.downloaded">Downloaded</div> <div class="download-state" v-show="isChooseSave && v.downloaded">Downloaded</div>
<div class="download-state" v-show="selectCount && isChooseOne && !v.selected"></div>
</div> </div>
</my-list> </my-list>
</div> </div>
<div class="btns" v-show="!visitRecordId"> <div class="creation-footer" v-if="isChooseOne">
<button @click="onContinue">Continue</button>
</div>
<div class="btns" v-else-if="!visitRecordId">
<template v-if="!isChooseSave"> <template v-if="!isChooseSave">
<button @click="onChooseSave">Choose to Save</button> <button @click="onChooseSave">Choose to Save</button>
<button @click="onContinue">Continue</button> <button @click="onContinue">Continue</button>
@@ -216,23 +331,47 @@ const onContinue = () => {
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
.creation-list { .creation-list {
width: 100%; width: 100%;
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
background-color: #e3e3e3; background-color: #f6f6f6;
border-radius: 1rem; border-radius: 1rem;
position: relative; position: relative;
color: #000; color: #000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
> .title { > .title {
font-family: satoshiRegular; font-family: 'satoshiBold';
font-size: 9rem; font-size: 8.6rem;
text-align: center; text-align: center;
line-height: 124%; line-height: 124%;
font-weight: 500; margin: 3.8rem 0;
margin: 7.2rem 0; }
> .nav {
margin: 2rem 14.3rem 5rem;
display: flex;
align-items: center;
justify-content: space-between;
> .item {
font-family: satoshiMedium;
font-size: 4rem;
color: #a1a1a1;
padding-bottom: 1.2rem;
&.active {
color: #000;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 0.8rem;
background-color: #000;
border-radius: 0.25rem;
}
}
}
} }
> .list { > .list {
flex: 1; flex: 1;
@@ -249,12 +388,20 @@ const onContinue = () => {
.item { .item {
width: 47%; width: 47%;
height: 62.2rem; height: 62.2rem;
// overflow: hidden;
border-radius: var(--border-radius); border-radius: var(--border-radius);
background-color: #fff; background-color: #fff;
margin-bottom: 4rem; margin-bottom: 4rem;
border: 0.1rem solid #000; // overflow: hidden;
position: relative; position: relative;
&.active {
border: 0.3rem solid #d9d9d9;
}
&[type='Outfit'] {
height: 50rem;
> img {
object-fit: contain;
}
}
> img { > img {
width: 100%; width: 100%;
height: 100%; height: 100%;
@@ -281,8 +428,9 @@ const onContinue = () => {
} }
> .ai { > .ai {
color: #646464; color: #646464;
background: linear-gradient( background: linear-gradient(0deg, rgba(230, 219, 219, 0.5), rgba(230, 219, 219, 0.5)),
137.95deg, linear-gradient(
165.5deg,
#7a96ac 2.28%, #7a96ac 2.28%,
#eaeff3 19.8%, #eaeff3 19.8%,
#c2d4e1 32.94%, #c2d4e1 32.94%,
@@ -290,8 +438,7 @@ const onContinue = () => {
#d4dee5 62.15%, #d4dee5 62.15%,
#abbdc8 78.69%, #abbdc8 78.69%,
#bccad7 95.24% #bccad7 95.24%
), );
linear-gradient(0deg, rgba(230, 219, 219, 0.5), rgba(230, 219, 219, 0.5));
} }
} }
> .icons { > .icons {
@@ -306,7 +453,7 @@ const onContinue = () => {
width: 5rem; width: 5rem;
height: 5rem; height: 5rem;
border-radius: 1rem; border-radius: 1rem;
border: 0.2rem solid #000; border: 0.2rem solid rgba(0, 0, 0, 0.5);
--svg-icon-color: #000; --svg-icon-color: #000;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -339,7 +486,7 @@ const onContinue = () => {
} }
} }
> .btns { > .btns {
margin: 9rem 0; margin: 5.4rem 0 6.4rem 0;
display: flex; display: flex;
justify-content: center; justify-content: center;
@@ -360,5 +507,27 @@ const onContinue = () => {
} }
} }
} }
} .creation-footer {
height: 16.5rem;
background-color: #fff;
box-shadow: -2.6rem -1.4rem 3.47rem 0rem rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
> button {
font-family: satoshiRegular;
font-size: 3.6rem;
color: #fff;
width: 24.6rem;
height: 6.7rem;
border-radius: 0.7rem;
border: none;
background: #000;
font-weight: 400;
margin: 0 5.6rem 0 auto;
&:active {
opacity: 0.7;
}
}
}
}
</style> </style>

View File

@@ -1,29 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, watch, computed } from 'vue' import { ref, reactive, onMounted, watch, computed } from 'vue'
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import CreationList from '@/views/Workshop/creation/creation-list.vue' import CreationList from '@/views/Workshop/creation/creation-list.vue'
import CreationDetails from '@/views/Workshop/creation/creation-details.vue' import CreationDetails from '@/views/Workshop/creation/creation-details.vue'
import { FlowType } from '@/types/enum'
import { useRouter } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
const router = useRouter() const router = useRouter()
const styleUrl = computed(() => router.currentRoute.value.query.styleUrl); const route = useRoute()
const emit = defineEmits(['view-type']) const styleUrl = computed(() => router.currentRoute.value.query.styleUrl)
watch( const isChooseOne = computed(() => route.query.flowType === FlowType.HISTORY)
() => router.currentRoute.value,
() => emit('view-type', 1)
)
onMounted(() => {
emit('view-type', 1)
})
</script> </script>
<template> <template>
<header-title style-type="2" /> <creation-list v-show="!styleUrl" :is-choose-one="isChooseOne" />
<creation-list v-show="!styleUrl" />
<creation-details v-if="styleUrl" /> <creation-details v-if="styleUrl" />
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">

View File

@@ -1,23 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue' import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
import gradientButton from '@/components/gradientButton.vue'
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { import {
generateTryOnEffect, generateTryOnEffect,
generateTryOnEffectDemo,
setTryOnEffectFavorite, setTryOnEffectFavorite,
cancelTryOnEffectFavorite cancelTryOnEffectFavorite
} from '@/api/workshop' } from '@/api/workshop'
const emit = defineEmits(['viewType'])
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useGenerateStore } from '@/stores' import { useGenerateStore, useHGenerateStore } from '@/stores'
import { FlowType, IsHistoryFlow } from '@/types/enum'
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
const hGenerateStore = useHGenerateStore()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const query = computed(() => route.query)
const isDemo = computed(() => route.query.demo === '1') const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
const customizeInfo = isDemo.value ? generateStore.customizeInfoDemo : generateStore.customizeInfo const customizeInfo = isHistoryFlow.value
? hGenerateStore.customizeInfo
: generateStore.customizeInfo
const loading = ref(false) const loading = ref(false)
const onSend = () => { const onSend = () => {
if (customizeInfo.inputText === '') return if (customizeInfo.inputText === '') return
@@ -38,51 +39,30 @@
customizeInfo.oldInputText = customizeInfo.inputText customizeInfo.oldInputText = customizeInfo.inputText
customizeInfo.oldTryOnId = customizeInfo.tryOnId customizeInfo.oldTryOnId = customizeInfo.tryOnId
loading.value = true loading.value = true
if (isDemo.value) {
// const data = {
// prompt: customizeInfo.inputText,
// tryonUrl: customizeInfo.tryOnUrl
// }
// if (generateStore.customerPhotoId && customizeInfo.count === 0) {
// data['customerPhotoId'] = generateStore.customerPhotoId
// }
const data = new FormData()
data.append('prompt', customizeInfo.inputText)
data.append('tryonUrl', customizeInfo.tryOnUrl)
if (generateStore.customerPhotoId && customizeInfo.count === 0) {
data.append('customerPhotoId', generateStore.customerPhotoId)
}
generateTryOnEffectDemo(data)
.then((res: any) => {
if(!res) return Promise.reject('生成失败');
customizeInfo.count++
customizeInfo.tryOnId = "1"
customizeInfo.tryOnUrl = res
// customizeInfo.styleUrl = res.styleUrl
// customizeInfo.isRegenerated = res.isRegenerated
// customizeInfo.isFavorite = !!res.isFavorite
loading.value = false
})
.catch((err) => {
console.error(err)
loading.value = false
})
} else {
const data = { const data = {
customerId: generateStore.customerId, customerId: generateStore.customerId,
visitRecordId: generateStore.visitRecordId, visitRecordId: generateStore.visitRecordId,
styleId: generateStore.styleId, // styleId: generateStore.styleId,
// modelPhotoId: generateStore.modelPhotoId, // modelPhotoId: generateStore.modelPhotoId,
originalTryOnId: type === 'reload' ? customizeInfo.oldTryOnId : generateStore.originalTryOnId, // originalTryOnId: type === 'reload' ? customizeInfo.oldTryOnId : generateStore.originalTryOnId,
isRegenerated: 1, isRegenerated: 1,
prompt: customizeInfo.inputText prompt: customizeInfo.inputText
} }
if (generateStore.customerPhotoId && customizeInfo.count === 0) if (generateStore.customerPhotoId && customizeInfo.count === 0) {
data['customerPhotoId'] = generateStore.customerPhotoId data['customerPhotoId'] = generateStore.customerPhotoId
}
if (isHistoryFlow.value) {
data['originalTryOnId'] =
type === 'reload' ? customizeInfo.oldTryOnId : hGenerateStore.originalTryOnId
} else {
data['styleId'] = generateStore.styleId
data['originalTryOnId'] =
type === 'reload' ? customizeInfo.oldTryOnId : generateStore.originalTryOnId
}
generateTryOnEffect(data) generateTryOnEffect(data)
.then((res: any) => { .then((res: any) => {
customizeInfo.count++ customizeInfo.count++
customizeInfo.tryOnId = res.tryOnId customizeInfo.tryOnId = res.id
customizeInfo.tryOnUrl = res.tryOnUrl customizeInfo.tryOnUrl = res.tryOnUrl
customizeInfo.styleUrl = res.styleUrl customizeInfo.styleUrl = res.styleUrl
customizeInfo.isRegenerated = res.isRegenerated customizeInfo.isRegenerated = res.isRegenerated
@@ -91,16 +71,15 @@
}) })
.catch((err) => { .catch((err) => {
console.error(err) console.error(err)
if (data['customerPhotoId']) router.back()
loading.value = false loading.value = false
}) })
} }
}
if (customizeInfo.tryOnId === '') generate() if (customizeInfo.tryOnId === '') generate()
// 喜欢 // 喜欢
const isLoveLoading = ref(false) const isLoveLoading = ref(false)
const onLove = () => { const onLove = () => {
if (isDemo.value) return
if (isLoveLoading.value) return if (isLoveLoading.value) return
const http = customizeInfo.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite const http = customizeInfo.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
customizeInfo.isFavorite = !customizeInfo.isFavorite customizeInfo.isFavorite = !customizeInfo.isFavorite
@@ -111,6 +90,7 @@
}) })
.catch((err) => { .catch((err) => {
console.error(err) console.error(err)
customizeInfo.isFavorite = !customizeInfo.isFavorite
isLoveLoading.value = false isLoveLoading.value = false
}) })
} }
@@ -121,21 +101,29 @@
router.back() router.back()
} }
const onFinish = () => { const onFinish = () => {
if (isDemo.value) { // router.push({ name: 'creation', query: query.value })
router.push({ name: 'end' }) const query_ = {
} else { ...query.value,
router.push({ name: 'creation' }) active: FlowType.H_AI
} }
router.push({ name: 'creation', query: query_ })
// if (isHistoryFlow.value) {
// router.push({ name: 'end' })
// } else {
// router.push({ name: 'creation' })
// }
} }
// 选择另一个穿搭 // 选择另一个穿搭
const onChooseAnotherOutfit = () => { const onChooseOutfit = () => {
generateStore.clearTryOn()
router.push({ name: 'SelectStyle' }) router.push({ name: 'SelectStyle' })
} }
</script> </script>
<template> <template>
<header-title style-type="2" /> <div class="loading" v-if="loading">
<div class="loading" v-if="loading"><generate-loading /></div> <generate-loading title="Generating Results..." />
</div>
<div class="customize" v-else> <div class="customize" v-else>
<div class="title">Customize your Look!</div> <div class="title">Customize your Look!</div>
<p class="tip">Refine your Look</p> <p class="tip">Refine your Look</p>
@@ -151,13 +139,14 @@
</div> </div>
<div class="card"> <div class="card">
<img :src="customizeInfo.tryOnUrl" /> <img :src="customizeInfo.tryOnUrl" />
<div class="select-box"> <div class="history-icon"><SvgIcon name="history" size="42" /></div>
<!-- <div class="select-box">
<div class="icon"><SvgIcon name="history" size="35" /></div> <div class="icon"><SvgIcon name="history" size="35" /></div>
<div class="label">History</div> <div class="label">History</div>
<div class="icon"><SvgIcon name="xialajiantou" size="29" /></div> <div class="icon"><SvgIcon name="xialajiantou" size="29" /></div>
</div> </div> -->
<div class="icons"> <div class="icons">
<div @click="onLove" v-if="!isDemo"> <div @click="onLove">
<SvgIcon :name="`love_${customizeInfo.isFavorite ? 1 : 0}`" size="35" /> <SvgIcon :name="`love_${customizeInfo.isFavorite ? 1 : 0}`" size="35" />
</div> </div>
<div @click="onReload" v-show="customizeInfo.oldInputText"> <div @click="onReload" v-show="customizeInfo.oldInputText">
@@ -167,12 +156,12 @@
</div> </div>
</div> </div>
<div class="btns"> <div class="btns">
<button v-show="!isDemo" @click="onChooseAnotherOutfit">Choose another outfit</button> <button class="choose-outfit" v-if="!isHistoryFlow" @click="onChooseOutfit">
<span></span> <span>Choose Outfit</span>
<button @click="onFinish">Finish</button> </button>
<button class="finish" @click="onFinish"><span>Finish</span></button>
</div> </div>
</div> </div>
<footer-navigation />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -245,7 +234,7 @@
margin-top: 5rem; margin-top: 5rem;
width: 73rem; width: 73rem;
height: 109.5rem; height: 109.5rem;
border-radius: 2rem; border-radius: 1rem;
// box-shadow: 1.3rem 1.4rem 2rem 0.2rem #0000004d; // box-shadow: 1.3rem 1.4rem 2rem 0.2rem #0000004d;
border: 0.2rem solid #d9d9d9; border: 0.2rem solid #d9d9d9;
overflow: hidden; overflow: hidden;
@@ -257,6 +246,10 @@
height: 100%; height: 100%;
object-fit: contain; object-fit: contain;
} }
> .history-icon {
top: 4.2rem;
left: 4.2rem;
}
> .select-box { > .select-box {
top: 1.8rem; top: 1.8rem;
left: 1.8rem; left: 1.8rem;
@@ -277,15 +270,15 @@
color: #000; color: #000;
} }
} }
> .icons { > .icons {
bottom: 0.27rem; bottom: 3.6rem;
right: 0; right: 0;
height: 10rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
> div { > div {
margin-right: 1.5rem; margin-right: 2.4rem;
width: 6.2rem; width: 6.2rem;
height: 6.2rem; height: 6.2rem;
border-radius: 1rem; border-radius: 1rem;
@@ -298,29 +291,49 @@
} }
} }
> .btns { > .btns {
margin-top: 5rem; margin-top: 4rem;
width: 85%; min-width: 68%;
display: flex; display: flex;
// justify-content: center; // justify-content: center;
justify-content: space-between; justify-content: space-between;
> button { > button {
box-sizing: content-box; padding: 0;
font-family: satoshiRegular; font-family: satoshiMedium;
// margin: 0 1.8rem;
border: none; border: none;
// margin: 0 5.2rem 0 auto; width: 34rem;
min-width: 18rem; height: 9.2rem;
padding: 0 3.5rem; border-radius: 1rem;
height: 6.9rem; font-weight: 500;
border-radius: 1.3rem; font-size: 4rem;
background: #000;
font-weight: 400;
font-size: 3.89rem;
color: #fff;
&:active { &:active {
opacity: 0.7; opacity: 0.7;
} }
} }
> :first-child.finish,
> .choose-outfit {
color: #000;
background: linear-gradient(165deg, #b3b3b3 0%, #000 50%, #b3b3b3 100%);
position: relative;
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #fff;
background-clip: padding-box;
border: 0.25rem solid transparent;
border-radius: 1rem;
}
}
> :first-child.finish {
width: 87.5rem;
}
> .finish {
background-color: #000;
color: #fff;
}
} }
} }
</style> </style>

View File

@@ -1,29 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const emit = defineEmits(['view-type'])
onMounted(() => {
emit('view-type', 1)
})
const onExit = () => { const onExit = () => {
console.log('exit') console.log('exit')
} }
</script> </script>
<template> <template>
<header-title />
<div class="end"> <div class="end">
<div class="content"> <div class="content">
<div class="title">Thank you.</div> <div class="title">Thank you.</div>
<div class="tip"> <div class="tip">
We are starting to learn your preferences, Looking forward to see you again, We are starting to learn your preferences,
<br />
Looking forward to see you again,
</div> </div>
</div> </div>
</div> </div>
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -40,21 +35,19 @@
background-size: cover; background-size: cover;
> .content { > .content {
position: absolute; position: absolute;
top: 12.9rem; top: 15.4rem;
left: 6rem; left: 9.6rem;
// width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
> .title { > .title {
font-family: satoshiBold; font-family: satoshiBold;
font-size: 14.7rem; font-size: 11rem;
line-height: 124%; line-height: 124%;
} }
> .tip { > .tip {
margin-top: 2.5rem; margin-top: 2.5rem;
width: 58.2rem;
font-family: satoshiRegular; font-family: satoshiRegular;
font-size: 4.1rem; font-size: 4rem;
line-height: 132%; line-height: 132%;
} }
} }

View File

@@ -1,126 +1,86 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, reactive, toRefs, computed, onActivated } from "vue"; import { onMounted, onUnmounted, reactive, toRefs, computed, onActivated } from "vue";
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
import { showConfirmDialog } from 'vant' import { showConfirmDialog } from 'vant'
import MyEvent from '@/utils/myEvent' import { FlowType } from '@/types/enum'
//const props = defineProps({ //const props = defineProps({
//}) //})
const emit = defineEmits([
'view-type'
])
// const data = reactive({ // const data = reactive({
// }) // })
const clickSwitchVIPID = ()=>{ const newJourney = ()=>{
showConfirmDialog({ router.push(`/workshop/styList/index?flowType=${FlowType.MAIN}`)
title: 'Switch VIP ID?', }
message: 'You have unsaved changes. Your progress will be lost.',
confirmButtonText: 'Yes', const historicalReview = ()=>{
cancelButtonText: 'Cancel', router.push(`/workshop/creation?flowType=${FlowType.HISTORY}`)
})
.then(() => {
MyEvent.emit('clear-generate-state')
MyEvent.emit('clearAllCache')
router.push({ name: 'customer', query: { demo: 1 } })
})
.catch(() => {})
}
const openFlow = (path:string,flowType:string)=>{
if(flowType == 'clientId')return clickSwitchVIPID()
if(flowType == 'main'){
router.push({ name: path })
}else{
router.push({ name: path, query: { demo: 1 } })
}
} }
onMounted(()=>{
emit('view-type', 1)
})
onUnmounted(()=>{ onUnmounted(()=>{
}) })
defineExpose({}) defineExpose({})
// const { } = toRefs(data); // const { } = toRefs(data);
</script> </script>
<template> <template>
<header-title style-type="2" /> <div class="homePage">
<div class="homeNavPage"> <img class="homeBg" src="@/assets/images/homeBg.png" alt="">
<div class="title"> <div class="button">
Welcome Back, <div class="item" @click="newJourney">New Journey</div>
What can I help you today? <div class="item" @click="historicalReview">Historical Review</div>
</div> </div>
<div class="navBox"> <div class="info">
<div class="navTitle"> Powered by AiDLab for Lane Crawford
Explore
</div>
<div class="navList">
<div class="item" @click="openFlow('index','main')">
<img src="@/assets/images/nav1.png" alt="">
</div>
<div class="item" @click="openFlow('recommended','reinventing')">
<img src="@/assets/images/nav2.png" alt="">
</div>
<div class="item" @click="openFlow('index','stylist')">
<img src="@/assets/images/nav3.png" alt="">
</div>
<div class="item" @click="openFlow('','clientId')">
<img src="@/assets/images/nav4.png" alt="">
</div> </div>
</div> </div>
</div>
</div>
<footer-navigation />
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.header-title { .header-title {
--header-title-background: #fff; --header-title-background: #fff;
--header-title-height: 12rem !important; --header-title-height: 12rem !important;
} }
.homeNavPage{ .homePage{
> .title{ position: relative;
padding: 0 8.4rem; flex: 1;
font-family: satoshiBold; > .homeBg{
font-weight: 700; position: absolute;
margin-top: 6.8rem;
font-size: 9.6rem;
line-height: 124%;
background: #B3B3B3;
background: linear-gradient(120deg, #b3b3b3 1%, rgba(0, 0, 0, 0) 48%), linear-gradient(
344deg, #B3B3B2 16%, #000000 66%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
> .navBox{
> .navTitle{
padding: 0 8.4rem;
font-family: satoshiBold;
font-weight: 700;
font-size: 5.2rem;
margin: 6.3rem 0;
}
> .navList{
display: flex;
flex-wrap: wrap;
padding: 0 7.4rem;
gap: 4.8rem;
> .item{
// width: 44.2rem;
// height: 41.6rem;
height: auto;
width: calc(50% - 4.8rem / 2);
> img{
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
> .button{
margin: 64.3rem auto 0;
font-family: satoshiBold;
font-size: 4.6rem;
color: #fff;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
> .item{
width: 45.6rem;
margin-bottom: 8rem;
line-height: 13.1rem;
border: 1.16px solid #FFFFFF;
backdrop-filter: blur(2.5rem);
background: radial-gradient(100% 100% at 0% 0%, rgba(115, 115, 115, 0.4) 0%, rgba(0, 0, 0, 0) 100%);
&:last-child{
margin-bottom: 0;
} }
} }
} }
> .info{
position: absolute;
bottom: 7rem;
font-size: 3rem;
font-family: satoshiRegular;
color: #fff;
text-align: center;
width: 100%;
font-weight: 400;
letter-spacing: .5rem;
}
} }
</style> </style>

View File

@@ -0,0 +1,192 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, computed, onActivated } from "vue";
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
import { showConfirmDialog } from 'vant'
import MyEvent from '@/utils/myEvent'
import { FlowType, IsHistoryFlow } from '@/types/enum'
import { useHGenerateStore } from '@/stores'
const hGenerateStore = useHGenerateStore()
//const props = defineProps({
//})
const navList = ref([])
const navDisabledList = ref([])
// const data = reactive({
// })
const openFlow = (item: any)=>{
item.click && item.click()
const query = route.query
router.push({ name: item.path, query: {...query} })
// if(flowType == 'clientId')return clickSwitchVIPID()
// if(flowType == 'main'){
// router.push({ name: path })
// }else{
// router.push({ name: path, query: { demo: 1 } })
// }
}
onMounted(()=>{
let nav = [
{
path: 'SelectStyle',
imgPath: new URL('@/assets/images/nav1.png',import.meta.url).href,
flowTypeList: [FlowType.H_TRYON,FlowType.H_AI],
},
{
path: 'product',
imgPath: new URL('@/assets/images/nav2.png',import.meta.url).href,
flowTypeList: [FlowType.H_OUTFIT],
},
{
path: 'uploadFace',
imgPath: new URL('@/assets/images/nav3.png',import.meta.url).href,
flowTypeList: [FlowType.H_TRYON,FlowType.H_AI],
click(){
hGenerateStore.clearCustomizeInfo()
},
},
{
path: 'customize',
imgPath: new URL('@/assets/images/nav4.png',import.meta.url).href,
flowTypeList: [FlowType.H_TRYON,FlowType.H_AI],
click(){
hGenerateStore.clearCustomizeInfo()
hGenerateStore.uploadCustomizeInfo({
tryOnId: hGenerateStore.originalTryOn.id,
tryOnUrl: hGenerateStore.originalTryOn.tryOnUrl,
isFavorite: hGenerateStore.originalTryOn.isLike,
styleUrl: hGenerateStore.style.url,
})
},
},
]
nav.forEach((item)=>{
const query = computed(() => router.currentRoute.value.query)
if(item.flowTypeList.includes(query.value?.flowType as string)){
navList.value.push(item)
}else{
navDisabledList.value.push(item)
}
})
})
onUnmounted(()=>{
})
defineExpose({})
// const { } = toRefs(data);
</script>
<template>
<div class="homeNavPage">
<div class="shadow top"></div>
<div class="shadow bottom"></div>
<div class="titleBox">
<div class="title">
Choose one<br/>
function to explore.
</div>
<div class="info">
Welcome back! What can I help you today?
</div>
</div>
<div class="navBox">
<div class="navList">
<div class="item active" v-for="item in navList" :key="item.path" @click="openFlow(item)">
<img :src="item.imgPath" alt="">
</div>
<div class="item" v-for="item in navDisabledList" :key="item.path" @click="openFlow(item)">
<img :src="item.imgPath" alt="">
</div>
<!-- <div class="item" @click="openFlow('index')">
<img src="@/assets/images/nav1.png" alt="">
</div>
<div class="item" :class="{'active': ['history-outfit','history-ai'].includes($route.query?.flowType as string)}" @click="openFlow('recommended','reinventing')">
<img src="@/assets/images/nav2.png" alt="">
</div>
<div class="item" :class="{'active': ['history-outfit','history-ai'].includes($route.query?.flowType as string)}" @click="openFlow('index','stylist')">
<img src="@/assets/images/nav3.png" alt="">
</div>
<div class="item" :class="{'active': ['history-ai'].includes($route.query?.flowType as string)}" @click="openFlow('','clientId')">
<img src="@/assets/images/nav4.png" alt="">
</div> -->
</div>
</div>
</div>
</template>
<style lang="less" scoped>
.header-title {
--header-title-background: #fff;
--header-title-height: 12rem !important;
}
.homeNavPage{
> .shadow{
position: absolute;
width: 91rem;
height: 92rem;
background: linear-gradient(88.42deg, #FFFFFF 32.58%, #D9D9D9 94.9%);
transform: rotate(312deg);
&.top{
top: -20%;
right: -20%;
}
&.bottom{
bottom: -30%;
left: -20%;
transform: rotate(134deg);
}
}
> .titleBox{
padding-left: 7.9rem;
margin-top: 13.4rem;
margin-bottom: 11.2rem;
> div{
background: linear-gradient(120deg, #b3b3b3 1%, rgba(0, 0, 0, 0) 48%), linear-gradient(
344deg, #B3B3B2 16%, #000000 66%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
> .title{
font-family: satoshiBold;
font-weight: 700;
font-size: 10rem;
line-height: 124%;
}
> .info{
margin-top: 4.7rem;
font-size: 4rem;
font-family: satoshiRegular;
}
}
> .navBox{
> .navList{
display: flex;
flex-wrap: wrap;
padding: 0 7.4rem;
gap: 4.8rem;
> .item{
// width: 44.2rem;
// height: 41.6rem;
height: auto;
width: calc(50% - 4.8rem / 2);
pointer-events: none;
opacity: .5;
&.active{
pointer-events: auto;
opacity: 1;
}
> img{
width: 100%;
height: 100%;
object-fit: contain;
}
}
}
}
}
</style>

View File

@@ -1,10 +1,37 @@
<script setup lang="ts"> <script setup lang="ts">
import { useRouter, useRoute } from 'vue-router'
import { FlowType } from '@/types/enum'
const router = useRouter()
const route = useRoute()
import { ref, computed } from 'vue'
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import RouteCache from '@/components/RouteCache.vue' import RouteCache from '@/components/RouteCache.vue'
//const props = defineProps({ import profile from './profile.vue'
//}) const props = defineProps({})
//const emit = defineEmits([ const emit = defineEmits([])
//]) const profileRef = ref(null)
const handleClickProfile = () => {
profileRef.value.open()
}
const notShowFooter = computed(() => route.query.flowType !== FlowType.HISTORY)
</script> </script>
<template> <template>
<RouteCache /> <div class="workshop">
<header-title @clickProfile="handleClickProfile" />
<RouteCache view-type="1" />
<footer-navigation v-if="notShowFooter" />
</div>
<profile ref="profileRef" />
</template> </template>
<style scoped lang="less">
.workshop {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
> .routeCache {
flex: 1;
}
}
</style>

View File

@@ -1,19 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, inject } from 'vue' import { ref, reactive, onMounted, inject } from 'vue'
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import MyList from '@/components/MyList.vue' import MyList from '@/components/MyList.vue'
import router from '@/router' import router from '@/router'
import { FormatDate } from '@/utils/tools' import { FormatDate } from '@/utils/tools'
import { getCustomerPhotos, deleteCustomerPhoto } from '@/api/workshop' import { getCustomerPhotos, deleteCustomerPhoto } from '@/api/workshop'
const emit = defineEmits(['view-type'])
import { showConfirmDialog } from 'vant' import { showConfirmDialog } from 'vant'
import { useGenerateStore } from '@/stores' import { useGenerateStore } from '@/stores'
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
onMounted(() => {
emit('view-type', 1)
})
const loading = ref(false) const loading = ref(false)
const finish = ref(false) const finish = ref(false)
const list = reactive([]) const list = reactive([])
@@ -49,7 +43,7 @@
cancelButtonText: 'Cancel' cancelButtonText: 'Cancel'
}).catch(() => 0) }).catch(() => 0)
if (res === 0) return if (res === 0) return
console.log(obj,i) console.log(obj, i)
deleteCustomerPhoto(obj.visitRecordId) deleteCustomerPhoto(obj.visitRecordId)
.then(() => { .then(() => {
list.splice(i, 1) list.splice(i, 1)
@@ -67,11 +61,15 @@
</script> </script>
<template> <template>
<header-title style-type="2" />
<div class="library"> <div class="library">
<div class="title">Library</div> <div class="title">Library</div>
<div class="list"> <div class="list">
<my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad"> <my-list
v-model:loading="loading"
v-model:finish="finish"
:empty="list.length === 0"
@load="onLoad"
>
<div class="item" v-for="(v, i) in list" :key="v.visitRecordId"> <div class="item" v-for="(v, i) in list" :key="v.visitRecordId">
<div class="image"> <div class="image">
<img v-lazy="v.defaultImageUrl" /> <img v-lazy="v.defaultImageUrl" />
@@ -87,7 +85,6 @@
</my-list> </my-list>
</div> </div>
</div> </div>
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -119,18 +116,18 @@
} }
> .title { > .title {
font-family: satoshiRegular; font-family: satoshiBold;
font-size: 9rem; font-size: 8.6rem;
text-align: left; text-align: left;
line-height: 124%; line-height: 124%;
margin: 5rem; margin: 5rem 9.1rem;
} }
> .list { > .list {
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
margin: 0 3rem; margin: 0 4.5rem;
> .my-list { > .my-list {
padding: 0 3.8rem; padding: 0 4.5rem;
--my-list-footer-margin: 2rem 0; --my-list-footer-margin: 2rem 0;
> .item { > .item {
position: relative; position: relative;
@@ -146,7 +143,7 @@
margin-top: 0; margin-top: 0;
} }
> .image { > .image {
width: 22.9rem; width: 19.2rem;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
border-radius: 2rem; border-radius: 2rem;
@@ -154,7 +151,7 @@
> img { > img {
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: contain;
display: block; display: block;
} }
} }
@@ -163,41 +160,33 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 90%; height: 90%;
> .userID {
font-family: satoshiRegular;
font-weight: 500;
font-size: 3.8rem;
line-height: 89%;
color: #000;
}
> .datetime { > .datetime {
margin-top: 1.8rem; margin-top: 1rem;
font-family: satoshiRegular; font-family: satoshiMedium;
font-weight: 400; font-weight: 500;
font-size: 3.2rem; font-size: 3.2rem;
line-height: 89%; line-height: 89%;
color: #000; color: #000;
} }
> .lastopened { > .lastopened {
margin-top: 1rem; margin-top: 1.8rem;
font-family: satoshiRegular; font-family: satoshiRegular;
font-weight: 400; font-weight: 400;
font-style: Regular;
font-size: 2.6rem; font-size: 2.6rem;
line-height: 89%; line-height: 89%;
color: #6f6f6f; color: #6f6f6f;
} }
> button { > button {
margin-top: auto; margin-top: auto;
font-family: satoshiRegular;
width: 12.3rem; width: 12.3rem;
height: 3.8rem; height: 3.8rem;
border-radius: 0.5rem; line-height: 100%;
box-sizing: content-box; border-radius: 0.4rem;
border: 0.193rem solid #000; border: 0.2rem solid #000;
background: transparent; background: transparent;
font-family: satoshiRegular;
font-weight: 400; font-weight: 400;
font-size: 2.576rem; font-size: 2.4rem;
color: #000; color: #000;
margin-right: 5rem; margin-right: 5rem;
&:active { &:active {
@@ -211,8 +200,8 @@
right: 2rem; right: 2rem;
width: 5.5rem; width: 5.5rem;
height: 5.5rem; height: 5.5rem;
border: 0.188rem solid #000; border: 0.188rem solid rgba(0, 0, 0, 0.5);
border-radius: 1.88rem; border-radius: 0.9rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;

View File

@@ -1,15 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, onUnmounted, reactive, ref, toRefs } from "vue"; import { onMounted, onUnmounted, reactive, ref, toRefs, computed } from "vue";
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue' import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
import { useGenerateStore } from '@/stores' import { useGenerateStore, useHGenerateStore } from '@/stores'
import { generateTryOnEffect, setTryOnEffectFavorite, cancelTryOnEffectFavorite, addTryOnEffectComment } from '@/api/workshop' import { generateTryOnEffect, setTryOnEffectFavorite, cancelTryOnEffectFavorite, addTryOnEffectComment } from '@/api/workshop'
import { FlowType, IsHistoryFlow } from '@/types/enum'
import gradientButton from '@/components/gradientButton.vue'
const router = useRouter() const router = useRouter()
//const props = defineProps({ //const props = defineProps({
//}) //})
const emit = defineEmits(['view-type'])
let data = reactive({ let data = reactive({
modelList: modelList:
[ [
@@ -17,8 +17,11 @@ let data = reactive({
], ],
isLoading: false, isLoading: false,
}) })
const query = computed(() => router.currentRoute.value.query)
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
const hGenerateStore = useHGenerateStore()
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
const vanDialogShow = ref(false) const vanDialogShow = ref(false)
const feedbackForm = ref({ const feedbackForm = ref({
@@ -32,7 +35,11 @@ const feedbackForm = ref({
const onContinue = ()=>{ const onContinue = ()=>{
router.push('uploadFace') if(!isHistoryFlow.value){
router.push({ path: 'uploadFace', query: {...query.value} })
}else{
router.push({ path: 'creation', query: {...query.value, active: FlowType.H_TRYON } })
}
} }
const changeModel = ()=>{ const changeModel = ()=>{
@@ -44,7 +51,7 @@ const startGenerate = ()=>{
let value = { let value = {
customerId:generateStore.customerId, customerId:generateStore.customerId,
visitRecordId:generateStore.visitRecordId, visitRecordId:generateStore.visitRecordId,
styleId:generateStore.styleId, styleId:isHistoryFlow.value ? hGenerateStore.styleId : generateStore.styleId,
// customerPhotoId:null, // customerPhotoId:null,
// modelPhotoId:null, // modelPhotoId:null,
// prompt:null, // prompt:null,
@@ -54,10 +61,9 @@ const startGenerate = ()=>{
generateTryOnEffect(value).then((res:any)=>{ generateTryOnEffect(value).then((res:any)=>{
data.isLoading = false; data.isLoading = false;
generateStore.originalTryOn.isLike = false generateStore.originalTryOn.isLike = false
generateStore.originalTryOn.id = res.tryOnId generateStore.originalTryOn.id = res.id
generateStore.originalTryOn.tryOnUrl = res.tryOnUrl generateStore.originalTryOn.tryOnUrl = res.tryOnUrl
generateStore.useStyleGenerate()//生成后需要对选择衣服页面设置不可选中样式 // generateStore.useStyleGenerate()//生成后需要对选择衣服页面设置不可选中样式
generateStore.setIsGenerate(false)
generateStore.clearCustomizeInfo() generateStore.clearCustomizeInfo()
}).catch((error)=>{ }).catch((error)=>{
@@ -113,8 +119,7 @@ const handleSubmit = ()=>{
} }
onMounted(() => { onMounted(() => {
emit('view-type', 1) if (!generateStore.originalTryOn.id) {
if (generateStore.isGenerate) {
startGenerate() startGenerate()
} }
}) })
@@ -124,11 +129,10 @@ defineExpose({})
const { isLoading } = toRefs(data); const { isLoading } = toRefs(data);
</script> </script>
<template> <template>
<header-title style-type="2" />
<div class="product" v-if="!isLoading"> <div class="product" v-if="!isLoading">
<div class="text"> <div class="text">
<div class="title"> <div class="title">
Go with this Look? {{ isHistoryFlow?'Generate Result':'Go with this look?' }}
</div> </div>
</div> </div>
<div class="selectContent"> <div class="selectContent">
@@ -144,18 +148,28 @@ const { isLoading } = toRefs(data);
<div class="operation"> <div class="operation">
<div @click="setLike"><SvgIcon :name="`love_${generateStore.originalTryOn.isLike ? '1' : '0'}`" size="35" /></div> <div @click="setLike"><SvgIcon :name="`love_${generateStore.originalTryOn.isLike ? '1' : '0'}`" size="35" /></div>
<div @click="feedback"><SvgIcon name="pen" size="40" /></div> <div @click="feedback"><SvgIcon name="pen" size="40" /></div>
<div @click="startGenerate"><SvgIcon name="reload" size="35" /></div>
<!-- <div><SvgIcon name="download" size="35" /></div> --> <!-- <div><SvgIcon name="download" size="35" /></div> -->
</div> </div>
</div> </div>
</div> </div>
<div class="again"> <div class="btn">
<!-- <div @click="changeModel">Change Model</div> --> <div class="btnItem style1" @click.stop="startGenerate()">
<div @click="onContinue" style="margin-left: auto;">Continue</div> <gradientButton>
<template #content>
<div class="text">
<span class="icon">
<SvgIcon name="reTry" size="40" />
</span>
Re-try
</div>
</template>
</gradientButton>
</div>
<div class="btnItem style2" @click.stop="onContinue">{{ isHistoryFlow?'Finish':'Continue' }}</div>
</div> </div>
</div> </div>
</div> </div>
<footer-navigation is-placeholder v-if="!isLoading"/>
<van-dialog v-model:show="vanDialogShow" :show-confirm-button="false" width="52.8rem"> <van-dialog v-model:show="vanDialogShow" :show-confirm-button="false" width="52.8rem">
<div class="feedback"> <div class="feedback">
<div class="feedbackClose" @click="closeFeedback"><SvgIcon name="close" size="40" /></div> <div class="feedbackClose" @click="closeFeedback"><SvgIcon name="close" size="40" /></div>
@@ -181,33 +195,34 @@ const { isLoading } = toRefs(data);
<div class="loading-container" v-if="isLoading"> <div class="loading-container" v-if="isLoading">
<GenerateLoading title="Generating Results..." /> <GenerateLoading title="Generating Results..." />
</div> </div>
<footer-navigation is-placeholder/>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.product{ .product{
width: 100%; width: 100%;
// height: 100%; height: 100%;
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
> .text{ > .text{
text-align: center; text-align: center;
width: 100%; width: 100%;
margin-top: 4.3rem; margin-top: 4rem;
> .title{ > .title{
font-family: satoshiBold; font-family: satoshiBold;
font-weight: 700; font-weight: 700;
font-size: 9.6rem; font-size: 8.6rem;
line-height: 124%; line-height: 124%;
color: #000;
} }
} }
> .selectContent{ > .selectContent{
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 0 14.1rem; padding: 0 14.1rem;
margin-top: 4.8rem; margin-top: 4.3rem;
> .history{ > .history{
width: 30.2rem; width: 30.2rem;
margin-left: calc(7% / 2);
height: 6.52rem; height: 6.52rem;
border-radius: 0.91rem; border-radius: 0.91rem;
border: 0.24rem solid #000; border: 0.24rem solid #000;
@@ -224,7 +239,7 @@ const { isLoading } = toRefs(data);
} }
} }
> .modelBox{ > .modelBox{
margin-top: 2.5rem; margin-top: 4.4rem;
> .model{ > .model{
border: .2rem solid #D9D9D9; border: .2rem solid #D9D9D9;
// height: 110rem; // height: 110rem;
@@ -257,26 +272,42 @@ const { isLoading } = toRefs(data);
} }
} }
} }
> .again{ > .btn{
margin-top: 4.4rem; display: flex;
gap: 6.6rem;
justify-content: center;
margin-top: 5.2rem;
> div {
border-radius: .96rem;
width: 33.7rem;
font-size: 4.8rem;
font-family: satoshiMedium; font-family: satoshiMedium;
line-height: 10.4rem; line-height: 9.2rem;
color: #fff;
font-size: 4.2rem;
display: flex; display: flex;
justify-content: center; justify-content: center;
> div{ &.style1{
width: 36rem; --borderRadius: .96rem;
border-radius: 15px; --borderWidth: 2px;
background-color: #000; .text{
width: 100%;
text-align: center; text-align: center;
margin-right: 3.9rem; > .icon{
justify-content: center; left: 4rem;
&:last-child{ top: 50%;
margin-right: 0; transform: translateY(-50%);
position: absolute;
} }
} }
} }
&.style2{
color: #fff;
background-color: #000;
}
}
.btnItem .text{
color: #000;
}
}
} }
} }
.loading-container{ .loading-container{
@@ -323,6 +354,7 @@ const { isLoading } = toRefs(data);
font-weight: 700; font-weight: 700;
line-height: 2rem; line-height: 2rem;
margin-bottom: 1.8rem; margin-bottom: 1.8rem;
color: #000;
} }
> .info{ > .info{
font-size: 3.2rem; font-size: 3.2rem;

View File

@@ -1,28 +1,91 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted, inject } from 'vue' import { ref, reactive, onMounted, inject, watch } from 'vue'
import HeaderTitle from '@/components/HeaderTitle.vue' import router from '@/router'
import FooterNavigation from '@/components/FooterNavigation.vue' import { showConfirmDialog, showToast } from 'vant'
import router from '@/router' import { useUserInfoStore, useOverallStore, useGenerateStore } from '@/stores'
import { showConfirmDialog } from 'vant' import { LogOut } from '@/api/login'
import { useUserInfoStore } from '@/stores' import { getCustomerList, type CustomerListParams, customerCheckin } from '@/api/workshop'
import { LogOut } from '@/api/login' import MyEvent from '@/utils/myEvent'
const userInfoStore = useUserInfoStore() import { encryptPassword } from '@/utils/tools'
const emit = defineEmits(['view-type']) import { updateUserInfo } from '@/api/login'
const form = reactive({
name: { edit: false, msg: '', value: userInfoStore.state.userInfo.username },
email: { edit: false, msg: '', value: userInfoStore.state.userInfo.email },
password: { show: false, edit: false, msg: '', value: userInfoStore.state.userInfo.password }
})
const onEditItem = (item) => { const props = defineProps<{
isCustomer?: boolean
}>()
const userInfoStore = useUserInfoStore()
const overallStore = useOverallStore()
const generateStore = useGenerateStore()
const emit = defineEmits(['selected-customer', 'change-visible'])
const show = ref(false)
const isEdit = ref(false)
const form = reactive({
name: { msg: '', value: '' },
email: { msg: '', value: '' },
password: { show: true, msg: '', value: '' }
})
const open = () => {
form.name.value = userInfoStore.state.userInfo.username
form.email.value = userInfoStore.state.userInfo.email
form.password.value = ''
isEdit.value = false
show.value = true
}
const close = () => {
show.value = false
}
watch(show, (newVal) => {
emit('change-visible', newVal)
MyEvent.emit('change-profile-visible', newVal)
})
const onEditItem = (item) => {
if (!form[item]) return if (!form[item]) return
form[item].edit = true form[item].edit = true
} }
const onSaveItem = (item) => { const onSaveItem = (item) => {
if (!form[item]) return if (!form[item]) return
form[item].edit = false form[item].edit = false
}
const switchCustomer = () => {
// console.log('switchCustomer')
handleShowPopup(true)
}
const edit = () => {
form.password.value = ''
isEdit.value = true
}
const confirm = () => {
if (!isEdit.value) return
const password = form.password.value
const params = {
username: form.name.value,
email: form.email.value,
password
} }
const logout = () => { if (password) {
if (password.length < 6) {
return showToast('Password must be at least 6 characters')
} else {
params.password = encryptPassword(password)
}
} else {
params.password = userInfoStore.state.userInfo.password
}
overallStore.setLoading(true)
updateUserInfo(params).then((res) => {
overallStore.setLoading(false)
showToast('Update success')
userInfoStore.setUserInfo({
...userInfoStore.state.userInfo,
...params
})
form.password.value = ''
isEdit.value = false
})
}
const logout = () => {
showConfirmDialog({ showConfirmDialog({
title: 'Log out', title: 'Log out',
message: 'Are you sure you want to log out?', message: 'Are you sure you want to log out?',
@@ -36,42 +99,147 @@
}) })
}) })
.catch(() => {}) .catch(() => {})
}
const showSwitchCustomerPopup = ref(false)
const custmerParams = ref<CustomerListParams>({
current: 1,
size: 5,
desc: true
})
const customerList = ref<any[]>([])
const pager = reactive({
loading: false,
noMore: false,
total: null as number | null
})
const customerListEl = ref<HTMLElement | null>(null)
const loadCustomers = async (reset = false) => {
if (pager.loading) return
if (reset) {
custmerParams.value.current = 1
pager.noMore = false
customerList.value = []
} }
if (pager.noMore) return
pager.loading = true
try {
const res: any = await getCustomerList(custmerParams.value)
let list: any[] = []
const pages = res?.pages ?? res?.data?.pages ?? null
if (res && Array.isArray(res.records)) {
list = res.records
pager.total = res.total ?? pager.total
} else if (res && Array.isArray(res.data)) {
list = res.data
} else if (res && res.data && Array.isArray(res.data.list)) {
list = res.data.list
pager.total = res.data.total ?? pager.total
}
if (reset) customerList.value = list
else customerList.value = customerList.value.concat(list)
if (pages != null) {
if (custmerParams.value.current >= pages) {
pager.noMore = true
} else {
custmerParams.value.current += 1
}
} else {
if (list.length === 0 || list.length < custmerParams.value.size) {
pager.noMore = true
} else {
custmerParams.value.current += 1
}
}
} catch (e) {
// ignore error for now
} finally {
pager.loading = false
}
}
const onScroll = (e: Event) => {
const el = e.target as HTMLElement
if (!el) return
if (pager.loading || pager.noMore) return
if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) {
loadCustomers(false)
}
}
// 打开customer选择时关闭profile弹窗 如果不是点击confirem关闭则重新打开profile弹窗
let isCustomerOnly = false
const handleShowPopup = (flag: boolean, customer: boolean) => {
console.log(flag,customer)
// customer: 是否是顾客页面只展示customer选择弹窗
isCustomerOnly = customer
showSwitchCustomerPopup.value = flag
if (props.isCustomer) return
show.value = !flag
if (flag) {
loadCustomers(true)
}
}
const handleChangeChecked = (item) => {
customerList.value.forEach((customer) => {
customer.checked = customer.vipId === item.vipId
})
}
const handleSelectCustomer = () => {
const selectedCustomer = customerList.value.find((customer) => customer.checked)
if (selectedCustomer) {
emit('selected-customer', selectedCustomer)
}
if (!isCustomerOnly) {
// show.value = true
customerCheckin({ nickname: selectedCustomer.name }).then((res) => {
useUserInfoStore().resetGenerateParams()
MyEvent.emit('clear-generate-state')
useGenerateStore().setCustomerInfo(res)
router.push({ path: '/workshop/home' })
})
}
showSwitchCustomerPopup.value = false
}
const handleFetchCustomerList = () => {
loadCustomers(true)
}
MyEvent.add('update-customer-list', handleFetchCustomerList)
// const openSwitchCustomerPopup = (flag = true) => {
// showSwitchCustomerPopup.value = flag
// }
onMounted(() => {
handleFetchCustomerList()
})
defineExpose({ open, close, handleShowPopup })
</script> </script>
<template> <template>
<header-title style-type="2" :is-placeholder="false" /> <van-popup class="van-popup" v-model:show="show" position="bottom" round>
<div class="profile"> <div class="profile">
<div class="header-bg"> <div class="header">
<svg <span class="title">Profile</span>
width="1079" <van-icon name="cross" class="close" @click="close" />
height="572"
viewBox="0 0 1079 572"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 -6H1079V504.057C1079 504.057 751.757 572.269 536.733 571.999C323.838 571.733 0 504.057 0 504.057V-6Z"
fill="#EBF0F0"
/>
</svg>
</div> </div>
<div class="profile"> <form class="box" @submit.prevent.stop="confirm">
<!-- <div class="edit"><SvgIcon name="edit" size="37" /></div> -->
</div>
<div class="title">Momo Fashion</div>
<p class="sub">Fashion Design</p>
<div class="form-item"> <div class="form-item">
<div class="label">Your Name</div> <div class="label">Your Name</div>
<label class="input"> <label class="input">
<div class="icon"><SvgIcon name="user" size="50" /></div> <div class="icon"><SvgIcon name="user" size="64" /></div>
<input <input
type="text" type="text"
placeholder="Enter your name" placeholder="Enter your name"
v-model="form.name.value" v-model="form.name.value"
:readonly="!form.name.edit" :readonly="!isEdit"
required
/> />
<!-- <div class="icon" v-if="form.name.edit" @click.stop="onSaveItem('name')"> <!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('name')">
<SvgIcon name="confirmation" size="37" /> <SvgIcon name="confirmation" size="37" />
</div> </div>
<div class="icon" v-else @click="onEditItem('name')"> <div class="icon" v-else @click="onEditItem('name')">
@@ -83,15 +251,15 @@
<div class="form-item"> <div class="form-item">
<div class="label">Your Email</div> <div class="label">Your Email</div>
<label class="input"> <label class="input">
<div class="icon"><SvgIcon name="email" size="50" /></div> <div class="icon"><SvgIcon name="email" size="64" /></div>
<input <input
type="email" type="email"
placeholder="Enter your email" placeholder="Enter your email"
v-model="form.email.value" v-model="form.email.value"
:readonly="!form.email.edit" :readonly="!isEdit"
required required
/> />
<!-- <div class="icon" v-if="form.email.edit" @click.stop="onSaveItem('email')"> <!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('email')">
<SvgIcon name="confirmation" size="37" /> <SvgIcon name="confirmation" size="37" />
</div> </div>
<div class="icon" v-else @click="onEditItem('email')"> <div class="icon" v-else @click="onEditItem('email')">
@@ -100,19 +268,19 @@
</label> </label>
<p class="error" v-show="form.email.msg">{{ form.email.msg }}</p> <p class="error" v-show="form.email.msg">{{ form.email.msg }}</p>
</div> </div>
<div class="form-item"> <div class="form-item" v-if="isEdit">
<div class="label">Password</div> <div class="label">Password</div>
<label class="input"> <label class="input">
<div class="icon" @click="form.password.show = !form.password.show"> <div class="icon" @click="form.password.show = !form.password.show">
<SvgIcon :name="form.password.show ? 'password_1' : 'password_0'" size="50" /> <SvgIcon :name="form.password.show ? 'password_1' : 'password_0'" size="64" />
</div> </div>
<input <input
:type="form.password.show ? 'text' : 'password'" :type="form.password.show ? 'text' : 'password'"
placeholder="Enter your password" placeholder="Enter your password"
v-model="form.password.value" v-model="form.password.value"
:readonly="!form.password.edit" :readonly="!isEdit"
/> />
<!-- <div class="icon" v-if="form.password.edit" @click.stop="onSaveItem('password')"> <!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('password')">
<SvgIcon name="confirmation" size="37" /> <SvgIcon name="confirmation" size="37" />
</div> </div>
<div class="icon" v-else @click="onEditItem('password')"> <div class="icon" v-else @click="onEditItem('password')">
@@ -121,64 +289,106 @@
</label> </label>
<p class="error" v-show="form.password.msg">{{ form.password.msg }}</p> <p class="error" v-show="form.password.msg">{{ form.password.msg }}</p>
</div> </div>
<template v-if="isEdit">
<button type="submit" class="confirm">Confirm</button>
<p class="tip">Powered by AiDLab for Lane Crawford</p>
</template>
<template v-else>
<button class="switch" @click="switchCustomer">Switch Customer</button>
<button class="edit" @click="edit">Edit Profile</button>
<button class="logout" @click="logout">Log out</button> <button class="logout" @click="logout">Log out</button>
</template>
</form>
</div> </div>
<footer-navigation is-placeholder /> </van-popup>
<van-popup
class="user-popup"
v-model:show="showSwitchCustomerPopup"
round
position="bottom"
teleport="body"
>
<div class="popup-title flex">
<div class="title-txt">Saved Customer ID</div>
<SvgIcon name="close_nocolor" color="#a1a1a1" size="40" @click="handleShowPopup(false,false)" />
</div>
<div ref="customerListEl" class="cusomter-list" @scroll="onScroll">
<div
class="customer-item flex flex-align-center flex-between"
v-for="(item, index) in customerList"
:key="index + 'customer'"
@click="handleChangeChecked(item)"
>
<div class="info">
<div class="name">{{ item.name }}</div>
<div class="id">{{ item.vipId }}</div>
</div>
<div class="select-box">
<div class="check-box flex flex-center" v-show="!item.checked">
<div class="check"></div>
</div>
<img v-show="item.checked" class="checked-icon" src="@/assets/images/checked.png" />
</div>
</div>
<div class="list-footer">
<div v-if="pager.loading">Loading...</div>
<div v-else-if="pager.noMore">No more</div>
</div>
</div>
<div class="confirm-btn" @click="handleSelectCustomer">Confirm</div>
<div class="van-safe-area-bottom"></div>
</van-popup>
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
.profile { .van-popup {
width: 100%; max-height: 90%;
--van-popup-round-radius: 7.8rem;
}
.profile {
margin: 6.5rem 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
> .header-bg { > .header {
width: 100%;
min-height: 57rem;
> svg {
width: 100%;
height: auto;
}
}
> .profile {
margin-top: -23rem;
width: 28.9rem;
height: 28.9rem;
border-radius: 50%;
background-color: #d9d9d9;
position: relative;
> .edit {
position: absolute;
right: 1.9rem;
bottom: 0;
width: 6.4rem;
height: 6.4rem;
border-radius: 50%;
background-color: #74716d;
display: flex; display: flex;
justify-content: center;
align-items: center; align-items: center;
--svg-icon-color: #fff; justify-content: center;
} width: 100%;
} padding: 0 7rem;
margin-bottom: 8rem;
> .title { > .title {
margin-top: 3.7rem; font-family: satoshiBold;
font-family: 'satoshiBold'; font-size: 4.6rem;
font-size: 4.1rem; color: #181725;
line-height: 120%;
color: #262422;
} }
> .sub { > .close {
font-family: satoshiRegular; margin-left: auto;
font-size: 2.9rem; font-size: 5rem;
line-height: 120%; color: #a1a1a1;
margin-top: 1.6rem; }
margin-bottom: 2.4rem; }
color: #ababab; > .box {
width: 100%;
padding: 0 13rem;
> div {
width: 100%;
margin-top: 5.1rem;
&:first-child {
margin-top: 0;
}
}
> button {
margin-top: 8.5rem;
width: 100%;
height: 14.5rem;
border-radius: 2rem;
font-size: 4rem;
border: 0.2rem solid #3b3b3b;
font-family: satoshiBold;
} }
> .form-item { > .form-item {
margin: 4.2rem auto 0;
width: 68rem;
> .label { > .label {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
font-family: satoshiBold; font-family: satoshiBold;
@@ -187,8 +397,8 @@
color: #262422; color: #262422;
} }
> .input { > .input {
width: 100%; // width: 100%;
height: 11.5rem; height: 13.9rem;
border-radius: 2.5rem; border-radius: 2.5rem;
border: 0.2rem solid #f1ecec; border: 0.2rem solid #f1ecec;
box-sizing: content-box; box-sizing: content-box;
@@ -211,7 +421,7 @@
flex: 1; flex: 1;
height: 100%; height: 100%;
font-family: satoshiMedium; font-family: satoshiMedium;
font-size: 2.9rem; font-size: 3.5rem;
color: #000; color: #000;
border: none; border: none;
&::placeholder { &::placeholder {
@@ -227,17 +437,95 @@
color: #ff0000; color: #ff0000;
} }
} }
> .logout { > .switch {
margin-top: 7rem;
width: 68rem;
height: 11.5rem;
border-radius: 2.5rem;
background-color: transparent; background-color: transparent;
border: 0.2rem solid #3b3b3b;
font-family: satoshiBold;
font-size: 3.32rem;
line-height: 120%;
color: #222; color: #222;
} }
> .confirm,
> .edit,
> .logout {
background-color: #000;
color: #fff;
} }
> .tip {
font-family: satoshiRegular;
text-align: center;
font-size: 3rem;
color: #a1a1a1;
margin-top: 17.8rem;
}
}
}
.van-popup.user-popup {
height: 116.1rem;
color: #181725;
border-top-left-radius: 7.8rem;
border-top-right-radius: 7.8rem;
.c-svg {
width: initial;
}
.popup-title {
align-items: center;
justify-content: space-between;
font-weight: 700;
font-family: 'satoshiBold';
font-size: 4.8rem;
padding: 12rem 7.4rem 7.8rem 6.7rem;
border-bottom: 0.26rem solid #e2e2e2b2;
}
.cusomter-list {
padding: 0 7rem 0 6.6rem;
font-family: 'satoshiMedium';
color: #000;
/* Fixed list area: show up to 4 items, enable vertical scroll when overflow */
height: 64.8rem; /* 4 * 16.2rem (customer-item height) */
overflow-y: auto;
-webkit-overflow-scrolling: touch;
.customer-item {
height: 16.2rem;
border-bottom: 0.26rem solid #e2e2e2b2;
.info {
.name {
font-size: 3.6rem;
}
.id {
font-size: 3.6rem;
color: #b3b3b3;
}
}
.checked-icon,
.check-box {
width: 6rem;
height: 6rem;
box-sizing: border-box;
}
.check {
width: 4.8rem;
height: 4.8rem;
border-radius: 50%;
border: 0.4rem solid #000;
}
}
}
.list-footer {
text-align: center;
padding: 2rem 0;
font-size: 3rem;
color: #9b9b9b;
}
.confirm-btn {
margin: 4.9rem 12.7rem 0 13.3rem;
border: 0.25rem solid #3b3b3b;
width: 82rem;
height: 14.5rem;
border-radius: 2rem;
font-family: 'satoshiBold';
font-weight: 700;
font-size: 4rem;
line-height: 14.5rem;
box-sizing: border-box;
text-align: center;
}
}
</style> </style>

View File

@@ -1,14 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useGenerateStore } from '@/stores' import { useGenerateStore } from '@/stores'
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
const emit = defineEmits(['view-type'])
onMounted(() => {
emit('view-type', 1)
})
const router = useRouter() const router = useRouter()
const clickNext = () => { const clickNext = () => {
generateStore.updatePhotoInfo({}) generateStore.updatePhotoInfo({})
@@ -17,7 +12,6 @@
</script> </script>
<template> <template>
<header-title />
<!-- 上传照片 --> <!-- 上传照片 -->
<div class="upload-face-1"> <div class="upload-face-1">
<img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" /> <img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" />
@@ -32,7 +26,6 @@
<button class="sandblasted-blurred" @click="clickNext"><span>Next</span></button> <button class="sandblasted-blurred" @click="clickNext"><span>Next</span></button>
</div> </div>
</div> </div>
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">

View File

@@ -6,9 +6,7 @@ import { useGenerateStore } from '@/stores'
const router = useRouter() const router = useRouter()
//const props = defineProps({ //const props = defineProps({
//}) //})
const emit = defineEmits([
'view-type'
])
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
let data = reactive({ let data = reactive({
modelList: modelList:
@@ -45,9 +43,7 @@ let data = reactive({
const setSelectedModelId = (item)=>{ const setSelectedModelId = (item)=>{
generateStore.selectModel(item) generateStore.selectModel(item)
} }
onMounted(()=>{
emit('view-type', 1)
})
const toProduct = ()=>{ const toProduct = ()=>{
router.push({ path: 'product' }) router.push({ path: 'product' })
} }
@@ -57,7 +53,6 @@ defineExpose({})
const { modelList, selectModel } = toRefs(data); const { modelList, selectModel } = toRefs(data);
</script> </script>
<template> <template>
<header-title style-type="2" />
<div class="selectModel"> <div class="selectModel">
<div class="text"> <div class="text">
<div class="title"> <div class="title">

View File

@@ -1,198 +0,0 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, toRefs, computed, onActivated } from "vue";
import SelectItem from "@/components/selectStyle/selectItem.vue";
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { useRouter } from 'vue-router'
import { useGenerateStore, useUserInfoStore } from '@/stores'
import { showToast } from 'vant';
import { generateRequestOutfit, getRequestOutfit } from '@/api/workshop'
const router = useRouter()
//const props = defineProps({
//})
const emit = defineEmits([
'view-type'
])
const generateStore = useGenerateStore()
const userInfoStore = useUserInfoStore()
let data = reactive({
select:computed(()=>generateStore.style),
styleList:computed(()=>generateStore.styleList),
})
let getGenerateTime = null as any
const selectItem = (item)=>{
if(!item.id || item.status != 'SUCCEEDED'){
return
}
generateStore.selectStyle(item)
}
const updateStyle = ({item,index})=>{
generateStore.updateStyle(item)
data.styleList[index] = {}
requestOutfit({num:1,index})
}
const toProduct = ()=>{
if(!generateStore.style.id){
showToast({ message: 'Please select a style.' });
return
}
if(generateStore.style.id != generateStore.style.oldId){
generateStore.setIsGenerate(true)
}
router.push({ path: 'product' })
}
const requestOutfit = ({num,index})=>{
let value = {
"customerId": generateStore.customerId,
"checkInId": generateStore.visitRecordId,
"stylist": userInfoStore.state.generateParams.stylist,
"gender": userInfoStore.state.generateParams.sex,
"sessionId": generateStore.sessionId,
num,
}
generateRequestOutfit(value).then((rv)=>{
let rvIndex = 0
data.styleList.forEach((item,styleIndex)=>{
if(styleIndex < index)return
item.taskId = rv[rvIndex]
rvIndex++
})
getRequestOutfitList(rv)
})
}
const getRequestOutfitList = (generateList)=>{
let value = {requestIDs:generateList.join(',')}
getRequestOutfit(value).then((rv:any)=>{
let pendingList = []
rv.forEach((item)=>{
if(['RUNNING','PENDING'].includes(item.status))pendingList.push(item.requestId)
let index = data.styleList.findIndex((styleItem)=>styleItem.taskId == item.requestId)
if(index != -1){
data.styleList[index].id = item.id
data.styleList[index].path = item.path
data.styleList[index].status = item.status
}
})
if(pendingList.length > 0){
getGenerateTime = setTimeout(()=>{
getRequestOutfitList(pendingList)
},3000)
}
})
}
onMounted(()=>{
// generateStore.clearProductData()
emit('view-type', 1)
// if(!data.styleList[0]?.id)getRequestOutfitList(0)
if(getGenerateTime)clearTimeout(getGenerateTime)
if(!data.styleList[0]?.taskId){
requestOutfit({num:4,index:0})
}else if(data.styleList.filter((item)=>item?.status != 'SUCCEEDED').length > 0){
let generateList = data.styleList.map((item)=>item.taskId)
getRequestOutfitList(generateList)
}
})
onUnmounted(()=>{
if(getGenerateTime)clearTimeout(getGenerateTime)
})
defineExpose({})
const { styleList, select } = toRefs(data);
</script>
<template>
<header-title style-type="2" />
<div class="selectStyle">
<div class="text">
<div class="title">
Whats your Style?
</div>
<div class="info">
Select the outfit that matches you the most.
</div>
</div>
<div class="selectContent">
<SelectItem :selectList="styleList" v-model:select="select" @selectItem="selectItem" @updateStyle="updateStyle" />
</div>
</div>
<!-- <div class="footer placeholder"></div> -->
<div class="footer">
<button @click.stop="toProduct">Continue</button>
</div>
<footer-navigation is-placeholder />
</template>
<style lang="less" scoped>
.header-title {
// --header-title-background: #f6f6f6;
}
.selectStyle{
width: 100%;
flex: 1;
// height: 100%;
position: relative;
display: flex;
flex-direction: column;
background-color: #f6f6f6;
overflow: hidden;
> .text{
text-align: center;
width: 100%;
margin-top: 3.4rem;
margin-bottom: 4.9rem;
> .title{
font-family: satoshiBold;
font-weight: 700;
font-size: 9.6rem;
line-height: 124%;
}
> .info{
font-size: 4rem;
font-weight: 400;
line-height: 124%;
margin-top: 1.3rem;
}
}
.selectContent{
padding: 0 4rem;
flex: 1;
overflow: auto;
}
}
.footer {
// position: fixed;
width: 100%;
bottom: 0;
left: 0;
height: 11.2rem;
display: flex;
align-items: center;
justify-content: flex-end;
background-color: #f6f6f6;
&.placeholder{
position: relative;
}
> button {
width: 24.6rem;
height: 5.9rem;
border-radius: 0.7rem;
box-sizing: content-box;
border: 0.3rem solid #000;
background-color: #000;
font-family: satoshiBold;
font-weight: 700;
font-size: 3.6rem;
color: #fff;
margin-right: 5rem;
&:active {
opacity: 0.7;
}
}
}
</style>

View File

@@ -0,0 +1,391 @@
<script setup lang="ts">
import { onMounted, onUnmounted, reactive, toRefs, computed, ref } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useGenerateStore, useUserInfoStore, useHGenerateStore } from '@/stores'
import { showToast } from 'vant'
import { shareImageToWhatsapp } from '@/utils/tools'
import {
generateRequestOutfit,
getRequestOutfit,
setStyleFavorite,
cancelStyleFavorite,
retrieveAndRegenerate
} from '@/api/workshop'
import { FlowType, IsHistoryFlow } from '@/types/enum'
import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
import gradientButton from '@/components/gradientButton.vue'
import StyleListDom from '@/views/Workshop/selectStyle/styleList.vue'
import { useStreamChat } from '@/hooks/useStreamChat'
const router = useRouter()
const route = useRoute()
//const props = defineProps({
//})
const generateStore = useGenerateStore()
const userInfoStore = useUserInfoStore()
const hGenerateStore = useHGenerateStore()
const query = computed(() => route.query)
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
const isLoading = ref(false)
// const loadingTitle= ref('Analyzing the Outfit...')
const loadingTitle = computed(() => {
let str = 'Analyzing the Outfit...'
if (!select.value.status) str = 'Analyzing the Outfit...'
if (select.value.status == 'RUNNING') str = 'Generating Results...'
if (select.value.status == 'PENDING' || select.value.status == 'ALMOST_DONE')
str = 'Almost there...'
return str
})
let data = reactive({
select: computed(() => generateStore.style),
styleList: computed(() => generateStore.styleList)
})
let dataDom = reactive({
styleListVue: null
})
let getGenerateTime = null as any
const updateStyle = () => {
// generateStore.updateStyle(item)
// data.styleList[index] = {}
requestOutfit({ num: 4 })
}
const setLikeStyle = (likeStyle) => {
if (!select.value.id) return
if (likeStyle) {
cancelStyleFavorite(select.value.id).then(() => {
select.value.isLike = false
})
} else {
setStyleFavorite(select.value.id).then(() => {
select.value.isLike = true
})
}
}
const setDownload = () => {
if (select.value.path) shareImageToWhatsapp(select.value.path)
}
const toProduct = () => {
// if(generateStore.style.id){
// generateStore.setIsGenerate(true)
// }
router.push({ path: 'product', query: { ...query.value } })
// if(!isHistoryFlow.value){
// router.push({ path: 'product', query: {...query.value} })
// }else{
// router.push({ path: 'creation', query: {...query.value, active: FlowType.H_OUTFIT} })
// }
}
const requestOutfit = async ({ num }) => {
isLoading.value = true
let rv: any = await new Promise<void>((resolve, reject) => {
if (isHistoryFlow.value) {
retrieveAndRegenerate({
tryOnEffectsId: hGenerateStore.originalTryOn.id,
checkInId: generateStore.visitRecordId
}).then((rv: any) => {
resolve(rv)
})
} else {
let value = {
customerId: generateStore.customerId,
checkInId: generateStore.visitRecordId,
stylist: userInfoStore.state.generateParams.stylist,
gender: userInfoStore.state.generateParams.sex,
sessionId: generateStore.sessionId,
num
}
generateRequestOutfit(value).then((rv: any) => {
resolve(rv)
})
}
})
generateStore.clearProductData()
data.select.taskId = rv[0]
rv.forEach((item, index) => (data.styleList[index].taskId = item))
getRequestOutfitList(rv)
}
const getRequestOutfitList = (generateList) => {
let value = { requestIDs: generateList.join(',') }
getRequestOutfit(value).then((rv: any) => {
let selectIndex = rv.findIndex((item) => item.requestId == data.select.taskId)
console.log(selectIndex)
if (selectIndex != -1) {
data.select.id = rv[selectIndex].id
data.select.path = rv[selectIndex].path
data.select.status = rv[selectIndex].status
}
rv.forEach((item) => {
let index = data.styleList.findIndex(
(styleListItem) => styleListItem?.taskId == item.requestId
)
data.styleList[index] = {
id: item.id,
taskId: item.requestId,
status: item.status,
path: item.path
}
})
if (['SUCCEEDED'].includes(data.select.status)) {
isLoading.value = false
if (isHistoryFlow.value) {
hGenerateStore.uploadStyle({
id: data.select.id,
path: data.select.path
})
}
}
if (data.styleList.filter((item) => item?.status == 'FAILED').length > 0) {
showToast({
message: 'One of the outfits failed to generate. Please try generating again.',
duration: 2000
})
isLoading.value = false
}
const taskIdList = data.styleList
.filter((item) => item?.taskId && item?.status !== 'SUCCEEDED' && item?.status !== 'FAILED')
.map((item) => item.taskId)
if (taskIdList.length > 0) {
getGenerateTime = setTimeout(() => {
getRequestOutfitList(taskIdList)
}, 3000)
}
})
}
const styleListInit = () => {
dataDom.styleListVue.init(data.select)
}
// 使用 useStreamChat在流式请求成功后执行原本的逻辑
const { fetchMessage, isGenerating } = useStreamChat()
onMounted(() => {
// generateStore.clearProductData()
// if(!data.styleList[0]?.id)getRequestOutfitList(0)
if (getGenerateTime) clearTimeout(getGenerateTime)
// 检查是否有从 dressfor 传递过来的消息
const message = query.value.message as string
const sessionId = query.value.sessionId as string
if (message && sessionId) {
// 有消息,说明是从 dressfor 跳转过来的,先发起流式请求
generateStore.setSessionId(sessionId)
fetchMessage(message, sessionId)
.then(() => {
console.log('对话请求完成')
// 清除 URL 参数避免返回时再次触发
router.replace({ path: '/workshop/selectStyle' })
// 开始生成outfit
requestOutfit({ num: 4 })
})
.catch(() => {
// 错误处理
})
}
// 原本的逻辑
const taskIdList = data.styleList
.filter((item) => item?.taskId && item?.status !== 'SUCCEEDED')
.map((item) => item.taskId)
if (data.select.status == 'SUCCEEDED' && taskIdList.length == 0) {
return
} else if (!data.select?.taskId) {
requestOutfit({ num: 4 })
} else if (data.select.status != 'SUCCEEDED' || taskIdList.length > 0) {
if (data.select.status != 'SUCCEEDED') isLoading.value = true
getRequestOutfitList(taskIdList)
}
})
onUnmounted(() => {
if (getGenerateTime) clearTimeout(getGenerateTime)
})
defineExpose({})
const { select } = toRefs(data)
const { styleListVue } = toRefs(dataDom)
</script>
<template>
<div class="selectStyle">
<div class="text">
<div class="title">Outfit Result</div>
<div class="info">Refine your Look</div>
</div>
<div class="selectContent">
<!-- {{ select }} -->
<div class="imgBox">
<img :src="select.path" alt="" />
</div>
<div v-if="!isHistoryFlow" class="chooseMore" @click.stop="styleListInit">
<gradientButton>
<template #content>
<div class="text">Choose More</div>
</template>
</gradientButton>
<div></div>
</div>
<div class="btn" v-else>
<div class="like" @click.stop="setLikeStyle(select.isLike)">
<SvgIcon :name="`love_${select.isLike ? 1 : 0}`" size="35" />
</div>
<div class="down" @click.stop="setDownload()">
<SvgIcon name="download" size="35" />
</div>
</div>
</div>
<div class="btn">
<div class="btnItem style1" @click.stop="updateStyle()">
<gradientButton>
<template #content>
<div class="text">
<span class="icon">
<SvgIcon name="reTry" size="40" />
</span>
Re-try
</div>
</template>
</gradientButton>
</div>
<div class="btnItem style2" @click.stop="toProduct">Continue</div>
</div>
</div>
<!-- <div class="footer placeholder"></div> -->
<div class="loading-container" v-if="isGenerating || isLoading">
<GenerateLoading :title="loadingTitle" />
</div>
<StyleListDom ref="styleListVue"></StyleListDom>
</template>
<style lang="less" scoped>
.header-title {
// --header-title-background: #f6f6f6;
}
.loading-container {
width: 100%;
height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
.selectStyle {
width: 100%;
flex: 1;
// height: 100%;
position: relative;
display: flex;
flex-direction: column;
background-color: #f6f6f6;
overflow: hidden;
> .text {
text-align: center;
width: 100%;
margin-top: 8.5rem;
margin-bottom: 8.5rem;
> .title {
font-family: satoshiBold;
font-weight: 700;
font-size: 8.6rem;
line-height: 124%;
color: #000;
}
> .info {
font-size: 4rem;
font-weight: 400;
line-height: 124%;
margin-top: 3.2rem;
color: rgba(0, 0, 0, 0.6);
}
}
.selectContent {
// padding: 0 4rem;
margin: 0 auto;
width: 73.7rem;
margin-bottom: 19rem;
> .imgBox {
height: 73.7rem;
width: 100%;
margin-bottom: 5.6rem;
> img {
width: 100%;
height: 100%;
}
}
> .chooseMore {
--borderRadius: 5.4rem;
--borderWidth: 2px;
width: 24.8rem;
margin: 0 auto;
height: 7.6rem;
.text {
font-size: 3.1rem;
color: #000;
font-family: satoshiMedium;
}
}
> .btn {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 2rem;
> div {
color: #000;
border-radius: 50%;
width: 7rem;
height: 7rem;
padding: 1rem;
background-color: #fff;
&:hover {
color: #000;
}
}
}
}
> .btn {
display: flex;
gap: 6.6rem;
justify-content: center;
> div {
border-radius: 0.96rem;
width: 33.7rem;
font-size: 4.8rem;
font-family: satoshiMedium;
line-height: 9.2rem;
display: flex;
justify-content: center;
&.style1 {
--borderRadius: 0.96rem;
--borderWidth: 2px;
.text {
width: 100%;
text-align: center;
> .icon {
left: 4rem;
top: 50%;
transform: translateY(-50%);
position: absolute;
}
}
}
&.style2 {
color: #fff;
background-color: #000;
}
}
.btnItem .text {
color: #000;
}
}
}
</style>

View File

@@ -0,0 +1,121 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, reactive, toRefs } from "vue";
import SelectItem from "@/components/selectStyle/selectItem.vue";
import { useGenerateStore, } from '@/stores'
//const props = defineProps({
//})
//const emit = defineEmits([
//])
const generateStore = useGenerateStore()
let data = reactive({
showStyleList:false,
list:computed(()=>generateStore.styleList),
selectStyle:computed(()=>generateStore.style),
oldSelectStyle:{} as any,
})
const close = ()=>{
showStyleList.value = false;
}
const init = (item)=>{
data.showStyleList = true
data.oldSelectStyle = JSON.parse(JSON.stringify(item))
}
const confirm = ()=>{
if(data.selectStyle.id != data.oldSelectStyle.id){
data.selectStyle.id = data.oldSelectStyle.id
data.selectStyle.path = data.oldSelectStyle.path
data.selectStyle.status = data.oldSelectStyle.status
data.selectStyle.taskId = data.oldSelectStyle.taskId
data.selectStyle.isLike = false
}
generateStore.clearTryOn()
close();
}
const setStyle = (item)=>{
data.oldSelectStyle = item
}
onMounted(()=>{
})
onUnmounted(()=>{
})
defineExpose({init})
const {showStyleList,list,oldSelectStyle} = toRefs(data);
</script>
<template>
<van-popup
class="user-popup"
v-model:show="showStyleList"
round
position="bottom"
teleport="body"
>
<div class="profile">
<div class="header">
<span class="title">Outfit Result</span>
<van-icon name="cross" class="close" @click="close" />
</div>
<div class="content">
<SelectItem :selectList="list" :select="oldSelectStyle" @selectItem="setStyle"></SelectItem>
</div>
<div class="bottom">
<div @click="confirm">Confirm</div>
</div>
</div>
</van-popup>
</template>
<style lang="less" scoped>
.van-popup {
max-height: 90%;
--van-popup-round-radius: 7.8rem;
}
.profile {
margin: 11.1rem 0 5.6rem;
display: flex;
flex-direction: column;
align-items: center;
> .header {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0 8rem 0 5.2rem;
margin-bottom: 8rem;
> .title {
font-family: satoshiBold;
font-size: 4.6rem;
color: #181725;
}
> .close {
margin-left: auto;
font-size: 5rem;
color: #a1a1a1;
}
}
> .content{
width: 96.2rem;
}
> .bottom{
margin: 3rem 0 4rem;
width: 100%;
> div{
width: 24.8rem;
line-height: 6.7rem;
font-family: satoshiMedium;
font-size: 3.6rem;
text-align: center;
border-radius: .7rem;
background-color: #000;
color: #fff;
margin-right: 4.2rem;
margin-left: auto;
}
}
}
</style>

View File

@@ -1,18 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { ref, onMounted, computed } from 'vue' import { ref, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useGenerateStore } from '@/stores' import { useGenerateStore } from '@/stores'
import { IsHistoryFlow } from '@/types/enum'
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
const emit = defineEmits(['view-type'])
onMounted(() => {
emit('view-type', 1)
})
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const query = computed(() => route.query) const query = computed(() => route.query)
const isDemo = computed(() => route.query.demo === '1') const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
// 上传照片 // 上传照片
const handleUploadFace = () => { const handleUploadFace = () => {
// generateStore.updatePhotoInfo({}) // generateStore.updatePhotoInfo({})
@@ -20,9 +15,6 @@
} }
// 跳过上传 // 跳过上传
const handleFinish = () => { const handleFinish = () => {
if (isDemo.value) {
handleUploadFace();
} else {
generateStore.updatePhotoInfo({}) generateStore.updatePhotoInfo({})
generateStore.clearCustomizeInfo() generateStore.clearCustomizeInfo()
generateStore.uploadCustomizeInfo({ generateStore.uploadCustomizeInfo({
@@ -32,11 +24,9 @@
}) })
router.push({ name: 'customize', query: query.value }) router.push({ name: 'customize', query: query.value })
} }
}
</script> </script>
<template> <template>
<header-title />
<!-- 上传照片 --> <!-- 上传照片 -->
<div class="upload-face-1"> <div class="upload-face-1">
<img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" /> <img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" />
@@ -48,13 +38,18 @@
</p> </p>
</div> </div>
<div class="btns"> <div class="btns">
<button class="sandblasted-blurred" @click="handleUploadFace"> <button class="sandblasted-blurred flex flex-center" @click="handleUploadFace">
<span>Upload Face</span> <span>Upload Face</span>
</button> </button>
<button class="sandblasted-blurred" @click="handleFinish" v-if="!isDemo"><span>Finish</span></button> <button
class="sandblasted-blurred flex flex-center"
@click="handleFinish"
v-if="!isHistoryFlow"
>
<span>Finish</span>
</button>
</div> </div>
</div> </div>
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -95,10 +90,7 @@
justify-content: center; justify-content: center;
bottom: 19.7rem; bottom: 19.7rem;
> button { > button {
width: 40rem; margin: 0 7.5rem;
height: 8.3rem;
border-radius: 0.7rem;
margin: 0 1.8rem;
} }
} }
} }

View File

@@ -1,20 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue'
import { ref, reactive, onMounted, computed } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { uploadCustomerPhoto } from '@/api/workshop' import { uploadCustomerPhoto } from '@/api/workshop'
import { useGenerateStore } from '@/stores' import { useGenerateStore, useHGenerateStore } from '@/stores'
import { IsHistoryFlow } from '@/types/enum'
const generateStore = useGenerateStore() const generateStore = useGenerateStore()
const hGenerateStore = useHGenerateStore()
const emit = defineEmits(['view-type'])
onMounted(() => {
emit('view-type', 1)
})
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const query = computed(() => route.query) const query = computed(() => route.query)
const isDemo = computed(() => route.query.demo === '1') const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
const fileData = generateStore.photoInfo const fileData = generateStore.photoInfo
if (!fileData.file?.size) generateStore.updatePhotoInfo({}) if (!fileData.file?.size) generateStore.updatePhotoInfo({})
// 上传照片 // 上传照片
@@ -48,7 +44,7 @@
formData.append('file', fileData.file) formData.append('file', fileData.file)
uploadCustomerPhoto(formData).then((res) => { uploadCustomerPhoto(formData).then((res) => {
generateStore.updatePhotoInfo({ ...res, file: fileData.file }) generateStore.updatePhotoInfo({ ...res, file: fileData.file })
isDemo.value ? generateStore.clearCustomizeInfoDemo() : generateStore.clearCustomizeInfo() isHistoryFlow.value ? hGenerateStore.clearCustomizeInfo() : generateStore.clearCustomizeInfo()
router.push({ name: 'customize', query: query.value }) router.push({ name: 'customize', query: query.value })
}) })
} }
@@ -59,7 +55,6 @@
</script> </script>
<template> <template>
<header-title />
<!-- 展示照片 --> <!-- 展示照片 -->
<div class="upload-face-2"> <div class="upload-face-2">
<img src="@/assets/images/workshop/bg/picture_bg.png" class="bg" /> <img src="@/assets/images/workshop/bg/picture_bg.png" class="bg" />
@@ -83,7 +78,6 @@
</div> </div>
</div> </div>
</div> </div>
<footer-navigation is-placeholder />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
@@ -148,9 +142,6 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
> button { > button {
width: 34.5rem;
height: 8.6rem;
border-radius: 4.3rem;
margin: 0 5rem; margin: 0 5rem;
} }
} }

View File

@@ -4,12 +4,19 @@
<img src="@/assets/images/chat_loading.png" alt="Loading" class="loading-image" /> <img src="@/assets/images/chat_loading.png" alt="Loading" class="loading-image" />
<!-- 阴影效果 --> <!-- 阴影效果 -->
<div class="loading-shadow"></div> <div class="loading-shadow"></div>
<div class="loading-text">{{ title }}</div> <div class="loading-text" ref="textBox">
<span>{{ text }}</span>
<div class="loading-dot" ref="dotBox"></div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, watch, reactive, toRefs, nextTick, ref } from "vue";
import { gsap, TweenMax } from "gsap";
// 这个组件只负责显示loading动画 // 这个组件只负责显示loading动画
// 定义组件props类型 // 定义组件props类型
@@ -20,6 +27,76 @@ const props = defineProps({
} }
}) })
const dotBox = ref(null)
const textBox = ref(null)
let tl1 = null;
const text = ref('')
watch(() => props.title, (newVal, oldVal) => {
if (newVal !== oldVal) {
destroyAnimation()
fadeOut(newVal)
}
})
function fadeOut(newVal) {
gsap.to(textBox.value,.5, {
opacity: 0,
duration: 1,
ease: "power2.in",
onComplete: () => {
// 消失后更换文字
text.value = newVal
// 然后出现
fadeIn();
}
});
}
// 出现动画
function fadeIn() {
gsap.to(textBox.value,.5, {
opacity: 1,
duration: 1,
onComplete:()=>{
setTl1();
}
});
}
const setTl1 = ()=>{
nextTick(()=>{
let el = dotBox.value
let width = el.offsetWidth + el.parentElement.offsetWidth
let time = el.parentElement.offsetWidth / el.offsetWidth / 2
console.log(time,width)
tl1 = gsap.timeline();
tl1.to(el,time,
{
ease: "power1.in",
left:width,
onComplete:()=>{
setTimeout(() => {
tl1.restart()
}, 1000)
}
},
)
tl1.progress(0);
})
}
function destroyAnimation() {
if (tl1) {
tl1.progress(0);
tl1.kill();
tl1 = null;
}
}
onMounted(() => {
text.value = props.title
setTl1()
})
</script> </script>
@@ -36,7 +113,7 @@ const props = defineProps({
.loading-image { .loading-image {
width: 36.4rem; width: 36.4rem;
height: 36.4rem; height: 36.4rem;
animation: rotate 1s linear infinite; animation: rotate 1.5s ease-in-out infinite;
} }
.loading-shadow { .loading-shadow {
@@ -54,14 +131,29 @@ const props = defineProps({
font-size: 4.8rem; font-size: 4.8rem;
letter-spacing: 0.02em; letter-spacing: 0.02em;
line-height: 124%; line-height: 124%;
position: relative;
}
.loading-dot{
height: 100%;
aspect-ratio: 1.2/1;
background: radial-gradient(ellipse 150% 150% at center, #ffffff,rgba(255,255,255,.4), transparent);
border-radius: 50%;
position: absolute;
top: 50%;
left: 0;
transform: translate(-100%, -50%);
} }
@keyframes rotate { @keyframes rotate {
from { 0% {
transform: rotate(0deg); transform: translateY(0px);
} }
to { 50% {
transform: rotate(360deg); transform: translateY(-100px);
}
100% {
transform: translateY(0px);
} }
} }
</style> </style>

View File

@@ -197,7 +197,9 @@ const startRecording = () => {
// 检查浏览器支持 // 检查浏览器支持
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) { if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
// alert('您的浏览器不支持语音识别功能') // alert('您的浏览器不支持语音识别功能')
showToast('Your browser does not support speech recognition, please try again with another browser') showToast(
'Your browser does not support speech recognition, please try again with another browser'
)
isRecording.value = false isRecording.value = false
return return
} }
@@ -296,7 +298,7 @@ const stopRecording = () => {
.shortcut-item { .shortcut-item {
font-size: 4.2rem; font-size: 4.2rem;
width: fit-content; width: fit-content;
font-family: 'robotoBold'; font-family: 'satoshiMedium';
white-space: nowrap; white-space: nowrap;
height: 8.1rem; height: 8.1rem;
line-height: 8.1rem; line-height: 8.1rem;
@@ -351,8 +353,8 @@ const stopRecording = () => {
outline: none; outline: none;
background: transparent; background: transparent;
font-size: 4rem; font-size: 4rem;
font-family: 'robotoBold'; font-family: 'satoshiMedium';
font-weight: 400; font-weight: 500;
line-height: 4.8rem; /* 设置行高等于实际渲染高度,实现垂直居中 */ line-height: 4.8rem; /* 设置行高等于实际渲染高度,实现垂直居中 */
padding: 0; padding: 0;
color: #000; color: #000;
@@ -361,8 +363,8 @@ const stopRecording = () => {
&::placeholder { &::placeholder {
color: #888; color: #888;
letter-spacing: -0.01em; letter-spacing: -0.01em;
font-weight: 400; font-weight: 500;
font-family: 'robotoBold'; font-family: 'satoshiMedium';
word-spacing: -5px; word-spacing: -5px;
line-height: 4.8rem; line-height: 4.8rem;
} }
@@ -405,7 +407,7 @@ const stopRecording = () => {
} }
.recording-text { .recording-text {
font-family: 'robotoBold'; font-family: 'robotoRegular';
font-size: 2.8rem; font-size: 2.8rem;
color: #6d6868; color: #6d6868;
letter-spacing: 0.02em; letter-spacing: 0.02em;

View File

@@ -2,14 +2,15 @@
<div class="chat-message" :class="{ 'user-message': isMyself, 'ai-message': !isMyself }"> <div class="chat-message" :class="{ 'user-message': isMyself, 'ai-message': !isMyself }">
<!-- AI消息显示头像 --> <!-- AI消息显示头像 -->
<div v-if="!isMyself" class="chat-avatar"> <div v-if="!isMyself" class="chat-avatar">
<img src="@/assets/images/asistant.png" alt="AI Avatar" /> <img class="avatar" :src="thumb" alt="AI Avatar" />
</div> </div>
<!-- 消息内容 --> <!-- 消息内容 -->
<div class="message-content"> <div class="message-content">
<div class="message-text" :class="{ streaming: isStreaming }"> <div class="message-text" :class="{ streaming: isStreaming }">
<div v-html="content"></div> <div v-html="content"></div>
<span v-if="isStreaming" class="streaming-cursor">|</span> <!-- <span v-if="isStreaming" class="streaming-cursor">|</span> -->
<WaveLoading v-if="isStreaming" />
</div> </div>
<!-- <div v-if="!isMyself" class="message-actions flex"> <!-- <div v-if="!isMyself" class="message-actions flex">
<SvgIcon <SvgIcon
@@ -27,9 +28,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue' import { computed } from 'vue'
import MarkdownIt from 'markdown-it' import MarkdownIt from 'markdown-it'
import WaveLoading from '@/components/WaveLoading.vue'
import { useUserInfoStore } from '@/stores'
const md = new MarkdownIt() const md = new MarkdownIt()
const userInfoStore = useUserInfoStore()
const thumb = computed(() => {
return userInfoStore.state.generateParams.stylistImage
})
// 定义消息类型 // 定义消息类型
interface ChatMessage { interface ChatMessage {
sessionId: string | number sessionId: string | number
@@ -95,13 +103,15 @@ const actionList: ActionItem[] = [
display: flex; display: flex;
margin-bottom: 16px; margin-bottom: 16px;
align-items: flex-start; align-items: flex-start;
color: #000;
.message-text { .message-text {
font-size: 4.2rem; font-size: 4rem;
font-family: 'robotoBold'; font-family: 'satoshiMedium';
line-height: 121%; line-height: 121%;
font-weight: 400; font-weight: 500;
background-color: #efefef; background-color: #efefef;
padding: 4.95rem 3.95rem; padding: 3.43rem 4.35rem;
letter-spacing: -0.01em;
} }
&.user-message { &.user-message {
@@ -126,6 +136,10 @@ const actionList: ActionItem[] = [
color: #000; color: #000;
border-radius: 0 2rem 2rem 2rem; border-radius: 0 2rem 2rem 2rem;
word-break: break-word; word-break: break-word;
:deep(strong){
font-family: 'satoshiBold';
font-size: 4.5rem;
}
} }
} }
} }
@@ -133,16 +147,17 @@ const actionList: ActionItem[] = [
.chat-avatar { .chat-avatar {
width: 7.8rem; width: 7.8rem;
height: 7.4rem; height: 7.8rem;
border-radius: 50%; border-radius: 50%;
margin-right: 1.88rem; margin-right: 1.88rem;
border: .2rem solid #000; overflow: hidden;
padding: 1.4rem; // border: 0.2rem solid #000;
// padding: 1.4rem;
img { img {
width: 4.9rem; width: 100%;
height: 4.9rem; // height: 100%;
border-radius: 50%; // border-radius: 50%;
// object-fit: cover; // object-fit: cover;
} }
} }

View File

@@ -1,13 +1,9 @@
<template> <template>
<div class="asistant-container flex flex-column"> <div class="asistant-container flex flex-column">
<div class="header"> <div class="header">
<HeaderTitle hasSetting styleType="3" /> <HeaderTitle hasSetting styleType="3" @clickProfile="handleClickProfile" />
</div> </div>
<div class="loading-container" v-if="isLoading"> <div class="content flex-1">
<GenerateLoading />
</div>
<template v-else>
<div class="content flex-1" v-if="!isLoading">
<NoticeList <NoticeList
ref="noticeListRef" ref="noticeListRef"
:list="messageList" :list="messageList"
@@ -15,20 +11,20 @@
:streaming-message="currentStreamingMessage" :streaming-message="currentStreamingMessage"
/> />
</div> </div>
<div class="footer" v-if="!isLoading"> <div class="footer">
<InputArea @send-message="handleSendMessage" /> <InputArea @send-message="handleSendMessage" />
<div class="continue"> <div class="continue flex">
<button class="btn" @click="handleContinue">Continue</button> <div class="btn flex flex-center" @click="handleContinue">Generate</div>
</div> </div>
</div> </div>
</template> <Profile ref="profileRef" />
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue' import HeaderTitle from '@/components/HeaderTitle.vue'
import NoticeList from './components/NoticeList.vue' import NoticeList from './components/NoticeList.vue'
import InputArea from './components/InputArea.vue' import InputArea from './components/InputArea.vue'
import GenerateLoading from './components/GenerateLoading.vue' import Profile from '../Workshop/profile.vue'
import { ref, onMounted, onUnmounted, onActivated } from 'vue' import { ref, onMounted, onUnmounted, onActivated } from 'vue'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
import { useUserInfoStore, useGenerateStore } from '@/stores' import { useUserInfoStore, useGenerateStore } from '@/stores'
@@ -60,7 +56,11 @@ interface ChatMessage {
self?: boolean self?: boolean
} }
const isLoading = ref<boolean>(false) const profileRef = ref<InstanceType<typeof Profile> | null>(null)
const handleClickProfile = () => {
profileRef.value.open()
}
const noticeListRef = ref<NoticeListRef | null>(null) const noticeListRef = ref<NoticeListRef | null>(null)
const messageList = ref<ChatMessage[]>([]) const messageList = ref<ChatMessage[]>([])
@@ -81,6 +81,8 @@ const sendPrefilledMessage = () => {
} }
onMounted(() => { onMounted(() => {
console.log('1111111111111');
sessionId.value = Math.floor(Date.now() / 1000).toString() sessionId.value = Math.floor(Date.now() / 1000).toString()
generateStore.setSessionId(sessionId.value) generateStore.setSessionId(sessionId.value)
}) })
@@ -117,7 +119,7 @@ const handleFetchMessage = (message: string) => {
const params = { const params = {
message: message, message: message,
sessionId: sessionId.value, sessionId: sessionId.value,
gender: 'male' gender: userInfoStore.state.generateParams.sex
} }
// 创建AI消息对象 // 创建AI消息对象
@@ -273,17 +275,13 @@ const handleFetchMessage = (message: string) => {
const handleContinue = () => { const handleContinue = () => {
// router.push('/workshop/selectStyle') // router.push('/workshop/selectStyle')
// 模拟接口之后再跳转 // 模拟接口之后再跳转
isLoading.value = true
generateStore.clearProductData() generateStore.clearProductData()
setTimeout(() => {
router.push('/workshop/selectStyle') router.push('/workshop/selectStyle')
isLoading.value = false
}, 1000)
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.asistant-container { .asistant-container {
height: 100vh; height: 100%;
overflow: hidden; overflow: hidden;
} }
@@ -306,20 +304,14 @@ const handleContinue = () => {
color: #fff; color: #fff;
text-align: right; text-align: right;
padding: 2.6rem 4.5rem; padding: 2.6rem 4.5rem;
flex-direction: row-reverse;
.btn { .btn {
border-radius: 7px; border-radius: 0.7rem;
background-color: #000; background-color: #000;
width: 24.6rem; width: 24.6rem;
height: 5.9rem; height: 6.7rem;
box-sizing: border-box;
} }
} }
} }
.loading-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background-color: #fff;
}
</style> </style>

View File

@@ -2,13 +2,16 @@
<div class="login-page"> <div class="login-page">
<div class="content"> <div class="content">
<div class="back-button" @click="goBack"> <div class="back-button" @click="goBack">
<img src="@/assets/images/arrow_left.png" class="back-icon" /> <SvgIcon name="left" size="50" color="#fff" />
</div> </div>
<div class="header"> <div class="header">
<div class="title">Log in.</div> <div class="title">Staff Login.</div>
<p class="subtitle">Redefine the styling experience with AI.</p> <p class="subtitle">
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p> <span>Experience our personalised styling journey with</span>
<br />
<span>Lane Crawford.</span>
</p>
</div> </div>
<div class="login-container"> <div class="login-container">
@@ -32,7 +35,11 @@
<img :src="google" class="google-icon" /> <img :src="google" class="google-icon" />
Sign in with Google Sign in with Google
</div> --> </div> -->
<GoogleLogin @googelLogin="handleGoogleLogin" ref="googleLoginRef" @click="clickGooleLogin"></GoogleLogin> <GoogleLogin
@googelLogin="handleGoogleLogin"
ref="googleLoginRef"
@click="clickGooleLogin"
></GoogleLogin>
<div class="sign-up-button" @click="handleSignup">Dont have an account? Sign Up</div> <div class="sign-up-button" @click="handleSignup">Dont have an account? Sign Up</div>
</div> </div>
</div> </div>
@@ -118,7 +125,7 @@ const handleLogin = async () => {
userInfoStore.setToken(response.token) userInfoStore.setToken(response.token)
userInfoStore.setUserInfo(response.user) userInfoStore.setUserInfo(response.user)
showToast('login success') showToast('login success')
router.replace('/stylist/customer') router.replace('/customer')
} }
) )
} }
@@ -141,7 +148,7 @@ const handleGoogleLogin = async (accessToken: string) => {
userInfoStore.setToken(result.token) userInfoStore.setToken(result.token)
userInfoStore.setUserInfo(result.user) userInfoStore.setUserInfo(result.user)
showToast('Google login successful') showToast('Google login successful')
router.replace('/stylist/customer') router.replace('/customer')
} catch (error) { } catch (error) {
console.error('Google登录失败:', error) console.error('Google登录失败:', error)
showToast('Google login failed, please try again') showToast('Google login failed, please try again')
@@ -176,33 +183,29 @@ const handleSignup = () => {
} }
.back-button { .back-button {
// position: absolute;
// top: 2rem;
// left: 2rem;
margin-top: 2.4rem; margin-top: 2.4rem;
margin-left: 6.1rem; margin-left: 6.1rem;
width: 2rem;
height: 3.4rem;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
z-index: 3; z-index: 3;
font-size: 3.4rem; font-size: 3.4rem;
.back-icon { .c-svg {
width: 2.83rem; width: initial;
height: 3.47rem; height: initial;
} }
} }
.header { .header {
margin-top: 1.42rem; margin-top: 1.42rem;
padding-left: 15.5rem; padding-left: 14.5rem;
padding-right: 14.3rem;
color: white; color: white;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
.title { .title {
font-size: 11rem; font-size: 11rem;
font-weight: bold; font-weight: bold;
font-weight: 700;
margin-bottom: 0.8rem; margin-bottom: 0.8rem;
color: white; color: white;
font-family: 'satoshiBold'; font-family: 'satoshiBold';
@@ -232,11 +235,7 @@ const handleSignup = () => {
position: relative; position: relative;
width: calc(100% - 28.4rem); width: calc(100% - 28.4rem);
height: 107.8rem; height: 107.8rem;
background: radial-gradient( background: radial-gradient(100% 100% at 0% 0%, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.2) 100%);
100% 100% at 0% 0%,
rgba(115, 115, 115, 0.4) 0%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(35px); backdrop-filter: blur(35px);
-webkit-backdrop-filter: blur(35px); -webkit-backdrop-filter: blur(35px);
-moz-backdrop-filter: blur(35px); -moz-backdrop-filter: blur(35px);

View File

@@ -4,14 +4,17 @@
<div class="content"> <div class="content">
<!-- 返回按钮 --> <!-- 返回按钮 -->
<div class="back-button" @click="goBack"> <div class="back-button" @click="goBack">
<img src="@/assets/images/arrow_left.png" class="back-icon" /> <SvgIcon name="left" size="50" color="#fff" />
</div> </div>
<!-- 标题区域 --> <!-- 标题区域 -->
<div class="header"> <div class="header">
<div class="title">Log in.</div> <div class="title">Staff Login.</div>
<p class="subtitle">Redefine the styling experience with AI.</p> <p class="subtitle">
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p> <span>Experience our personalised styling journey with</span>
<br />
<span>Lane Crawford.</span>
</p>
</div> </div>
<div class="login-container"> <div class="login-container">
@@ -130,9 +133,9 @@ const handleSuccess = (data: any) => {
cursor: pointer; cursor: pointer;
z-index: 3; z-index: 3;
font-size: 3.4rem; font-size: 3.4rem;
.back-icon { .c-svg {
width: 2.83rem; width: initial;
height: 3.47rem; height: initial;
} }
} }

View File

@@ -2,13 +2,12 @@
<div class="login-page"> <div class="login-page">
<div class="content"> <div class="content">
<div class="back-button" @click="goBack"> <div class="back-button" @click="goBack">
<img src="@/assets/images/arrow_left.png" class="back-icon" /> <SvgIcon name="left" size="50" color="#fff" />
</div> </div>
<div class="header"> <div class="header">
<div class="title">Sign up.</div> <div class="title">Staff Sign up.</div>
<p class="subtitle">Redefine the styling experience with AI.</p> <p class="subtitle">Start my personalised styling journey with Lane Crawford.</p>
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p>
</div> </div>
<div class="login-container"> <div class="login-container">
@@ -30,7 +29,12 @@
<button type="button" class="login-button" @click="handleConfirm">Sign Up</button> <button type="button" class="login-button" @click="handleConfirm">Sign Up</button>
<GoogleLogin text="Sign up with Google" @googelLogin="handleGoogleSignup" ref="googleSignupRef" @click="clickGooleLogin" /> <GoogleLogin
text="Sign up with Google"
@googelLogin="handleGoogleSignup"
ref="googleSignupRef"
@click="clickGooleLogin"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -142,7 +146,7 @@ const handleGoogleSignup = async (accessToken: string) => {
userInfoStore.setToken(result.token) userInfoStore.setToken(result.token)
userInfoStore.setUserInfo(result.user) userInfoStore.setUserInfo(result.user)
showToast('Google sign up successful') showToast('Google sign up successful')
router.replace('/stylist/customer') router.replace('/customer')
} catch (error) { } catch (error) {
console.error('Google注册失败:', error) console.error('Google注册失败:', error)
showToast(error?.message || 'Google sign up failed, please try again') showToast(error?.message || 'Google sign up failed, please try again')
@@ -183,15 +187,16 @@ const handleGoogleSignup = async (accessToken: string) => {
cursor: pointer; cursor: pointer;
z-index: 3; z-index: 3;
font-size: 3.4rem; font-size: 3.4rem;
.back-icon { .c-svg {
width: 2.83rem; width: initial;
height: 3.47rem; height: initial;
} }
} }
.header { .header {
margin-top: 1.42rem; margin-top: 1.42rem;
padding-left: 15.5rem; padding-left: 14.5rem;
padding-right: 14.3rem;
color: white; color: white;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
.title { .title {
@@ -281,7 +286,6 @@ const handleGoogleSignup = async (accessToken: string) => {
} }
} }
.footer { .footer {
position: relative; position: relative;
text-align: center; text-align: center;

View File

@@ -2,10 +2,14 @@
<div class="welcome-page safe-area-top safe-area-bottom"> <div class="welcome-page safe-area-top safe-area-bottom">
<div class="content"> <div class="content">
<div class="title"> <div class="title">
AI STYLING <br /> Styling <br />
ASSISTANT Assistant
</div> </div>
<p class="subtitle">Redefine the styling experience with AI.</p> <p class="subtitle">
<span>Experience our personalised styling journey with</span>
<br>
<span> Lane Crawford.</span>
</p>
<div class="btn-container flex"> <div class="btn-container flex">
<div class="log btn" @click="goToLogin">Log in</div> <div class="log btn" @click="goToLogin">Log in</div>
<div class="sign btn" @click="goToSignup">Sign up</div> <div class="sign btn" @click="goToSignup">Sign up</div>
@@ -49,15 +53,17 @@ const goToLogin = () => {
font-weight: 400; font-weight: 400;
margin-bottom: 31.5rem; margin-bottom: 31.5rem;
.title { .title {
font-family: 'boskaRegular'; font-family: 'satoshiMedium';
line-height: 93%; line-height: 120%;
letter-spacing: -0.02em; font-size: 11rem;
// letter-spacing: -0.02em;
} }
.subtitle { .subtitle {
font-size: 3.2rem; font-size: 3.2rem;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
margin: 3.2rem 0 6rem; margin: 3.2rem 0 6rem;
list-style: 124%; font-weight: 400;
line-height: 124%;
letter-spacing: 0.08em; letter-spacing: 0.08em;
} }
.btn-container { .btn-container {
@@ -72,8 +78,14 @@ const goToLogin = () => {
border: 1px solid #fff; border: 1px solid #fff;
text-align: center; text-align: center;
// padding: 2rem 1.3rem; // padding: 2rem 1.3rem;
box-sizing: content-box; box-sizing: border-box;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
backdrop-filter: blur(5.27rem);
-webkit-backdrop-filter: blur(5.27rem);
-moz-backdrop-filter: blur(5.27rem);
-ms-backdrop-filter: blur(5.27rem);
-o-backdrop-filter: blur(5.27rem);
box-shadow: 1.9rem 2.3rem 1.66rem 0.23rem -0.3rem 0.23rem #36180c40;
} }
} }
} }

View File

@@ -0,0 +1,343 @@
<template>
<div
class="customer-container safe-area-top flex flex-column"
:class="{ 'form-mode': pageMode !== 'entry' }"
>
<div class="setting flex flex-between">
<SvgIcon name="left" size="70" @click.stop="handleBack" />
<SvgIcon
:name="profileVisible ? 'profileFilledWhite' : 'profile_white'"
size="55"
@click="handleOpenProfile"
/>
</div>
<template v-if="pageMode === 'entry'">
<div class="content flex-1 flex flex-center flex-column">
<div class="text">Who is Your Customer?</div>
<div class="btn-list flex flex-center">
<button class="sandblasted-blurred btn" @click="handleChangeMode('create')">
<span>Create</span>
</button>
<button class="sandblasted-blurred btn" @click="handleChangeMode('form')">
<span>Entry</span>
</button>
</div>
</div>
</template>
<template v-else>
<div class="form-container flex-1 flex flex-column flex-center">
<div class="text">
<div class="form-title">{{ formTitle }}</div>
<div class="description">
<p>Unlock personalized styling insights.</p>
<p>Enter a client profile to begin curating their next look with Styling Angel.</p>
</div>
</div>
<div class="glass-form" :class="{ create: pageMode === 'create' }">
<div class="form-field" v-if="pageMode === 'create'">
<label class="field-label">VIP ID</label>
<input
v-model="customerData.vipId"
type="text"
placeholder="Enter your ID"
class="form-input"
/>
</div>
<div class="form-field">
<label class="field-label">Nickname</label>
<input
v-model="customerData.nickname"
type="text"
placeholder="Enter name"
class="form-input"
/>
</div>
<button class="confirm-btn" @click="handleConfirm">Confirm</button>
</div>
<div v-if="pageMode === 'form'" class="show-all" @click="handleShowPopup(true)">
Show All
</div>
<div class="copyright">Powered by AiDLab for Lane Crawford</div>
</div>
</template>
<Profile
ref="profileRef"
@change-visible="handleChangeVisible"
@selected-customer="handleSelectCustomer"
is-customer
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useGenerateStore, useUserInfoStore } from '@/stores'
import { showToast, closeToast } from 'vant'
import { customerCheckin, createCustomer, type CreateCustomerParams } from '@/api/workshop'
import Profile from '../Workshop/profile.vue'
import MyEvent from '@/utils/myEvent'
const profileRef = ref<typeof Profile>(null)
const handleOpenProfile = () => {
profileRef.value.open()
}
const router = useRouter()
const generateStore = useGenerateStore()
const loading = ref(false)
type PageMode = 'entry' | 'form' | 'create'
const pageMode = ref<PageMode>('entry')
const formTitle = computed(() => {
return pageMode.value === 'entry' || pageMode.value === 'form' ? 'Customer ID' : 'Create Profile'
})
const handleChangeMode = (mode: PageMode) => {
pageMode.value = mode
}
const customerData = ref({
vipId: '',
nickname: ''
// email: ''
})
const handleConfirm = async () => {
if (loading.value) return
const nickname = (customerData.value.nickname || '').trim()
const vipId = (customerData.value.vipId || '').trim()
if (!nickname) {
showToast({ message: 'please input the nickname' })
return
}
if (pageMode.value === 'create' && !vipId) {
showToast({ message: 'please input the VIP ID' })
return
}
loading.value = true
showToast({ message: 'Processing...', duration: 0, type: 'loading' })
try {
if (pageMode.value === 'create') {
await createCustomer({ nickname, vipId } as CreateCustomerParams)
showToast({ message: 'Customer created successfully' })
MyEvent.emit('update-customer-list')
}
const res = await customerCheckin({ nickname })
useUserInfoStore().resetGenerateParams()
generateStore.setCustomerInfo(res)
MyEvent.emit('clear-generate-state')
router.push('/workshop/home')
} catch (err: any) {
showToast({ message: err?.message || 'Operation failed' })
} finally {
loading.value = false
closeToast()
}
}
const handleShowPopup = (flag: Boolean) => {
// showPopup.value = flag
profileRef.value.handleShowPopup(flag,true)
}
const handleSelectCustomer = (value) => {
if (value && pageMode.value === 'form') {
customerData.value.nickname = value.name
}
}
const profileVisible = ref(false)
const handleChangeVisible = (visible: boolean) => {
profileVisible.value = visible
}
const handleBack = (e?: Event) => {
if (e) {
e.stopPropagation()
e.preventDefault()
}
if (pageMode.value !== 'entry') {
pageMode.value = 'entry'
customerData.value = {
vipId: '',
nickname: ''
}
return
}
router.go(-1)
}
</script>
<style lang="less" scoped>
.customer-container {
height: 100vh;
overflow: hidden;
color: #fff;
position: relative;
background: url('@/assets/images/no_shouder_bg.png') no-repeat center center;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
&.form-mode {
background: url('@/assets/images/has_shouder_bg.png') no-repeat center center/cover;
}
.setting {
z-index: 1;
padding: 16.4rem 4.9rem 0 8.4rem;
font-size: 7rem;
.c-svg {
width: initial;
}
}
.content {
// margin-top: 55.3rem;
row-gap: 12.7rem;
.text {
font-family: 'satoshiBold';
font-size: 13rem;
line-height: 112.99%;
text-align: center;
letter-spacing: 2;
}
.start-btn {
font-size: 5.6rem;
width: 32.5rem;
height: 8.1rem;
border: 0.2rem solid #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4rem;
}
.btn-list {
column-gap: 17rem;
.btn {
height: 8.3rem;
width: 29.7rem;
line-height: 8.3rem;
font-size: 4.8rem;
border: 0.2rem solid #fff;
box-sizing: border-box;
}
}
}
.form-container {
width: 78.8rem;
margin: 0 auto;
.text {
letter-spacing: 0.02rem;
}
.form-title {
font-family: 'satoshiBold';
font-size: 11rem;
line-height: 1.24em;
}
.description {
font-size: 3.6rem;
line-height: 141%;
letter-spacing: 0.08rem;
margin: 3.1rem 0 11rem 0;
font-family: 'satoshiRegular';
}
.glass-form {
height: 68.7rem;
width: 78.8rem;
border: 0.2rem solid #ffffff;
border-radius: 4.7rem;
padding: 11rem 7.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
&.create {
height: 78.8rem;
}
background: radial-gradient(
100% 100% at 0% 0%,
rgba(115, 115, 115, 0.4) 0%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(35px);
justify-content: flex-start;
.form-field {
margin-bottom: 6.8rem;
&.email {
margin-bottom: 6.8rem;
}
.field-label {
display: block;
color: #fff;
font-size: 3.6rem;
font-family: 'satoshiRegular';
margin-bottom: 3rem;
}
.form-input {
width: 100%;
height: 10rem;
line-height: 10rem;
border: 0.2rem solid #fff;
border-radius: 7rem;
padding: 0 5.5rem;
color: #fff;
font-size: 3.8rem;
font-family: 'satoshiRegular';
background: transparent;
&::placeholder {
color: rgba(255, 255, 255, 0.6);
}
}
}
.confirm-btn {
width: 100%;
height: 10rem;
line-height: 10rem;
background: #000;
border: none;
border-radius: 7rem;
color: #fff;
font-size: 4rem;
font-family: 'satoshiRegular';
cursor: pointer;
box-shadow: 0 0.2rem 8px rgba(0, 0, 0, 0.2);
}
}
.show-all {
margin-top: 4rem;
width: 19rem;
height: 7.6rem;
border-radius: 54px;
color: #000;
text-align: center;
line-height: 7.6rem;
background-color: #fff;
font-size: 3.1rem;
font-family: 'satoshiRegular';
}
.copyright {
font-family: 'satoshiRegular';
font-size: 3rem;
line-height: 124%;
letter-spacing: 0.08rem;
margin-top: 2.31rem;
text-align: center;
font-weight: 400;
margin-top: 11.5rem;
}
}
}
.c-svg {
width: initial;
}
</style>

View File

@@ -1,266 +0,0 @@
<template>
<div class="customer-container safe-area-top" :class="{ 'form-mode': pageMode === 'form' }">
<template v-if="pageMode === 'entry'">
<div class="setting flex flex-between">
<van-icon name="arrow-left" color="#fff" @click="handleBack" />
<SvgIcon name="setting" size="70" />
</div>
<div class="content flex flex-center flex-column">
<div class="text">Who is Your Customer?</div>
</div>
<div class="entry-btn flex flex-center" @click="handleChangeMode('form')">Entry</div>
</template>
<template v-else>
<div class="form-container">
<div class="back-container flex flex-center" @click="handleChangeMode('entry')">
<van-icon name="arrow-left" color="#fff" />
</div>
<div class="text">
<div class="form-title">Entry ID</div>
<div class="description">
Redefine the styling experience with AI.<br />
Use Styling Angel to speed up your fashion journey.
</div>
</div>
<div class="glass-form">
<div class="form-field">
<label class="field-label">VIP ID</label>
<input
v-model="customerData.vipId"
type="text"
placeholder="Enter your ID"
class="form-input"
/>
</div>
<!-- <div class="form-field email">
<label class="field-label">Email Address</label>
<input
v-model="customerData.email"
type="email"
placeholder="Enter your email"
class="form-input"
/>
</div> -->
<button class="confirm-btn" @click="handleConfirm">Confirm</button>
</div>
<div class="copyright">Powered by AiDLab for Lane Crawford</div>
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useGenerateStore, useUserInfoStore } from '@/stores'
import { showToast } from 'vant'
import { customerCheckin } from '@/api/workshop'
const router = useRouter()
const generateStore = useGenerateStore()
type PageMode = 'form' | 'entry'
const pageMode = ref<PageMode>('entry')
const handleBack = () => {
router.go(-1)
}
const handleChangeMode = (mode: PageMode) => {
pageMode.value = mode
}
const customerData = ref({
vipId: ''
// email: ''
})
const handleConfirm = async () => {
if (customerData.value.vipId === '') {
showToast({
message: 'please input name and email',
position: 'top'
})
return
}
customerCheckin(customerData.value).then((res) => {
useUserInfoStore().resetGenerateParams()
// console.log('res', res)
generateStore.setCustomerInfo(res)
// router.push('/stylist/index')
router.push('/homeNav')
})
}
</script>
<style lang="less" scoped>
.customer-container {
height: 100vh;
overflow: hidden;
color: #fff;
position: relative;
background: url('@/assets/images/no_shouder_bg.png') no-repeat center center;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
&.form-mode {
background: url('@/assets/images/has_shouder_bg.png') no-repeat center center;
padding-top: 21.47rem;
}
.setting {
z-index: 1;
padding: 3.17rem 4.9rem 0 8.4rem;
font-size: 7rem;
.c-svg {
width: initial;
}
}
.content {
margin-top: 55.3rem;
.text {
font-family: 'satoshiBold';
font-size: 13rem;
line-height: 112.99%;
text-align: center;
letter-spacing: 2;
}
.start-btn {
font-size: 5.6rem;
width: 32.5rem;
height: 8.1rem;
border: 0.2rem solid #fff;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4rem;
}
}
.entry-btn {
position: absolute;
border: 0.2rem solid #fff;
bottom: 10.3rem;
right: 5.5rem;
height: 9rem;
width: 27.5rem;
line-height: 9rem;
font-size: 5.6rem;
}
.form-container {
.back-container {
width: 7.3rem;
height: 7.3rem;
border: 0.2rem solid #fff;
border-radius: 1.8rem;
font-size: 4.3rem;
background: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
-moz-backdrop-filter: blur(20px);
-ms-backdrop-filter: blur(20px);
-o-backdrop-filter: blur(20px);
margin-left: 7rem;
position: relative;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1), inset 0 1px 0 #000000;
.back-icon {
width: 2.08rem;
height: 3.47rem;
}
}
.text {
padding-left: 15.2rem;
margin-top: 7.9rem;
letter-spacing: 0.02rem;
}
.form-title {
font-family: 'satoshiBold';
font-size: 11rem;
line-height: 1.24em;
}
.description {
font-size: 3rem;
line-height: 141%;
letter-spacing: 0.08rem;
margin-top: 2.7rem;
font-family: 'satoshiRegular';
}
.glass-form {
height: 84.8rem;
border: 0.2rem solid #ffffff;
border-radius: 4.7rem;
margin: 0 14.2rem;
padding: 8.2rem 7.9rem;
margin-top: 7.5rem;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1);
display: flex;
flex-direction: column;
justify-content: space-evenly;
background: radial-gradient(
100% 100% at 0% 0%,
rgba(115, 115, 115, 0.4) 0%,
rgba(0, 0, 0, 0) 100%
);
backdrop-filter: blur(35px);
.form-field {
margin-bottom: 3.6rem;
&.email {
margin-bottom: 6.8rem;
}
.field-label {
display: block;
color: #fff;
font-size: 3.6rem;
font-family: 'satoshiRegular';
margin-bottom: 3rem;
}
.form-input {
width: 100%;
height: 10rem;
line-height: 10rem;
border: 0.2rem solid #fff;
border-radius: 7rem;
padding: 0 5.5rem;
color: #fff;
font-size: 3.8rem;
font-family: 'satoshiRegular';
background: transparent;
&::placeholder {
color: rgba(255, 255, 255, 0.6);
}
}
}
.confirm-btn {
width: 100%;
height: 10rem;
line-height: 10rem;
background: #000;
border: none;
border-radius: 7rem;
color: #fff;
font-size: 4rem;
font-family: 'satoshiRegular';
cursor: pointer;
box-shadow: 0 0.2rem 8px rgba(0, 0, 0, 0.2);
}
}
.copyright {
font-family: 'satoshiRegular';
font-size: 3rem;
line-height: 124%;
letter-spacing: 0.08rem;
margin-top: 2.7rem;
text-align: center;
font-weight: 400;
margin-top: 18.7rem;
}
}
}
</style>

View File

@@ -1,14 +1,19 @@
<template> <template>
<header-title style-type="3" />
<div class="dressfor-container flex"> <div class="dressfor-container flex">
<div class="content flex-1 flex flex-column"> <div class="content flex-1 flex flex-column">
<!-- <div class="setting flex flex-between"> <!-- 移除始终显示的 loading改为按需显示 -->
<van-icon name="arrow-left" color="#fff" @click="handleBack" /> <!-- <div class="loading-container flex flex-center">
<SvgIcon name="setting" size="70" /> <Icon class="icon-element" title="" />
</div> --> </div> -->
<div class="text">What are you dressing for?</div> <!-- <div class="text">
What are you <br />
dressing for?
</div> -->
<div class="slogan flex flex-center">
<img class="text" src="@/assets/images/dressfor.png" alt="" />
</div>
<!-- <div class="start-btn" @click="handleStart">Start</div> --> <!-- <div class="start-btn" @click="handleStart">Start</div> -->
<div class="chatbox flex flex-center"> <!-- <div class="chatbox flex flex-center">
<div class="input-box flex"> <div class="input-box flex">
<div class="input-wrapper flex-1 flex"> <div class="input-wrapper flex-1 flex">
<input <input
@@ -25,27 +30,74 @@
<SvgIcon <SvgIcon
class="audio-icon" class="audio-icon"
:name="isRecording ? 'pause' : 'audio'" :name="isRecording ? 'pause' : 'audio'"
size="52" size="35"
color="#6D6868"
@click="handleClickAudio" @click="handleClickAudio"
/> />
</div> </div>
<div class="send flex flex-center" @click="handleSendMessage"> <div class="send flex flex-center" @click="handleSendMessage">
<SvgIcon class="send-icon" name="send" size="26" color="#000000" /> <SvgIcon class="send-icon" name="send_bold" size="26" color="#6d6868" />
</div>
</div> -->
<div class="tag-container flex flex-column flex-center">
<div class="tag-list short flex">
<div
class="tag-item"
:class="{ active: item === inputValue }"
v-for="item in tagListShort"
:key="item"
@click="handleClickTag(item)"
>
{{ item }}
</div>
</div>
<!-- <div class="tag-list long flex flex-justify-center">
<div
class="tag-item"
v-for="item in tagListLong"
:class="{ active: item === inputValue }"
:key="item"
@click="handleClickTag(item)"
>
{{ item }}
</div>
</div> -->
</div> </div>
</div> </div>
</div> </div>
</div>
<footer-navigation />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onUnmounted, nextTick, watch } from 'vue' import { ref, onUnmounted, nextTick, watch } from 'vue'
import { showToast } from 'vant' import { useUserInfoStore, useGenerateStore } from '@/stores'
import { showToast, closeToast } from 'vant'
import { useRouter } from 'vue-router'
import HeaderTitle from '@/components/HeaderTitle.vue' import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue' import FooterNavigation from '@/components/FooterNavigation.vue'
import { useRouter } from 'vue-router'
import AudioVisualizer from '@/views/asistant/components/AudioVisualizer.vue' import AudioVisualizer from '@/views/asistant/components/AudioVisualizer.vue'
import Icon from '../asistant/components/GenerateLoading.vue'
const router = useRouter() const router = useRouter()
const userInfoStore = useUserInfoStore()
const generateStore = useGenerateStore()
const tagListShort = [
'Casual',
'Formal',
'Activewear',
'Resort',
'Business casual',
'Evening',
'Outdoor',
'Business',
'Cocktail',
'Bridal',
'Festival',
'Travel',
'Athleisure',
'Beach',
'Ski'
]
// const tagListLong = ['Linen Suit For Summer Gaka', 'Recomment Evening Bags']
const inputValue = ref('') const inputValue = ref('')
const isRecording = ref(false) const isRecording = ref(false)
@@ -70,14 +122,10 @@ watch(isRecording, async (newVal) => {
const handleSendMessage = () => { const handleSendMessage = () => {
const message = inputValue.value.trim() const message = inputValue.value.trim()
if(!message){ if (!message) {
showToast('Please enter a message') showToast('Please enter a message')
return return
} }
router.push({
path: '/asistant',
query: message ? { message } : undefined
})
} }
const handleClickAudio = () => { const handleClickAudio = () => {
@@ -109,6 +157,10 @@ const startRecording = () => {
} }
speechRecognition.onstart = () => { speechRecognition.onstart = () => {
showToast({
message: 'Listening...',
duration: 0
})
isRecording.value = true isRecording.value = true
} }
@@ -136,6 +188,7 @@ const startRecording = () => {
} }
speechRecognition.onend = () => { speechRecognition.onend = () => {
closeToast()
isRecording.value = false isRecording.value = false
lastTranscript = '' lastTranscript = ''
isSpeechRecognitionActive = false isSpeechRecognitionActive = false
@@ -143,6 +196,7 @@ const startRecording = () => {
speechRecognition.onerror = (event: any) => { speechRecognition.onerror = (event: any) => {
console.error('Speech recognition error:', event.error) console.error('Speech recognition error:', event.error)
closeToast()
isRecording.value = false isRecording.value = false
isSpeechRecognitionActive = false isSpeechRecognitionActive = false
showToast('Speech recognition failed, please try again') showToast('Speech recognition failed, please try again')
@@ -159,6 +213,20 @@ const stopRecording = () => {
} }
} }
const handleClickTag = (tag: string) => {
inputValue.value = tag
const sessionId = Math.floor(Date.now() / 1000).toString()
generateStore.setSessionId(sessionId.value)
// 直接跳转到 selectStyle 页面,传递消息和 sessionId
router.push({
path: '/workshop/selectStyle',
query: {
message: tag,
sessionId
}
})
}
onUnmounted(() => { onUnmounted(() => {
if (speechRecognition && isRecording.value) { if (speechRecognition && isRecording.value) {
speechRecognition.stop() speechRecognition.stop()
@@ -167,77 +235,136 @@ onUnmounted(() => {
}) })
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.c-svg {
width: initial;
height: initial;
}
.dressfor-container { .dressfor-container {
height: calc(100vh - 12rem - 14.9rem); height: calc(100vh - 12rem - 14.9rem);
overflow: hidden; // overflow: hidden;
color: #fff; color: #fff;
position: relative; position: relative;
background: url('@/assets/images/dress_for_bg.png') no-repeat center center; // background: url('@/assets/images/dress_for_bg.png') no-repeat center center;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
padding: 6rem 0 0 0; padding: 15.9rem 0 0 0;
.loading-container {
width: 100%;
height: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
color: #000;
}
.content { .content {
.setting { .loading-container {
padding: 0 4.9rem 0 8.4rem; :deep(.loading-image) {
font-size: 7rem; width: 17rem;
.c-svg { height: 17rem;
width: initial; animation: none;
}
:deep(.loading-shadow) {
background-color: #000;
width: 9.2rem;
height: 2.4rem;
filter: blur(6px);
opacity: 0.2;
margin: 2.4rem 0 0;
// background-color: #d9d9d9;
} }
} }
.text { .text {
font-family: 'satoshiBold'; padding-top: 8.9rem;
font-size: 11rem; padding-bottom: 7.7rem;
line-height: 106%; width: 60rem;
text-align: center;
margin-top: 43.8rem;
margin-bottom: 14rem;
} }
.chatbox { // .chatbox {
height: 9.3rem; // height: 9.3rem;
// background-color: #fff; // // background-color: #fff;
column-gap: 2.29rem; // column-gap: 2.29rem;
.input-box { // .input-box {
width: 59.8rem; // width: 59.8rem;
height: 100%; // height: 100%;
background-color: #fff; // background-color: #efefef;
border: 2px solid #5f5f5f; // // border: 2px solid #5f5f5f;
border-radius: 1rem; // border-radius: 1rem;
color: #222222; // color: #222222;
font-size: 3.2rem; // font-size: 3.2rem;
font-family: 'satoshiRegular'; // font-family: 'satoshiRegular';
padding: 0 2.6rem; // padding: 0 2.6rem;
column-gap: 2.6rem; // column-gap: 2.6rem;
overflow: hidden; // overflow: hidden;
.input-wrapper{ // .input-wrapper {
overflow: hidden; // overflow: hidden;
} // }
.recording-visualizer { // .recording-visualizer {
display: flex; // display: flex;
align-items: center; // align-items: center;
height: 100%; // height: 100%;
:deep(.audio-visualizer) { // :deep(.audio-visualizer) {
width: 100%;
padding: 0;
}
:deep(.visualizer-container) {
height: 100%;
}
}
.input-item {
// width: 100%; // width: 100%;
height: 100%; // padding: 0;
outline: none; // }
border: none; // :deep(.visualizer-container) {
// height: 100%;
// }
// }
// .input-item {
// // width: 100%;
// height: 100%;
// outline: none;
// border: none;
// background-color: #efefef;
// }
// .audio-icon {
// width: initial;
// }
// }
// .send {
// width: 7.6rem;
// height: 7.6rem;
// background-color: #efefef;
// border-radius: 1rem;
// }
// }
.tag-container {
// padding: 5.7rem 0;
margin: 0 auto;
width: 65.8rem;
.tag-list {
color: #000;
flex-wrap: wrap;
justify-content: space-between;
align-content: flex-start;
gap: 3rem;
&::after {
content: '';
flex-grow: 1;
height: 0;
} }
.audio-icon {
width: initial; .tag-item {
height: 6.8rem;
min-width: 12rem;
line-height: 6.8rem;
box-sizing: border-box;
font-family: 'satoshiRegular';
font-size: 2.8rem;
border: 0.15rem solid #cdcdcd;
text-align: center;
border-radius: 4.6rem;
padding: 0 2.15rem;
&.active {
background-color: #f5f5f5;
} }
} }
.send {
width: 7.6rem;
height: 7.6rem;
background-color: #fff;
} }
} }
} }

View File

@@ -1,16 +1,17 @@
<template> <template>
<header-title style-type="3" />
<div class="stylist-page"> <div class="stylist-page">
<!-- 主要内容区域 --> <!-- 主要内容区域 -->
<div class="content"> <div class="content">
<!-- 标题 --> <!-- 标题 -->
<div class="header"> <div class="header">
<div class="title">CHOOSE YOUR STYLIST</div> <div class="title">Choose Stylist.</div>
<div class="sub-title">What style are you looking for?</div>
</div> </div>
<div class="carousel-container" v-show="!showVideo"> <div class="carousel-container" v-show="!showVideo">
<div class="nav-arrow left" @click="handleChangeSwiper('prev')"> <div class="nav-arrow left" @click="handleChangeSwiper('prev')">
<van-icon name="arrow-left" color="#fff" /> <!-- <van-icon name="arrow-left" color="#fff" /> -->
<SvgIcon name="left" size="70" color="#fff" />
</div> </div>
<van-swipe touchable ref="swiperRef" @change="handleChangeCurrent"> <van-swipe touchable ref="swiperRef" @change="handleChangeCurrent">
@@ -28,14 +29,20 @@
</van-swipe> </van-swipe>
<div class="nav-arrow right" @click="handleChangeSwiper('next')"> <div class="nav-arrow right" @click="handleChangeSwiper('next')">
<van-icon name="arrow" color="#fff" /> <SvgIcon class="arrow-right" name="left" size="70" color="#fff" />
</div> </div>
</div> </div>
</div> </div>
<!-- Continue按钮 --> <!-- Continue按钮 -->
<div class="continue-button" @click="handleContinue" v-if="!$route.query?.demo">Continue</div> <button
<van-dialog class="sandblasted-blurred continue-button flex flex-center"
@click="handleContinue"
v-if="!$route.query?.demo"
>
<span>Continue</span>
</button>
<!-- <van-dialog
class="video-dialog" class="video-dialog"
:show-confirm-button="false" :show-confirm-button="false"
:show-cancel-button="false" :show-cancel-button="false"
@@ -47,9 +54,8 @@
<van-icon name="cross" class="close-icon" /> <van-icon name="cross" class="close-icon" />
</div> </div>
<Video ref="videoRef" /> <Video ref="videoRef" />
</van-dialog> </van-dialog> -->
</div> </div>
<footer-navigation />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -58,9 +64,15 @@ import { useRouter } from 'vue-router'
import Video from './components/Video.vue' import Video from './components/Video.vue'
import { useUserInfoStore } from '@/stores' import { useUserInfoStore } from '@/stores'
import male from '@/assets/images/male.png' import male from '@/assets/images/male.png'
import maleThumb from '@/assets/images/male_thumb.png'
import female from '@/assets/images/female.png' import female from '@/assets/images/female.png'
import femaleThumb from '@/assets/images/female_thumb.png'
import HeaderTitle from '@/components/HeaderTitle.vue' import HeaderTitle from '@/components/HeaderTitle.vue'
import FooterNavigation from '@/components/FooterNavigation.vue' import FooterNavigation from '@/components/FooterNavigation.vue'
import mini from '@/assets/images/mini.jpg'
import miniThumb from '@/assets/images/mini_thumb.jpg'
import Crystal from '@/assets/images/Crystal.jpg'
import CrystalThumb from '@/assets/images/Crystal_thumb.jpg'
const router = useRouter() const router = useRouter()
const userInfoStore = useUserInfoStore() const userInfoStore = useUserInfoStore()
@@ -69,30 +81,34 @@ const stylists = ref<any[]>([
{ {
id: 1, id: 1,
value: 'crystal', value: 'crystal',
name: 'Vera Lo', name: 'Crystal',
description: 'Contemporary, Classic, Simple Silhouettes, Statement Pieces', description: 'Contemporary, Classic, Simple Silhouettes, Statement Pieces',
image: female image: Crystal,
thumb: CrystalThumb
}, },
{ {
id: 2, id: 2,
value: 'mini', value: 'mini',
name: 'Sarah Chen', name: 'Mini',
description: 'Modern, Edgy, Bold Colors, Street Style', description: 'Modern, Edgy, Bold Colors, Street Style',
image: male image: mini,
thumb: miniThumb
}, },
{ {
id: 3, id: 3,
value: 'crystal', value: 'vera',
name: 'Emma Wilson', name: 'Vera',
description: 'Elegant, Feminine, Vintage Inspired, Soft Tones', description: 'Elegant, Feminine, Vintage Inspired, Soft Tones',
image: female image: female,
thumb: femaleThumb
}, },
{ {
id: 4, id: 4,
value: 'mini', value: 'edi',
name: 'Alex Johnson', name: 'Edi',
description: 'Minimalist, Professional, Neutral Palette, Clean Lines', description: 'Minimalist, Professional, Neutral Palette, Clean Lines',
image: male image: male,
thumb: maleThumb
} }
]) ])
const currentChoosed = ref(1) const currentChoosed = ref(1)
@@ -101,7 +117,6 @@ const swiperRef = ref<any>(null)
const showVideo = ref<boolean>(false) const showVideo = ref<boolean>(false)
const videoRef = ref<any>(null) const videoRef = ref<any>(null)
const handleChangeCurrent = (index: number) => { const handleChangeCurrent = (index: number) => {
currentChoosed.value = stylists.value[index].id currentChoosed.value = stylists.value[index].id
} }
@@ -116,25 +131,27 @@ const handleChangeSwiper = (type: 'next' | 'prev') => {
const handleClickStylist = (item: any) => { const handleClickStylist = (item: any) => {
console.log(item) console.log(item)
// showVideo.value = true // showVideo.value = true
} }
const handleContinue = () => { const handleContinue = () => {
const generateParams = userInfoStore.getGenerateParams() const generateParams = userInfoStore.getGenerateParams()
generateParams.stylist = const selected = stylists.value.find((item) => item.id === currentChoosed.value)
stylists.value.find((item) => item.id === currentChoosed.value)?.value || '' generateParams.stylist = selected?.value
generateParams.stylistImage = selected?.thumb
userInfoStore.setGenerateParams(generateParams) userInfoStore.setGenerateParams(generateParams)
router.push('/stylist/sex') router.push('/workshop/stylist/sex')
} }
// 监听showVideo变化关闭时暂停视频 // 监听showVideo变化关闭时暂停视频
watch(showVideo, (newValue) => { // watch(showVideo, (newValue) => {
if (!newValue && videoRef.value) { // if (!newValue && videoRef.value) {
videoRef.value.pause() // videoRef.value.pause()
videoRef.value.reset() // videoRef.value.reset()
} // }
}) // })
</script> </script>
<style scoped lang="less"> <style scoped lang="less">
@@ -159,18 +176,24 @@ watch(showVideo, (newValue) => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 2rem; padding: 2rem;
padding-top: 6rem; padding-top: 10rem;
} }
.header { .header {
text-align: center; text-align: center;
margin-bottom: 4rem; margin-bottom: 4rem;
.title { .title {
font-size: 15rem; font-size: 11rem;
font-weight: 400; font-weight: 400;
color: white; color: white;
font-family: 'boskaRegular';
line-height: 96%; line-height: 96%;
font-family: 'satoshiBold';
letter-spacing: -0.04rem;
margin-bottom: 3.2rem;
}
.sub-title {
font-family: 'satoshiRegular';
font-size: 4rem;
} }
} }
@@ -200,13 +223,16 @@ watch(showVideo, (newValue) => {
z-index: 3; z-index: 3;
box-shadow: 0 2rem 2.5rem rgba(0, 0, 0, 0.25), 0 0 6rem rgba(0, 0, 0, 0.25); box-shadow: 0 2rem 2.5rem rgba(0, 0, 0, 0.25), 0 0 6rem rgba(0, 0, 0, 0.25);
border: 0.1rem solid rgba(255, 255, 255, 0.2); border: 0.1rem solid rgba(255, 255, 255, 0.2);
filter: drop-shadow(.2rem 4px 6.6px rgba(0, 0, 0, 0.25)); filter: drop-shadow(0.2rem 4px 6.6px rgba(0, 0, 0, 0.25));
&.left { &.left {
left: 1rem; left: 1rem;
} }
&.right { &.right {
right: 1rem; right: 1rem;
.arrow-right {
transform: rotate(180deg);
}
} }
} }
@@ -217,7 +243,7 @@ watch(showVideo, (newValue) => {
.swiper-container { .swiper-container {
width: 66rem; width: 66rem;
height: 100rem; height: 100rem;
border: .2rem solid #fff; border: 0.2rem solid #fff;
border-radius: 1.2rem; border-radius: 1.2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -278,19 +304,21 @@ watch(showVideo, (newValue) => {
} }
} }
.continue-button { button.sandblasted-blurred.continue-button {
height: 6.7rem;
width: 24.6rem;
position: absolute; position: absolute;
bottom: 6.4rem; bottom: 6.4rem;
right: 7.6rem; right: 7.6rem;
padding: 1.2rem 2.4rem; padding: 0 3.4rem;
border: 1px solid #fff; border: 0.3rem solid #fff;
border-radius: 1rem; border-radius: 1rem;
color: white; color: white;
font-size: 4rem; font-size: 4rem;
font-weight: 500; font-weight: 500;
cursor: pointer;
z-index: 3; z-index: 3;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
box-sizing: border-box;
} }
:deep(.van-overlay) { :deep(.van-overlay) {
@@ -324,12 +352,6 @@ watch(showVideo, (newValue) => {
inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.3); inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.3);
transition: all 0.3s ease; transition: all 0.3s ease;
&:hover {
transform: translateY(-0.2rem);
box-shadow: 0 1.2rem 2.4rem rgba(0, 0, 0, 0.5),
inset 0 0.2rem 0.4rem rgba(255, 255, 255, 0.15), inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.4);
}
.close-icon { .close-icon {
color: white; color: white;
font-size: 3.64rem; font-size: 3.64rem;

View File

@@ -1,12 +1,11 @@
<template> <template>
<header-title style-type="3" />
<div class="sex-select"> <div class="sex-select">
<div class="text">Before we begin.</div> <div class="text">Before we begin.</div>
<div class="desc">Who are you styling?</div> <div class="desc">Who are you styling?</div>
<div class="select-list"> <div class="select-list">
<div <div
class="option" class="option flex flex-center"
v-for="option in options" v-for="option in options"
:key="option.value" :key="option.value"
@click="handleSelect(option.value)" @click="handleSelect(option.value)"
@@ -15,7 +14,6 @@
</div> </div>
</div> </div>
</div> </div>
<footer-navigation />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.vue' import HeaderTitle from '@/components/HeaderTitle.vue'
@@ -23,7 +21,7 @@ import FooterNavigation from '@/components/FooterNavigation.vue'
import { ref } from 'vue' import { ref } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { showToast } from 'vant' import { showToast } from 'vant'
import {useUserInfoStore} from '@/stores' import { useUserInfoStore } from '@/stores'
const router = useRouter() const router = useRouter()
const userInfoStore = useUserInfoStore() const userInfoStore = useUserInfoStore()
@@ -33,13 +31,16 @@ const options = ref<any[]>([
]) ])
const handleSelect = (value: string) => { const handleSelect = (value: string) => {
if (value === 'male') {// 男性开发中 if (value === 'male') {
return showToast(`This feature is currently under development. Please select the 'Female' option for now.`) // 男性开发中
return showToast(
`This feature is currently under development. Please select the 'Female' option for now.`
)
} }
const generateParams = userInfoStore.getGenerateParams() const generateParams = userInfoStore.getGenerateParams()
generateParams.sex = value generateParams.sex = value
userInfoStore.setGenerateParams(generateParams) userInfoStore.setGenerateParams(generateParams)
router.push('/stylist/dressfor') router.push('/workshop/stylist/dressfor')
} }
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@@ -54,14 +55,18 @@ const handleSelect = (value: string) => {
background-repeat: no-repeat; background-repeat: no-repeat;
padding: 6rem 12.4rem 0 8.5rem; padding: 6rem 12.4rem 0 8.5rem;
.text { .text {
font-family: 'robotoBold'; font-family: 'satoshiBold';
font-size: 13rem; font-weight: 700;
font-size: 11rem;
line-height: 106%; line-height: 106%;
letter-spacing: -0.02rem;
margin-bottom: 4.6rem;
} }
.desc { .desc {
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
font-size: 6.4rem; font-size: 6rem;
line-height: 132%; line-height: 132%;
letter-spacing: 0.02rem;
} }
.select-list { .select-list {
display: flex; display: flex;
@@ -71,12 +76,24 @@ const handleSelect = (value: string) => {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
.option { .option {
// flex: 1; // frosted glass style
text-align: center; text-align: center;
font-family: 'satoshiRegular'; font-family: 'satoshiRegular';
font-size: 6.4rem; font-size: 4.8rem;
width: 29.7rem; width: 35rem;
border: .2rem solid #fff; height: 8.3rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: .2rem;
background: rgba(255, 255, 255, 0.06);
border: 0.2rem solid #fff;
backdrop-filter: blur(95px);
-webkit-backdrop-filter: blur(95px);
-moz-backdrop-filter: blur(95px);
background-clip: padding-box;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
} }
} }
} }

View File

@@ -56,7 +56,7 @@ export default defineConfig(({ mode }) => {
}, },
server: { server: {
host: '0.0.0.0', // 允许局域网内的IP访问 host: '0.0.0.0', // 允许局域网内的IP访问
port: 8060, // 根据环境设置端口 port: 8066, // 根据环境设置端口
open: false, // 自动打开浏览器 open: false, // 自动打开浏览器
strictPort: true, // 如果端口已被占用,则尝试下一个可用端口 strictPort: true, // 如果端口已被占用,则尝试下一个可用端口
hmr: { hmr: {