Files
aida_front/src/component/home/tools/poseTransfer/index.vue
2025-11-20 17:26:05 +08:00

1681 lines
52 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="poseTransfer">
<div class="configuratioBox" :class="{ active: button.left }">
<div class="configuratio">
<div class="content">
<div class="video-type-container">
<div class="title">{{ $t('poseTransfer.VideoType') }}</div>
<a-select
class="video-type-selection"
v-model:value="videoType"
size="large"
placeholder="Please select"
>
<a-select-option
v-for="item in options"
:key="item.vlaue"
:value="item.value"
>
{{ item.label }}
</a-select-option>
</a-select>
</div>
<div class="selectImg">
<div class="head">
<div class="text">{{ $t('poseTransfer.SelectDesign') }}</div>
<i
v-show="videoType !== 2"
class="fi fi-rs-interrogation tips-icon"
@click="showTips = true"
/>
</div>
<div class="imgBox" v-mousewheel>
<div
class="item first-frame"
:class="{ active: item.id == selectImg.id }"
v-for="(item, index) in showFirstFrameList"
@click="selectImgItem(item)"
>
<div
class=""
v-if="item.status == 'uploading'"
style="display: flex; align-items: center"
>
<a-spin size="large" />
</div>
<img
v-show="item.designOutfitUrl || item.imgUrl || item.url"
:src="item.designOutfitUrl || item.imgUrl || item.url"
alt=""
/>
<div
v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox"
>
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i>
</div>
<div
@click.stop="setUploadDelete(item, index)"
v-if="source != 'design' || (isDesignPage && videoType === 3)"
>
<i class="fi fi-rr-trash icon_delete"></i>
</div>
</div>
</div>
<div class="upload_item item first_frames" v-show="showFirstFrameUpload">
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('first')"
/>
<div class="upload_file_item">
<a-upload
key="common"
:action="getUploadUrl() + '/api/history/toProductImageElementUpload'"
list-type="picture-card"
:capture="null"
:data="{
...upload
}"
:headers="{ Authorization: token }"
:before-upload="beforeUpload"
:multiple="!!upload.projectId"
accept=".jpg,.png,.jpeg,.bmp"
@change="file => fileUploadChange(file)"
>
<div class="upload_tip_block">
<i class="fi fi-br-upload"></i>
<!-- <img class="upload_img_icon" src="@/assets/images/homePage/add_file.png"> -->
</div>
</a-upload>
</div>
</div>
<template v-if="videoType === 3">
<div
v-show="lastFrameList?.length < 1"
class="upload_item item last_frames"
>
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('last')"
/>
<div class="upload_file_item">
<a-upload
key="lastframes"
:action="
getUploadUrl() + '/api/history/toProductImageElementUpload'
"
list-type="picture-card"
:capture="null"
:data="{
...upload,
type: 'last'
}"
:headers="{ Authorization: token }"
:before-upload="beforeUpload"
accept=".jpg,.png,.jpeg,.bmp"
@change="file => fileUploadChangeLast(file)"
>
<div class="upload_tip_block">
<i class="fi fi-br-upload"></i>
<!-- <img class="upload_img_icon" src="@/assets/images/homePage/add_file.png"> -->
</div>
</a-upload>
</div>
</div>
<div
class="item lastframe-list"
:class="{ active: item.id == lastSelectImg.id }"
v-for="(item, index) in lastFrameList"
@click="selectImgItem(item, true)"
>
<div
class=""
v-if="item.status == 'uploading'"
style="display: flex; align-items: center"
>
<a-spin size="large" />
</div>
<img
v-show="item.designOutfitUrl || item.imgUrl || item.url"
:src="item.designOutfitUrl || item.imgUrl || item.url"
alt=""
/>
<div
v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox"
>
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i>
</div>
<div
@click.stop="setUploadDelete(item, index, true)"
v-if="source != 'design' || (isDesignPage && videoType === 3)"
>
<i class="fi fi-rr-trash icon_delete"></i>
</div>
</div>
</div>
</template>
</div>
</div>
<div class="prompt-input-container" v-show="!showMotion">
<div class="title" style="display: flex; gap: 0.5rem">
{{ $t('ProductImg.Prompt') }}
<a
:href="
locale == 'CHINESE_SIMPLIFIED'
? 'https://aida-user-manual-chinese.super.site/2b08f755cedd80a985cffdf2af80c538'
: 'https://aida-user-manual.super.site/advanced-tool/animated-product-image/to-product-video-prompt-assist '
"
target="_blank"
>
<i
class="fi fi-rs-interrogation tips-icon"
style="font-size: 1.8rem; color: #000"
/>
</a>
</div>
<!-- <div class="title">
<span>{{ $t('ProductImg.Prompt') }}</span>
<i class="fi fi-rs-interrogation tips-icon" />
</div> -->
<promptInput :content="inputPrompt" ref="promptInput" />
</div>
<div class="poses" v-show="showMotion">
<div class="head">
<div class="text">{{ $t('poseTransfer.Selectpose') }}</div>
</div>
<div class="imgBox" v-mousewheel>
<div
class="item"
v-for="item in poseList"
:key="item.id"
@click="selectPose(item)"
>
<video :ref="el => setVideoRef(item.id, el)" :src="item.video" />
<div class="btnBox">
<div :class="{ active: item.isChecked }">
<i class="fi fi-br-check"></i>
</div>
</div>
<div class="control-container">
<div class="icon-list">
<SvgIcon
class="play-icon"
@click.stop="handlePlayMotion(item)"
:name="isVideoPlaying(item.id) ? 'CPause' : 'CPlay'"
size="20"
color="#fff"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="generage_btn_box" style="margin-left: auto">
<!-- <div class="generage_btn started_btn" v-show="!isGenerate" @click="getgenerate">
{{ $t('Generate.Generate') }}
</div> -->
<div class="generage_btn started_btn" v-show="!isGenerate">
<i
class="fi fi-bs-magic-wand"
style="background-color: #000; font-size: 2.3rem; flex: 1; margin: 0"
@click="getgenerate()"
></i>
<div
v-show="showMotion"
class="icon iconfont icon-xiala"
:class="{ active: speedState }"
@click.stop="openSpeed"
></div>
<div class="content" v-show="speedState">
<div
v-for="item in speedList"
:key="item.value"
:class="{ active: item.value == speedData.value }"
@click.stop="setSpeed(item)"
:title="item.title"
>
{{ item.label }}
</div>
</div>
</div>
<div v-show="isGenerate" class="generage_btn started_btn">
<i class="fi fi-br-loading"></i>
</div>
</div>
</div>
<div class="likeBox">
<!-- {{ locale }}
<img src="@/assets/images/homePage/toolsGuide/productCN.png" alt="">
<img src="@/assets/images/homePage/toolsGuide/productEN.png" alt=""> -->
<div class="element">
<div class="title">
<i class="fi fi-rs-comments"></i>
<span>{{ $t('poseTransfer.LikeVideo') }}</span>
</div>
<div class="content">
<generalDrag
ref="generalDragLeft"
:list="likeList"
:isLike="true"
:isVideo="true"
@setBtn="likeSetBtn"
></generalDrag>
</div>
<!-- <div class="btnLeft" @click="setSize('left')" :class="{'active':button.left}">
<span class="icon iconfont icon-xiala"></span>
</div> -->
<div class="btnRight" @click="setSize('right')" :class="{ active: button.right }">
<span class="icon iconfont icon-xiala"></span>
</div>
</div>
</div>
<div class="noLikeBox" :class="{ active: button.right, designPage: isDesignPage }">
<div class="element">
<div class="title">
<i class="fi fi-rs-comments"></i>
<span>{{ $t('poseTransfer.InputVideo') }}</span>
</div>
<div class="content">
<generalDrag
ref="generalDragRight"
:list="noLikeList"
:isVideo="true"
@setBtn="noLikeSetBtn"
></generalDrag>
</div>
</div>
</div>
<Tips v-model:showModal="showTips" />
<Product
v-model:showModal="showProductList"
:frameList="productFrameList"
:type="productType"
:key="productType"
@confirm="handleConfirmProduct"
/>
</div>
</template>
<script lang="ts">
import {
defineComponent,
computed,
ref,
inject,
nextTick,
watch,
createVNode,
toRefs,
reactive,
onMounted
} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue'
import { Https } from '@/tool/https'
import { useStore } from 'vuex'
import { Upload, message, Modal } from 'ant-design-vue'
import { useI18n } from 'vue-i18n'
import generalDrag from '@/component/modules/generalDrag.vue'
import { getUploadUrl, isMoible, getMinioUrl } from '@/tool/util'
import { getCookie, setCookie } from '@/tool/cookie'
import showViewVideo from '@/tool/mount'
import router from '@/router'
import promptInput from './promptInput.vue'
import Tips from './Tips.vue'
import Product from './Product.vue'
import { getFirstFrame, getFirstAndLastFrame } from './prompt'
export default defineComponent({
components: {
generalDrag,
promptInput,
Tips,
Product,
},
props: {
isDesignPage: {
type: Boolean,
default: false
},
source: {
type: String,
default: ''
}
},
emit: ['unLike'],
setup(props, { emit }) {
const { t, locale } = useI18n()
const store = useStore()
const route = useRoute()
const data: any = reactive({
button: {
left: false,
right: false
},
currentList: [],
fileList: [], // 默认上传图片的fileList
lastSelectImg: {}, // 选中的尾帧
selectImg: {},
token: getCookie('token'),
upload: {
projectId: computed(() => store.state.Workspace.probjects.id),
type: null // first首帧 last尾帧
},
waitList: [],
likeList: computed(() => {
if (!route.query?.id && route.query.tools == 'poseTransfer') {
return []
} else {
return store.state.HomeStoreModule.poseTransfer.likedList
}
}),
noLikeList: computed(() => {
if (!route.query?.id && route.query.tools == 'poseTransfer') {
return []
} else {
return store.state.HomeStoreModule.poseTransfer.list
}
}),
isGenerate: false, //判断是否正在进行generate
removeGenerate: false,
generateTime: null as any,
poseList: [],
selectPose: null as any
})
let speed = reactive({
speedList: [
{
title: '',
label: t('speedList.toproductBasic'),
value: ''
},
{
title: '',
label: t('speedList.toproductFlus'),
value: 'wx'
}
],
speedState: false,
speedData: {
title: 'Generate high-quality content',
label: t('speedList.toproductBasic'),
value: ''
}
})
const showTips = ref(false)
const setIsShowMark: any = inject('setIsShowMark')
const createProbject: any = inject('createProbject', () => {})
const dataDom = reactive({
generalDragLeft: null as any,
generalDragRight: null as any,
scaleVideo: null as any,
promptInput: null as any
})
const selectImgItem = (item: any, isLastFrame = false) => {
// 如果点击已选中的项,则取消选中并清除对应选择
if (item.isChecked) {
item.isChecked = false
if (isLastFrame) {
data.lastSelectImg = {}
} else {
data.selectImg = {}
}
return
}
// 处理尾帧选择
if (isLastFrame) {
;(lastFrameList.value || []).forEach(
(listItem: any) => (listItem.isChecked = false)
)
item.isChecked = true
data.lastSelectImg = item
if (item.url || item.imgUrl) {
data.lastSelectImg.minioUrl = getMinioUrl(item.url || item.imgUrl)
}
return
}
// 处理首帧选择
showFirstFrameList.value.forEach((listItem: any) => (listItem.isChecked = false))
data.currentList.forEach((listItem: any) => (listItem.isChecked = false))
item.isChecked = true
data.selectImg = item
if (item.url || item.imgUrl) {
data.selectImg.minioUrl = getMinioUrl(item.url || item.imgUrl)
}
}
const openSetData = (designList: any) => {
if (props.isDesignPage) {
//标识design页面打开的tools
data.fileList = designList
} else {
data.currentList = store.state.UploadFilesModule.modularData.toProduct
data.currentList = data.currentList ? data.currentList : []
setIsShowMark(false)
}
// dataDom.generalDrag.openSetData()
if (data.poseList.length == 0) {
getPoseList()
}
}
// 存储视频元素的引用
const videoRefs = ref<Record<number, HTMLVideoElement | null>>({})
// 存储视频播放状态
const videoPlayingStates = ref<Record<number, boolean>>({})
// 设置视频 ref
const setVideoRef = (id: number, el: HTMLVideoElement | null) => {
if (el) {
videoRefs.value[id] = el
// 初始化播放状态
videoPlayingStates.value[id] = !el.paused
// 监听播放事件
el.addEventListener('play', () => {
videoPlayingStates.value[id] = true
})
// 监听暂停事件
el.addEventListener('pause', () => {
videoPlayingStates.value[id] = false
})
// 监听结束事件
el.addEventListener('ended', () => {
videoPlayingStates.value[id] = false
})
}
}
// 检查视频是否正在播放
const isVideoPlaying = (id: number) => {
return videoPlayingStates.value[id] || false
}
// 播放视频
const handlePlayMotion = (item: any) => {
const video = videoRefs.value[item.id]
if (video) {
if (video.paused) {
video.play().catch(err => {
console.error('播放视频失败:', err)
})
} else {
video.pause()
}
}
}
const getPoseList = () => {
Https.axiosGet(Https.httpUrls.getAllPose).then(rv => {
rv[0].isChecked = true
data.selectPose = rv[0]?.id || 1
data.poseList = rv
})
}
const setSize = (str: any) => {
data.button[str] = !data.button[str]
setItemPosition()
}
const setItemPosition = () => {
setTimeout(() => {
dataDom.generalDragLeft.setItemPosition()
dataDom.generalDragRight.setItemPosition()
}, 200)
}
const getgenerate = async () => {
if (videoType.value === 3 && !data.lastSelectImg.minioUrl) {
message.info(t('poseTransfer.NeedLastFrame'))
return
}
if (!data.selectImg.minioUrl) return message.info(t('poseTransfer.NeedFirstFrame'))
if (data.isGenerate) return
await new Promise((res, reject) => {
Modal.confirm({
title: t('poseTransfer.jsContent1'),
icon: createVNode(ExclamationCircleOutlined),
okText: 'Yes',
cancelText: 'No',
mask: false,
centered: true,
onOk() {
res()
},
cancel() {
reject()
}
})
})
data.isGenerate = true
let value = {
poseId: data.selectPose,
projectId: store.state.Workspace.probjects.id,
productImage: data.selectImg.minioUrl,
modelName: speed.speedData.value,
isDefaultLike: false, //表示是否生成就like
parentId: data.selectImg?.parentId, //parentId 添加在指定父级里面
userLikeSortId: null, //是否进行排序
mode: videoType.value
}
if (!showMotion.value) {
value.modelName = ''
}
if (videoType.value === 2) {
const prompt = dataDom.promptInput.getFullText()
value.prompt = prompt
}
if (videoType.value === 3) {
const prompt = dataDom.promptInput.getFullText()
value.prompt = prompt
const lastFrameProductImage = data.lastSelectImg.minioUrl
value.lastFrameProductImage = lastFrameProductImage
}
Https.axiosPost(Https.httpUrls.poseTransform, value)
.then(rv => {
data.noLikeList.unshift({
taskId: rv.taskId,
parentId: data.selectImg.parentId
})
setGenerate(rv.taskId)
})
.catch((res: any) => {
data.isGenerate = false
if (res.errCode === 2) {
Modal.confirm({
title: res.errMsg,
icon: createVNode(ExclamationCircleOutlined),
okText: 'Yes',
cancelText: 'No',
mask: false,
zIndex: 99999,
centered: true,
onOk() {
store.commit('setUpgradePlan', true)
},
onCancel() {}
})
}
})
}
const setGenerate = (dataList: any) => {
let list: any = [dataList]
data.waitList = list
let state = true
data.generateTime = setInterval(() => {
if (!data.isGenerate) return
if (!state) return
state = false
Https.axiosPost(Https.httpUrls.poseTransformResult, list)
.then(rv => {
state = true
if (data.isGenerate) {
//防止取消后有正在执行的获取状态
// data.waitList = rv.filter((item:any)=>item.status != 'Success' && item.status != 'Fail' && item.status != 'Invalid')
rv.forEach((element: any) => {
if (element.status == 'Success') {
element.url = element.firstFrameUrl
let index = data.noLikeList.findIndex(
(obj: any) => obj.taskId === element.taskId
)
data.noLikeList[index] = element
list = ''
clearInterval(data.generateTime)
data.isGenerate = false
// if(list?.filter)list = list?.filter((item:any) => item !== element.taskId);
store.dispatch('getCredits')
} else if (element.status == 'Fail') {
let index = data.noLikeList.findIndex(
(obj: any) => obj.taskId === element.taskId
)
message.info(t('ProductImg.jsContent3'))
data.noLikeList.splice(index, 1)
clearInterval(data.generateTime)
data.isGenerate = false
store.dispatch('getCredits')
}
})
}
})
.catch(res => {
let index = data.noLikeList.findIndex((obj: any) => obj.taskId === list)
data.noLikeList.splice(index, 1)
clearInterval(data.generateTime)
data.isGenerate = false
})
}, 10000)
}
const setRemoveGenerate = () => {
//取消操作
data.isGenerate = false
clearInterval(data.generateTime)
if (data.waitList) {
// let str = data.waitList.map((obj:any) => obj.taskId).join(',');
let value = {
uniqueId: data.waitList[0],
userId: store.state.UserHabit.userDetail.userId,
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
type: 'PoseTransferation'
}
Https.axiosGet(Https.httpUrls.generateStopWaiting, { params: value })
.then(rv => {
data.waitList = []
const indexes = data.noLikeList
.map((item, index) => (item?.status !== 'Success' ? index : -1))
.filter(index => index !== -1)
indexes.forEach((itemIndex: number) => {
data.noLikeList.splice(itemIndex, 1)
})
})
.catch(res => {})
}
}
let isSelectObject = false
watch(
() => route?.query,
newVal => {
if (!newVal.id) {
isSelectObject = false
}
},
{ immediate: true }
)
let beforeUpload = async (file: any) => {
const isJpgOrPng =
file.type === 'image/jpeg' ||
file.type === 'image/png' ||
file.type === 'image/jpg' ||
file.type === 'image/bmp'
if (!isJpgOrPng) {
message.info(useI18n().t('MoodboardUpload.jsContent3'))
}
const isLt2M = file.size / 1024 / 1024 < 5
if (!isLt2M) {
message.info(useI18n().t('MoodboardUpload.jsContent4'))
}
if (!route?.query?.id && !isSelectObject) {
isSelectObject = true
await createProbject()
}
return (isJpgOrPng && isLt2M && data.upload.projectId) || Upload.LIST_IGNORE
}
const fileUploadChange = (value: any) => {
let file = value.file
let bor = true
if (file.status === 'done') {
let res = JSON.parse(file.xhr.response)
if (res.errCode == 0) {
file.imgUrl = res.data.url
file.id = res.data.id
data.currentList.forEach((listItem: any) => (listItem.isChecked = false))
file.type = 'ProductElement'
if (videoType.value === 3) {
file.frameType = 'first'
}
let storeData = {
str: 'add',
list: [file]
}
store.commit('setUploadElement', storeData)
// 上传成功后,设置选中状态
file.isChecked = true
// 取消 showFirstFrameList 中其他项的选中状态
showFirstFrameList.value.forEach((listItem: any) => {
if (listItem.id !== file.id) {
listItem.isChecked = false
}
})
data.selectImg = res.data
if (res.data.url || res.data.imgUrl) {
data.selectImg.minioUrl = getMinioUrl(res.data.url || res.data.imgUrl)
}
} else {
bor = false
}
} else if (file.status === 'error') {
bor = false
}
}
const fileUploadChangeLast = (value: any) => {
const file = value.file
if (file.status === 'done') {
const res = JSON.parse(file.xhr.response)
if (res.errCode == 0) {
file.imgUrl = res.data.url
file.id = res.data.id
file.type = 'ProductElement'
file.frameType = 'last'
// 取消尾帧列表选中状态
;(value?.fileList || []).forEach(
(listItem: any) => (listItem.isChecked = false)
)
// 设置当前为选中并记录选择
file.isChecked = true
data.lastSelectImg = res.data
}
}
// 同步尾帧文件列表到全局 store
store.commit('setPoseTransferLastFrameList', {
str: 'set',
list: [file]
})
}
const likeSetBtn = (id: any, str: string) => {
data.likeList.forEach((item: any, index: any) => {
if (item.id == id && id) {
if (str == 'zoom') {
showViewVideo({ url: item.videoUrl })
} else if (str == 'like') {
likeFile(item, 'noLike', index)
}
}
})
}
const noLikeSetBtn = (id: any, str: string) => {
if (str == 'delete') {
let value = {
id,
projectId: store.state.Workspace.probjects.id
}
Https.axiosGet(Https.httpUrls.generateDeleteResult, { params: value }).then(
() => {
let index = data.noLikeList.findIndex((v: any) => v.id == id)
data.noLikeList.splice(index, 1)
}
)
} else {
data.noLikeList.forEach((item: any, index: any) => {
if (item.id == id && id) {
if (str == 'zoom') {
showViewVideo({ url: item.videoUrl })
} else if (str == 'like') {
likeFile(item, 'like', index)
}
}
})
}
}
let likeFile = (item: any, str: any, index: any) => {
let url
let value = {}
if (str == 'like') {
value = {
likeOrDislike: 'like',
transformedId: item.id,
projectId: store.state.Workspace.probjects.id,
collectionSortParentId: props.isDesignPage ? item.parentId : ''
}
} else {
value = {
likeOrDislike: 'dislike',
transformedId: item.id,
projectId: store.state.Workspace.probjects.id,
collectionSortParentId: props.isDesignPage ? item.parentId : ''
}
if (props.isDesignPage) {
emit('unLike', item)
}
}
Https.axiosPost(Https.httpUrls.poselikeOrDisike, {}, { params: value })
.then(rv => {
if (str == 'like') {
item.newLike = true
// 从 noLikeList 中查找索引并删除
const noLikeIndex = store.state.HomeStoreModule.poseTransfer.list.findIndex(
(v: any) => v.id === item.id
)
if (noLikeIndex !== -1) {
let deleteValue = {
list: [],
str: 'splice',
index: noLikeIndex
}
store.commit('setPoseTransfer', deleteValue)
}
// 添加到 likedList
let addValue = {
likedList: [
{
...item,
sort: rv?.sort ?? item.sort,
parentId: rv?.parentId ?? item.parentId
}
],
str: 'add',
index: -1
}
store.commit('setPoseTransfer', addValue)
} else {
item.newLike = false
// 从 likedList 中查找索引并删除
const likedIndex =
store.state.HomeStoreModule.poseTransfer.likedList.findIndex(
(v: any) => v.id === item.id
)
if (likedIndex !== -1) {
let deleteValue = {
likedList: [],
str: 'splice',
index: likedIndex
}
store.commit('setPoseTransfer', deleteValue)
}
// 添加到 noLikeList
let addValue = {
list: [
{
...item
}
],
str: 'add',
index: -1
}
store.commit('setPoseTransfer', addValue)
}
})
.catch(res => {
console.error('likeFile error:', res)
})
}
const selectPose = (item: any) => {
data.poseList.forEach((listItem: any) => (listItem.isChecked = false))
item.isChecked = true
data.selectPose = item?.id || 1
}
const openSpeed = () => {
speed.speedState = !speed.speedState
if (speed.speedState) {
document.addEventListener('click', openSpeed)
} else {
document.removeEventListener('click', openSpeed)
}
}
const setSpeed = (item: any) => {
speed.speedData = item
speed.speedState = false
document.removeEventListener('click', openSpeed)
}
const setUploadDelete = (item: any, index: any, isLastFrame: boolean = false) => {
// 在 isDesignPage 模式且 videoType === 3 时,直接从列表中删除,并将图片重新添加到产品列表中以便重新选择
if (props.isDesignPage && videoType.value === 3) {
if (isLastFrame) {
// 从 lastFrameList 中删除
const deleteIndex = lastFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
if (deleteIndex >= 0) {
lastFrameList.value.splice(deleteIndex, 1)
}
// 将删除的项重新添加到 lastFrameProductList 中,以便可以重新选择
const productItem = { ...item }
// 移除 frameType 和 isChecked 属性
delete productItem.frameType
delete productItem.isChecked
// 检查是否已存在,避免重复添加
const existingIndex = lastFrameProductList.value.findIndex(
(listItem: any) => listItem.id === productItem.id
)
if (existingIndex < 0) {
lastFrameProductList.value.unshift(productItem)
}
// 清空选中状态
if (data.lastSelectImg?.id === item.id) {
data.lastSelectImg = {}
}
} else {
// 从 firstFrameList 中删除
const deleteIndex = firstFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
if (deleteIndex >= 0) {
firstFrameList.value.splice(deleteIndex, 1)
}
// 将删除的项重新添加到 firstFrameProductList 中,以便可以重新选择
const productItem = { ...item }
// 移除 frameType 和 isChecked 属性
delete productItem.frameType
delete productItem.isChecked
// 检查是否已存在,避免重复添加
const existingIndex = firstFrameProductList.value.findIndex(
(listItem: any) => listItem.id === productItem.id
)
if (existingIndex < 0) {
firstFrameProductList.value.unshift(productItem)
}
// 清空选中状态
if (data.selectImg?.id === item.id) {
data.selectImg = {}
}
}
return
}
// 非 design 页面的原有逻辑
let value = {
id: item.id
}
Https.axiosPost(
Https.httpUrls.toProductImageElementDelete,
{},
{ params: value }
).then(rv => {
if (isLastFrame) {
// 如果删除的是当前选中的尾帧,清空选中状态
if (data.lastSelectImg?.id === item.id) {
data.lastSelectImg = {}
}
store.commit('setPoseTransferLastFrameList')
} else {
// 如果删除的是当前选中的首帧,清空选中状态
if (data.selectImg?.id === item.id) {
data.selectImg = {}
}
let storeData = {
str: 'delete',
index
}
store.commit('setUploadElement', storeData)
}
})
}
const videoType = ref(2)
const showMotion = computed(() => videoType.value === 1)
const inputPrompt = computed(() => {
if (videoType.value === 2) {
return getFirstFrame(t)
}
if (videoType.value === 3) {
return getFirstAndLastFrame(t)
}
})
const options = ref([
{ vlaue: 2, label: t('poseTransfer.FirstFrame') },
{ value: 3, label: t('poseTransfer.FirstAndLastFrames') },
{ value: 1, label: t('poseTransfer.FirstFrameAndSkeleton') }
])
const firstFrameList = ref([])
const lastFrameList = ref([])
const showFirstFrameList = computed(() => {
if (props.isDesignPage) {
// 在 design 页面,如果 videoType === 3显示 firstFrameList否则显示 data.fileList
return videoType.value === 3 ? firstFrameList.value : data.fileList
}
return videoType.value === 3 ? firstFrameList.value : data.fileList
})
const showProductList = ref(false)
const productType = ref('first')
// 首帧和尾帧的独立数据源
const firstFrameProductList = ref([])
const lastFrameProductList = ref([])
const showFirstFrameUpload = computed(() => {
// 只读依赖
const isDesign = props.isDesignPage
const isFirstAndLast = videoType.value === 3
const hasFirstFrames = Array.isArray(showFirstFrameList.value)
? showFirstFrameList.value.length > 0
: false
if (isDesign) {
// design 页面下,仅 videoType 为 3 时可展示上传,且没有首帧时显示上传按钮
return isFirstAndLast && !hasFirstFrames
}
// 非 design 页面下也是类似逻辑
return !(isFirstAndLast && hasFirstFrames)
})
const showLastFrameUpload = computed(() => {
if (videoType.value !== 3) return false
if (props.isDesignPage && lastFrameList.length > 0) return false
return true
})
// 根据 productType 返回对应的数据源
const productFrameList = computed(() => {
if (productType.value === 'first') {
return firstFrameProductList.value.length > 0
? firstFrameProductList.value
: data.fileList
} else {
return lastFrameProductList.value.length > 0
? lastFrameProductList.value
: data.fileList
}
})
const handleClickDesignMask = (type: 'first' | 'last') => {
productType.value = type
// 初始化独立的数据源,如果为空则从 fileList 复制
if (type === 'first') {
if (firstFrameProductList.value.length === 0) {
firstFrameProductList.value = JSON.parse(JSON.stringify(data.fileList || []))
}
} else {
if (lastFrameProductList.value.length === 0) {
lastFrameProductList.value = JSON.parse(JSON.stringify(data.fileList || []))
}
}
showProductList.value = true
}
const handleConfirmProduct = ({ data: productData }) => {
if (productType.value === 'first') {
// 准备图片数据
const imageItem: any = {
...productData,
isChecked: true,
type: 'ProductElement'
}
// 设置 minioUrl
imageItem.minioUrl =
productData.miniourl || getMinioUrl(imageItem.url || imageItem.imgUrl)
// 更新首帧产品列表,移除已选择的项
const productIndex = firstFrameProductList.value.findIndex(
(item: any) => item.id === productData.id
)
if (productIndex >= 0) {
firstFrameProductList.value.splice(productIndex, 1)
}
// 首尾帧模式,添加到 firstFrameList
// 先取消其他项的选中状态
firstFrameList.value.forEach((listItem: any) => {
listItem.isChecked = false
})
// 检查是否已存在,如果存在则更新,否则添加
const existingIndex = firstFrameList.value.findIndex(
(item: any) => item.id === productData.id
)
if (existingIndex >= 0) {
Object.assign(firstFrameList.value[existingIndex], imageItem)
} else {
imageItem.frameType = 'first'
firstFrameList.value.push(imageItem)
}
// 赋值给首帧选中对象
data.selectImg = { ...imageItem }
} else if (productType.value === 'last') {
// 准备图片数据
const imageItem: any = {
...productData,
isChecked: true,
type: 'ProductElement',
frameType: 'last'
}
// 设置 minioUrl
imageItem.minioUrl =
productData.miniourl || getMinioUrl(imageItem.url || imageItem.imgUrl)
// 更新尾帧产品列表,移除已选择的项
const productIndex = lastFrameProductList.value.findIndex(
(item: any) => item.id === productData.id
)
if (productIndex >= 0) {
lastFrameProductList.value.splice(productIndex, 1)
}
// 先取消其他项的选中状态
lastFrameList.value.forEach((listItem: any) => {
listItem.isChecked = false
})
// 检查是否已存在,如果存在则更新,否则添加
const existingIndex = lastFrameList.value.findIndex(
(item: any) => item.id === productData.id
)
if (existingIndex >= 0) {
Object.assign(lastFrameList.value[existingIndex], imageItem)
} else {
lastFrameList.value.push(imageItem)
}
// 赋值给尾帧选中对象
data.lastSelectImg = { ...imageItem }
}
showProductList.value = false
}
watch(
() => store.state.HomeStoreModule.uploadElement.length,
(newVal, oldVal) => {
if (props.isDesignPage || (!route.query?.id && !route.query?.history)) return
data.fileList = store.state.HomeStoreModule.uploadElement.filter(
item => !item.frameType
)
firstFrameList.value = store.state.HomeStoreModule.uploadElement.filter(
item => item.frameType === 'first'
)
lastFrameList.value = store.state.HomeStoreModule.uploadElement.filter(
item => item.frameType === 'last'
)
// 更新 showFirstFrameList 中项的选中状态
showFirstFrameList.value.forEach((listItem: any) => {
if (listItem.id == data.selectImg.id) {
listItem.isChecked = true
} else {
listItem.isChecked = false
}
})
},
{
immediate: true
}
)
// 监听 fileList 的变化,如果独立列表为空则重新初始化
watch(
() => data.fileList,
newFileList => {
if (firstFrameProductList.value.length === 0 && newFileList?.length > 0) {
firstFrameProductList.value = JSON.parse(JSON.stringify(newFileList))
}
if (lastFrameProductList.value.length === 0 && newFileList?.length > 0) {
lastFrameProductList.value = JSON.parse(JSON.stringify(newFileList))
}
},
{ deep: true }
)
watch(
() => store.state.HomeStoreModule.lastFrameList,
val => {
if (props.isDesignPage) return
lastFrameList.value = val
if (val.length > 0) {
val[0].isChecked = true
}
},
{ immediate: true }
)
// 首帧图片的type 切换为首尾帧时type=first,其他情况type为null
watch(
() => videoType.value,
newVal => {
if (newVal === 3) {
data.upload.type = 'first'
} else {
delete data.upload.type
}
// 当 videoType 改变时,检查 selectImg 是否在当前 showFirstFrameList 中
// 如果不在,清空选中状态或选择列表中的第一项
const currentList = showFirstFrameList.value
if (data.selectImg?.id) {
const exists = currentList.some((item: any) => item.id === data.selectImg.id)
if (!exists) {
// 如果当前选中的项不在新列表中,清空选中状态
data.selectImg = {}
// 取消所有项的选中状态
currentList.forEach((item: any) => (item.isChecked = false))
}
}
},
{ immediate: true }
)
watch(
() => data.noLikeList.length,
(newVal, oldVal) => {
nextTick(() => {
let list = store.state.HomeStoreModule.poseTransfer.list
let taskIdList = list.filter((item: any) => !item.videoUrl)[0]
if (taskIdList?.length > 0) {
data.isGenerate = true
setGenerate(taskIdList[0].taskId)
}
})
},
{ immediate: true }
)
onBeforeUnmount(() => {
clearInterval(data.generateTime)
data.isGenerate = false
})
return {
...toRefs(speed),
...toRefs(dataDom),
...toRefs(data),
lastFrameList,
firstFrameList,
showFirstFrameList,
openSetData,
selectImgItem,
setSize,
setItemPosition,
getgenerate,
getUploadUrl,
beforeUpload,
fileUploadChange,
fileUploadChangeLast,
setRemoveGenerate,
likeSetBtn,
noLikeSetBtn,
selectPose,
openSpeed,
setSpeed,
setUploadDelete,
locale,
videoType,
options,
showMotion,
setVideoRef,
handlePlayMotion,
isVideoPlaying,
showTips,
showProductList,
handleClickDesignMask,
productType,
handleConfirmProduct,
productFrameList,
showFirstFrameUpload,
showLastFrameUpload,
inputPrompt
}
},
directives: {
mousewheel: {
mounted(el) {
el.addEventListener(
'wheel',
(e: WheelEvent) => {
let num = 0
if (e.deltaY > 0) {
num = 25
} else {
num = -25
}
el.scrollBy(num, 0)
},
{ passive: true }
)
}
}
},
provide() {
return {}
}
})
</script>
<style lang="less" scoped>
.poseTransfer {
width: 100%;
height: 100%;
position: relative;
font-size: 2rem;
font-weight: 900;
display: flex;
> .configuratioBox {
display: flex;
flex-direction: column;
align-items: center;
padding-right: 3.8rem;
width: 39rem;
&.active {
width: 0;
overflow: hidden;
}
> .generate {
margin-left: auto;
> .started_btn {
font-weight: 300;
}
.generage_btn {
width: 10rem;
position: relative;
}
.icon-xiala {
margin-left: 1rem;
transition: all 0.3s;
cursor: pointer;
&.active {
transform: rotate(180deg);
}
}
.content {
position: absolute;
top: 100%;
width: 100%;
left: 0rem;
text-align: center;
border-radius: calc(1rem * 1.2);
overflow: hidden;
z-index: 3;
margin-top: 0.2rem;
> div {
background: #cccccc;
line-height: 2;
font-size: 1.8rem;
cursor: pointer;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.active {
background-color: #616161;
}
}
> div:hover {
background: #999999;
}
}
}
}
> .configuratioBox > .configuratio {
width: 100%;
padding-bottom: 1rem;
> .content {
> .selectImg,
> .poses {
display: flex;
flex-direction: column;
> .imgBox {
flex: 1;
max-height: 45rem;
margin-top: 2rem;
display: flex;
flex-wrap: nowrap;
width: 100%;
overflow: hidden;
overflow-x: auto;
column-gap: 2.4rem;
position: relative;
> .item {
width: 12.7rem;
height: 17.8rem;
cursor: pointer;
overflow: hidden;
display: flex;
justify-content: center;
flex-shrink: 0;
position: relative;
:deep(.ant-checkbox-wrapper) {
position: absolute;
top: 2rem;
right: 2rem;
}
> .btnBox {
position: absolute;
right: 1rem;
top: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
> div {
width: 2rem;
height: 2rem;
display: flex;
border: 1px solid;
align-items: center;
border-radius: 0.5rem;
justify-content: center;
cursor: pointer;
margin-bottom: 1rem;
> i {
display: flex;
}
> .fi-br-check {
color: #fff;
display: none;
}
&.active {
background: #000;
> i {
display: flex;
color: #fff;
}
}
}
}
> .btnBox {
position: absolute;
right: 1rem;
top: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
> div {
width: 2rem;
height: 2rem;
display: flex;
border: 1px solid;
align-items: center;
border-radius: 0.5rem;
justify-content: center;
cursor: pointer;
margin-bottom: 1rem;
> i {
display: flex;
}
> .fi-br-check {
color: #fff;
display: none;
}
&.active {
background: #000;
> i {
display: flex;
color: #fff;
}
}
}
}
> img {
width: 100%;
object-fit: contain;
border: 1px solid #d0d0d0;
}
}
> .upload_item {
border: none;
position: relative;
.design-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
.control-container {
width: 100%;
height: 3.3rem;
position: absolute;
bottom: 0;
left: 0;
background: linear-gradient(
180deg,
rgba(8, 9, 13, 0) 0%,
rgba(8, 9, 13, 0.27) 80.37%
);
display: flex;
align-items: center;
justify-content: flex-start;
.icon-list {
// height: 50%;
// width: calc(100% - 1.6rem);
// border-top: 1px solid #fff;
padding-left: 1rem;
display: flex;
box-sizing: border-box;
justify-content: flex-start;
align-items: center;
.play-icon {
width: 2rem;
height: 2rem;
}
}
}
}
> .head {
color: #000;
font-weight: 500;
display: flex;
align-items: center;
column-gap: 1rem;
.tips-icon {
cursor: pointer;
display: flex;
}
> .text {
// display: inline-block;
font-size: 1.8rem;
}
}
}
.prompt-input-container {
margin-top: 4rem;
.title {
display: flex;
align-items: center;
column-gap: 1rem;
i {
display: flex;
cursor: pointer;
}
}
}
.title {
font-size: 1.8rem;
color: #000;
margin-bottom: 1.4rem;
font-weight: 500;
}
> .poses {
margin-top: 3rem;
}
}
}
> .likeBox,
> .noLikeBox {
flex: 1;
position: relative;
> .element {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
> .btnLeft,
> .btnRight {
position: absolute;
width: 3rem;
height: 7rem;
background: #fff;
border: 2px solid;
border-right: none;
border-radius: 2rem 0 0 2rem;
top: 50%;
transform: translate(-100%, -50%);
left: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 2;
cursor: pointer;
transition: all 0.3s;
> span {
transition: all 0.3s;
transform: rotate(90deg);
}
&.active {
> span {
transform: rotate(270deg);
}
}
}
> .btnRight {
left: calc(100% + 3rem);
transform: translate(-100%, -50%) rotate(180deg);
&:hover {
width: 4rem;
left: calc(100% + 4rem);
}
}
> .content {
flex: 1;
overflow: hidden;
padding: 1.7rem 2rem;
border-radius: 3rem;
background: #f7f8fa;
}
> .title {
margin-bottom: 2rem;
font-size: 1.6rem;
font-weight: 900;
> span {
margin-left: 1rem;
}
}
}
}
> .designPage {
margin-right: 4rem;
}
> .noLikeBox {
padding-left: 2.3rem;
&.active {
flex: 0;
overflow: hidden;
}
}
.upload_file_item {
width: 100%;
height: 100%;
:deep(.ant-upload-picture-card-wrapper) {
width: 100% !important;
height: 100%;
.ant-upload-list-picture-card {
width: 100%;
height: 100%;
.ant-upload-select-picture-card {
width: 100%;
height: 100%;
border: 1px solid #000;
background: #fff;
}
}
}
}
.video-type-container {
margin-bottom: 4rem;
font-weight: 500;
.title {
font-size: 1.8rem;
color: #000;
margin-bottom: 1.4rem;
}
:deep(.ant-select) {
width: 100%;
.ant-select-selector {
border: 2px solid #d0d0d0;
border-radius: 1rem;
}
}
}
}
</style>