Compare commits
129 Commits
2487e4e045
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| df5cfc5eba | |||
| 8100459c4e | |||
| 10ee247b8d | |||
| 7f34ce80b9 | |||
| 57359d1067 | |||
| 9101116430 | |||
|
|
7a87c6cd11 | ||
|
|
c8dc6cf8d1 | ||
| f092a76162 | |||
| 48a32a60a1 | |||
|
|
f157c6ead3 | ||
|
|
592792d071 | ||
|
|
aace73d5c4 | ||
|
|
02dcfba4ba | ||
|
|
465fa5e6ae | ||
|
|
0ec9e4dc46 | ||
|
|
e230b4c83f | ||
|
|
cf15e371ab | ||
|
|
ef314931eb | ||
|
|
71a3946aae | ||
|
|
38f027d3ad | ||
|
|
d497471e09 | ||
| 92c571b5f9 | |||
| 6156c88429 | |||
|
|
319130dc6a | ||
|
|
6a2b456bf3 | ||
|
|
135adb3907 | ||
| 089a266c35 | |||
| a9732a659e | |||
|
|
24e221902e | ||
|
|
8b92d360c9 | ||
|
|
f10039e9dc | ||
| 9d0b29ab95 | |||
| 433dec935a | |||
| dfdfce2076 | |||
|
|
eebe7b5b40 | ||
|
|
fc1c212eca | ||
|
|
9f338a772d | ||
| 8e50f3d8a5 | |||
| f5bfad7973 | |||
| 27bb0394f8 | |||
|
|
73ae6744ab | ||
|
|
2b42ef6ab7 | ||
|
|
0794749f9a | ||
|
|
5f84eb496c | ||
| 10fc7f3e30 | |||
| 6b19672f1c | |||
| 32245b8931 | |||
| 25852e7c5a | |||
|
|
5d89d06531 | ||
|
|
279776c4f7 | ||
|
|
94cb354483 | ||
|
|
88abe91538 | ||
|
|
55ed8474be | ||
|
|
fab733c7c7 | ||
|
|
3826e553b1 | ||
|
|
8cb77593f8 | ||
|
|
891fb3f4b0 | ||
|
|
c2b800eb35 | ||
|
|
15a4220fbc | ||
| 9da2cd9dc2 | |||
| 8af7092f56 | |||
| f0ff9ebcbe | |||
| 9c41bf4711 | |||
|
|
59c864fef3 | ||
|
|
28f67cf228 | ||
|
|
1be879fbe4 | ||
| 8c2f4ca6e6 | |||
| 53b9a83b80 | |||
|
|
9dd3bed850 | ||
| a5cf919ded | |||
| 42a7194156 | |||
|
|
b206cae025 | ||
|
|
1a52eaa3c3 | ||
|
|
70cf624d88 | ||
| a629a08e66 | |||
| 73e8662895 | |||
|
|
9a51259b11 | ||
|
|
e6d542fb78 | ||
| 33659049ce | |||
| 9310761a5d | |||
|
|
321c0bdb92 | ||
|
|
30e46dff15 | ||
|
|
6b255728af | ||
| 8e73c90746 | |||
| 65617baa96 | |||
| e8a72ec0aa | |||
| c356b1b503 | |||
|
|
1b7694cee0 | ||
| b17637b2bc | |||
|
|
665194f18e | ||
|
|
c6a9d91159 | ||
|
|
51336fff77 | ||
|
|
d45fd93c4f | ||
|
|
bf605ea297 | ||
| b7abd2dbf9 | |||
| 69e6408d55 | |||
|
|
e2f1e6fa5b | ||
|
|
1bf3038b29 | ||
|
|
4aac607f6a | ||
|
|
53264e209b | ||
|
|
f266bb4213 | ||
|
|
3dde004166 | ||
|
|
9ba6804d85 | ||
|
|
41e605a8bf | ||
|
|
d0ada4e6f0 | ||
|
|
48890121f6 | ||
| e14ac0f15f | |||
|
|
60b0f4bbec | ||
|
|
33a73643b4 | ||
|
|
bd36a237ec | ||
|
|
ea84256cf6 | ||
|
|
67702b36b2 | ||
|
|
c157c0a14a | ||
|
|
c04b99a9aa | ||
| 58ef526542 | |||
| 543ac89f9b | |||
| 5b3eb52841 | |||
| 21751700f6 | |||
| 662f0e2115 | |||
| 927e07bf79 | |||
| 1424e3025d | |||
| 2929f13e93 | |||
| 94796110c4 | |||
| 3f6652c2ec | |||
| 2db23b8d05 | |||
| 6dbef599bb | |||
|
|
2259edb1a1 | ||
|
|
1a8cda5d5d |
@@ -59,3 +59,19 @@ export const googleAuth = (data: GoogleAuthParamsType): Promise<LoginResponse> =
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,26 +8,26 @@ import request from '@/utils/request'
|
||||
* @param data.stylist 提示词
|
||||
* @param data.gender 原始试穿效果id
|
||||
* @param data.num 是否重新生成 0-否,1-是
|
||||
*/
|
||||
*/
|
||||
export function generateRequestOutfit(data: Object) {
|
||||
return request({
|
||||
url: '/api/style/requestOutfit',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
return request({
|
||||
url: '/api/style/requestOutfit',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取传达风格
|
||||
* @param data 获取传达风格数据
|
||||
* @param data.requestIDs 获取生成结果的taskId
|
||||
*/
|
||||
*/
|
||||
export function getRequestOutfit(data: Object) {
|
||||
return request({
|
||||
url: '/api/style/getOutfitResult',
|
||||
method: 'get',
|
||||
params:data
|
||||
})
|
||||
return request({
|
||||
url: '/api/style/getOutfitResult',
|
||||
method: 'get',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,13 +41,13 @@ export function getRequestOutfit(data: Object) {
|
||||
* @param data.prompt 提示词
|
||||
* @param data.originalTryOnId 原始试穿效果id
|
||||
* @param data.isRegenerated 是否重新生成 0-否,1-是
|
||||
*/
|
||||
*/
|
||||
export function generateTryOnEffect(data: Object) {
|
||||
return request({
|
||||
url: '/api/try-on-effects/generate',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
return request({
|
||||
url: '/api/try-on-effects/generate',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 生成试穿效果-演示
|
||||
@@ -55,13 +55,13 @@ export function generateTryOnEffect(data: Object) {
|
||||
* @param data.customerPhotoId 顾客照片id
|
||||
* @param data.prompt 提示词
|
||||
* @param data.tryonUrl AI魔改url
|
||||
*/
|
||||
*/
|
||||
export function generateTryOnEffectDemo(data: Object) {
|
||||
return request({
|
||||
url: '/api/try-on-effects/reFace',
|
||||
method: 'post',
|
||||
data,
|
||||
})
|
||||
return request({
|
||||
url: '/api/try-on-effects/reFace',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
/** 上传图片-AI换脸
|
||||
@@ -71,96 +71,165 @@ export function generateTryOnEffectDemo(data: Object) {
|
||||
* @param data.file 顾客照片文件
|
||||
*/
|
||||
export function uploadCustomerPhoto(data: FormData) {
|
||||
return request({
|
||||
url: '/api/customer-photos/upload',
|
||||
method: 'post',
|
||||
data,
|
||||
loading: true,
|
||||
})
|
||||
return request({
|
||||
url: '/api/customer-photos/upload',
|
||||
method: 'post',
|
||||
data,
|
||||
loading: true
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 设置喜欢
|
||||
* @param tryOnId 试穿效果id
|
||||
*/
|
||||
*/
|
||||
export function setTryOnEffectFavorite(tryOnId: string | number) {
|
||||
if (!tryOnId) return Promise.reject('试穿效果id不能为空');
|
||||
return request({
|
||||
url: `/api/try-on-effects/set-favorite/${tryOnId}`,
|
||||
method: 'post',
|
||||
})
|
||||
if (!tryOnId) return Promise.reject('试穿效果id不能为空')
|
||||
return request({
|
||||
url: `/api/try-on-effects/set-favorite/${tryOnId}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 取消喜欢
|
||||
* @param tryOnId 试穿效果id
|
||||
*/
|
||||
*/
|
||||
export function cancelTryOnEffectFavorite(tryOnId: string | number) {
|
||||
if (!tryOnId) return Promise.reject('试穿效果id不能为空');
|
||||
return request({
|
||||
url: `/api/try-on-effects/cancel-favorite/${tryOnId}`,
|
||||
method: 'post',
|
||||
})
|
||||
if (!tryOnId) return Promise.reject('试穿效果id不能为空')
|
||||
return request({
|
||||
url: `/api/try-on-effects/cancel-favorite/${tryOnId}`,
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
|
||||
/** 查询进店记录-library
|
||||
* @param customerId 客户id
|
||||
*/
|
||||
export function getCustomerPhotos(customerId: string | number) {
|
||||
if (!customerId) return Promise.reject('客户id不能为空');
|
||||
return request({
|
||||
url: `/api/visit-records/customer/${customerId}`,
|
||||
method: 'get',
|
||||
})
|
||||
if (!customerId) return Promise.reject('客户id不能为空')
|
||||
return request({
|
||||
url: `/api/visit-records/customer/${customerId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
/** 删除进店记录-library
|
||||
* @param visitRecordId 进店记录id
|
||||
*/
|
||||
export function deleteCustomerPhoto(visitRecordId: string | number) {
|
||||
if (!visitRecordId) return Promise.reject('进店记录id不能为空');
|
||||
return request({
|
||||
url: `/api/visit-records/${visitRecordId}`,
|
||||
method: 'delete',
|
||||
// loading: true,
|
||||
})
|
||||
if (!visitRecordId) return Promise.reject('进店记录id不能为空')
|
||||
return request({
|
||||
url: `/api/visit-records/${visitRecordId}`,
|
||||
method: 'delete'
|
||||
// loading: true,
|
||||
})
|
||||
}
|
||||
/** 查询收藏列表
|
||||
* @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',
|
||||
})
|
||||
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',
|
||||
})
|
||||
if (!styleId) return Promise.reject('服装id不能为空')
|
||||
return request({
|
||||
url: `/api/try-on-effects/style/${styleId}`,
|
||||
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 {
|
||||
vipId: string
|
||||
}
|
||||
export const customerCheckin = (data: CustomerInfo) => {
|
||||
return request({
|
||||
url: '/api/customers/checkIn',
|
||||
method: 'get',
|
||||
params: data,
|
||||
})
|
||||
export const customerCheckin = (data: CustomerInfo) => {
|
||||
return request({
|
||||
url: '/api/customers/checkIn',
|
||||
method: 'get',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// AI对话
|
||||
interface AIConversation {
|
||||
message: string
|
||||
sessionId: string | number //用户ID
|
||||
gender: 'male' | 'female' //性别
|
||||
message: string
|
||||
sessionId: string | number //用户ID
|
||||
gender: 'male' | 'female' //性别
|
||||
}
|
||||
export const streamChatAddress = '/api/llm/streamChat'
|
||||
|
||||
@@ -171,11 +240,46 @@ export const streamChatAddress = '/api/llm/streamChat'
|
||||
* @param data.visitRecordId 进店记录id
|
||||
* @param data.customerId 顾客id
|
||||
* @param data.suggestion 意见和建议
|
||||
*/
|
||||
*/
|
||||
export function addTryOnEffectComment(data: Object) {
|
||||
return request({
|
||||
url: '/api/try-on-effects/add-comment',
|
||||
method: 'post',
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
button.sandblasted-blurred {
|
||||
box-sizing: content-box;
|
||||
border: 0.4rem solid #fff;
|
||||
width: 35rem;
|
||||
height: 8.3rem;
|
||||
box-sizing: border-box;
|
||||
border: 0.25rem solid #fff;
|
||||
font-family: satoshiMedium;
|
||||
font-weight: 500;
|
||||
font-size: 5.5rem;
|
||||
font-size: 4rem;
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
@@ -71,6 +73,7 @@ html:root {
|
||||
padding: 0 2rem;
|
||||
min-height: fit-content;
|
||||
}
|
||||
button.general,
|
||||
.general_button {
|
||||
border-radius: 0.7rem;
|
||||
border: 3px solid #000;
|
||||
@@ -79,11 +82,13 @@ html:root {
|
||||
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;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
button.sandblasted-blurred {
|
||||
box-sizing: content-box;
|
||||
border: 0.4rem solid #fff;
|
||||
width: 35rem;
|
||||
height: 8.3rem;
|
||||
box-sizing: border-box;
|
||||
border: 0.25rem solid #fff;
|
||||
font-family: satoshiMedium;
|
||||
font-weight: 500;
|
||||
font-size: 5.5rem;
|
||||
font-size: 4rem;
|
||||
color: #fff;
|
||||
background-color: transparent;
|
||||
position: relative;
|
||||
@@ -41,7 +43,9 @@ button.sandblasted-blurred {
|
||||
|
||||
//只使用浅色模式
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root, body {
|
||||
|
||||
:root,
|
||||
body {
|
||||
background: white !important;
|
||||
color: black !important;
|
||||
}
|
||||
@@ -71,10 +75,11 @@ html:root {
|
||||
--van-toast-default-width: 88rem;
|
||||
}
|
||||
|
||||
.van-toast{
|
||||
.van-toast {
|
||||
min-height: fit-content;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.van-toast__text {
|
||||
font-size: 4rem;
|
||||
height: 5rem;
|
||||
@@ -83,19 +88,22 @@ html:root {
|
||||
min-height: fit-content;
|
||||
}
|
||||
|
||||
.general_button{
|
||||
button.general,
|
||||
.general_button {
|
||||
border-radius: .7rem;
|
||||
border: 3px solid #000;
|
||||
background-color: #000;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-family: satoshiMedium;
|
||||
&.smail{
|
||||
|
||||
&.smail {
|
||||
font-size: 3.6rem;
|
||||
width: 24.6rem;
|
||||
line-height: 6.7rem;
|
||||
}
|
||||
&.big{
|
||||
|
||||
&.big {
|
||||
font-size: 3.8rem;
|
||||
line-height: 7.4rem;
|
||||
width: 3.4rem;
|
||||
|
||||
6
src/assets/icons/close_nocolor.svg
Normal 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 |
@@ -1,4 +1,4 @@
|
||||
<svg width="34" height="33" viewBox="0 0 34 33" 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.96387 1.81152V10.1449H10.2972M16.9639 8.47819V16.8115L23.6305 20.1448" stroke="black" stroke-width="2.28788" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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 1V12.1111H12.1111M21 9.88889V21L29.8889 25.4444" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 759 B |
@@ -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">
|
||||
<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>
|
||||
<pattern id="pattern0_1_2934" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_1_2934" transform="scale(0.0111111)"/>
|
||||
<pattern id="pattern0_2099_1006" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2099_1006" transform="scale(0.0111111)"/>
|
||||
</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>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.1 KiB |
9
src/assets/icons/profile.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
11
src/assets/icons/profileFilledBlack.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
16
src/assets/icons/profileFilledWhite.svg
Normal file
|
After Width: | Height: | Size: 22 KiB |
4
src/assets/icons/profile_white.svg
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
7
src/assets/icons/reTry.svg
Normal 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 |
@@ -1,3 +1,3 @@
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M29 15C29 15.3788 28.786 15.7251 28.4473 15.8945L2.44726 28.8945C2.0805 29.0779 1.63862 29.0185 1.33301 28.7451C1.02768 28.4718 0.92023 28.0399 1.06152 27.6553L5.71582 15L1.06152 2.34473C0.920232 1.96008 1.02768 1.52819 1.33301 1.25488C1.63863 0.981488 2.0805 0.922085 2.44727 1.10547L28.4473 14.1055L28.5684 14.1768C28.8363 14.3617 29 14.6685 29 15Z" fill="white" stroke="black" stroke-width="2" stroke-linejoin="round"/>
|
||||
<svg width="47" height="47" viewBox="0 0 47 47" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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>
|
||||
|
||||
|
Before Width: | Height: | Size: 535 B After Width: | Height: | Size: 562 B |
3
src/assets/icons/send_bold.svg
Normal 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 |
9
src/assets/icons/share.svg
Normal 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 |
BIN
src/assets/images/Crystal.jpg
Normal file
|
After Width: | Height: | Size: 667 KiB |
BIN
src/assets/images/Crystal_thumb.jpg
Normal file
|
After Width: | Height: | Size: 206 KiB |
BIN
src/assets/images/checked.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
src/assets/images/dressfor.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
src/assets/images/female_thumb.png
Normal file
|
After Width: | Height: | Size: 1.9 MiB |
BIN
src/assets/images/homeBg.png
Normal file
|
After Width: | Height: | Size: 849 KiB |
BIN
src/assets/images/male_thumb.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
src/assets/images/mini.jpg
Normal file
|
After Width: | Height: | Size: 655 KiB |
BIN
src/assets/images/mini_thumb.jpg
Normal file
|
After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 226 KiB After Width: | Height: | Size: 226 KiB |
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 217 KiB |
|
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 242 KiB |
@@ -25,15 +25,15 @@
|
||||
})
|
||||
.then(() => {
|
||||
MyEvent.emit('clear-generate-state')
|
||||
MyEvent.emit('clearAllCache')
|
||||
// MyEvent.emit('clearAllCache')
|
||||
nav.path && router.push(nav.path)
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
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: 'Profile', icon: 'profile', size: 55, path: '/workshop/profile' }
|
||||
// { label: 'Profile', icon: 'profile', size: 55, path: '/workshop/profile' }
|
||||
]
|
||||
const onNavClick = (nav) => {
|
||||
if (currentRoute.value !== nav.path) nav.on ? nav.on(nav) : nav.path && router.push(nav.path)
|
||||
@@ -49,10 +49,10 @@
|
||||
@click="onNavClick(nav)"
|
||||
>
|
||||
<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 class="footer-navigation placeholder" v-show="isPlaceholder"></div>
|
||||
<!-- <div class="footer-navigation placeholder" v-show="isPlaceholder"></div> -->
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -61,9 +61,9 @@
|
||||
height: var(--footer-navigation-height, 14.9rem);
|
||||
}
|
||||
.footer-navigation.main {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
z-index: var(--footer-navigation-z-index, 999);
|
||||
// position: fixed;
|
||||
// bottom: 0;
|
||||
// z-index: var(--footer-navigation-z-index, 999);
|
||||
background-color: var(--footer-navigation-background, #fff);
|
||||
box-shadow: -2.6rem -1.4rem 3.47rem 0 rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
|
||||
@@ -1,79 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
defineProps({
|
||||
title: { type: String, default: 'AI STYLING ASSISTANT' },
|
||||
hasSetting: { type: Boolean, default: false },
|
||||
styleType: { type: String, default: '1' },//1低 2高 3-12rem
|
||||
isPlaceholder: { type: Boolean, default: true },
|
||||
title: { type: String, default: 'STYLING ASSISTANT' }
|
||||
})
|
||||
|
||||
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 = () => {
|
||||
router.back()
|
||||
// emit('clickReturn')
|
||||
}
|
||||
const handleClickProfile = () => {
|
||||
// router.push('/workshop/profile')
|
||||
emit('clickProfile')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="header-title" :style-type="styleType">
|
||||
<div class="main">
|
||||
<div class="return" @click="handleClickReturn"><SvgIcon name="return" size="34" /></div>
|
||||
<span class="title">{{ title }}</span>
|
||||
<div class="setting" v-if="hasSetting"><SvgIcon name="setting" size="44" /></div>
|
||||
<div class="header-title">
|
||||
<div class="return" @click="handleClickReturn"><SvgIcon name="return" size="34" /></div>
|
||||
<span class="title">{{ title }}</span>
|
||||
<div class="profile" @click="handleClickProfile">
|
||||
<SvgIcon :name="profileVisible ? 'profileFilledBlack' : 'profile_white'" size="45" />
|
||||
</div>
|
||||
<div class="placeholder" v-if="isPlaceholder"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header-title {
|
||||
width: 100%;
|
||||
&[style-type="2"] {
|
||||
--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-color: var(--header-title-border-bottom-color, #D5D5D5);
|
||||
}
|
||||
> .main {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
z-index: var(--header-title-z-index, 999);
|
||||
background-color: var(--header-title-background, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: var(--header-title-height, 14.6rem);
|
||||
border-bottom-style: solid;
|
||||
border-bottom-width: var(--header-title-border-bottom-width, 0.2rem);
|
||||
border-bottom-color: var(--header-title-border-bottom-color, #d5d5d5);
|
||||
z-index: var(--header-title-z-index, 999);
|
||||
background-color: var(--header-title-background, #fff);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
> .return {
|
||||
position: absolute;
|
||||
left: var(--header-title-return-left, 3.2rem);
|
||||
display: inline-block;
|
||||
--svg-icon-color: var(--header-title-color, #000);
|
||||
}
|
||||
> .title {
|
||||
font-size: 4.8rem;
|
||||
color: var(--header-title-color, #000);
|
||||
font-family: 'boskaRegular';
|
||||
}
|
||||
> .setting {
|
||||
position: absolute;
|
||||
right: 3.2rem;
|
||||
display: inline-block;
|
||||
color: var(--header-title-color, #000);
|
||||
}
|
||||
> .return {
|
||||
color: #2c2c2c;
|
||||
position: absolute;
|
||||
left: 7rem;
|
||||
display: inline-block;
|
||||
}
|
||||
> .title {
|
||||
color: #2c2c2c;
|
||||
font-family: 'satoshiRegular';
|
||||
font-size: 4rem;
|
||||
letter-spacing: 0.3rem;
|
||||
}
|
||||
> .profile {
|
||||
position: absolute;
|
||||
right: 7rem;
|
||||
display: inline-block;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
const props = defineProps({
|
||||
loading: { default: false, type: Boolean },
|
||||
finish: { default: false, type: Boolean },
|
||||
empty: { default: false, type: Boolean },
|
||||
pel: { default: () => {}, type: Function }
|
||||
})
|
||||
const emit = defineEmits(['load'])
|
||||
@@ -23,12 +24,13 @@
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="my-list" ref="el">
|
||||
<div class="my-list" ref="el" :class="{ empty: !loading && empty }">
|
||||
<slot></slot>
|
||||
<div class="footer">
|
||||
<p v-show="!loading" class="placeholder" ref="placeholder"></p>
|
||||
<span class="loading" v-show="loading">Loading...</span>
|
||||
<span class="nomore" v-show="finish">No more</span>
|
||||
<span class="loading" v-if="loading">Loading...</span>
|
||||
<span class="empty" v-else-if="empty">Nothing Here</span>
|
||||
<span class="nomore" v-else-if="finish">No more</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -40,12 +42,28 @@
|
||||
> .footer {
|
||||
width: 100%;
|
||||
font-size: 3rem;
|
||||
color: #000;
|
||||
color: #a1a1a1;
|
||||
text-align: center;
|
||||
margin: var(--my-list-footer-margin, 0);
|
||||
> .placeholder {
|
||||
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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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">
|
||||
<component :is="Component" :key="route.name" />
|
||||
</keep-alive>
|
||||
@@ -14,6 +14,13 @@ import { useRoute } from 'vue-router'
|
||||
import { useRouter } from 'vue-router'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
|
||||
const props = defineProps({
|
||||
viewType: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 缓存的组件名称列表
|
||||
@@ -59,17 +66,6 @@ onMounted(() => {
|
||||
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({
|
||||
clearCache,
|
||||
|
||||
78
src/components/WaveLoading.vue
Normal 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>
|
||||
58
src/components/gradientButton.vue
Normal 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>
|
||||
@@ -82,22 +82,11 @@ const {} = toRefs(data);
|
||||
</div>
|
||||
<div class="mask" v-if="item.id == select?.oldId"></div>
|
||||
</div>
|
||||
<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 class="btn">
|
||||
<div>
|
||||
<SvgIcon @click.stop="updateStyle(item,index)" name="update" size="30" />
|
||||
</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>
|
||||
<SvgIcon @click.stop="deleteStyle(index)" name="delete" size="30" />
|
||||
</div> -->
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -109,7 +98,7 @@ const {} = toRefs(data);
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
> .item{
|
||||
width: calc(50% - 3.1rem / 2);
|
||||
width: calc(50% - 3.5rem / 2);
|
||||
position: relative;
|
||||
// margin-bottom: 3.3rem;
|
||||
display: flex;
|
||||
@@ -126,7 +115,7 @@ const {} = toRefs(data);
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
height: 45rem;
|
||||
margin: 2.4rem 0;
|
||||
margin: 2.3rem 0;
|
||||
background-color: #fff;
|
||||
justify-content: center;
|
||||
border: .6px solid #acacac;
|
||||
@@ -170,26 +159,26 @@ const {} = toRefs(data);
|
||||
// max-height: 50%;
|
||||
}
|
||||
}
|
||||
> .btn{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
> div{
|
||||
color: #000;
|
||||
margin-right: 1.2rem;
|
||||
border-radius: 50%;
|
||||
width: 5.2rem;
|
||||
height: 5.2rem;
|
||||
padding: 1rem;
|
||||
background-color: #fff;
|
||||
&:last-child{
|
||||
margin-right: 0rem;
|
||||
}
|
||||
&:hover{
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
}
|
||||
// > .btn{
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: flex-end;
|
||||
// > div{
|
||||
// color: #000;
|
||||
// margin-right: 1.2rem;
|
||||
// border-radius: 50%;
|
||||
// width: 5.2rem;
|
||||
// height: 5.2rem;
|
||||
// padding: 1rem;
|
||||
// background-color: #fff;
|
||||
// &:last-child{
|
||||
// margin-right: 0rem;
|
||||
// }
|
||||
// &:hover{
|
||||
// color: #000;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
112
src/hooks/useStreamChat.ts
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useGenerateStore } from '@/stores/modules/generate'
|
||||
const VerifyIDs = (num: number) => {
|
||||
return true
|
||||
return true
|
||||
const ids = [
|
||||
!!useGenerateStore().customerId,
|
||||
!!useGenerateStore().visitRecordId,
|
||||
!!useGenerateStore().styleId,
|
||||
// !!useGenerateStore().modelPhotoId,
|
||||
true,
|
||||
!!useGenerateStore().originalTryOnId,
|
||||
];
|
||||
return ids.splice(0, num).every(id => id) ? true : "/stylist/customer";
|
||||
!!useGenerateStore().customerId,
|
||||
!!useGenerateStore().visitRecordId,
|
||||
!!useGenerateStore().styleId,
|
||||
// !!useGenerateStore().modelPhotoId,
|
||||
true,
|
||||
!!useGenerateStore().originalTryOnId
|
||||
]
|
||||
return ids.splice(0, num).every((id) => id) ? true : '/stylist/customer'
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,7 +18,7 @@ const VerifyIDs = (num: number) => {
|
||||
* 1. 设置路由的meta属性为{ cache: true },表示需要缓存
|
||||
* 2. App.vue中使用RouteCache组件,通过路由的name来进行匹配
|
||||
* 3. 路由的name默认是文件名,如果文件名与name不一致,通过defineOptions({ name: 'componentName' })来设置
|
||||
*
|
||||
*
|
||||
* 自定义验证规则:
|
||||
* meta:{ verify: ()=> boolean || string }
|
||||
* 1. boolean true 跳转 false 不跳转
|
||||
@@ -48,8 +48,8 @@ const router = createRouter({
|
||||
component: () => import('@/views/login/LoginPage.vue')
|
||||
},
|
||||
{
|
||||
path:'/reset',
|
||||
name:'ResetPage',
|
||||
path: '/reset',
|
||||
name: 'ResetPage',
|
||||
component: () => import('@/views/login/ResetPage.vue')
|
||||
},
|
||||
{
|
||||
@@ -62,129 +62,134 @@ const router = createRouter({
|
||||
name: 'WelcomePage',
|
||||
component: () => import('@/views/login/WelcomePage.vue')
|
||||
},
|
||||
{
|
||||
path: '/homeNav',
|
||||
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',
|
||||
component: () => import('@/views/stylist/customer.vue'),
|
||||
}
|
||||
]
|
||||
path: '/customer',
|
||||
name: 'customer',
|
||||
component: () => import('@/views/login/customer.vue')
|
||||
},
|
||||
{
|
||||
path: '/asistant',
|
||||
name: 'asistant',
|
||||
component: () => import('../views/asistant/index.vue'),
|
||||
meta: { cache: true, verify: ()=> VerifyIDs(2) }
|
||||
meta: { cache: true, verify: () => VerifyIDs(2) }
|
||||
},
|
||||
{
|
||||
path: '/workshop',
|
||||
name: 'Workshop',
|
||||
component: () => import('../views/Workshop/index.vue'),
|
||||
children: [
|
||||
// {
|
||||
// path: '/workshop',
|
||||
// redirect: '/workshop/selectStyle'
|
||||
// },
|
||||
{
|
||||
path: '/workshop',
|
||||
redirect: '/workshop/selectStyle'
|
||||
path: '/workshop/home',
|
||||
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',
|
||||
name: 'SelectStyle',
|
||||
component: () => import('../views/Workshop/selectStyle.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(2) }
|
||||
component: () => import('../views/Workshop/selectStyle/index.vue'),
|
||||
meta: { verify: () => VerifyIDs(2) }
|
||||
},
|
||||
{
|
||||
path: '/workshop/selectModel',
|
||||
name: 'SelectModel',
|
||||
component: () => import('../views/Workshop/selectModel.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(3) }
|
||||
meta: { verify: () => VerifyIDs(3) }
|
||||
},
|
||||
{
|
||||
path: '/workshop/product',
|
||||
name: 'Product',
|
||||
name: 'product',
|
||||
component: () => import('../views/Workshop/product.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(4) }
|
||||
meta: { verify: () => VerifyIDs(4) }
|
||||
},
|
||||
{
|
||||
// 推荐try on
|
||||
path: '/workshop/recommended',
|
||||
name: 'recommended',
|
||||
component: () => import('../views/Workshop/recommended.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(5) }
|
||||
meta: { verify: () => VerifyIDs(5) }
|
||||
},
|
||||
{
|
||||
// 上传照片1
|
||||
path: '/workshop/uploadFace',
|
||||
name: 'uploadFace',
|
||||
component: () => import('../views/Workshop/uploadFace1.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(5) }
|
||||
meta: { verify: () => VerifyIDs(5) }
|
||||
},
|
||||
{
|
||||
// 上传照片2
|
||||
path: '/workshop/uploadFace2',
|
||||
name: 'uploadFace2',
|
||||
component: () => import('../views/Workshop/uploadFace2.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(5) }
|
||||
meta: { verify: () => VerifyIDs(5) }
|
||||
},
|
||||
{
|
||||
// 自定义创作
|
||||
path: '/workshop/customize',
|
||||
name: 'customize',
|
||||
component: () => import('../views/Workshop/customize.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(5) }
|
||||
meta: { verify: () => VerifyIDs(5) }
|
||||
},
|
||||
{
|
||||
// library
|
||||
path: '/workshop/library',
|
||||
name: 'library',
|
||||
component: () => import('../views/Workshop/library.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(2) }
|
||||
meta: { verify: () => VerifyIDs(2) }
|
||||
},
|
||||
{
|
||||
path: '/workshop/profile',
|
||||
name: 'profile',
|
||||
component: () => import('../views/Workshop/profile.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(1) }
|
||||
meta: { verify: () => VerifyIDs(1) }
|
||||
},
|
||||
{
|
||||
// creation
|
||||
path: '/workshop/creation',
|
||||
name: 'creation',
|
||||
component: () => import('../views/Workshop/creation/index.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(2) }
|
||||
meta: { verify: () => VerifyIDs(2) }
|
||||
},
|
||||
{
|
||||
// 完成创建
|
||||
path: '/workshop/end',
|
||||
name: 'end',
|
||||
component: () => import('../views/Workshop/end.vue'),
|
||||
meta: { verify: ()=> VerifyIDs(2) }
|
||||
meta: { verify: () => VerifyIDs(2) }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const store = createPinia()
|
||||
// 使用持久化插件(全局持久化)
|
||||
store.use(createPersistedState())
|
||||
export default store
|
||||
export * from './modules/h_generate'
|
||||
export * from './modules/generate'
|
||||
export * from './modules/overall'
|
||||
export * from './modules/userInfo'
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
MyEvent.add('clear-generate-state', () => useGenerateStore().clearGenerateData())
|
||||
MyEvent.add('clear-client-state', () => useGenerateStore().clearCustomerInfo())
|
||||
|
||||
export const useGenerateStore = defineStore({
|
||||
id: 'generate', // 必须指明唯一的pinia仓库的id
|
||||
@@ -9,9 +10,17 @@ export const useGenerateStore = defineStore({
|
||||
return {
|
||||
style: {
|
||||
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: {
|
||||
id: ''
|
||||
},
|
||||
@@ -68,7 +77,7 @@ export const useGenerateStore = defineStore({
|
||||
/** 进店记录id */
|
||||
visitRecordId: (state) => state.customerInfo.visitRecordId,
|
||||
/** 服装id */
|
||||
styleId: (state) => state.style.id || state.style.oldId,
|
||||
styleId: (state) => state.style.id,
|
||||
/** 模特照片id */
|
||||
modelPhotoId: (state) => state.model.id,
|
||||
/** 原始试穿id不包含魔改id */
|
||||
@@ -82,45 +91,39 @@ export const useGenerateStore = defineStore({
|
||||
},
|
||||
actions: {
|
||||
selectStyle(data: any) {
|
||||
this.style.id = data.id
|
||||
},
|
||||
//生成后去掉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 = ''
|
||||
this.style = {
|
||||
...data,
|
||||
}
|
||||
console.log(this.style)
|
||||
},
|
||||
//模特相关
|
||||
selectModel(data: any) {
|
||||
this.model.id = data.id
|
||||
},
|
||||
setIsGenerate(isGenerate: boolean) {
|
||||
this.isGenerate = isGenerate
|
||||
},
|
||||
clearProductData() {
|
||||
this.styleList = [{}, {}, {}, {}]
|
||||
this.styleList = [
|
||||
{id:'',taskId:'',status:'',path:''},
|
||||
{id:'',taskId:'',status:'',path:''},
|
||||
{id:'',taskId:'',status:'',path:''},
|
||||
{id:'',taskId:'',status:'',path:''},
|
||||
]
|
||||
this.style = {
|
||||
id: '',
|
||||
oldId: ''
|
||||
path: '',
|
||||
isLike: false,
|
||||
taskId:'',
|
||||
status: ''
|
||||
}
|
||||
this.model = {
|
||||
id: ''
|
||||
}
|
||||
this.clearTryOn()
|
||||
},
|
||||
clearTryOn() {
|
||||
this.originalTryOn = {
|
||||
id: '',
|
||||
isLike: false,
|
||||
tryOnUrl: ''
|
||||
}
|
||||
this.isGenerate = false
|
||||
},
|
||||
/** 更新顾客照片信息 */
|
||||
updatePhotoInfo(data: any) {
|
||||
@@ -174,7 +177,7 @@ export const useGenerateStore = defineStore({
|
||||
this.updatePhotoInfo({})
|
||||
this.clearCustomizeInfo()
|
||||
this.clearCustomizeInfoDemo()
|
||||
this.clearCustomerInfo()
|
||||
// this.clearCustomerInfo()
|
||||
this.setSessionId('')
|
||||
},
|
||||
setCustomerInfo(data: any) {
|
||||
|
||||
72
src/stores/modules/h_generate.ts
Normal 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()
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -9,7 +9,8 @@ export const useUserInfoStore = defineStore('userInfo', () => {
|
||||
token: '',
|
||||
generateParams: {
|
||||
stylist: '',
|
||||
sex: ''
|
||||
sex: '',
|
||||
stylistImage: ''
|
||||
}
|
||||
})
|
||||
|
||||
@@ -37,7 +38,8 @@ export const useUserInfoStore = defineStore('userInfo', () => {
|
||||
const resetGenerateParams = () => {
|
||||
state.value.generateParams = {
|
||||
stylist: '',
|
||||
sex: ''
|
||||
sex: '',
|
||||
stylistImage: ''
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +51,7 @@ export const useUserInfoStore = defineStore('userInfo', () => {
|
||||
removeLocal('token')
|
||||
resetGenerateParams()
|
||||
MyEvent.emit('clear-generate-state')
|
||||
MyEvent.emit('clear-client-state')
|
||||
MyEvent.emit('clearAllCache')
|
||||
resolve('')
|
||||
})
|
||||
|
||||
18
src/types/enum.ts
Normal 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)
|
||||
}
|
||||
@@ -80,7 +80,7 @@ service.interceptors.response.use(
|
||||
}
|
||||
const res = response.data
|
||||
// 处理异常的情况
|
||||
console.log(res)
|
||||
// console.log(res)
|
||||
if (res.code != 0) {
|
||||
showToast({
|
||||
message: res.errMsg || res.message,
|
||||
|
||||
@@ -156,4 +156,31 @@ export async function DownloadImages(list: Array<{ url: string, name?: string }>
|
||||
*/
|
||||
export function encryptPassword(password: string): string {
|
||||
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')
|
||||
}
|
||||
}
|
||||
@@ -1,367 +1,533 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import MyList from '@/components/MyList.vue'
|
||||
import { DownloadImages } from '@/utils/tools'
|
||||
import {
|
||||
getTryOnEffectFavoriteList,
|
||||
getTryOnEffectStyleList,
|
||||
setTryOnEffectFavorite,
|
||||
cancelTryOnEffectFavorite
|
||||
} from '@/api/workshop'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import MyList from '@/components/MyList.vue'
|
||||
import { DownloadImages } from '@/utils/tools'
|
||||
import { showToast } from 'vant'
|
||||
import { FlowType } from '@/types/enum'
|
||||
import {
|
||||
getGenerateHistoricals,
|
||||
setTryOnEffectFavorite,
|
||||
cancelTryOnEffectFavorite,
|
||||
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 router = useRouter()
|
||||
const emit = defineEmits(['view-type'])
|
||||
const query = computed(() => router.currentRoute.value.query)
|
||||
const visitRecordId = computed(() => query.value.visitRecordId) // 访问记录ID
|
||||
import { useGenerateStore } from '@/stores'
|
||||
const generateStore = useGenerateStore()
|
||||
const navLst = [
|
||||
{ label: 'Outfit', value: 'Outfit', flowType: FlowType.H_OUTFIT },
|
||||
{ label: 'Try-on', value: 'Try-on', flowType: FlowType.H_TRYON },
|
||||
{ label: 'Gen-AI', value: 'Gen-AI', flowType: FlowType.H_AI }
|
||||
]
|
||||
const navActive = ref('Outfit')
|
||||
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(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
const list = reactive([])
|
||||
const loading = ref(false)
|
||||
const finish = ref(false)
|
||||
const selectCount = computed(() => list.filter((v) => v.selected).length)
|
||||
const maxSelectCount = 10
|
||||
const isChooseSave = ref(false) //是否选择保存模式
|
||||
const clickNav = (v) => {
|
||||
if (v.value === navActive.value || loading.value) return
|
||||
navActive.value = v.value
|
||||
onBackChooseSave()
|
||||
onLoad('reload')
|
||||
}
|
||||
const onLoad = (type?: 'reload') => {
|
||||
if (type === 'reload') {
|
||||
finish.value = false
|
||||
list.splice(0, list.length)
|
||||
}
|
||||
loading.value = true
|
||||
const params = {
|
||||
customerId: generateStore.customerId,
|
||||
type: navActive.value,
|
||||
isLibrary: false,
|
||||
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 = {
|
||||
// tryOnId: v.tryOnId,
|
||||
tryOnUrl: v.tryOnUrl,
|
||||
styleUrl: v.styleUrl,
|
||||
isFavorite: !!v.isFavorite,
|
||||
isRegenerated: !!v.isRegenerated,
|
||||
id: v.id,
|
||||
url: v.url,
|
||||
|
||||
const onLoad = () => {
|
||||
loading.value = true
|
||||
const http = visitRecordId.value ? getTryOnEffectFavoriteList : getTryOnEffectStyleList
|
||||
const id = visitRecordId.value || generateStore.styleId
|
||||
http(id)
|
||||
.then((data) => {
|
||||
data?.forEach((v) => {
|
||||
const obj = {
|
||||
tryOnId: v.tryOnId,
|
||||
tryOnUrl: v.tryOnUrl,
|
||||
styleUrl: v.styleUrl,
|
||||
isFavorite: !!v.isFavorite,
|
||||
isRegenerated: !!v.isRegenerated,
|
||||
selected: false,
|
||||
loading: false,
|
||||
downloaded: false
|
||||
}
|
||||
list.push(obj)
|
||||
})
|
||||
loading.value = false
|
||||
finish.value = !data.hasNext
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
loading.value = false
|
||||
finish.value = true
|
||||
})
|
||||
}
|
||||
const onItem = (v) => {
|
||||
if (props.isChooseOne) {
|
||||
onSelectItem(v)
|
||||
} else {
|
||||
isChooseSave.value ? onSelectItem(v) : onDetailsItem(v)
|
||||
}
|
||||
}
|
||||
// 详情页
|
||||
const onDetailsItem = (v) => {
|
||||
if (v.isRegenerated || !v.styleUrl) return
|
||||
router.push({ query: { ...query.value, styleUrl: v.styleUrl } })
|
||||
}
|
||||
// 喜欢
|
||||
const isLoveLoading = ref(false)
|
||||
const onLoveItem = (v) => {
|
||||
if (isLoveLoading.value) return
|
||||
var http
|
||||
if (navActive.value === 'Outfit') {
|
||||
http = v.isFavorite ? cancelStyleFavorite : setStyleFavorite
|
||||
} else {
|
||||
http = v.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
|
||||
}
|
||||
isLoveLoading.value = true
|
||||
v.isFavorite = !v.isFavorite
|
||||
http(v.id)
|
||||
.then(() => {
|
||||
isLoveLoading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
isLoveLoading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
selected: list.length < maxSelectCount,
|
||||
loading: false,
|
||||
downloaded: false
|
||||
}
|
||||
list.push(obj)
|
||||
})
|
||||
loading.value = false
|
||||
finish.value = true
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
loading.value = false
|
||||
finish.value = true
|
||||
})
|
||||
}
|
||||
const onItem = (v) => {
|
||||
isChooseSave.value ? onSelectItem(v) : onDetailsItem(v)
|
||||
}
|
||||
// 详情页
|
||||
const onDetailsItem = (v) => {
|
||||
if (v.isRegenerated) return
|
||||
router.push({ query: { ...query.value, styleUrl: v.styleUrl } })
|
||||
}
|
||||
// 喜欢
|
||||
const isLoveLoading = ref(false)
|
||||
const onLoveItem = (v) => {
|
||||
if (isLoveLoading.value) return
|
||||
const http = v.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
|
||||
isLoveLoading.value = true
|
||||
v.isFavorite = !v.isFavorite
|
||||
http(v.tryOnId)
|
||||
.then(() => {
|
||||
isLoveLoading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
isLoveLoading.value = false
|
||||
})
|
||||
}
|
||||
const shareImageToWhatsapp = async (url) => {
|
||||
// 把图片 URL 转为 Blob
|
||||
const blob = await fetch(url).then((res) => res.blob())
|
||||
|
||||
const shareImageToWhatsapp = async (url) => {
|
||||
// 把图片 URL 转为 Blob
|
||||
const blob = await fetch(url).then((res) => res.blob())
|
||||
// 创建文件对象
|
||||
const file = new File([blob], 'image.jpg', { type: 'image/jpeg' })
|
||||
|
||||
// 创建文件对象
|
||||
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
|
||||
|
||||
// 判断浏览器是否支持文件分享
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
// 构造WhatsApp链接
|
||||
const whatsappLink = `https://api.whatsapp.com/send/?text=${encodeURIComponent(message)}`
|
||||
window.open(whatsappLink, '_blank')
|
||||
}
|
||||
}
|
||||
const isShare = ref(false)
|
||||
const handleOpenShare = () => {
|
||||
isShare.value = !isShare.value
|
||||
|
||||
const isShare = ref(false)
|
||||
const handleOpenShare = () => {
|
||||
isShare.value = !isShare.value
|
||||
|
||||
alert(`现在${isShare.value ? '可以' : '不可以'}分享`)
|
||||
}
|
||||
|
||||
const onDownloadItem = async (v) => {
|
||||
if (isShare.value) {
|
||||
await shareImageToWhatsapp(v.tryOnUrl)
|
||||
return
|
||||
}
|
||||
if (v.loading) return
|
||||
v.loading = true
|
||||
v.selected = false
|
||||
DownloadImages([{ url: v.tryOnUrl }], null, null, () => {
|
||||
v.loading = false
|
||||
v.downloaded = true
|
||||
})
|
||||
}
|
||||
const onSelectItem = (v) => {
|
||||
if (selectCount.value >= maxSelectCount && !v.selected) return
|
||||
v.selected = !v.selected
|
||||
}
|
||||
const onChooseSave = () => {
|
||||
isChooseSave.value = true
|
||||
}
|
||||
const onBackChooseSave = () => {
|
||||
isChooseSave.value = false
|
||||
}
|
||||
// 下载选中项
|
||||
const onConfirm = () => {
|
||||
const downloadList = []
|
||||
if (selectCount.value > 0) {
|
||||
list.forEach((v, i) => {
|
||||
if (v.selected) {
|
||||
v.selected = false
|
||||
v.loading = true
|
||||
downloadList.push({
|
||||
index: i,
|
||||
url: v.tryOnUrl
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
if (selectCount.value < maxSelectCount) {
|
||||
list.forEach((v) => {
|
||||
if (!v.selected && !v.downloaded && !v.loading && selectCount.value < maxSelectCount)
|
||||
v.selected = true
|
||||
})
|
||||
}
|
||||
if (downloadList.length > 0) {
|
||||
DownloadImages(
|
||||
downloadList,
|
||||
(count, total, item) => {
|
||||
list[item.index].loading = false
|
||||
list[item.index].downloaded = true
|
||||
console.log('下载成功', count, total, item)
|
||||
},
|
||||
(count, total, item) => {
|
||||
list[item.index].loading = false
|
||||
console.log('下载失败', count, total, item)
|
||||
},
|
||||
(successCount, errCount) => {
|
||||
console.log('下载完成', successCount, errCount)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
const onContinue = () => {
|
||||
router.push({ name: 'end' })
|
||||
}
|
||||
alert(`现在${isShare.value ? '可以' : '不可以'}分享`)
|
||||
}
|
||||
const onShareItem = (v) => {
|
||||
const url = v.tryOnUrl || v.url
|
||||
if (url) shareImageToWhatsapp(url)
|
||||
}
|
||||
const onDownloadItem = async (v) => {
|
||||
if (isShare.value) {
|
||||
await shareImageToWhatsapp(v.tryOnUrl)
|
||||
return
|
||||
}
|
||||
if (v.loading) return
|
||||
v.loading = true
|
||||
v.selected = false
|
||||
DownloadImages([{ url: v.tryOnUrl }], null, null, () => {
|
||||
v.loading = false
|
||||
v.downloaded = true
|
||||
})
|
||||
}
|
||||
const onSelectItem = (v) => {
|
||||
if (props.isChooseOne) {
|
||||
list.forEach((v) => (v.selected = false))
|
||||
v.selected = true
|
||||
} else {
|
||||
if (selectCount.value >= maxSelectCount && !v.selected) return
|
||||
v.selected = !v.selected
|
||||
}
|
||||
}
|
||||
const onChooseSave = () => {
|
||||
isChooseSave.value = true
|
||||
if (selectCount.value < maxSelectCount) {
|
||||
list.forEach((v) => {
|
||||
if (!v.selected && !v.downloaded && !v.loading && selectCount.value < maxSelectCount)
|
||||
v.selected = true
|
||||
})
|
||||
}
|
||||
}
|
||||
const onBackChooseSave = () => {
|
||||
isChooseSave.value = false
|
||||
}
|
||||
// 下载选中项
|
||||
const onConfirm = () => {
|
||||
const downloadList = []
|
||||
if (selectCount.value > 0) {
|
||||
list.forEach((v, i) => {
|
||||
if (v.selected) {
|
||||
v.selected = false
|
||||
v.loading = true
|
||||
downloadList.push({
|
||||
index: i,
|
||||
url: v.tryOnUrl
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
onChooseSave()
|
||||
if (downloadList.length > 0) {
|
||||
DownloadImages(
|
||||
downloadList,
|
||||
(count, total, item) => {
|
||||
list[item.index].loading = false
|
||||
list[item.index].downloaded = true
|
||||
console.log('下载成功', count, total, item)
|
||||
},
|
||||
(count, total, item) => {
|
||||
list[item.index].loading = false
|
||||
console.log('下载失败', count, total, item)
|
||||
},
|
||||
(successCount, errCount) => {
|
||||
console.log('下载完成', successCount, errCount)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
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' })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="creation-list">
|
||||
<div class="title" @click="handleOpenShare">Your Creation</div>
|
||||
<div class="list">
|
||||
<my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad">
|
||||
<div class="item" v-for="(v, i) in list" :key="i" @click="onItem(v)">
|
||||
<img v-lazy="v.tryOnUrl" />
|
||||
<div class="corner">
|
||||
<div class="ai" v-if="v.isRegenerated">Gen-AI</div>
|
||||
<div class="tryon" v-else>Try-on</div>
|
||||
</div>
|
||||
<div class="icons">
|
||||
<div @click.stop="onLoveItem(v)">
|
||||
<SvgIcon :name="`love_${v.isFavorite ? '1' : '0'}`" size="27" />
|
||||
</div>
|
||||
<div @click.stop="onDownloadItem(v)">
|
||||
<SvgIcon name="download" size="27" v-show="!v.loading" />
|
||||
<van-loading color="#000" size="3rem" v-show="v.loading" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon-selected" v-show="isChooseSave && v.selected">
|
||||
<SvgIcon name="modelSelected" size="50" />
|
||||
</div>
|
||||
<div class="download-state" v-show="isChooseSave && v.loading">Downloading...</div>
|
||||
<div class="download-state" v-show="isChooseSave && v.downloaded">Downloaded</div>
|
||||
</div>
|
||||
</my-list>
|
||||
</div>
|
||||
<div class="btns" v-show="!visitRecordId">
|
||||
<template v-if="!isChooseSave">
|
||||
<button @click="onChooseSave">Choose to Save</button>
|
||||
<button @click="onContinue">Continue</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="onBackChooseSave">Back</button>
|
||||
<button @click="onConfirm">Confirm ({{ selectCount }}/{{ maxSelectCount }})</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="creation-list">
|
||||
<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">
|
||||
<my-list
|
||||
v-model:loading="loading"
|
||||
v-model:finish="finish"
|
||||
@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="tryon" v-else>Try-on</div>
|
||||
</div> -->
|
||||
<div class="icons" v-if="!isChooseOne">
|
||||
<div @click.stop="onLoveItem(v)">
|
||||
<SvgIcon :name="`love_${v.isFavorite ? '1' : '0'}`" size="27" />
|
||||
</div>
|
||||
<!-- <div @click.stop="onDownloadItem(v)">
|
||||
<SvgIcon name="download" size="27" 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 class="icon-selected" v-show="(isChooseSave || isChooseOne) && v.selected">
|
||||
<SvgIcon name="modelSelected" size="50" />
|
||||
</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="selectCount && isChooseOne && !v.selected"></div>
|
||||
</div>
|
||||
</my-list>
|
||||
</div>
|
||||
<div class="creation-footer" v-if="isChooseOne">
|
||||
<button @click="onContinue">Continue</button>
|
||||
</div>
|
||||
<div class="btns" v-else-if="!visitRecordId">
|
||||
<template v-if="!isChooseSave">
|
||||
<button @click="onChooseSave">Choose to Save</button>
|
||||
<button @click="onContinue">Continue</button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button @click="onBackChooseSave">Back</button>
|
||||
<button @click="onConfirm">Confirm ({{ selectCount }}/{{ maxSelectCount }})</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
.creation-list {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: #e3e3e3;
|
||||
border-radius: 1rem;
|
||||
position: relative;
|
||||
color: #000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .title {
|
||||
font-family: satoshiRegular;
|
||||
font-size: 9rem;
|
||||
text-align: center;
|
||||
line-height: 124%;
|
||||
font-weight: 500;
|
||||
margin: 7.2rem 0;
|
||||
}
|
||||
> .list {
|
||||
flex: 1;
|
||||
margin: 0 3.8rem;
|
||||
overflow: hidden;
|
||||
--border-radius: 2rem;
|
||||
> .my-list {
|
||||
padding: 0 6rem;
|
||||
--my-list-footer-margin: 0 0 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-content: flex-start;
|
||||
.item {
|
||||
width: 47%;
|
||||
height: 62.2rem;
|
||||
// overflow: hidden;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: #fff;
|
||||
margin-bottom: 4rem;
|
||||
border: 0.1rem solid #000;
|
||||
position: relative;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
> .corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12.6rem;
|
||||
height: 3.6rem;
|
||||
font-family: satoshiBold;
|
||||
font-size: 1.6rem;
|
||||
border-bottom-left-radius: 1.6rem;
|
||||
border-top-right-radius: var(--border-radius);
|
||||
.creation-list {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background-color: #f6f6f6;
|
||||
border-radius: 1rem;
|
||||
position: relative;
|
||||
color: #000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .title {
|
||||
font-family: 'satoshiBold';
|
||||
font-size: 8.6rem;
|
||||
text-align: center;
|
||||
line-height: 124%;
|
||||
margin: 3.8rem 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 {
|
||||
flex: 1;
|
||||
margin: 0 3.8rem;
|
||||
overflow: hidden;
|
||||
--border-radius: 2rem;
|
||||
> .my-list {
|
||||
padding: 0 6rem;
|
||||
--my-list-footer-margin: 0 0 2rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-content: flex-start;
|
||||
.item {
|
||||
width: 47%;
|
||||
height: 62.2rem;
|
||||
border-radius: var(--border-radius);
|
||||
background-color: #fff;
|
||||
margin-bottom: 4rem;
|
||||
// overflow: hidden;
|
||||
position: relative;
|
||||
&.active {
|
||||
border: 0.3rem solid #d9d9d9;
|
||||
}
|
||||
&[type='Outfit'] {
|
||||
height: 50rem;
|
||||
> img {
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
> .corner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
> div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 12.6rem;
|
||||
height: 3.6rem;
|
||||
font-family: satoshiBold;
|
||||
font-size: 1.6rem;
|
||||
border-bottom-left-radius: 1.6rem;
|
||||
border-top-right-radius: var(--border-radius);
|
||||
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
> .ai {
|
||||
color: #646464;
|
||||
background: linear-gradient(
|
||||
137.95deg,
|
||||
#7a96ac 2.28%,
|
||||
#eaeff3 19.8%,
|
||||
#c2d4e1 32.94%,
|
||||
#ffffff 50.16%,
|
||||
#d4dee5 62.15%,
|
||||
#abbdc8 78.69%,
|
||||
#bccad7 95.24%
|
||||
),
|
||||
linear-gradient(0deg, rgba(230, 219, 219, 0.5), rgba(230, 219, 219, 0.5));
|
||||
}
|
||||
}
|
||||
> .icons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 1.7rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
> div {
|
||||
margin-bottom: 2.2rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 1rem;
|
||||
border: 0.2rem solid #000;
|
||||
--svg-icon-color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
> .icon-selected {
|
||||
position: absolute;
|
||||
bottom: -2rem;
|
||||
right: -2rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
> .download-state {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: var(--border-radius);
|
||||
color: #fff;
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .btns {
|
||||
margin: 9rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
> .ai {
|
||||
color: #646464;
|
||||
background: linear-gradient(0deg, rgba(230, 219, 219, 0.5), rgba(230, 219, 219, 0.5)),
|
||||
linear-gradient(
|
||||
165.5deg,
|
||||
#7a96ac 2.28%,
|
||||
#eaeff3 19.8%,
|
||||
#c2d4e1 32.94%,
|
||||
#ffffff 50.16%,
|
||||
#d4dee5 62.15%,
|
||||
#abbdc8 78.69%,
|
||||
#bccad7 95.24%
|
||||
);
|
||||
}
|
||||
}
|
||||
> .icons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 1.7rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
> div {
|
||||
margin-bottom: 2.2rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
border-radius: 1rem;
|
||||
border: 0.2rem solid rgba(0, 0, 0, 0.5);
|
||||
--svg-icon-color: #000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
> .icon-selected {
|
||||
position: absolute;
|
||||
bottom: -2rem;
|
||||
right: -2rem;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
}
|
||||
> .download-state {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border-radius: var(--border-radius);
|
||||
color: #fff;
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> .btns {
|
||||
margin: 5.4rem 0 6.4rem 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
> button {
|
||||
box-sizing: content-box;
|
||||
font-family: satoshiRegular;
|
||||
width: 35rem;
|
||||
height: 8rem;
|
||||
border-radius: 1.3rem;
|
||||
border: none;
|
||||
background: #000;
|
||||
font-weight: 400;
|
||||
font-size: 4.2rem;
|
||||
margin: 0 3.25rem;
|
||||
color: #fff;
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
> button {
|
||||
box-sizing: content-box;
|
||||
font-family: satoshiRegular;
|
||||
width: 35rem;
|
||||
height: 8rem;
|
||||
border-radius: 1.3rem;
|
||||
border: none;
|
||||
background: #000;
|
||||
font-weight: 400;
|
||||
font-size: 4.2rem;
|
||||
margin: 0 3.25rem;
|
||||
color: #fff;
|
||||
&:active {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
.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>
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
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 CreationDetails from '@/views/Workshop/creation/creation-details.vue'
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
import { FlowType } from '@/types/enum'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const styleUrl = computed(() => router.currentRoute.value.query.styleUrl);
|
||||
const emit = defineEmits(['view-type'])
|
||||
watch(
|
||||
() => router.currentRoute.value,
|
||||
() => emit('view-type', 1)
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
const route = useRoute()
|
||||
const styleUrl = computed(() => router.currentRoute.value.query.styleUrl)
|
||||
const isChooseOne = computed(() => route.query.flowType === FlowType.HISTORY)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<creation-list v-show="!styleUrl" />
|
||||
<creation-list v-show="!styleUrl" :is-choose-one="isChooseOne" />
|
||||
<creation-details v-if="styleUrl" />
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
<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 gradientButton from '@/components/gradientButton.vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import {
|
||||
generateTryOnEffect,
|
||||
generateTryOnEffectDemo,
|
||||
setTryOnEffectFavorite,
|
||||
cancelTryOnEffectFavorite
|
||||
} from '@/api/workshop'
|
||||
const emit = defineEmits(['viewType'])
|
||||
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 hGenerateStore = useHGenerateStore()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
const isDemo = computed(() => route.query.demo === '1')
|
||||
const customizeInfo = isDemo.value ? generateStore.customizeInfoDemo : generateStore.customizeInfo
|
||||
const query = computed(() => route.query)
|
||||
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
|
||||
const customizeInfo = isHistoryFlow.value
|
||||
? hGenerateStore.customizeInfo
|
||||
: generateStore.customizeInfo
|
||||
const loading = ref(false)
|
||||
const onSend = () => {
|
||||
if (customizeInfo.inputText === '') return
|
||||
@@ -38,69 +39,47 @@
|
||||
customizeInfo.oldInputText = customizeInfo.inputText
|
||||
customizeInfo.oldTryOnId = customizeInfo.tryOnId
|
||||
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 = {
|
||||
customerId: generateStore.customerId,
|
||||
visitRecordId: generateStore.visitRecordId,
|
||||
styleId: generateStore.styleId,
|
||||
// modelPhotoId: generateStore.modelPhotoId,
|
||||
originalTryOnId: type === 'reload' ? customizeInfo.oldTryOnId : generateStore.originalTryOnId,
|
||||
isRegenerated: 1,
|
||||
prompt: customizeInfo.inputText
|
||||
}
|
||||
if (generateStore.customerPhotoId && customizeInfo.count === 0)
|
||||
data['customerPhotoId'] = generateStore.customerPhotoId
|
||||
generateTryOnEffect(data)
|
||||
.then((res: any) => {
|
||||
customizeInfo.count++
|
||||
customizeInfo.tryOnId = res.tryOnId
|
||||
customizeInfo.tryOnUrl = res.tryOnUrl
|
||||
customizeInfo.styleUrl = res.styleUrl
|
||||
customizeInfo.isRegenerated = res.isRegenerated
|
||||
customizeInfo.isFavorite = !!res.isFavorite
|
||||
loading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
loading.value = false
|
||||
})
|
||||
const data = {
|
||||
customerId: generateStore.customerId,
|
||||
visitRecordId: generateStore.visitRecordId,
|
||||
// styleId: generateStore.styleId,
|
||||
// modelPhotoId: generateStore.modelPhotoId,
|
||||
// originalTryOnId: type === 'reload' ? customizeInfo.oldTryOnId : generateStore.originalTryOnId,
|
||||
isRegenerated: 1,
|
||||
prompt: customizeInfo.inputText
|
||||
}
|
||||
if (generateStore.customerPhotoId && customizeInfo.count === 0) {
|
||||
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)
|
||||
.then((res: any) => {
|
||||
customizeInfo.count++
|
||||
customizeInfo.tryOnId = res.id
|
||||
customizeInfo.tryOnUrl = res.tryOnUrl
|
||||
customizeInfo.styleUrl = res.styleUrl
|
||||
customizeInfo.isRegenerated = res.isRegenerated
|
||||
customizeInfo.isFavorite = !!res.isFavorite
|
||||
loading.value = false
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
if (data['customerPhotoId']) router.back()
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
if (customizeInfo.tryOnId === '') generate()
|
||||
|
||||
// 喜欢
|
||||
const isLoveLoading = ref(false)
|
||||
const onLove = () => {
|
||||
if (isDemo.value) return
|
||||
if (isLoveLoading.value) return
|
||||
const http = customizeInfo.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
|
||||
customizeInfo.isFavorite = !customizeInfo.isFavorite
|
||||
@@ -111,6 +90,7 @@
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
customizeInfo.isFavorite = !customizeInfo.isFavorite
|
||||
isLoveLoading.value = false
|
||||
})
|
||||
}
|
||||
@@ -121,21 +101,29 @@
|
||||
router.back()
|
||||
}
|
||||
const onFinish = () => {
|
||||
if (isDemo.value) {
|
||||
router.push({ name: 'end' })
|
||||
} else {
|
||||
router.push({ name: 'creation' })
|
||||
// router.push({ name: 'creation', query: query.value })
|
||||
const query_ = {
|
||||
...query.value,
|
||||
active: FlowType.H_AI
|
||||
}
|
||||
router.push({ name: 'creation', query: query_ })
|
||||
// if (isHistoryFlow.value) {
|
||||
// router.push({ name: 'end' })
|
||||
// } else {
|
||||
// router.push({ name: 'creation' })
|
||||
// }
|
||||
}
|
||||
// 选择另一个穿搭
|
||||
const onChooseOutfit = () => {
|
||||
generateStore.clearTryOn()
|
||||
router.push({ name: 'SelectStyle' })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<div class="loading" v-if="loading"><generate-loading /></div>
|
||||
<div class="loading" v-if="loading">
|
||||
<generate-loading title="Generating Results..." />
|
||||
</div>
|
||||
<div class="customize" v-else>
|
||||
<div class="title">Customize your Look!</div>
|
||||
<p class="tip">Refine your Look</p>
|
||||
@@ -151,13 +139,14 @@
|
||||
</div>
|
||||
<div class="card">
|
||||
<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="label">History</div>
|
||||
<div class="icon"><SvgIcon name="xialajiantou" size="29" /></div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="icons">
|
||||
<div @click="onLove" v-if="!isDemo">
|
||||
<div @click="onLove">
|
||||
<SvgIcon :name="`love_${customizeInfo.isFavorite ? 1 : 0}`" size="35" />
|
||||
</div>
|
||||
<div @click="onReload" v-show="customizeInfo.oldInputText">
|
||||
@@ -167,12 +156,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<button v-show="!isDemo" @click="onChooseOutfit">Choose Outfit</button>
|
||||
<span></span>
|
||||
<button @click="onFinish">Finish</button>
|
||||
<button class="choose-outfit" v-if="!isHistoryFlow" @click="onChooseOutfit">
|
||||
<span>Choose Outfit</span>
|
||||
</button>
|
||||
<button class="finish" @click="onFinish"><span>Finish</span></button>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -245,7 +234,7 @@
|
||||
margin-top: 5rem;
|
||||
width: 73rem;
|
||||
height: 109.5rem;
|
||||
border-radius: 2rem;
|
||||
border-radius: 1rem;
|
||||
// box-shadow: 1.3rem 1.4rem 2rem 0.2rem #0000004d;
|
||||
border: 0.2rem solid #d9d9d9;
|
||||
overflow: hidden;
|
||||
@@ -257,6 +246,10 @@
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
> .history-icon {
|
||||
top: 4.2rem;
|
||||
left: 4.2rem;
|
||||
}
|
||||
> .select-box {
|
||||
top: 1.8rem;
|
||||
left: 1.8rem;
|
||||
@@ -277,15 +270,15 @@
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
> .icons {
|
||||
bottom: 0.27rem;
|
||||
bottom: 3.6rem;
|
||||
right: 0;
|
||||
height: 10rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> div {
|
||||
margin-right: 1.5rem;
|
||||
margin-right: 2.4rem;
|
||||
width: 6.2rem;
|
||||
height: 6.2rem;
|
||||
border-radius: 1rem;
|
||||
@@ -298,30 +291,49 @@
|
||||
}
|
||||
}
|
||||
> .btns {
|
||||
margin-top: 5rem;
|
||||
width: 70%;
|
||||
margin-top: 4rem;
|
||||
min-width: 68%;
|
||||
display: flex;
|
||||
// justify-content: center;
|
||||
justify-content: space-between;
|
||||
> button {
|
||||
box-sizing: content-box;
|
||||
font-family: satoshiRegular;
|
||||
// margin: 0 1.8rem;
|
||||
padding: 0;
|
||||
font-family: satoshiMedium;
|
||||
border: none;
|
||||
// margin: 0 5.2rem 0 auto;
|
||||
// min-width: 18rem;
|
||||
width: 34rem;
|
||||
// padding: 0 3.5rem;
|
||||
height: 6.9rem;
|
||||
border-radius: 1.3rem;
|
||||
background: #000;
|
||||
font-weight: 400;
|
||||
font-size: 3.89rem;
|
||||
color: #fff;
|
||||
height: 9.2rem;
|
||||
border-radius: 1rem;
|
||||
font-weight: 500;
|
||||
font-size: 4rem;
|
||||
&:active {
|
||||
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>
|
||||
|
||||
@@ -1,29 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const router = useRouter()
|
||||
const emit = defineEmits(['view-type'])
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
|
||||
const onExit = () => {
|
||||
console.log('exit')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title />
|
||||
<div class="end">
|
||||
<div class="content">
|
||||
<div class="title">Thank you.</div>
|
||||
<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>
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -40,21 +35,19 @@
|
||||
background-size: cover;
|
||||
> .content {
|
||||
position: absolute;
|
||||
top: 12.9rem;
|
||||
left: 6rem;
|
||||
// width: 100%;
|
||||
top: 15.4rem;
|
||||
left: 9.6rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .title {
|
||||
font-family: satoshiBold;
|
||||
font-size: 14.7rem;
|
||||
font-size: 11rem;
|
||||
line-height: 124%;
|
||||
}
|
||||
> .tip {
|
||||
margin-top: 2.5rem;
|
||||
width: 58.2rem;
|
||||
font-family: satoshiRegular;
|
||||
font-size: 4.1rem;
|
||||
font-size: 4rem;
|
||||
line-height: 132%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,126 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
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'
|
||||
const router = useRouter()
|
||||
import { showConfirmDialog } from 'vant'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
import { FlowType } from '@/types/enum'
|
||||
|
||||
//const props = defineProps({
|
||||
//})
|
||||
const emit = defineEmits([
|
||||
'view-type'
|
||||
])
|
||||
|
||||
// const data = reactive({
|
||||
// })
|
||||
|
||||
const clickSwitchVIPID = ()=>{
|
||||
showConfirmDialog({
|
||||
title: 'Switch VIP ID?',
|
||||
message: 'You have unsaved changes. Your progress will be lost.',
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'Cancel',
|
||||
})
|
||||
.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 } })
|
||||
}
|
||||
const newJourney = ()=>{
|
||||
router.push(`/workshop/styList/index?flowType=${FlowType.MAIN}`)
|
||||
}
|
||||
|
||||
const historicalReview = ()=>{
|
||||
router.push(`/workshop/creation?flowType=${FlowType.HISTORY}`)
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
emit('view-type', 1)
|
||||
})
|
||||
onUnmounted(()=>{
|
||||
})
|
||||
defineExpose({})
|
||||
// const { } = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<div class="homeNavPage">
|
||||
<div class="title">
|
||||
Welcome Back,
|
||||
What can I help you today?
|
||||
<div class="homePage">
|
||||
<img class="homeBg" src="@/assets/images/homeBg.png" alt="">
|
||||
<div class="button">
|
||||
<div class="item" @click="newJourney">New Journey</div>
|
||||
<div class="item" @click="historicalReview">Historical Review</div>
|
||||
</div>
|
||||
<div class="navBox">
|
||||
<div class="navTitle">
|
||||
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 class="info">
|
||||
Powered by AiDLab for Lane Crawford
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.header-title {
|
||||
--header-title-background: #fff;
|
||||
--header-title-height: 12rem !important;
|
||||
}
|
||||
.homeNavPage{
|
||||
> .title{
|
||||
padding: 0 8.4rem;
|
||||
font-family: satoshiBold;
|
||||
font-weight: 700;
|
||||
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;
|
||||
.homePage{
|
||||
position: relative;
|
||||
flex: 1;
|
||||
> .homeBg{
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
> .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%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
> .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>
|
||||
192
src/views/Workshop/homeNav.vue
Normal 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>
|
||||
@@ -1,10 +1,37 @@
|
||||
<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'
|
||||
//const props = defineProps({
|
||||
//})
|
||||
//const emit = defineEmits([
|
||||
//])
|
||||
import profile from './profile.vue'
|
||||
const props = defineProps({})
|
||||
const emit = defineEmits([])
|
||||
const profileRef = ref(null)
|
||||
const handleClickProfile = () => {
|
||||
profileRef.value.open()
|
||||
}
|
||||
const notShowFooter = computed(() => route.query.flowType !== FlowType.HISTORY)
|
||||
</script>
|
||||
<template>
|
||||
<RouteCache />
|
||||
</template>
|
||||
<div class="workshop">
|
||||
<header-title @clickProfile="handleClickProfile" />
|
||||
<RouteCache view-type="1" />
|
||||
<footer-navigation v-if="notShowFooter" />
|
||||
</div>
|
||||
<profile ref="profileRef" />
|
||||
</template>
|
||||
<style scoped lang="less">
|
||||
.workshop {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .routeCache {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,19 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
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 router from '@/router'
|
||||
import { FormatDate } from '@/utils/tools'
|
||||
import { getCustomerPhotos, deleteCustomerPhoto } from '@/api/workshop'
|
||||
const emit = defineEmits(['view-type'])
|
||||
import { showConfirmDialog } from 'vant'
|
||||
import { useGenerateStore } from '@/stores'
|
||||
const generateStore = useGenerateStore()
|
||||
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
const loading = ref(false)
|
||||
const finish = ref(false)
|
||||
const list = reactive([])
|
||||
@@ -49,7 +43,7 @@
|
||||
cancelButtonText: 'Cancel'
|
||||
}).catch(() => 0)
|
||||
if (res === 0) return
|
||||
console.log(obj,i)
|
||||
console.log(obj, i)
|
||||
deleteCustomerPhoto(obj.visitRecordId)
|
||||
.then(() => {
|
||||
list.splice(i, 1)
|
||||
@@ -67,11 +61,15 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<div class="library">
|
||||
<div class="title">Library</div>
|
||||
<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="image">
|
||||
<img v-lazy="v.defaultImageUrl" />
|
||||
@@ -87,7 +85,6 @@
|
||||
</my-list>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -119,18 +116,18 @@
|
||||
}
|
||||
|
||||
> .title {
|
||||
font-family: satoshiRegular;
|
||||
font-size: 9rem;
|
||||
font-family: satoshiBold;
|
||||
font-size: 8.6rem;
|
||||
text-align: left;
|
||||
line-height: 124%;
|
||||
margin: 5rem;
|
||||
margin: 5rem 9.1rem;
|
||||
}
|
||||
> .list {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin: 0 3rem;
|
||||
margin: 0 4.5rem;
|
||||
> .my-list {
|
||||
padding: 0 3.8rem;
|
||||
padding: 0 4.5rem;
|
||||
--my-list-footer-margin: 2rem 0;
|
||||
> .item {
|
||||
position: relative;
|
||||
@@ -146,7 +143,7 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
> .image {
|
||||
width: 22.9rem;
|
||||
width: 19.2rem;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
border-radius: 2rem;
|
||||
@@ -154,7 +151,7 @@
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -163,41 +160,33 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 90%;
|
||||
> .userID {
|
||||
font-family: satoshiRegular;
|
||||
font-weight: 500;
|
||||
font-size: 3.8rem;
|
||||
line-height: 89%;
|
||||
color: #000;
|
||||
}
|
||||
> .datetime {
|
||||
margin-top: 1.8rem;
|
||||
font-family: satoshiRegular;
|
||||
font-weight: 400;
|
||||
margin-top: 1rem;
|
||||
font-family: satoshiMedium;
|
||||
font-weight: 500;
|
||||
font-size: 3.2rem;
|
||||
line-height: 89%;
|
||||
color: #000;
|
||||
}
|
||||
> .lastopened {
|
||||
margin-top: 1rem;
|
||||
margin-top: 1.8rem;
|
||||
font-family: satoshiRegular;
|
||||
font-weight: 400;
|
||||
font-style: Regular;
|
||||
font-size: 2.6rem;
|
||||
line-height: 89%;
|
||||
color: #6f6f6f;
|
||||
}
|
||||
> button {
|
||||
margin-top: auto;
|
||||
font-family: satoshiRegular;
|
||||
width: 12.3rem;
|
||||
height: 3.8rem;
|
||||
border-radius: 0.5rem;
|
||||
box-sizing: content-box;
|
||||
border: 0.193rem solid #000;
|
||||
line-height: 100%;
|
||||
border-radius: 0.4rem;
|
||||
border: 0.2rem solid #000;
|
||||
background: transparent;
|
||||
font-family: satoshiRegular;
|
||||
font-weight: 400;
|
||||
font-size: 2.576rem;
|
||||
font-size: 2.4rem;
|
||||
color: #000;
|
||||
margin-right: 5rem;
|
||||
&:active {
|
||||
@@ -211,8 +200,8 @@
|
||||
right: 2rem;
|
||||
width: 5.5rem;
|
||||
height: 5.5rem;
|
||||
border: 0.188rem solid #000;
|
||||
border-radius: 1.88rem;
|
||||
border: 0.188rem solid rgba(0, 0, 0, 0.5);
|
||||
border-radius: 0.9rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, reactive, ref, toRefs } from "vue";
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { onMounted, onUnmounted, reactive, ref, toRefs, computed } from "vue";
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
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 { FlowType, IsHistoryFlow } from '@/types/enum'
|
||||
import gradientButton from '@/components/gradientButton.vue'
|
||||
|
||||
const router = useRouter()
|
||||
//const props = defineProps({
|
||||
//})
|
||||
const emit = defineEmits(['view-type'])
|
||||
let data = reactive({
|
||||
modelList:
|
||||
[
|
||||
@@ -17,8 +17,11 @@ let data = reactive({
|
||||
],
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
const query = computed(() => router.currentRoute.value.query)
|
||||
const generateStore = useGenerateStore()
|
||||
const hGenerateStore = useHGenerateStore()
|
||||
|
||||
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
|
||||
|
||||
const vanDialogShow = ref(false)
|
||||
const feedbackForm = ref({
|
||||
@@ -32,7 +35,11 @@ const feedbackForm = ref({
|
||||
|
||||
|
||||
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 = ()=>{
|
||||
@@ -44,7 +51,7 @@ const startGenerate = ()=>{
|
||||
let value = {
|
||||
customerId:generateStore.customerId,
|
||||
visitRecordId:generateStore.visitRecordId,
|
||||
styleId:generateStore.styleId,
|
||||
styleId:isHistoryFlow.value ? hGenerateStore.styleId : generateStore.styleId,
|
||||
// customerPhotoId:null,
|
||||
// modelPhotoId:null,
|
||||
// prompt:null,
|
||||
@@ -54,10 +61,9 @@ const startGenerate = ()=>{
|
||||
generateTryOnEffect(value).then((res:any)=>{
|
||||
data.isLoading = false;
|
||||
generateStore.originalTryOn.isLike = false
|
||||
generateStore.originalTryOn.id = res.tryOnId
|
||||
generateStore.originalTryOn.id = res.id
|
||||
generateStore.originalTryOn.tryOnUrl = res.tryOnUrl
|
||||
generateStore.useStyleGenerate()//生成后需要对选择衣服页面设置不可选中样式
|
||||
generateStore.setIsGenerate(false)
|
||||
// generateStore.useStyleGenerate()//生成后需要对选择衣服页面设置不可选中样式
|
||||
|
||||
generateStore.clearCustomizeInfo()
|
||||
}).catch((error)=>{
|
||||
@@ -113,8 +119,7 @@ const handleSubmit = ()=>{
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
if (generateStore.isGenerate) {
|
||||
if (!generateStore.originalTryOn.id) {
|
||||
startGenerate()
|
||||
}
|
||||
})
|
||||
@@ -124,11 +129,10 @@ defineExpose({})
|
||||
const { isLoading } = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<div class="product" v-if="!isLoading">
|
||||
<div class="text">
|
||||
<div class="title">
|
||||
Go with this Look?
|
||||
{{ isHistoryFlow?'Generate Result':'Go with this look?' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="selectContent">
|
||||
@@ -144,18 +148,28 @@ const { isLoading } = toRefs(data);
|
||||
<div class="operation">
|
||||
<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="startGenerate"><SvgIcon name="reload" size="35" /></div>
|
||||
<!-- <div><SvgIcon name="download" size="35" /></div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="again">
|
||||
<!-- <div @click="changeModel">Change Model</div> -->
|
||||
<button @click="onContinue" class="general_button smail" style="margin-left: auto;">Continue</button>
|
||||
<div class="btn">
|
||||
<div class="btnItem style1" @click.stop="startGenerate()">
|
||||
<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>
|
||||
<footer-navigation is-placeholder v-if="!isLoading"/>
|
||||
<van-dialog v-model:show="vanDialogShow" :show-confirm-button="false" width="52.8rem">
|
||||
<div class="feedback">
|
||||
<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">
|
||||
<GenerateLoading title="Generating Results..." />
|
||||
</div>
|
||||
<footer-navigation is-placeholder/>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.product{
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
> .text{
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: 4.3rem;
|
||||
margin-top: 4rem;
|
||||
> .title{
|
||||
font-family: satoshiBold;
|
||||
font-weight: 700;
|
||||
font-size: 9.6rem;
|
||||
font-size: 8.6rem;
|
||||
line-height: 124%;
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
> .selectContent{
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 14.1rem;
|
||||
margin-top: 4.8rem;
|
||||
margin-top: 4.3rem;
|
||||
> .history{
|
||||
width: 30.2rem;
|
||||
margin-left: calc(7% / 2);
|
||||
height: 6.52rem;
|
||||
border-radius: 0.91rem;
|
||||
border: 0.24rem solid #000;
|
||||
@@ -224,7 +239,7 @@ const { isLoading } = toRefs(data);
|
||||
}
|
||||
}
|
||||
> .modelBox{
|
||||
margin-top: 2.5rem;
|
||||
margin-top: 4.4rem;
|
||||
> .model{
|
||||
border: .2rem solid #D9D9D9;
|
||||
// height: 110rem;
|
||||
@@ -257,15 +272,40 @@ const { isLoading } = toRefs(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
> .again{
|
||||
margin-top: 4.4rem;
|
||||
> .btn{
|
||||
display: flex;
|
||||
gap: 6.6rem;
|
||||
justify-content: center;
|
||||
> div{
|
||||
margin-right: 3.9rem;
|
||||
&:last-child{
|
||||
margin-right: 0;
|
||||
margin-top: 5.2rem;
|
||||
> div {
|
||||
border-radius: .96rem;
|
||||
width: 33.7rem;
|
||||
font-size: 4.8rem;
|
||||
font-family: satoshiMedium;
|
||||
line-height: 9.2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&.style1{
|
||||
--borderRadius: .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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -314,6 +354,7 @@ const { isLoading } = toRefs(data);
|
||||
font-weight: 700;
|
||||
line-height: 2rem;
|
||||
margin-bottom: 1.8rem;
|
||||
color: #000;
|
||||
}
|
||||
> .info{
|
||||
font-size: 3.2rem;
|
||||
|
||||
@@ -1,243 +1,531 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, inject } from 'vue'
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import router from '@/router'
|
||||
import { showConfirmDialog } from 'vant'
|
||||
import { useUserInfoStore } from '@/stores'
|
||||
import { LogOut } from '@/api/login'
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const emit = defineEmits(['view-type'])
|
||||
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 }
|
||||
})
|
||||
import { ref, reactive, onMounted, inject, watch } from 'vue'
|
||||
import router from '@/router'
|
||||
import { showConfirmDialog, showToast } from 'vant'
|
||||
import { useUserInfoStore, useOverallStore, useGenerateStore } from '@/stores'
|
||||
import { LogOut } from '@/api/login'
|
||||
import { getCustomerList, type CustomerListParams, customerCheckin } from '@/api/workshop'
|
||||
import MyEvent from '@/utils/myEvent'
|
||||
import { encryptPassword } from '@/utils/tools'
|
||||
import { updateUserInfo } from '@/api/login'
|
||||
|
||||
const onEditItem = (item) => {
|
||||
if (!form[item]) return
|
||||
form[item].edit = true
|
||||
}
|
||||
const onSaveItem = (item) => {
|
||||
if (!form[item]) return
|
||||
form[item].edit = false
|
||||
}
|
||||
const logout = () => {
|
||||
showConfirmDialog({
|
||||
title: 'Log out',
|
||||
message: 'Are you sure you want to log out?',
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'Cancel'
|
||||
})
|
||||
.then(() => {
|
||||
LogOut().then(() => {
|
||||
userInfoStore.logOut()
|
||||
router.push({ path: '/welcome' })
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
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
|
||||
form[item].edit = true
|
||||
}
|
||||
const onSaveItem = (item) => {
|
||||
if (!form[item]) return
|
||||
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
|
||||
}
|
||||
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({
|
||||
title: 'Log out',
|
||||
message: 'Are you sure you want to log out?',
|
||||
confirmButtonText: 'Yes',
|
||||
cancelButtonText: 'Cancel'
|
||||
})
|
||||
.then(() => {
|
||||
LogOut().then(() => {
|
||||
userInfoStore.logOut()
|
||||
router.push({ path: '/welcome' })
|
||||
})
|
||||
})
|
||||
.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>
|
||||
|
||||
<template>
|
||||
<header-title style-type="2" :is-placeholder="false" />
|
||||
<div class="profile">
|
||||
<div class="header-bg">
|
||||
<svg
|
||||
width="1079"
|
||||
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 class="profile">
|
||||
<!-- <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="label">Your Name</div>
|
||||
<label class="input">
|
||||
<div class="icon"><SvgIcon name="user" size="50" /></div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
v-model="form.name.value"
|
||||
:readonly="!form.name.edit"
|
||||
/>
|
||||
<!-- <div class="icon" v-if="form.name.edit" @click.stop="onSaveItem('name')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('name')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div> -->
|
||||
</label>
|
||||
<p class="error" v-show="form.name.msg">{{ form.name.msg }}</p>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="label">Your Email</div>
|
||||
<label class="input">
|
||||
<div class="icon"><SvgIcon name="email" size="50" /></div>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
v-model="form.email.value"
|
||||
:readonly="!form.email.edit"
|
||||
required
|
||||
/>
|
||||
<!-- <div class="icon" v-if="form.email.edit" @click.stop="onSaveItem('email')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('email')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div> -->
|
||||
</label>
|
||||
<p class="error" v-show="form.email.msg">{{ form.email.msg }}</p>
|
||||
</div>
|
||||
<!-- <div class="form-item">
|
||||
<div class="label">Password</div>
|
||||
<label class="input">
|
||||
<div class="icon" @click="form.password.show = !form.password.show">
|
||||
<SvgIcon :name="form.password.show ? 'password_1' : 'password_0'" size="50" />
|
||||
</div>
|
||||
<input
|
||||
:type="form.password.show ? 'text' : 'password'"
|
||||
placeholder="Enter your password"
|
||||
v-model="form.password.value"
|
||||
:readonly="!form.password.edit"
|
||||
/>
|
||||
<div class="icon" v-if="form.password.edit" @click.stop="onSaveItem('password')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('password')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div>
|
||||
</label>
|
||||
<p class="error" v-show="form.password.msg">{{ form.password.msg }}</p>
|
||||
</div> -->
|
||||
<button class="logout" @click="logout">Log out</button>
|
||||
</div>
|
||||
<footer-navigation is-placeholder />
|
||||
<van-popup class="van-popup" v-model:show="show" position="bottom" round>
|
||||
<div class="profile">
|
||||
<div class="header">
|
||||
<span class="title">Profile</span>
|
||||
<van-icon name="cross" class="close" @click="close" />
|
||||
</div>
|
||||
<form class="box" @submit.prevent.stop="confirm">
|
||||
<div class="form-item">
|
||||
<div class="label">Your Name</div>
|
||||
<label class="input">
|
||||
<div class="icon"><SvgIcon name="user" size="64" /></div>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Enter your name"
|
||||
v-model="form.name.value"
|
||||
:readonly="!isEdit"
|
||||
required
|
||||
/>
|
||||
<!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('name')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('name')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div> -->
|
||||
</label>
|
||||
<p class="error" v-show="form.name.msg">{{ form.name.msg }}</p>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="label">Your Email</div>
|
||||
<label class="input">
|
||||
<div class="icon"><SvgIcon name="email" size="64" /></div>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Enter your email"
|
||||
v-model="form.email.value"
|
||||
:readonly="!isEdit"
|
||||
required
|
||||
/>
|
||||
<!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('email')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('email')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div> -->
|
||||
</label>
|
||||
<p class="error" v-show="form.email.msg">{{ form.email.msg }}</p>
|
||||
</div>
|
||||
<div class="form-item" v-if="isEdit">
|
||||
<div class="label">Password</div>
|
||||
<label class="input">
|
||||
<div class="icon" @click="form.password.show = !form.password.show">
|
||||
<SvgIcon :name="form.password.show ? 'password_1' : 'password_0'" size="64" />
|
||||
</div>
|
||||
<input
|
||||
:type="form.password.show ? 'text' : 'password'"
|
||||
placeholder="Enter your password"
|
||||
v-model="form.password.value"
|
||||
:readonly="!isEdit"
|
||||
/>
|
||||
<!-- <div class="icon" v-if="isEdit" @click.stop="onSaveItem('password')">
|
||||
<SvgIcon name="confirmation" size="37" />
|
||||
</div>
|
||||
<div class="icon" v-else @click="onEditItem('password')">
|
||||
<SvgIcon name="edit" size="37" />
|
||||
</div> -->
|
||||
</label>
|
||||
<p class="error" v-show="form.password.msg">{{ form.password.msg }}</p>
|
||||
</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>
|
||||
</template>
|
||||
</form>
|
||||
</div>
|
||||
</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>
|
||||
|
||||
<style scoped lang="less">
|
||||
.profile {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .header-bg {
|
||||
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;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
--svg-icon-color: #fff;
|
||||
}
|
||||
}
|
||||
> .title {
|
||||
margin-top: 3.7rem;
|
||||
font-family: 'satoshiBold';
|
||||
font-size: 4.1rem;
|
||||
line-height: 120%;
|
||||
color: #262422;
|
||||
}
|
||||
> .sub {
|
||||
font-family: satoshiRegular;
|
||||
font-size: 2.9rem;
|
||||
line-height: 120%;
|
||||
margin-top: 1.6rem;
|
||||
margin-bottom: 2.4rem;
|
||||
color: #ababab;
|
||||
}
|
||||
> .form-item {
|
||||
margin: 4.2rem auto 0;
|
||||
width: 68rem;
|
||||
> .label {
|
||||
margin-bottom: 2.5rem;
|
||||
font-family: satoshiBold;
|
||||
font-size: 2.9rem;
|
||||
line-height: 120%;
|
||||
color: #262422;
|
||||
}
|
||||
> .input {
|
||||
width: 100%;
|
||||
height: 11.5rem;
|
||||
border-radius: 2.5rem;
|
||||
border: 0.2rem solid #f1ecec;
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0 2.5rem;
|
||||
> * {
|
||||
margin-right: 2.5rem;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
> .icon {
|
||||
// margin: 0 2.5rem;
|
||||
--svg-icon-color: #ababab;
|
||||
}
|
||||
> input {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-family: satoshiMedium;
|
||||
font-size: 2.9rem;
|
||||
color: #000;
|
||||
border: none;
|
||||
&::placeholder {
|
||||
color: #ababab;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .error {
|
||||
margin-top: 1rem;
|
||||
font-family: satoshiRegular;
|
||||
font-size: 2.5rem;
|
||||
line-height: 120%;
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
> .logout {
|
||||
margin-top: 7rem;
|
||||
width: 68rem;
|
||||
height: 11.5rem;
|
||||
border-radius: 2.5rem;
|
||||
background-color: transparent;
|
||||
border: 0.2rem solid #3b3b3b;
|
||||
font-family: satoshiBold;
|
||||
font-size: 3.32rem;
|
||||
line-height: 120%;
|
||||
color: #222;
|
||||
}
|
||||
}
|
||||
.van-popup {
|
||||
max-height: 90%;
|
||||
--van-popup-round-radius: 7.8rem;
|
||||
}
|
||||
.profile {
|
||||
margin: 6.5rem 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
> .header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 0 7rem;
|
||||
margin-bottom: 8rem;
|
||||
> .title {
|
||||
font-family: satoshiBold;
|
||||
font-size: 4.6rem;
|
||||
color: #181725;
|
||||
}
|
||||
> .close {
|
||||
margin-left: auto;
|
||||
font-size: 5rem;
|
||||
color: #a1a1a1;
|
||||
}
|
||||
}
|
||||
> .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 {
|
||||
> .label {
|
||||
margin-bottom: 2.5rem;
|
||||
font-family: satoshiBold;
|
||||
font-size: 2.9rem;
|
||||
line-height: 120%;
|
||||
color: #262422;
|
||||
}
|
||||
> .input {
|
||||
// width: 100%;
|
||||
height: 13.9rem;
|
||||
border-radius: 2.5rem;
|
||||
border: 0.2rem solid #f1ecec;
|
||||
box-sizing: content-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0 2.5rem;
|
||||
> * {
|
||||
margin-right: 2.5rem;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
> .icon {
|
||||
// margin: 0 2.5rem;
|
||||
--svg-icon-color: #ababab;
|
||||
}
|
||||
> input {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
font-family: satoshiMedium;
|
||||
font-size: 3.5rem;
|
||||
color: #000;
|
||||
border: none;
|
||||
&::placeholder {
|
||||
color: #ababab;
|
||||
}
|
||||
}
|
||||
}
|
||||
> .error {
|
||||
margin-top: 1rem;
|
||||
font-family: satoshiRegular;
|
||||
font-size: 2.5rem;
|
||||
line-height: 120%;
|
||||
color: #ff0000;
|
||||
}
|
||||
}
|
||||
> .switch {
|
||||
background-color: transparent;
|
||||
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>
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGenerateStore } from '@/stores'
|
||||
const generateStore = useGenerateStore()
|
||||
const emit = defineEmits(['view-type'])
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const clickNext = () => {
|
||||
generateStore.updatePhotoInfo({})
|
||||
@@ -17,7 +12,6 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title />
|
||||
<!-- 上传照片 -->
|
||||
<div class="upload-face-1">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
||||
@@ -6,9 +6,7 @@ import { useGenerateStore } from '@/stores'
|
||||
const router = useRouter()
|
||||
//const props = defineProps({
|
||||
//})
|
||||
const emit = defineEmits([
|
||||
'view-type'
|
||||
])
|
||||
|
||||
const generateStore = useGenerateStore()
|
||||
let data = reactive({
|
||||
modelList:
|
||||
@@ -45,9 +43,7 @@ let data = reactive({
|
||||
const setSelectedModelId = (item)=>{
|
||||
generateStore.selectModel(item)
|
||||
}
|
||||
onMounted(()=>{
|
||||
emit('view-type', 1)
|
||||
})
|
||||
|
||||
const toProduct = ()=>{
|
||||
router.push({ path: 'product' })
|
||||
}
|
||||
@@ -57,7 +53,6 @@ defineExpose({})
|
||||
const { modelList, selectModel } = toRefs(data);
|
||||
</script>
|
||||
<template>
|
||||
<header-title style-type="2" />
|
||||
<div class="selectModel">
|
||||
<div class="text">
|
||||
<div class="title">
|
||||
|
||||
@@ -1,183 +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">
|
||||
What’s 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 class="general_button smail" @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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
background-color: #f6f6f6;
|
||||
&.placeholder{
|
||||
position: relative;
|
||||
}
|
||||
> button {
|
||||
margin-right: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
391
src/views/Workshop/selectStyle/index.vue
Normal 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>
|
||||
121
src/views/Workshop/selectStyle/styleList.vue
Normal 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>
|
||||
@@ -1,18 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useGenerateStore } from '@/stores'
|
||||
import { IsHistoryFlow } from '@/types/enum'
|
||||
const generateStore = useGenerateStore()
|
||||
const emit = defineEmits(['view-type'])
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const query = computed(() => route.query)
|
||||
const isDemo = computed(() => route.query.demo === '1')
|
||||
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
|
||||
// 上传照片
|
||||
const handleUploadFace = () => {
|
||||
// generateStore.updatePhotoInfo({})
|
||||
@@ -20,23 +15,18 @@
|
||||
}
|
||||
// 跳过上传
|
||||
const handleFinish = () => {
|
||||
if (isDemo.value) {
|
||||
handleUploadFace();
|
||||
} else {
|
||||
generateStore.updatePhotoInfo({})
|
||||
generateStore.clearCustomizeInfo()
|
||||
generateStore.uploadCustomizeInfo({
|
||||
tryOnId: generateStore.originalTryOn.id,
|
||||
tryOnUrl: generateStore.originalTryOn.tryOnUrl,
|
||||
isFavorite: generateStore.originalTryOn.isLike
|
||||
})
|
||||
router.push({ name: 'customize', query: query.value })
|
||||
}
|
||||
generateStore.updatePhotoInfo({})
|
||||
generateStore.clearCustomizeInfo()
|
||||
generateStore.uploadCustomizeInfo({
|
||||
tryOnId: generateStore.originalTryOn.id,
|
||||
tryOnUrl: generateStore.originalTryOn.tryOnUrl,
|
||||
isFavorite: generateStore.originalTryOn.isLike
|
||||
})
|
||||
router.push({ name: 'customize', query: query.value })
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title />
|
||||
<!-- 上传照片 -->
|
||||
<div class="upload-face-1">
|
||||
<img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" />
|
||||
@@ -48,13 +38,18 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="btns">
|
||||
<button class="sandblasted-blurred" @click="handleUploadFace">
|
||||
<button class="sandblasted-blurred flex flex-center" @click="handleUploadFace">
|
||||
<span>Upload Face</span>
|
||||
</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>
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -95,10 +90,7 @@
|
||||
justify-content: center;
|
||||
bottom: 19.7rem;
|
||||
> button {
|
||||
width: 40rem;
|
||||
height: 8.3rem;
|
||||
border-radius: 0.7rem;
|
||||
margin: 0 1.8rem;
|
||||
margin: 0 7.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { ref, reactive, onMounted, computed } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { uploadCustomerPhoto } from '@/api/workshop'
|
||||
import { useGenerateStore } from '@/stores'
|
||||
import { useGenerateStore, useHGenerateStore } from '@/stores'
|
||||
import { IsHistoryFlow } from '@/types/enum'
|
||||
const generateStore = useGenerateStore()
|
||||
const hGenerateStore = useHGenerateStore()
|
||||
|
||||
const emit = defineEmits(['view-type'])
|
||||
onMounted(() => {
|
||||
emit('view-type', 1)
|
||||
})
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const query = computed(() => route.query)
|
||||
const isDemo = computed(() => route.query.demo === '1')
|
||||
const isHistoryFlow = computed(() => IsHistoryFlow(query.value.flowType))
|
||||
const fileData = generateStore.photoInfo
|
||||
if (!fileData.file?.size) generateStore.updatePhotoInfo({})
|
||||
// 上传照片
|
||||
@@ -48,7 +44,7 @@
|
||||
formData.append('file', fileData.file)
|
||||
uploadCustomerPhoto(formData).then((res) => {
|
||||
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 })
|
||||
})
|
||||
}
|
||||
@@ -59,7 +55,6 @@
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header-title />
|
||||
<!-- 展示照片 -->
|
||||
<div class="upload-face-2">
|
||||
<img src="@/assets/images/workshop/bg/picture_bg.png" class="bg" />
|
||||
@@ -83,7 +78,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation is-placeholder />
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -148,9 +142,6 @@
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
> button {
|
||||
width: 34.5rem;
|
||||
height: 8.6rem;
|
||||
border-radius: 4.3rem;
|
||||
margin: 0 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,19 @@
|
||||
<img src="@/assets/images/chat_loading.png" alt="Loading" class="loading-image" />
|
||||
<!-- 阴影效果 -->
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, watch, reactive, toRefs, nextTick, ref } from "vue";
|
||||
import { gsap, TweenMax } from "gsap";
|
||||
|
||||
|
||||
// 这个组件只负责显示loading动画
|
||||
|
||||
// 定义组件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>
|
||||
|
||||
@@ -36,7 +113,7 @@ const props = defineProps({
|
||||
.loading-image {
|
||||
width: 36.4rem;
|
||||
height: 36.4rem;
|
||||
animation: rotate 1s linear infinite;
|
||||
animation: rotate 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loading-shadow {
|
||||
@@ -54,14 +131,29 @@ const props = defineProps({
|
||||
font-size: 4.8rem;
|
||||
letter-spacing: 0.02em;
|
||||
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 {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
0% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
50% {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -197,7 +197,9 @@ const startRecording = () => {
|
||||
// 检查浏览器支持
|
||||
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
||||
// 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
|
||||
return
|
||||
}
|
||||
@@ -296,7 +298,7 @@ const stopRecording = () => {
|
||||
.shortcut-item {
|
||||
font-size: 4.2rem;
|
||||
width: fit-content;
|
||||
font-family: 'robotoRegular';
|
||||
font-family: 'satoshiMedium';
|
||||
white-space: nowrap;
|
||||
height: 8.1rem;
|
||||
line-height: 8.1rem;
|
||||
@@ -351,8 +353,8 @@ const stopRecording = () => {
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 4rem;
|
||||
font-family: 'robotoRegular';
|
||||
font-weight: 400;
|
||||
font-family: 'satoshiMedium';
|
||||
font-weight: 500;
|
||||
line-height: 4.8rem; /* 设置行高等于实际渲染高度,实现垂直居中 */
|
||||
padding: 0;
|
||||
color: #000;
|
||||
@@ -361,8 +363,8 @@ const stopRecording = () => {
|
||||
&::placeholder {
|
||||
color: #888;
|
||||
letter-spacing: -0.01em;
|
||||
font-weight: 400;
|
||||
font-family: 'robotoRegular';
|
||||
font-weight: 500;
|
||||
font-family: 'satoshiMedium';
|
||||
word-spacing: -5px;
|
||||
line-height: 4.8rem;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
<div class="chat-message" :class="{ 'user-message': isMyself, 'ai-message': !isMyself }">
|
||||
<!-- AI消息显示头像 -->
|
||||
<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 class="message-content">
|
||||
<div class="message-text" :class="{ streaming: isStreaming }">
|
||||
<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 v-if="!isMyself" class="message-actions flex">
|
||||
<SvgIcon
|
||||
@@ -27,9 +28,16 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
import WaveLoading from '@/components/WaveLoading.vue'
|
||||
import { useUserInfoStore } from '@/stores'
|
||||
|
||||
const md = new MarkdownIt()
|
||||
|
||||
const userInfoStore = useUserInfoStore()
|
||||
const thumb = computed(() => {
|
||||
return userInfoStore.state.generateParams.stylistImage
|
||||
})
|
||||
|
||||
// 定义消息类型
|
||||
interface ChatMessage {
|
||||
sessionId: string | number
|
||||
@@ -95,13 +103,15 @@ const actionList: ActionItem[] = [
|
||||
display: flex;
|
||||
margin-bottom: 16px;
|
||||
align-items: flex-start;
|
||||
color: #000;
|
||||
.message-text {
|
||||
font-size: 4.2rem;
|
||||
font-family: 'robotoRegular';
|
||||
font-size: 4rem;
|
||||
font-family: 'satoshiMedium';
|
||||
line-height: 121%;
|
||||
font-weight: 400;
|
||||
font-weight: 500;
|
||||
background-color: #efefef;
|
||||
padding: 3.43rem 4.35rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
&.user-message {
|
||||
@@ -126,6 +136,10 @@ const actionList: ActionItem[] = [
|
||||
color: #000;
|
||||
border-radius: 0 2rem 2rem 2rem;
|
||||
word-break: break-word;
|
||||
:deep(strong){
|
||||
font-family: 'satoshiBold';
|
||||
font-size: 4.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,16 +147,17 @@ const actionList: ActionItem[] = [
|
||||
|
||||
.chat-avatar {
|
||||
width: 7.8rem;
|
||||
height: 7.4rem;
|
||||
height: 7.8rem;
|
||||
border-radius: 50%;
|
||||
margin-right: 1.88rem;
|
||||
border: .2rem solid #000;
|
||||
padding: 1.4rem;
|
||||
overflow: hidden;
|
||||
// border: 0.2rem solid #000;
|
||||
// padding: 1.4rem;
|
||||
|
||||
img {
|
||||
width: 4.9rem;
|
||||
height: 4.9rem;
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
// height: 100%;
|
||||
// border-radius: 50%;
|
||||
// object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,30 @@
|
||||
<template>
|
||||
<div class="asistant-container flex flex-column">
|
||||
<div class="header">
|
||||
<HeaderTitle hasSetting styleType="3" />
|
||||
<HeaderTitle hasSetting styleType="3" @clickProfile="handleClickProfile" />
|
||||
</div>
|
||||
<div class="loading-container" v-if="isLoading">
|
||||
<GenerateLoading />
|
||||
<div class="content flex-1">
|
||||
<NoticeList
|
||||
ref="noticeListRef"
|
||||
:list="messageList"
|
||||
:is-streaming="isStreaming"
|
||||
:streaming-message="currentStreamingMessage"
|
||||
/>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="content flex-1" v-if="!isLoading">
|
||||
<NoticeList
|
||||
ref="noticeListRef"
|
||||
:list="messageList"
|
||||
:is-streaming="isStreaming"
|
||||
:streaming-message="currentStreamingMessage"
|
||||
/>
|
||||
<div class="footer">
|
||||
<InputArea @send-message="handleSendMessage" />
|
||||
<div class="continue flex">
|
||||
<div class="btn flex flex-center" @click="handleContinue">Generate</div>
|
||||
</div>
|
||||
<div class="footer" v-if="!isLoading">
|
||||
<InputArea @send-message="handleSendMessage" />
|
||||
<div class="continue">
|
||||
<button class="btn" @click="handleContinue">Continue</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<Profile ref="profileRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
import NoticeList from './components/NoticeList.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 { useRouter, useRoute } from 'vue-router'
|
||||
import { useUserInfoStore, useGenerateStore } from '@/stores'
|
||||
@@ -60,7 +56,11 @@ interface ChatMessage {
|
||||
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 messageList = ref<ChatMessage[]>([])
|
||||
|
||||
@@ -81,6 +81,8 @@ const sendPrefilledMessage = () => {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('1111111111111');
|
||||
|
||||
sessionId.value = Math.floor(Date.now() / 1000).toString()
|
||||
generateStore.setSessionId(sessionId.value)
|
||||
})
|
||||
@@ -117,7 +119,7 @@ const handleFetchMessage = (message: string) => {
|
||||
const params = {
|
||||
message: message,
|
||||
sessionId: sessionId.value,
|
||||
gender: 'male'
|
||||
gender: userInfoStore.state.generateParams.sex
|
||||
}
|
||||
|
||||
// 创建AI消息对象
|
||||
@@ -273,17 +275,13 @@ const handleFetchMessage = (message: string) => {
|
||||
const handleContinue = () => {
|
||||
// router.push('/workshop/selectStyle')
|
||||
// 模拟接口之后再跳转
|
||||
isLoading.value = true
|
||||
generateStore.clearProductData()
|
||||
setTimeout(() => {
|
||||
router.push('/workshop/selectStyle')
|
||||
isLoading.value = false
|
||||
}, 1000)
|
||||
router.push('/workshop/selectStyle')
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.asistant-container {
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -306,20 +304,14 @@ const handleContinue = () => {
|
||||
color: #fff;
|
||||
text-align: right;
|
||||
padding: 2.6rem 4.5rem;
|
||||
flex-direction: row-reverse;
|
||||
.btn {
|
||||
border-radius: 7px;
|
||||
border-radius: 0.7rem;
|
||||
background-color: #000;
|
||||
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>
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
<div class="login-page">
|
||||
<div class="content">
|
||||
<div class="back-button" @click="goBack">
|
||||
<SvgIcon name="left" size="50" color="#fff" />
|
||||
<SvgIcon name="left" size="50" color="#fff" />
|
||||
</div>
|
||||
|
||||
<div class="header">
|
||||
<div class="title">Log in.</div>
|
||||
<p class="subtitle">Redefine the styling experience with AI.</p>
|
||||
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p>
|
||||
<div class="title">Staff Login.</div>
|
||||
<p class="subtitle">
|
||||
<span>Experience our personalised styling journey with</span>
|
||||
<br />
|
||||
<span>Lane Crawford.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="login-container">
|
||||
@@ -32,7 +35,11 @@
|
||||
<img :src="google" class="google-icon" />
|
||||
Sign in with Google
|
||||
</div> -->
|
||||
<GoogleLogin @googelLogin="handleGoogleLogin" ref="googleLoginRef" @click="clickGooleLogin"></GoogleLogin>
|
||||
<GoogleLogin
|
||||
@googelLogin="handleGoogleLogin"
|
||||
ref="googleLoginRef"
|
||||
@click="clickGooleLogin"
|
||||
></GoogleLogin>
|
||||
<div class="sign-up-button" @click="handleSignup">Don’t have an account? Sign Up</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -118,7 +125,7 @@ const handleLogin = async () => {
|
||||
userInfoStore.setToken(response.token)
|
||||
userInfoStore.setUserInfo(response.user)
|
||||
showToast('login success')
|
||||
router.replace('/stylist/customer')
|
||||
router.replace('/customer')
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -141,7 +148,7 @@ const handleGoogleLogin = async (accessToken: string) => {
|
||||
userInfoStore.setToken(result.token)
|
||||
userInfoStore.setUserInfo(result.user)
|
||||
showToast('Google login successful')
|
||||
router.replace('/stylist/customer')
|
||||
router.replace('/customer')
|
||||
} catch (error) {
|
||||
console.error('Google登录失败:', error)
|
||||
showToast('Google login failed, please try again')
|
||||
@@ -183,7 +190,7 @@ const handleSignup = () => {
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
font-size: 3.4rem;
|
||||
.c-svg{
|
||||
.c-svg {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
@@ -191,12 +198,14 @@ const handleSignup = () => {
|
||||
|
||||
.header {
|
||||
margin-top: 1.42rem;
|
||||
padding-left: 15.5rem;
|
||||
padding-left: 14.5rem;
|
||||
padding-right: 14.3rem;
|
||||
color: white;
|
||||
font-family: 'satoshiRegular';
|
||||
.title {
|
||||
font-size: 11rem;
|
||||
font-weight: bold;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.8rem;
|
||||
color: white;
|
||||
font-family: 'satoshiBold';
|
||||
@@ -226,11 +235,7 @@ const handleSignup = () => {
|
||||
position: relative;
|
||||
width: calc(100% - 28.4rem);
|
||||
height: 107.8rem;
|
||||
background: radial-gradient(
|
||||
100% 100% at 0% 0%,
|
||||
rgba(115, 115, 115, 0.4) 0%,
|
||||
rgba(0, 0, 0, 0) 100%
|
||||
);
|
||||
background: radial-gradient(100% 100% at 0% 0%, rgba(0, 0, 0, 0.4) 0%, rgba(0, 0, 0, 0.2) 100%);
|
||||
backdrop-filter: blur(35px);
|
||||
-webkit-backdrop-filter: blur(35px);
|
||||
-moz-backdrop-filter: blur(35px);
|
||||
|
||||
@@ -4,14 +4,17 @@
|
||||
<div class="content">
|
||||
<!-- 返回按钮 -->
|
||||
<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 class="header">
|
||||
<div class="title">Log in.</div>
|
||||
<p class="subtitle">Redefine the styling experience with AI.</p>
|
||||
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p>
|
||||
<div class="title">Staff Login.</div>
|
||||
<p class="subtitle">
|
||||
<span>Experience our personalised styling journey with</span>
|
||||
<br />
|
||||
<span>Lane Crawford.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="login-container">
|
||||
@@ -130,9 +133,9 @@ const handleSuccess = (data: any) => {
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
font-size: 3.4rem;
|
||||
.back-icon {
|
||||
width: 2.83rem;
|
||||
height: 3.47rem;
|
||||
.c-svg {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
<div class="login-page">
|
||||
<div class="content">
|
||||
<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 class="header">
|
||||
<div class="title">Sign up.</div>
|
||||
<p class="subtitle">Redefine the styling experience with AI.</p>
|
||||
<p class="subtitle">Use Styling Angel to speed up your fashion journey.</p>
|
||||
<div class="title">Staff Sign up.</div>
|
||||
<p class="subtitle">Start my personalised styling journey with Lane Crawford.</p>
|
||||
</div>
|
||||
|
||||
<div class="login-container">
|
||||
@@ -30,7 +29,12 @@
|
||||
|
||||
<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>
|
||||
@@ -142,7 +146,7 @@ const handleGoogleSignup = async (accessToken: string) => {
|
||||
userInfoStore.setToken(result.token)
|
||||
userInfoStore.setUserInfo(result.user)
|
||||
showToast('Google sign up successful')
|
||||
router.replace('/stylist/customer')
|
||||
router.replace('/customer')
|
||||
} catch (error) {
|
||||
console.error('Google注册失败:', error)
|
||||
showToast(error?.message || 'Google sign up failed, please try again')
|
||||
@@ -183,15 +187,16 @@ const handleGoogleSignup = async (accessToken: string) => {
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
font-size: 3.4rem;
|
||||
.back-icon {
|
||||
width: 2.83rem;
|
||||
height: 3.47rem;
|
||||
.c-svg {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-top: 1.42rem;
|
||||
padding-left: 15.5rem;
|
||||
padding-left: 14.5rem;
|
||||
padding-right: 14.3rem;
|
||||
color: white;
|
||||
font-family: 'satoshiRegular';
|
||||
.title {
|
||||
@@ -281,7 +286,6 @@ const handleGoogleSignup = async (accessToken: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
@@ -2,10 +2,14 @@
|
||||
<div class="welcome-page safe-area-top safe-area-bottom">
|
||||
<div class="content">
|
||||
<div class="title">
|
||||
AI STYLING <br />
|
||||
ASSISTANT
|
||||
Styling <br />
|
||||
Assistant
|
||||
</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="log btn" @click="goToLogin">Log in</div>
|
||||
<div class="sign btn" @click="goToSignup">Sign up</div>
|
||||
@@ -49,15 +53,17 @@ const goToLogin = () => {
|
||||
font-weight: 400;
|
||||
margin-bottom: 31.5rem;
|
||||
.title {
|
||||
font-family: 'boskaRegular';
|
||||
line-height: 93%;
|
||||
letter-spacing: -0.02em;
|
||||
font-family: 'satoshiMedium';
|
||||
line-height: 120%;
|
||||
font-size: 11rem;
|
||||
// letter-spacing: -0.02em;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 3.2rem;
|
||||
font-family: 'satoshiRegular';
|
||||
margin: 3.2rem 0 6rem;
|
||||
list-style: 124%;
|
||||
font-weight: 400;
|
||||
line-height: 124%;
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
.btn-container {
|
||||
@@ -74,6 +80,12 @@ const goToLogin = () => {
|
||||
// padding: 2rem 1.3rem;
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
343
src/views/login/customer.vue
Normal 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>
|
||||
@@ -1,269 +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">
|
||||
<SvgIcon name="left" size="70" @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="menu flex flex-between">
|
||||
<div class="back-container flex flex-between" @click="handleBack">
|
||||
<SvgIcon name="left" size="70" @click="handleBack" />
|
||||
</div>
|
||||
<SvgIcon name="setting" size="70" />
|
||||
</div>
|
||||
<div class="text">
|
||||
<div class="form-title">Customer ID</div>
|
||||
<div class="description">
|
||||
Redefine the styling experience with AI. Use<br />Styling Angel to speed up your
|
||||
fashion<br />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>('form')
|
||||
// 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: 15.9rem;
|
||||
}
|
||||
|
||||
.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 {
|
||||
.menu {
|
||||
padding: 0 4.8rem 0 8.4rem;
|
||||
.back-container {
|
||||
width: 7.3rem;
|
||||
height: 7.3rem;
|
||||
border-radius: 1.8rem;
|
||||
font-size: 4.3rem;
|
||||
position: relative;
|
||||
|
||||
.back-icon {
|
||||
width: 2.08rem;
|
||||
height: 3.47rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.text {
|
||||
padding-left: 15.2rem;
|
||||
margin-top: 15.4rem;
|
||||
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-top: 2.7rem;
|
||||
font-family: 'satoshiRegular';
|
||||
}
|
||||
|
||||
.glass-form {
|
||||
height: 68.8rem;
|
||||
border: 0.2rem solid #ffffff;
|
||||
border-radius: 4.7rem;
|
||||
margin: 0 14.2rem;
|
||||
// padding: 8.2rem 7.9rem;
|
||||
padding: 11rem 7.5rem;
|
||||
margin-top: 11rem;
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
.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: 18.7rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
.c-svg {
|
||||
width: initial;
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +1,19 @@
|
||||
<template>
|
||||
<header-title style-type="3" />
|
||||
<div class="dressfor-container flex">
|
||||
<div class="content flex-1 flex flex-column">
|
||||
<!-- <div class="setting flex flex-between">
|
||||
<van-icon name="arrow-left" color="#fff" @click="handleBack" />
|
||||
<SvgIcon name="setting" size="70" />
|
||||
<!-- 移除始终显示的 loading,改为按需显示 -->
|
||||
<!-- <div class="loading-container flex flex-center">
|
||||
<Icon class="icon-element" title="" />
|
||||
</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="chatbox flex flex-center">
|
||||
<!-- <div class="chatbox flex flex-center">
|
||||
<div class="input-box flex">
|
||||
<div class="input-wrapper flex-1 flex">
|
||||
<input
|
||||
@@ -26,27 +31,73 @@
|
||||
class="audio-icon"
|
||||
:name="isRecording ? 'pause' : 'audio'"
|
||||
size="35"
|
||||
color="#6D6868"
|
||||
color="#6D6868"
|
||||
@click="handleClickAudio"
|
||||
/>
|
||||
</div>
|
||||
<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>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
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 FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import AudioVisualizer from '@/views/asistant/components/AudioVisualizer.vue'
|
||||
import Icon from '../asistant/components/GenerateLoading.vue'
|
||||
|
||||
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 isRecording = ref(false)
|
||||
@@ -71,15 +122,10 @@ watch(isRecording, async (newVal) => {
|
||||
|
||||
const handleSendMessage = () => {
|
||||
const message = inputValue.value.trim()
|
||||
if(!message){
|
||||
if (!message) {
|
||||
showToast('Please enter a message')
|
||||
return
|
||||
}
|
||||
showToast('Voice-to-text conversion has been completed.')
|
||||
router.push({
|
||||
path: '/asistant',
|
||||
query: message ? { message } : undefined
|
||||
})
|
||||
}
|
||||
|
||||
const handleClickAudio = () => {
|
||||
@@ -111,6 +157,10 @@ const startRecording = () => {
|
||||
}
|
||||
|
||||
speechRecognition.onstart = () => {
|
||||
showToast({
|
||||
message: 'Listening...',
|
||||
duration: 0
|
||||
})
|
||||
isRecording.value = true
|
||||
}
|
||||
|
||||
@@ -138,6 +188,7 @@ const startRecording = () => {
|
||||
}
|
||||
|
||||
speechRecognition.onend = () => {
|
||||
closeToast()
|
||||
isRecording.value = false
|
||||
lastTranscript = ''
|
||||
isSpeechRecognitionActive = false
|
||||
@@ -145,6 +196,7 @@ const startRecording = () => {
|
||||
|
||||
speechRecognition.onerror = (event: any) => {
|
||||
console.error('Speech recognition error:', event.error)
|
||||
closeToast()
|
||||
isRecording.value = false
|
||||
isSpeechRecognitionActive = false
|
||||
showToast('Speech recognition failed, please try again')
|
||||
@@ -161,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(() => {
|
||||
if (speechRecognition && isRecording.value) {
|
||||
speechRecognition.stop()
|
||||
@@ -169,82 +235,136 @@ onUnmounted(() => {
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.c-svg{
|
||||
.c-svg {
|
||||
width: initial;
|
||||
height: initial;
|
||||
}
|
||||
.dressfor-container {
|
||||
height: calc(100vh - 12rem - 14.9rem);
|
||||
overflow: hidden;
|
||||
// overflow: hidden;
|
||||
color: #fff;
|
||||
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-position: center;
|
||||
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 {
|
||||
.setting {
|
||||
padding: 0 4.9rem 0 8.4rem;
|
||||
font-size: 7rem;
|
||||
.c-svg {
|
||||
width: initial;
|
||||
.loading-container {
|
||||
:deep(.loading-image) {
|
||||
width: 17rem;
|
||||
height: 17rem;
|
||||
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 {
|
||||
font-family: 'satoshiBold';
|
||||
font-size: 11rem;
|
||||
line-height: 106%;
|
||||
text-align: center;
|
||||
margin-top: 43.8rem;
|
||||
margin-bottom: 14rem;
|
||||
padding-top: 8.9rem;
|
||||
padding-bottom: 7.7rem;
|
||||
width: 60rem;
|
||||
}
|
||||
.chatbox {
|
||||
height: 9.3rem;
|
||||
// background-color: #fff;
|
||||
column-gap: 2.29rem;
|
||||
.input-box {
|
||||
width: 59.8rem;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
// border: 2px solid #5f5f5f;
|
||||
border-radius: 1rem;
|
||||
color: #222222;
|
||||
font-size: 3.2rem;
|
||||
font-family: 'satoshiRegular';
|
||||
padding: 0 2.6rem;
|
||||
column-gap: 2.6rem;
|
||||
overflow: hidden;
|
||||
.input-wrapper{
|
||||
overflow: hidden;
|
||||
// .chatbox {
|
||||
// height: 9.3rem;
|
||||
// // background-color: #fff;
|
||||
// column-gap: 2.29rem;
|
||||
// .input-box {
|
||||
// width: 59.8rem;
|
||||
// height: 100%;
|
||||
// background-color: #efefef;
|
||||
// // border: 2px solid #5f5f5f;
|
||||
// border-radius: 1rem;
|
||||
// color: #222222;
|
||||
// font-size: 3.2rem;
|
||||
// font-family: 'satoshiRegular';
|
||||
// padding: 0 2.6rem;
|
||||
// column-gap: 2.6rem;
|
||||
// overflow: hidden;
|
||||
// .input-wrapper {
|
||||
// overflow: hidden;
|
||||
// }
|
||||
// .recording-visualizer {
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// height: 100%;
|
||||
// :deep(.audio-visualizer) {
|
||||
// width: 100%;
|
||||
// padding: 0;
|
||||
// }
|
||||
// :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;
|
||||
}
|
||||
.recording-visualizer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
:deep(.audio-visualizer) {
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
:deep(.visualizer-container) {
|
||||
height: 100%;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
.input-item {
|
||||
// width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
.audio-icon {
|
||||
width: initial;
|
||||
}
|
||||
}
|
||||
.send {
|
||||
width: 7.6rem;
|
||||
height: 7.6rem;
|
||||
background-color: #fff;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<header-title style-type="3" />
|
||||
<div class="stylist-page">
|
||||
<!-- 主要内容区域 -->
|
||||
<div class="content">
|
||||
<!-- 标题 -->
|
||||
<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 class="carousel-container" v-show="!showVideo">
|
||||
@@ -35,8 +35,14 @@
|
||||
</div>
|
||||
|
||||
<!-- Continue按钮 -->
|
||||
<div class="continue-button" @click="handleContinue" v-if="!$route.query?.demo">Continue</div>
|
||||
<van-dialog
|
||||
<button
|
||||
class="sandblasted-blurred continue-button flex flex-center"
|
||||
@click="handleContinue"
|
||||
v-if="!$route.query?.demo"
|
||||
>
|
||||
<span>Continue</span>
|
||||
</button>
|
||||
<!-- <van-dialog
|
||||
class="video-dialog"
|
||||
:show-confirm-button="false"
|
||||
:show-cancel-button="false"
|
||||
@@ -48,9 +54,8 @@
|
||||
<van-icon name="cross" class="close-icon" />
|
||||
</div>
|
||||
<Video ref="videoRef" />
|
||||
</van-dialog>
|
||||
</van-dialog> -->
|
||||
</div>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -59,9 +64,15 @@ import { useRouter } from 'vue-router'
|
||||
import Video from './components/Video.vue'
|
||||
import { useUserInfoStore } from '@/stores'
|
||||
import male from '@/assets/images/male.png'
|
||||
import maleThumb from '@/assets/images/male_thumb.png'
|
||||
import female from '@/assets/images/female.png'
|
||||
import femaleThumb from '@/assets/images/female_thumb.png'
|
||||
import HeaderTitle from '@/components/HeaderTitle.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 userInfoStore = useUserInfoStore()
|
||||
@@ -70,30 +81,34 @@ const stylists = ref<any[]>([
|
||||
{
|
||||
id: 1,
|
||||
value: 'crystal',
|
||||
name: 'Vera Lo',
|
||||
name: 'Crystal',
|
||||
description: 'Contemporary, Classic, Simple Silhouettes, Statement Pieces',
|
||||
image: female
|
||||
image: Crystal,
|
||||
thumb: CrystalThumb
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
value: 'mini',
|
||||
name: 'Sarah Chen',
|
||||
name: 'Mini',
|
||||
description: 'Modern, Edgy, Bold Colors, Street Style',
|
||||
image: male
|
||||
image: mini,
|
||||
thumb: miniThumb
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
value: 'crystal',
|
||||
name: 'Emma Wilson',
|
||||
value: 'vera',
|
||||
name: 'Vera',
|
||||
description: 'Elegant, Feminine, Vintage Inspired, Soft Tones',
|
||||
image: female
|
||||
image: female,
|
||||
thumb: femaleThumb
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
value: 'mini',
|
||||
name: 'Alex Johnson',
|
||||
value: 'edi',
|
||||
name: 'Edi',
|
||||
description: 'Minimalist, Professional, Neutral Palette, Clean Lines',
|
||||
image: male
|
||||
image: male,
|
||||
thumb: maleThumb
|
||||
}
|
||||
])
|
||||
const currentChoosed = ref(1)
|
||||
@@ -121,20 +136,22 @@ const handleClickStylist = (item: any) => {
|
||||
|
||||
const handleContinue = () => {
|
||||
const generateParams = userInfoStore.getGenerateParams()
|
||||
generateParams.stylist =
|
||||
stylists.value.find((item) => item.id === currentChoosed.value)?.value || ''
|
||||
const selected = stylists.value.find((item) => item.id === currentChoosed.value)
|
||||
generateParams.stylist = selected?.value
|
||||
generateParams.stylistImage = selected?.thumb
|
||||
|
||||
userInfoStore.setGenerateParams(generateParams)
|
||||
|
||||
router.push('/stylist/sex')
|
||||
router.push('/workshop/stylist/sex')
|
||||
}
|
||||
|
||||
// 监听showVideo变化,关闭时暂停视频
|
||||
watch(showVideo, (newValue) => {
|
||||
if (!newValue && videoRef.value) {
|
||||
videoRef.value.pause()
|
||||
videoRef.value.reset()
|
||||
}
|
||||
})
|
||||
// watch(showVideo, (newValue) => {
|
||||
// if (!newValue && videoRef.value) {
|
||||
// videoRef.value.pause()
|
||||
// videoRef.value.reset()
|
||||
// }
|
||||
// })
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
@@ -159,18 +176,24 @@ watch(showVideo, (newValue) => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem;
|
||||
padding-top: 6rem;
|
||||
padding-top: 10rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 4rem;
|
||||
.title {
|
||||
font-size: 15rem;
|
||||
font-size: 11rem;
|
||||
font-weight: 400;
|
||||
color: white;
|
||||
font-family: 'boskaRegular';
|
||||
line-height: 96%;
|
||||
font-family: 'satoshiBold';
|
||||
letter-spacing: -0.04rem;
|
||||
margin-bottom: 3.2rem;
|
||||
}
|
||||
.sub-title {
|
||||
font-family: 'satoshiRegular';
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,9 +304,9 @@ watch(showVideo, (newValue) => {
|
||||
}
|
||||
}
|
||||
|
||||
.continue-button {
|
||||
height: 5.9rem;
|
||||
box-sizing: border-box;
|
||||
button.sandblasted-blurred.continue-button {
|
||||
height: 6.7rem;
|
||||
width: 24.6rem;
|
||||
position: absolute;
|
||||
bottom: 6.4rem;
|
||||
right: 7.6rem;
|
||||
@@ -293,11 +316,9 @@ watch(showVideo, (newValue) => {
|
||||
color: white;
|
||||
font-size: 4rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
z-index: 3;
|
||||
font-family: 'satoshiRegular';
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:deep(.van-overlay) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<template>
|
||||
<header-title style-type="3" />
|
||||
<div class="sex-select">
|
||||
<div class="text">Before we begin.</div>
|
||||
<div class="desc">Who are you styling?</div>
|
||||
@@ -15,7 +14,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer-navigation />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||
@@ -23,7 +21,7 @@ import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showToast } from 'vant'
|
||||
import {useUserInfoStore} from '@/stores'
|
||||
import { useUserInfoStore } from '@/stores'
|
||||
const router = useRouter()
|
||||
const userInfoStore = useUserInfoStore()
|
||||
|
||||
@@ -33,13 +31,16 @@ const options = ref<any[]>([
|
||||
])
|
||||
|
||||
const handleSelect = (value: string) => {
|
||||
if (value === 'male') {// 男性开发中
|
||||
return showToast(`This feature is currently under development. Please select the 'Female' option for now.`)
|
||||
if (value === 'male') {
|
||||
// 男性开发中
|
||||
return showToast(
|
||||
`This feature is currently under development. Please select the 'Female' option for now.`
|
||||
)
|
||||
}
|
||||
const generateParams = userInfoStore.getGenerateParams()
|
||||
generateParams.sex = value
|
||||
generateParams.sex = value
|
||||
userInfoStore.setGenerateParams(generateParams)
|
||||
router.push('/stylist/dressfor')
|
||||
router.push('/workshop/stylist/dressfor')
|
||||
}
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
@@ -54,14 +55,18 @@ const handleSelect = (value: string) => {
|
||||
background-repeat: no-repeat;
|
||||
padding: 6rem 12.4rem 0 8.5rem;
|
||||
.text {
|
||||
font-family: 'robotoBold';
|
||||
font-size: 13rem;
|
||||
font-family: 'satoshiBold';
|
||||
font-weight: 700;
|
||||
font-size: 11rem;
|
||||
line-height: 106%;
|
||||
letter-spacing: -0.02rem;
|
||||
margin-bottom: 4.6rem;
|
||||
}
|
||||
.desc {
|
||||
font-family: 'satoshiRegular';
|
||||
font-size: 6.4rem;
|
||||
font-size: 6rem;
|
||||
line-height: 132%;
|
||||
letter-spacing: 0.02rem;
|
||||
}
|
||||
.select-list {
|
||||
display: flex;
|
||||
@@ -71,13 +76,24 @@ const handleSelect = (value: string) => {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.option {
|
||||
// flex: 1;
|
||||
// frosted glass style
|
||||
text-align: center;
|
||||
font-family: 'satoshiRegular';
|
||||
font-size: 6.4rem;
|
||||
width: 29.7rem;
|
||||
font-size: 4.8rem;
|
||||
width: 35rem;
|
||||
height: 8.3rem;
|
||||
border: .2rem solid #fff;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default defineConfig(({ mode }) => {
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0', // 允许局域网内的IP访问
|
||||
port: 8060, // 根据环境设置端口
|
||||
port: 8066, // 根据环境设置端口
|
||||
open: false, // 自动打开浏览器
|
||||
strictPort: true, // 如果端口已被占用,则尝试下一个可用端口
|
||||
hmr: {
|
||||
|
||||