Your Creation详情页

This commit is contained in:
李志鹏
2025-10-21 13:41:54 +08:00
parent fe2b1194bb
commit b2003d8a85
7 changed files with 327 additions and 91 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@@ -45,4 +45,51 @@ const getMousePosition = (e:any,bor:any) => {
export { export {
getUniversalZoomLevel, getUniversalZoomLevel,
getMousePosition, getMousePosition,
} }
/**
* 下载图片
* @param list 图片列表
* @param onProgress 下载进度回调
* @param onError 下载错误回调
* @param onSuccess 下载成功回调
*/
export async function DownloadImages(list:Array<{url:string,name:string}>, onProgress?:(count:number,total:number,item:any)=>void, onError?:(count:number,total:number,item:any)=>void, onSuccess?:(successCount:number,errCount:number)=>void) {
const total = list.length;
var count = 0;
var successCount = 0;
var errCount = 0;
for (let i = 0; i < list.length; i++) {
await new Promise((resolve) => {
let xhr = new XMLHttpRequest();
xhr.open("GET", list[i].url);
xhr.responseType = "blob"
xhr.onload = function () {
count++;
if (this.status === 200) {
let blob = this.response;
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = list[i].name || list[i].url;
a.click();
successCount++;
typeof onProgress === "function" && onProgress(count,total,list[i]);
resolve(blob);
} else {
errCount++;
typeof onError === "function" && onError(count,total,list[i]);
resolve(true);
}
};
xhr.onerror = function () {
count++;
errCount++;
typeof onError === "function" && onError(count,total,list[i]);
resolve(true);
};
xhr.send();
})
}
typeof onSuccess === "function" && onSuccess(successCount,errCount);
}

View File

@@ -1,15 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { DownloadImages } from '@/utils/tools'
const router = useRouter() const router = useRouter()
const query = router.currentRoute.value.query const query = computed(() => router.currentRoute.value.query)
onMounted(() => {}) onMounted(() => {})
const onDownload = () => {
DownloadImages([{
url: 'http://118.31.39.42:3000/falls/2.png',
name: 'lane-crawford.png'
}])
}
const onEdit = () => {
console.log('edit')
}
</script> </script>
<template> <template>
<div class="creation-details"> <div class="creation-details">
{{ query }} <div class="title">Lane Crawford</div>
<p class="tip">Business Casual Suits</p>
<div class="image"><img src="@/assets/images/workshop/creation-details.png" /></div>
<div class="btns">
<div class="icon" @click="onDownload"><SvgIcon name="download" size="33" /></div>
<button @click="onEdit">Edit</button>
</div>
</div> </div>
</template> </template>
@@ -17,12 +34,73 @@
.creation-details { .creation-details {
width: 100%; width: 100%;
flex: 1; flex: 1;
overflow: hidden; overflow-y: auto;
background-color: #e3e3e3; background-color: #f3f3f3;
border-radius: 1rem; border-radius: 1rem;
position: relative;
color: #000; color: #000;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0 16.3rem;
display: flex;
flex-direction: column;
align-items: center;
> .title {
font-family: satoshiBold;
font-size: 9.6rem;
line-height: 124%;
margin-top: 12rem;
}
> .tip {
margin-top: 2rem;
font-family: satoshiRegular;
font-size: 4rem;
line-height: 124%;
color: rgba(0, 0, 0, 0.6);
}
> .image {
margin-top: 13.7rem;
width: 100%;
height: 73.5rem;
border: 0.1rem solid #acacac;
background-color: #fff;
> img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
> .btns {
margin-top: 4.4rem;
margin-left: auto;
display: flex;
align-items: center;
> * {
margin-left: 1.7rem;
}
> .icon {
width: 7rem;
height: 7rem;
border-radius: 50%;
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
}
> button {
box-sizing: content-box;
font-family: satoshiBold;
// margin: 0 1.8rem;
border: none;
width: 14.6rem;
height: 7rem;
border-radius: 7rem;
background: #000;
font-size: 2.75rem;
color: #fff;
&:active {
opacity: 0.7;
}
}
}
} }
</style> </style>

View File

@@ -1,55 +1,121 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted, computed } from 'vue'
import MyList from '@/components/myList.vue' import MyList from '@/components/myList.vue'
import { DownloadImages } from '@/utils/tools'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const emit = defineEmits(['view-type']) const emit = defineEmits(['view-type'])
const query = router.currentRoute.value.query
onMounted(() => { onMounted(() => {
emit('view-type', 1) 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 onLoad = () => {
loading.value = true
setTimeout(() => {
for (var i = 0; i < 10; i++)
list.push({
id: list.length,
love: Math.random() > 0.5,
isAi: Math.random() > 0.5,
selected: list.length < maxSelectCount,
loading: false,
downloaded: false
})
loading.value = false
if (list.length >= 30) {
finish.value = true
}
}, 500)
}
const onItem = (v) => {
isChooseSave.value ? onSelectItem(v) : onDetailsItem(v)
}
const onDetailsItem = (v) => { const onDetailsItem = (v) => {
if (v.isAi) return if (v.isAi) return
console.log('详情', v) console.log('详情', v)
router.push({ query: { did: v.id } }) router.push({ query: { did: v.id } })
} }
const onDownloadItem = (v) => {
console.log('保存', v)
}
const onLoveItem = (v) => { const onLoveItem = (v) => {
// Outfit暂时不可以like // Outfit暂时不可以like
console.log('喜欢', v) console.log('喜欢', v)
v.love = !v.love v.love = !v.love
} }
const onSaveAll = () => { const onDownloadItem = (v) => {
// console.log('保存') // console.log('保存', v)
for (var i = 1; i <= 20; i++) { if (v.loading) return
const a = document.createElement('a') v.loading = true
a.href = `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAOsSURBVHgB7VrNahRBEK5dBRE16GJWElEJQbyYkJziTRRBX0OfQRB8AQmaHLwYX8CD68Z4U/AlBK/JNepBIdElcbOz9me6dntqeqa7JzOTPewHlR5nquuv/6prJRqipqmh6LWibUWRon4K9TQPeJtG/ywwz5Sij4r2tCy0nxXNGjyXLTyfFF1RVJe6Tuq2pZmzDE+jdSErDcspOnq6XVX0IoXnQLfPjYAMPNk0GPJQV9GWKVjghKKn5A5O5MkDWXVW1hbG9wIMPxDPLYsD+PdESr/ICEDfwSMDfA7CL1ki2VE0R+45vaBo36J8UvStacciEaBr+vt1i4Esc0rz3BR90b7EhzUheF93qJMbzNMRgt9ZeHco7uSS0R/Ta5aSIzCjvzHPHfH9G+k/5ssFo5MPwDsnZOxY+KRxNvD8RxC6lu91m5zIQ7ALNXIbKPXULMbdouEozqfwyJ3LKzI+cMn5aTiB9jYlp2k95RmAMw8oHogtH8W+cMlZofg6+WUY5gLz7BkyeCutxAEYcIbse7nPesNo4ICT2+gpl+IQuOTAiTcWI25QthM42ZcoeT60tWOVOgBENHSC27NkT0HwrkHx/f/AkF/zUewLHzkwaIaGU8g8sE5TfCTwPCF4uA9GbbDIXdtbkQ6wYQ8F71/dNrWT4LkqjGcHHpEYrW0hbI78TmEGeBeFjF1HHwTpGcVzLjYUZ8Fdik8XNn6ZLAFeo/j21jEM8zEe2BfKNsgPKxTPPk2HzO0StKr7JBxoUDx6XW3QImUDgjBaHUomc9Pkv78/pvj8llst2ieUcWHiTNE0IuReIDPIdQpbRxjF+xZZ/HzPJc+80HQDDLc5silk+gKL8oKiLzRM5r4qOk+eiSUztSnccJ6rLcOYPLAZGrKZDC7KmL/vKZm/2wg8yP35ApN3Cy4UeYwYCcPHGBVUNYXyrJkEf90i8CIdLkyfRYyU4QMNDy7fw4v1tAL1TKXpqaoyV4qeIitzWQdZaRXAmvaqzMpcqXqaFg+RoM1TsZW56RQ9R6kAIhH9Xx6vojK3QfHIF6HnFV5WVZnbKUHPNoYO0ZfDHQqWI9+Z6Du+59HTT/zSkRN9qgaJIASlq6OIsQPHjbEDxw3+wcBEVberQnY/OPBdvMSPaaGVuQXx7reFb7cEPT/wp6rKXLsEPbD9yJW5P+SuzOF5sgQ9TWZ4S+VX5orW0ybLfzUoszJXqp6qKnOl6uHLMuYV8myk2Vl31kjzgLdB4Zf6UD1rNj3/ANvoQHJNUkmmAAAAAElFTkSuQmCC`; v.selected = false
a.download = "content.png"; const obj = {
a.click() url: 'http://118.31.39.42:3000/falls/1.png',
name: v.id + '.png'
}
DownloadImages([obj], 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: 'http://118.31.39.42:3000/falls/1.png',
name: i + 1 + '.png'
})
}
})
}
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 = () => { const onContinue = () => {
router.push({ name: 'end' }) router.push({ name: 'end' })
} }
const list = reactive([])
const loading = ref(false)
const finish = ref(false)
const onLoad = () => {
loading.value = true
setTimeout(() => {
for (var i = 0; i < 10; i++)
list.push({ id: list.length, love: Math.random() > 0.5, isAi: Math.random() > 0.5 })
loading.value = false
if (list.length >= 30) {
finish.value = true
}
}, 500)
}
</script> </script>
<template> <template>
@@ -57,7 +123,7 @@
<div class="title">Your Creation</div> <div class="title">Your Creation</div>
<div class="list"> <div class="list">
<my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad"> <my-list v-model:loading="loading" v-model:finish="finish" @load="onLoad">
<div class="item" v-for="(v, i) in list" :key="i" @click="onDetailsItem(v)"> <div class="item" v-for="(v, i) in list" :key="i" @click="onItem(v)">
<img src="@/assets/images/workshop/posture/posture_1.png" /> <img src="@/assets/images/workshop/posture/posture_1.png" />
<div class="corner"> <div class="corner">
<div class="ai" v-if="v.isAi">Gen-AI</div> <div class="ai" v-if="v.isAi">Gen-AI</div>
@@ -67,14 +133,28 @@
<div @click.stop="onLoveItem(v)"> <div @click.stop="onLoveItem(v)">
<SvgIcon :name="`love_${v.love ? '1' : '0'}`" size="27" /> <SvgIcon :name="`love_${v.love ? '1' : '0'}`" size="27" />
</div> </div>
<div @click.stop="onDownloadItem(v)"><SvgIcon name="download" 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>
<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> </div>
</my-list> </my-list>
</div> </div>
<div class="btns"> <div class="btns" v-show="!query.date">
<button @click="onSaveAll">Save All</button> <template v-if="!isChooseSave">
<button @click="onContinue">Continue</button> <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> </div>
</template> </template>
@@ -102,8 +182,10 @@
flex: 1; flex: 1;
margin: 0 3.8rem; margin: 0 3.8rem;
overflow: hidden; overflow: hidden;
--border-radius: 2rem;
> .my-list { > .my-list {
padding: 0 6rem; padding: 0 6rem;
--my-list-footer-margin: 0 0 2rem;
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
@@ -111,8 +193,8 @@
.item { .item {
width: 47%; width: 47%;
height: 52.9rem; height: 52.9rem;
overflow: hidden; // overflow: hidden;
border-radius: 2rem; border-radius: var(--border-radius);
background-color: #fff; background-color: #fff;
margin-bottom: 4rem; margin-bottom: 4rem;
border: 0.1rem solid #000; border: 0.1rem solid #000;
@@ -135,6 +217,7 @@
font-family: satoshiBold; font-family: satoshiBold;
font-size: 1.6rem; font-size: 1.6rem;
border-bottom-left-radius: 1.6rem; border-bottom-left-radius: 1.6rem;
border-top-right-radius: var(--border-radius);
background-color: #000; background-color: #000;
color: #fff; color: #fff;
@@ -173,6 +256,27 @@
justify-content: center; justify-content: center;
} }
} }
> .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;
}
} }
} }
} }
@@ -187,6 +291,7 @@
width: 35rem; width: 35rem;
height: 8rem; height: 8rem;
border-radius: 1.3rem; border-radius: 1.3rem;
border: none;
background: #000; background: #000;
font-weight: 400; font-weight: 400;
font-size: 4.2rem; font-size: 4.2rem;

View File

@@ -22,7 +22,7 @@
<template> <template>
<header-title style-type="2" /> <header-title style-type="2" />
<creation-list v-show="!detailsID" /> <creation-list v-show="!detailsID" />
<creation-details v-show="detailsID" /> <creation-details v-if="detailsID" />
<footer-navigation is-placeholder /> <footer-navigation is-placeholder />
</template> </template>

View File

@@ -1,14 +1,18 @@
<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 } from 'vue' import GenerateLoading from '@/views/asistant/components/GenerateLoading.vue'
import { ref,onMounted } from 'vue'
const emit = defineEmits(['viewType']) const emit = defineEmits(['viewType'])
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const inputText = ref('') const inputText = ref('')
const isLoved = ref(false) const isLoved = ref(false)
const loading = ref(true)
setTimeout(() => {
loading.value = false
}, 500);
const onSend = () => { const onSend = () => {
if (inputText.value === '') return if (inputText.value === '') return
const text = inputText.value const text = inputText.value
@@ -34,40 +38,50 @@
<template> <template>
<header-title style-type="2" /> <header-title style-type="2" />
<div class="customize"> <div class="loading" v-if="loading"><generate-loading /></div>
<div class="title">Customize your Look!</div> <template v-else>
<p class="tip">Refine your Look</p> <div class="customize">
<div class="input-box"> <div class="title">Customize your Look!</div>
<div class="help">?</div> <p class="tip">Refine your Look</p>
<input <div class="input-box">
type="text" <div class="help">?</div>
v-model="inputText" <input
@keyup.enter="onSend" type="text"
placeholder="Try: “Change background to Tokyo City”" v-model="inputText"
/> @keyup.enter="onSend"
<div class="send" @click="onSend"><SvgIcon name="send" size="48" /></div> placeholder="Try: “Change background to Tokyo City”"
</div> />
<div class="card"> <div class="send" @click="onSend"><SvgIcon name="send" size="48" /></div>
<img src="@/assets/images/workshop/posture/posture_1.png" /> </div>
<!-- <div class="select-box"> <div class="card">
<img src="@/assets/images/workshop/posture/posture_1.png" />
<!-- <div class="select-box">
<div class="icon"><SvgIcon name="history" size="35" /></div> <div class="icon"><SvgIcon name="history" size="35" /></div>
<div class="label">History</div> <div class="label">History</div>
<div class="icon"><SvgIcon name="xialajiantou" size="29" /></div> <div class="icon"><SvgIcon name="xialajiantou" size="29" /></div>
</div> --> </div> -->
<div class="icons"> <div class="icons">
<div @click="onLove"><SvgIcon :name="`love_${isLoved ? 1 : 0}`" size="35" /></div> <div @click="onLove"><SvgIcon :name="`love_${isLoved ? 1 : 0}`" size="35" /></div>
<div @click="onReload"><SvgIcon name="reload" size="35" /></div> <div @click="onReload"><SvgIcon name="reload" size="35" /></div>
<!-- <div @click="onDownload"><SvgIcon name="download" size="35" /></div> --> <!-- <div @click="onDownload"><SvgIcon name="download" size="35" /></div> -->
</div>
</div>
<div class="btns">
<button @click="onFinish">Finish</button>
</div> </div>
</div> </div>
<div class="btns"> <footer-navigation />
<button @click="onFinish">Finish</button> </template>
</div>
</div>
<footer-navigation />
</template> </template>
<style scoped lang="less"> <style scoped lang="less">
.loading{
width: 100%;
margin-top: 36.6rem;
display: flex;
align-items: center;
justify-content: center;
}
.customize { .customize {
width: 100%; width: 100%;
position: relative; position: relative;
@@ -189,6 +203,7 @@
box-sizing: content-box; box-sizing: content-box;
font-family: satoshiRegular; font-family: satoshiRegular;
// margin: 0 1.8rem; // margin: 0 1.8rem;
border: none;
margin: 0 5.2rem 0 auto; margin: 0 5.2rem 0 auto;
width: 23.8rem; width: 23.8rem;
height: 6.9rem; height: 6.9rem;

View File

@@ -32,7 +32,9 @@
</p> </p>
</div> </div>
<div class="btns"> <div class="btns">
<button class="sandblasted-blurred" @click="handleUploadFace"><span>Upload Face</span></button> <button class="sandblasted-blurred" @click="handleUploadFace">
<span>Upload Face</span>
</button>
<button class="sandblasted-blurred" @click="handleFinish"><span>Finish</span></button> <button class="sandblasted-blurred" @click="handleFinish"><span>Finish</span></button>
</div> </div>
</div> </div>
@@ -54,25 +56,6 @@
width: 100%; width: 100%;
height: auto; height: auto;
} }
.btns {
width: 100%;
display: flex;
justify-content: center;
> button {
width: 40rem;
height: 8.3rem;
border-radius: 0.7rem;
border: 0.4rem solid #fff;
font-family: satoshiMedium;
font-weight: 500;
font-size: 5.5rem;
margin: 0 1.8rem;
color: #fff;
}
}
}
.upload-face-1 {
> .texts { > .texts {
top: 0; top: 0;
left: 0; left: 0;
@@ -88,9 +71,17 @@
line-height: 132%; line-height: 132%;
} }
} }
> .btns { >.btns {
width: 100%;
display: flex;
justify-content: center;
bottom: 19.7rem; bottom: 19.7rem;
> button {
width: 40rem;
height: 8.3rem;
border-radius: 0.7rem;
margin: 0 1.8rem;
}
} }
} }
</style> </style>