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

@@ -46,3 +46,50 @@ export {
getUniversalZoomLevel,
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">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { DownloadImages } from '@/utils/tools'
const router = useRouter()
const query = router.currentRoute.value.query
const query = computed(() => router.currentRoute.value.query)
onMounted(() => {})
const onDownload = () => {
DownloadImages([{
url: 'http://118.31.39.42:3000/falls/2.png',
name: 'lane-crawford.png'
}])
}
const onEdit = () => {
console.log('edit')
}
</script>
<template>
<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>
</template>
@@ -17,12 +34,73 @@
.creation-details {
width: 100%;
flex: 1;
overflow: hidden;
background-color: #e3e3e3;
overflow-y: auto;
background-color: #f3f3f3;
border-radius: 1rem;
position: relative;
color: #000;
display: flex;
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>

View File

@@ -1,55 +1,121 @@
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ref, reactive, onMounted, computed } from 'vue'
import MyList from '@/components/myList.vue'
import { DownloadImages } from '@/utils/tools'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['view-type'])
const query = router.currentRoute.value.query
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 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) => {
if (v.isAi) return
console.log('详情', v)
router.push({ query: { did: v.id } })
}
const onDownloadItem = (v) => {
console.log('保存', v)
}
const onLoveItem = (v) => {
// Outfit暂时不可以like
console.log('喜欢', v)
v.love = !v.love
}
const onSaveAll = () => {
// console.log('保存')
for (var i = 1; i <= 20; i++) {
const a = document.createElement('a')
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`;
a.download = "content.png";
a.click()
const onDownloadItem = (v) => {
// console.log('保存', v)
if (v.loading) return
v.loading = true
v.selected = false
const obj = {
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 = () => {
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>
<template>
@@ -57,7 +123,7 @@
<div class="title">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="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" />
<div class="corner">
<div class="ai" v-if="v.isAi">Gen-AI</div>
@@ -67,14 +133,28 @@
<div @click.stop="onLoveItem(v)">
<SvgIcon :name="`love_${v.love ? '1' : '0'}`" size="27" />
</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 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">
<button @click="onSaveAll">Save All</button>
<div class="btns" v-show="!query.date">
<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>
@@ -102,8 +182,10 @@
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;
@@ -111,8 +193,8 @@
.item {
width: 47%;
height: 52.9rem;
overflow: hidden;
border-radius: 2rem;
// overflow: hidden;
border-radius: var(--border-radius);
background-color: #fff;
margin-bottom: 4rem;
border: 0.1rem solid #000;
@@ -135,6 +217,7 @@
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;
@@ -173,6 +256,27 @@
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;
height: 8rem;
border-radius: 1.3rem;
border: none;
background: #000;
font-weight: 400;
font-size: 4.2rem;

View File

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

View File

@@ -1,14 +1,18 @@
<script setup lang="ts">
import HeaderTitle from '@/components/HeaderTitle.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'])
import { useRouter } from 'vue-router'
const router = useRouter()
const inputText = ref('')
const isLoved = ref(false)
const loading = ref(true)
setTimeout(() => {
loading.value = false
}, 500);
const onSend = () => {
if (inputText.value === '') return
const text = inputText.value
@@ -34,6 +38,8 @@
<template>
<header-title style-type="2" />
<div class="loading" v-if="loading"><generate-loading /></div>
<template v-else>
<div class="customize">
<div class="title">Customize your Look!</div>
<p class="tip">Refine your Look</p>
@@ -66,8 +72,16 @@
</div>
<footer-navigation />
</template>
</template>
<style scoped lang="less">
.loading{
width: 100%;
margin-top: 36.6rem;
display: flex;
align-items: center;
justify-content: center;
}
.customize {
width: 100%;
position: relative;
@@ -189,6 +203,7 @@
box-sizing: content-box;
font-family: satoshiRegular;
// margin: 0 1.8rem;
border: none;
margin: 0 5.2rem 0 auto;
width: 23.8rem;
height: 6.9rem;

View File

@@ -32,7 +32,9 @@
</p>
</div>
<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>
</div>
</div>
@@ -54,25 +56,6 @@
width: 100%;
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 {
top: 0;
left: 0;
@@ -89,8 +72,16 @@
}
}
>.btns {
width: 100%;
display: flex;
justify-content: center;
bottom: 19.7rem;
> button {
width: 40rem;
height: 8.3rem;
border-radius: 0.7rem;
margin: 0 1.8rem;
}
}
}
</style>