Merge branches 'main' and 'main' of ssh://18.167.251.121:10002/aidlab/lanecarford_front
This commit is contained in:
@@ -49,6 +49,25 @@ export function generateTryOnEffect(data: Object) {
|
|||||||
data,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 生成试穿效果-演示
|
||||||
|
* @param data 试穿效果数据
|
||||||
|
* @param data.customerId 顾客ID
|
||||||
|
* @param data.visitRecordId 进店记录id
|
||||||
|
* @param data.styleId 样式id
|
||||||
|
* @param data.modelPhotoId 模型照片id
|
||||||
|
* @param data.customerPhotoId 顾客照片id
|
||||||
|
* @param data.prompt 提示词
|
||||||
|
* @param data.originalTryOnId 原始试穿效果id
|
||||||
|
* @param data.isRegenerated 是否重新生成 0-否,1-是
|
||||||
|
*/
|
||||||
|
export function generateTryOnEffectDemo(data: Object) {
|
||||||
|
return request({
|
||||||
|
url: '/api/try-on-effects/reFace/{customerPhotold}o',
|
||||||
|
method: 'post',
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/** 上传图片-AI换脸
|
/** 上传图片-AI换脸
|
||||||
* @param data 图片数据
|
* @param data 图片数据
|
||||||
|
|||||||
BIN
src/assets/images/workshop/template.png
Normal file
BIN
src/assets/images/workshop/template.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 893 KiB |
@@ -1,6 +1,7 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { useGenerateStore } from '@/stores/modules/generate'
|
import { useGenerateStore } from '@/stores/modules/generate'
|
||||||
const VerifyIDs = (num: number) => {
|
const VerifyIDs = (num: number) => {
|
||||||
|
return true
|
||||||
const ids = [
|
const ids = [
|
||||||
!!useGenerateStore().customerId,
|
!!useGenerateStore().customerId,
|
||||||
!!useGenerateStore().visitRecordId,
|
!!useGenerateStore().visitRecordId,
|
||||||
@@ -130,6 +131,13 @@ const router = createRouter({
|
|||||||
component: () => import('../views/Workshop/product.vue'),
|
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) }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// 上传照片1
|
// 上传照片1
|
||||||
path: '/workshop/uploadFace',
|
path: '/workshop/uploadFace',
|
||||||
|
|||||||
@@ -42,6 +42,19 @@ export const useGenerateStore = defineStore({
|
|||||||
isRegenerated: '',
|
isRegenerated: '',
|
||||||
isFavorite: false
|
isFavorite: false
|
||||||
},
|
},
|
||||||
|
/** AI魔改信息-演示 */
|
||||||
|
customizeInfoDemo: {
|
||||||
|
inputText: '',
|
||||||
|
count: 0,
|
||||||
|
oldInputText: '',
|
||||||
|
oldTryOnId: '',
|
||||||
|
|
||||||
|
tryOnId: '',
|
||||||
|
tryOnUrl: '',
|
||||||
|
styleUrl: '',
|
||||||
|
isRegenerated: '',
|
||||||
|
isFavorite: false
|
||||||
|
},
|
||||||
customerInfo: {
|
customerInfo: {
|
||||||
customerId: '',
|
customerId: '',
|
||||||
visitRecordId: ''
|
visitRecordId: ''
|
||||||
@@ -126,6 +139,18 @@ export const useGenerateStore = defineStore({
|
|||||||
this.customizeInfo.styleUrl = ''
|
this.customizeInfo.styleUrl = ''
|
||||||
this.customizeInfo.isRegenerated = ''
|
this.customizeInfo.isRegenerated = ''
|
||||||
this.customizeInfo.isFavorite = false
|
this.customizeInfo.isFavorite = false
|
||||||
|
},
|
||||||
|
/** 清空 AI魔改信息-演示 */
|
||||||
|
clearCustomizeInfoDemo() {
|
||||||
|
this.customizeInfoDemo.inputText = ''
|
||||||
|
this.customizeInfoDemo.count = 0
|
||||||
|
this.customizeInfoDemo.oldInputText = ''
|
||||||
|
this.customizeInfoDemo.oldTryOnId = ''
|
||||||
|
this.customizeInfoDemo.tryOnId = ''
|
||||||
|
this.customizeInfoDemo.tryOnUrl = ''
|
||||||
|
this.customizeInfoDemo.styleUrl = ''
|
||||||
|
this.customizeInfoDemo.isRegenerated = ''
|
||||||
|
this.customizeInfoDemo.isFavorite = false
|
||||||
},
|
},
|
||||||
uploadCustomizeInfo(data: object) {
|
uploadCustomizeInfo(data: object) {
|
||||||
for (const key in data) {
|
for (const key in data) {
|
||||||
@@ -143,6 +168,7 @@ export const useGenerateStore = defineStore({
|
|||||||
this.clearProductData()
|
this.clearProductData()
|
||||||
this.updatePhotoInfo({})
|
this.updatePhotoInfo({})
|
||||||
this.clearCustomizeInfo()
|
this.clearCustomizeInfo()
|
||||||
|
this.clearCustomizeInfoDemo()
|
||||||
this.clearCustomerInfo()
|
this.clearCustomerInfo()
|
||||||
this.setSessionId('')
|
this.setSessionId('')
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,19 +2,22 @@
|
|||||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||||
import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
|
import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import {
|
import {
|
||||||
generateTryOnEffect,
|
generateTryOnEffect,
|
||||||
|
generateTryOnEffectDemo,
|
||||||
setTryOnEffectFavorite,
|
setTryOnEffectFavorite,
|
||||||
cancelTryOnEffectFavorite
|
cancelTryOnEffectFavorite
|
||||||
} from '@/api/workshop'
|
} from '@/api/workshop'
|
||||||
const emit = defineEmits(['viewType'])
|
const emit = defineEmits(['viewType'])
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useGenerateStore } from '@/stores'
|
import { useGenerateStore } from '@/stores'
|
||||||
const generateStore = useGenerateStore()
|
const generateStore = useGenerateStore()
|
||||||
|
|
||||||
const customizeInfo = generateStore.customizeInfo
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const isDemo = computed(() => route.query.demo === '1')
|
||||||
|
const customizeInfo = isDemo.value ? generateStore.customizeInfoDemo : generateStore.customizeInfo
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const onSend = () => {
|
const onSend = () => {
|
||||||
if (customizeInfo.inputText === '') return
|
if (customizeInfo.inputText === '') return
|
||||||
@@ -45,8 +48,8 @@
|
|||||||
}
|
}
|
||||||
if (generateStore.customerPhotoId && customizeInfo.count === 0)
|
if (generateStore.customerPhotoId && customizeInfo.count === 0)
|
||||||
data['customerPhotoId'] = generateStore.customerPhotoId
|
data['customerPhotoId'] = generateStore.customerPhotoId
|
||||||
loading.value = true
|
loading.value = true;
|
||||||
generateTryOnEffect(data)
|
(isDemo.value ? generateTryOnEffectDemo : generateTryOnEffect)(data)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
customizeInfo.count++
|
customizeInfo.count++
|
||||||
customizeInfo.tryOnId = res.tryOnId
|
customizeInfo.tryOnId = res.tryOnId
|
||||||
@@ -66,6 +69,7 @@
|
|||||||
// 喜欢
|
// 喜欢
|
||||||
const isLoveLoading = ref(false)
|
const isLoveLoading = ref(false)
|
||||||
const onLove = () => {
|
const onLove = () => {
|
||||||
|
if (isDemo.value) return
|
||||||
if (isLoveLoading.value) return
|
if (isLoveLoading.value) return
|
||||||
const http = customizeInfo.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
|
const http = customizeInfo.isFavorite ? cancelTryOnEffectFavorite : setTryOnEffectFavorite
|
||||||
customizeInfo.isFavorite = !customizeInfo.isFavorite
|
customizeInfo.isFavorite = !customizeInfo.isFavorite
|
||||||
@@ -86,7 +90,11 @@
|
|||||||
router.back()
|
router.back()
|
||||||
}
|
}
|
||||||
const onFinish = () => {
|
const onFinish = () => {
|
||||||
router.push({ name: 'creation' })
|
if (isDemo.value) {
|
||||||
|
router.push({ name: 'end' })
|
||||||
|
} else {
|
||||||
|
router.push({ name: 'creation' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -114,7 +122,7 @@
|
|||||||
<div class="icon"><SvgIcon name="xialajiantou" size="29" /></div>
|
<div class="icon"><SvgIcon name="xialajiantou" size="29" /></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="icons">
|
<div class="icons">
|
||||||
<div @click="onLove">
|
<div @click="onLove" v-if="!isDemo">
|
||||||
<SvgIcon :name="`love_${customizeInfo.isFavorite ? 1 : 0}`" size="35" />
|
<SvgIcon :name="`love_${customizeInfo.isFavorite ? 1 : 0}`" size="35" />
|
||||||
</div>
|
</div>
|
||||||
<div @click="onReload" v-show="customizeInfo.oldInputText">
|
<div @click="onReload" v-show="customizeInfo.oldInputText">
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import HeaderTitle from '@/components/HeaderTitle.vue'
|
|||||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
import { showConfirmDialog } from 'vant'
|
||||||
|
import MyEvent from '@/utils/myEvent'
|
||||||
|
|
||||||
//const props = defineProps({
|
//const props = defineProps({
|
||||||
//})
|
//})
|
||||||
@@ -14,6 +16,21 @@ const emit = defineEmits([
|
|||||||
// const data = reactive({
|
// 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('/stylist/customer')
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(()=>{
|
onMounted(()=>{
|
||||||
emit('view-type', 1)
|
emit('view-type', 1)
|
||||||
})
|
})
|
||||||
@@ -37,13 +54,13 @@ defineExpose({})
|
|||||||
<div class="item" @click="()=>router.push('/stylist/index')">
|
<div class="item" @click="()=>router.push('/stylist/index')">
|
||||||
<img src="@/assets/images/nav1.png" alt="">
|
<img src="@/assets/images/nav1.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="item" @click="()=>router.push('/workshop/uploadFace')">
|
<div class="item" @click="()=>router.push('/workshop/recommended')">
|
||||||
<img src="@/assets/images/nav2.png" alt="">
|
<img src="@/assets/images/nav2.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="item" @click="()=>router.push('/stylist/index')">
|
<div class="item" @click="()=>router.push('/stylist/index')">
|
||||||
<img src="@/assets/images/nav3.png" alt="">
|
<img src="@/assets/images/nav3.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="item" @click="()=>router.push('/stylist/customer')">
|
<div class="item" @click="clickSwitchVIPID">
|
||||||
<img src="@/assets/images/nav4.png" alt="">
|
<img src="@/assets/images/nav4.png" alt="">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
102
src/views/Workshop/recommended.vue
Normal file
102
src/views/Workshop/recommended.vue
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
<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({})
|
||||||
|
router.push({ name: 'uploadFace', query: { demo: 1 } })
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<header-title />
|
||||||
|
<!-- 上传照片 -->
|
||||||
|
<div class="upload-face-1">
|
||||||
|
<img src="@/assets/images/workshop/bg/upload_bg.png" class="bg" />
|
||||||
|
<div class="content">
|
||||||
|
<div class="texts">
|
||||||
|
<p class="title">Recommended<br />Try-on</p>
|
||||||
|
<p class="desc">Come and try face swapping!</p>
|
||||||
|
</div>
|
||||||
|
<div class="image">
|
||||||
|
<img src="../../assets/images/workshop/template.png" />
|
||||||
|
</div>
|
||||||
|
<button class="sandblasted-blurred" @click="clickNext"><span>Next</span></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer-navigation is-placeholder />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.upload-face-1 {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
color: #fff;
|
||||||
|
> .bg {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
> .content {
|
||||||
|
margin-top: 12rem;
|
||||||
|
text-align: center;
|
||||||
|
> .texts {
|
||||||
|
// left: 0;
|
||||||
|
width: 100%;
|
||||||
|
// padding: 9.9rem 0 0 7.2rem;
|
||||||
|
> .title {
|
||||||
|
font-family: 'satoshiBold';
|
||||||
|
font-size: 11rem;
|
||||||
|
line-height: 124%;
|
||||||
|
}
|
||||||
|
> .desc {
|
||||||
|
font-family: 'satoshiMedium';
|
||||||
|
font-size: 4rem;
|
||||||
|
margin-top: 4rem;
|
||||||
|
line-height: 132%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> .image {
|
||||||
|
margin: 4.9rem auto 9.5rem;
|
||||||
|
width: 65.3rem;
|
||||||
|
height: 86.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
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;
|
||||||
|
border: 0.439rem solid #fff;
|
||||||
|
// border-image: linear-gradient(90deg,#BF926E94, #ffffff) 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
> img {
|
||||||
|
width: 58.9rem;
|
||||||
|
height: 79.2rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: 0.2rem solid #d9d9d9;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
> button {
|
||||||
|
width: 40rem;
|
||||||
|
height: 8.3rem;
|
||||||
|
border-radius: 0.7rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useGenerateStore } from '@/stores'
|
import { useGenerateStore } from '@/stores'
|
||||||
const generateStore = useGenerateStore()
|
const generateStore = useGenerateStore()
|
||||||
const emit = defineEmits(['view-type'])
|
const emit = defineEmits(['view-type'])
|
||||||
@@ -10,22 +10,26 @@
|
|||||||
emit('view-type', 1)
|
emit('view-type', 1)
|
||||||
})
|
})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const faceUrl = ref('')
|
const route = useRoute()
|
||||||
|
const query = computed(() => route.query)
|
||||||
|
const isDemo = computed(() => route.query.demo === '1')
|
||||||
// 上传照片
|
// 上传照片
|
||||||
const handleUploadFace = () => {
|
const handleUploadFace = () => {
|
||||||
// generateStore.updatePhotoInfo({})
|
// generateStore.updatePhotoInfo({})
|
||||||
router.push({ name: 'uploadFace2' })
|
router.push({ name: 'uploadFace2', query: query.value })
|
||||||
}
|
}
|
||||||
// 跳过上传
|
// 跳过上传
|
||||||
const handleFinish = () => {
|
const handleFinish = () => {
|
||||||
generateStore.updatePhotoInfo({})
|
generateStore.updatePhotoInfo({})
|
||||||
generateStore.clearCustomizeInfo()
|
if (!isDemo.value) {
|
||||||
generateStore.uploadCustomizeInfo({
|
generateStore.clearCustomizeInfo()
|
||||||
tryOnId: generateStore.originalTryOn.id,
|
generateStore.uploadCustomizeInfo({
|
||||||
tryOnUrl: generateStore.originalTryOn.tryOnUrl,
|
tryOnId: generateStore.originalTryOn.id,
|
||||||
isFavorite: generateStore.originalTryOn.isLike
|
tryOnUrl: generateStore.originalTryOn.tryOnUrl,
|
||||||
})
|
isFavorite: generateStore.originalTryOn.isLike
|
||||||
router.push({ name: 'customize' })
|
})
|
||||||
|
}
|
||||||
|
router.push({ name: 'customize', query: query.value })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||||
import { ref, reactive, onMounted } from 'vue'
|
import { ref, reactive, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { uploadCustomerPhoto } from '@/api/workshop'
|
import { uploadCustomerPhoto } from '@/api/workshop'
|
||||||
import { useGenerateStore } from '@/stores'
|
import { useGenerateStore } from '@/stores'
|
||||||
const generateStore = useGenerateStore()
|
const generateStore = useGenerateStore()
|
||||||
@@ -12,6 +12,9 @@
|
|||||||
emit('view-type', 1)
|
emit('view-type', 1)
|
||||||
})
|
})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
|
const query = computed(() => route.query)
|
||||||
|
const isDemo = computed(() => route.query.demo === '1')
|
||||||
const fileData = generateStore.photoInfo
|
const fileData = generateStore.photoInfo
|
||||||
if (!fileData.file?.size) generateStore.updatePhotoInfo({})
|
if (!fileData.file?.size) generateStore.updatePhotoInfo({})
|
||||||
// 上传照片
|
// 上传照片
|
||||||
@@ -37,7 +40,7 @@
|
|||||||
}
|
}
|
||||||
// 生成照片
|
// 生成照片
|
||||||
const handleGenerate = () => {
|
const handleGenerate = () => {
|
||||||
if (fileData.id) return router.push({ name: 'customize' })
|
if (fileData.id) return router.push({ name: 'customize', query: query.value })
|
||||||
if (!fileData.file) return
|
if (!fileData.file) return
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('customerId', generateStore.customerId + '')
|
formData.append('customerId', generateStore.customerId + '')
|
||||||
@@ -45,8 +48,8 @@
|
|||||||
formData.append('file', fileData.file)
|
formData.append('file', fileData.file)
|
||||||
uploadCustomerPhoto(formData).then((res) => {
|
uploadCustomerPhoto(formData).then((res) => {
|
||||||
generateStore.updatePhotoInfo({ ...res, file: fileData.file })
|
generateStore.updatePhotoInfo({ ...res, file: fileData.file })
|
||||||
generateStore.clearCustomizeInfo()
|
isDemo.value ? generateStore.clearCustomizeInfoDemo() : generateStore.clearCustomizeInfo()
|
||||||
router.push({ name: 'customize' })
|
router.push({ name: 'customize', query: query.value })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 处理照片加载错误
|
// 处理照片加载错误
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
v-for="(line, index) in lines"
|
v-for="(line, index) in lines"
|
||||||
:key="index"
|
:key="index"
|
||||||
:class="['line', `line-${line.type}`]"
|
:class="['line', `line-${line.type}`]"
|
||||||
></div> </template
|
></div>
|
||||||
>>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -44,7 +44,8 @@ const calculateLines = (): Line[] => {
|
|||||||
// 这样当滚动到50%时,内容看起来和开始一样
|
// 这样当滚动到50%时,内容看起来和开始一样
|
||||||
const availableWidth = containerWidth
|
const availableWidth = containerWidth
|
||||||
const lineWithGap = lineWidthPx + gapPx
|
const lineWithGap = lineWidthPx + gapPx
|
||||||
const linesNeeded = Math.ceil(availableWidth / lineWithGap)
|
// 使用四舍五入让一个周期的宽度尽量接近容器宽度,减少滚动结束时的“回跳”感
|
||||||
|
const linesNeeded = Math.max(1, Math.round(availableWidth / lineWithGap))
|
||||||
|
|
||||||
const generatedLines: Line[] = []
|
const generatedLines: Line[] = []
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const shortcutList: string[] = [
|
|||||||
|
|
||||||
const handleSend = (): void => {
|
const handleSend = (): void => {
|
||||||
if (inputValue.value.trim()) {
|
if (inputValue.value.trim()) {
|
||||||
console.log('input发送消息:', inputValue.value)
|
// console.log('input发送消息:', inputValue.value)
|
||||||
emit('send-message', inputValue.value)
|
emit('send-message', inputValue.value)
|
||||||
inputValue.value = ''
|
inputValue.value = ''
|
||||||
// 重置textarea高度
|
// 重置textarea高度
|
||||||
|
|||||||
@@ -30,13 +30,14 @@ import NoticeList from './components/NoticeList.vue'
|
|||||||
import InputArea from './components/InputArea.vue'
|
import InputArea from './components/InputArea.vue'
|
||||||
import GenerateLoading from './components/GenerateLoading.vue'
|
import GenerateLoading from './components/GenerateLoading.vue'
|
||||||
import { ref, onMounted, onUnmounted, onActivated } from 'vue'
|
import { ref, onMounted, onUnmounted, onActivated } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useUserInfoStore, useGenerateStore } from '@/stores'
|
import { useUserInfoStore, useGenerateStore } from '@/stores'
|
||||||
import { streamChatAddress } from '@/api/workshop'
|
import { streamChatAddress } from '@/api/workshop'
|
||||||
import { generateUUID } from '@/utils/tools'
|
import { generateUUID } from '@/utils/tools'
|
||||||
import { showToast } from 'vant'
|
import { showToast } from 'vant'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const generateStore = useGenerateStore()
|
const generateStore = useGenerateStore()
|
||||||
const userInfoStore = useUserInfoStore()
|
const userInfoStore = useUserInfoStore()
|
||||||
|
|
||||||
@@ -68,9 +69,21 @@ const isStreaming = ref<boolean>(false)
|
|||||||
const currentStreamingMessage = ref<ChatMessage | null>(null)
|
const currentStreamingMessage = ref<ChatMessage | null>(null)
|
||||||
const sessionId = ref<string>('')
|
const sessionId = ref<string>('')
|
||||||
|
|
||||||
|
const sendPrefilledMessage = () => {
|
||||||
|
const { message, ...restQuery } = route.query
|
||||||
|
if (typeof message === 'string' && message.trim()) {
|
||||||
|
handleSendMessage(message)
|
||||||
|
router.replace({
|
||||||
|
path: route.path,
|
||||||
|
query: restQuery
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
sessionId.value = Math.floor(Date.now() / 1000).toString()
|
sessionId.value = Math.floor(Date.now() / 1000).toString()
|
||||||
generateStore.setSessionId(sessionId.value)
|
generateStore.setSessionId(sessionId.value)
|
||||||
|
sendPrefilledMessage()
|
||||||
})
|
})
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
|
|||||||
@@ -25,15 +25,15 @@
|
|||||||
|
|
||||||
<div class="glass-form">
|
<div class="glass-form">
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label class="field-label">Customer Name</label>
|
<label class="field-label">VIP ID</label>
|
||||||
<input v-model="customerData.name" type="text" placeholder="Name" class="form-input" />
|
<input v-model="customerData.name" type="text" placeholder="Enter your ID" class="form-input" />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-field email">
|
<div class="form-field email">
|
||||||
<label class="field-label">Customer Email</label>
|
<label class="field-label">Email Address</label>
|
||||||
<input
|
<input
|
||||||
v-model="customerData.email"
|
v-model="customerData.email"
|
||||||
type="email"
|
type="email"
|
||||||
placeholder="Email"
|
placeholder="Enter your email"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -78,13 +78,13 @@ const handleConfirm = async () => {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.log('customerData.value', customerData.value)
|
|
||||||
|
|
||||||
customerCheckin(customerData.value).then((res) => {
|
customerCheckin(customerData.value).then((res) => {
|
||||||
useUserInfoStore().resetGenerateParams()
|
useUserInfoStore().resetGenerateParams()
|
||||||
// console.log('res', res)
|
// console.log('res', res)
|
||||||
generateStore.setCustomerInfo(res)
|
generateStore.setCustomerInfo(res)
|
||||||
router.push('/stylist/index')
|
// router.push('/stylist/index')
|
||||||
|
router.push('/homeNav')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,25 +7,164 @@
|
|||||||
<SvgIcon name="setting" size="70" />
|
<SvgIcon name="setting" size="70" />
|
||||||
</div> -->
|
</div> -->
|
||||||
<div class="text">What are you dressing for?</div>
|
<div class="text">What are you dressing for?</div>
|
||||||
<div class="start-btn" @click="handleStart">Start</div>
|
<!-- <div class="start-btn" @click="handleStart">Start</div> -->
|
||||||
|
<div class="chatbox flex flex-center">
|
||||||
|
<div class="input-box flex">
|
||||||
|
<div class="input-wrapper flex-1 flex">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="input-item flex-1"
|
||||||
|
v-model="inputValue"
|
||||||
|
placeholder="Ask sth!"
|
||||||
|
v-show="!isRecording"
|
||||||
|
/>
|
||||||
|
<div class="recording-visualizer flex-1" v-show="isRecording">
|
||||||
|
<AudioVisualizer ref="audioVisualizerRef" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SvgIcon
|
||||||
|
class="audio-icon"
|
||||||
|
:name="isRecording ? 'pause' : 'audio'"
|
||||||
|
size="52"
|
||||||
|
@click="handleClickAudio"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="send flex flex-center" @click="handleSendMessage">
|
||||||
|
<SvgIcon class="send-icon" name="send" size="26" color="#000000" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer-navigation />
|
<footer-navigation />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onUnmounted, nextTick, watch } from 'vue'
|
||||||
|
import { showToast } from 'vant'
|
||||||
import HeaderTitle from '@/components/HeaderTitle.vue'
|
import HeaderTitle from '@/components/HeaderTitle.vue'
|
||||||
import FooterNavigation from '@/components/FooterNavigation.vue'
|
import FooterNavigation from '@/components/FooterNavigation.vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import AudioVisualizer from '@/views/asistant/components/AudioVisualizer.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const handleBack = () => {
|
const inputValue = ref('')
|
||||||
router.go(-1)
|
const isRecording = ref(false)
|
||||||
|
const audioVisualizerRef = ref<InstanceType<typeof AudioVisualizer> | null>(null)
|
||||||
|
let speechRecognition: any = null
|
||||||
|
let lastTranscript = ''
|
||||||
|
let isSpeechRecognitionActive = false
|
||||||
|
|
||||||
|
const refreshAudioVisualizer = () => {
|
||||||
|
audioVisualizerRef.value?.updateLines?.()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleStart = () => {
|
watch(isRecording, async (newVal) => {
|
||||||
console.log('click start')
|
if (newVal) {
|
||||||
router.push('/asistant')
|
await nextTick()
|
||||||
|
refreshAudioVisualizer()
|
||||||
|
setTimeout(() => {
|
||||||
|
refreshAudioVisualizer()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSendMessage = () => {
|
||||||
|
const message = inputValue.value.trim()
|
||||||
|
if(!message){
|
||||||
|
showToast('Please enter a message')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push({
|
||||||
|
path: '/asistant',
|
||||||
|
query: message ? { message } : undefined
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickAudio = () => {
|
||||||
|
if (isRecording.value) {
|
||||||
|
stopRecording()
|
||||||
|
} else {
|
||||||
|
startRecording()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const startRecording = () => {
|
||||||
|
if (isSpeechRecognitionActive) {
|
||||||
|
console.warn('Speech recognition already running')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!speechRecognition) {
|
||||||
|
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
||||||
|
showToast(
|
||||||
|
'Your browser does not support speech recognition, please try again with another browser'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const SpeechRecognition =
|
||||||
|
(window as any).SpeechRecognition || (window as any).webkitSpeechRecognition
|
||||||
|
speechRecognition = new SpeechRecognition()
|
||||||
|
speechRecognition.continuous = true
|
||||||
|
speechRecognition.interimResults = true
|
||||||
|
speechRecognition.lang = 'en-US'
|
||||||
|
}
|
||||||
|
|
||||||
|
speechRecognition.onstart = () => {
|
||||||
|
isRecording.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
speechRecognition.onresult = (event: any) => {
|
||||||
|
let finalTranscript = ''
|
||||||
|
let interimTranscript = ''
|
||||||
|
|
||||||
|
for (let i = event.resultIndex; i < event.results.length; i++) {
|
||||||
|
const transcript = event.results[i][0].transcript
|
||||||
|
if (event.results[i].isFinal) {
|
||||||
|
finalTranscript += transcript
|
||||||
|
} else {
|
||||||
|
interimTranscript += transcript
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (finalTranscript && finalTranscript !== lastTranscript) {
|
||||||
|
lastTranscript = finalTranscript
|
||||||
|
inputValue.value = finalTranscript
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interimTranscript) {
|
||||||
|
console.log('Speech recognition interim result:', interimTranscript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
speechRecognition.onend = () => {
|
||||||
|
isRecording.value = false
|
||||||
|
lastTranscript = ''
|
||||||
|
isSpeechRecognitionActive = false
|
||||||
|
}
|
||||||
|
|
||||||
|
speechRecognition.onerror = (event: any) => {
|
||||||
|
console.error('Speech recognition error:', event.error)
|
||||||
|
isRecording.value = false
|
||||||
|
isSpeechRecognitionActive = false
|
||||||
|
showToast('Speech recognition failed, please try again')
|
||||||
|
}
|
||||||
|
|
||||||
|
speechRecognition.start()
|
||||||
|
isSpeechRecognitionActive = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopRecording = () => {
|
||||||
|
if (speechRecognition && isSpeechRecognitionActive) {
|
||||||
|
speechRecognition.stop()
|
||||||
|
isSpeechRecognitionActive = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
if (speechRecognition && isRecording.value) {
|
||||||
|
speechRecognition.stop()
|
||||||
|
}
|
||||||
|
speechRecognition = null
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.dressfor-container {
|
.dressfor-container {
|
||||||
@@ -54,16 +193,52 @@ const handleStart = () => {
|
|||||||
margin-top: 43.8rem;
|
margin-top: 43.8rem;
|
||||||
margin-bottom: 14rem;
|
margin-bottom: 14rem;
|
||||||
}
|
}
|
||||||
.start-btn {
|
.chatbox {
|
||||||
font-size: 5.6rem;
|
height: 9.3rem;
|
||||||
width: 32.5rem;
|
// background-color: #fff;
|
||||||
height: 8.1rem;
|
column-gap: 2.29rem;
|
||||||
border: .2rem solid #fff;
|
.input-box {
|
||||||
display: flex;
|
width: 59.8rem;
|
||||||
align-items: center;
|
height: 100%;
|
||||||
justify-content: center;
|
background-color: #fff;
|
||||||
border-radius: 4rem;
|
border: 2px solid #5f5f5f;
|
||||||
margin: 0 auto;
|
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;
|
||||||
|
}
|
||||||
|
.audio-icon {
|
||||||
|
width: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.send {
|
||||||
|
width: 7.6rem;
|
||||||
|
height: 7.6rem;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user