Files
aida_front/src/component/home/tools/poseTransfer/index.vue

1681 lines
52 KiB
Vue
Raw Normal View History

2025-04-01 15:25:15 +08:00
<template>
2025-11-14 10:06:44 +08:00
<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>
2025-11-14 15:10:32 +08:00
<i
v-show="videoType !== 2"
class="fi fi-rs-interrogation tips-icon"
@click="showTips = true"
/>
2025-11-14 10:06:44 +08:00
</div>
<div class="imgBox" v-mousewheel>
<div
2025-11-18 11:31:03 +08:00
class="item first-frame"
2025-11-14 10:06:44 +08:00
:class="{ active: item.id == selectImg.id }"
v-for="(item, index) in showFirstFrameList"
2025-11-14 10:06:44 +08:00
@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"
>
2025-11-18 14:49:45 +08:00
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
2025-11-14 10:06:44 +08:00
<i class="fi fi-br-check"></i>
</div>
<div
@click.stop="setUploadDelete(item, index)"
2025-11-18 11:31:03 +08:00
v-if="source != 'design' || (isDesignPage && videoType === 3)"
2025-11-14 10:06:44 +08:00
>
<i class="fi fi-rr-trash icon_delete"></i>
</div>
</div>
</div>
2025-11-18 11:31:03 +08:00
<div class="upload_item item first_frames" v-show="showFirstFrameUpload">
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('first')"
/>
2025-11-14 15:10:32 +08:00
<div class="upload_file_item">
<a-upload
key="common"
:action="getUploadUrl() + '/api/history/toProductImageElementUpload'"
list-type="picture-card"
:capture="null"
:data="{
...upload
2025-11-14 15:10:32 +08:00
}"
: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>
2025-11-14 10:06:44 +08:00
<template v-if="videoType === 3">
2025-11-14 15:10:32 +08:00
<div
2025-11-18 11:31:03 +08:00
v-show="lastFrameList?.length < 1"
2025-11-14 15:10:32 +08:00
class="upload_item item last_frames"
>
2025-11-18 11:31:03 +08:00
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('last')"
/>
2025-11-14 15:10:32 +08:00
<div class="upload_file_item">
<a-upload
key="lastframes"
:action="
getUploadUrl() + '/api/history/toProductImageElementUpload'
"
list-type="picture-card"
:capture="null"
:data="{
2025-11-14 15:20:46 +08:00
...upload,
type: 'last'
2025-11-14 15:10:32 +08:00
}"
: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>
2025-11-14 10:06:44 +08:00
<div
class="item lastframe-list"
:class="{ active: item.id == lastSelectImg.id }"
2025-11-14 10:06:44 +08:00
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"
>
2025-11-18 14:49:45 +08:00
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
2025-11-14 10:06:44 +08:00
<i class="fi fi-br-check"></i>
</div>
<div
2025-11-14 16:34:09 +08:00
@click.stop="setUploadDelete(item, index, true)"
2025-11-18 11:31:03 +08:00
v-if="source != 'design' || (isDesignPage && videoType === 3)"
2025-11-14 10:06:44 +08:00
>
<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" />
2025-11-14 10:06:44 +08:00
</div>
<div class="poses" v-show="showMotion">
<div class="head">
<div class="text">{{ $t('poseTransfer.Selectpose') }}</div>
</div>
<div class="imgBox" v-mousewheel>
2025-11-14 15:10:32 +08:00
<div
class="item"
v-for="item in poseList"
:key="item.id"
@click="selectPose(item)"
>
<video :ref="el => setVideoRef(item.id, el)" :src="item.video" />
2025-11-14 10:06:44 +08:00
<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"
2025-11-14 15:10:32 +08:00
@click.stop="handlePlayMotion(item)"
2025-11-18 14:49:45 +08:00
:name="isVideoPlaying(item.id) ? 'CPause' : 'CPlay'"
size="20"
2025-11-14 15:10:32 +08:00
color="#fff"
/>
2025-11-14 10:06:44 +08:00
</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">
2025-04-01 15:25:15 +08:00
{{ $t('Generate.Generate') }}
2025-06-09 10:25:54 +08:00
</div> -->
2025-11-14 10:06:44 +08:00
<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"
2025-11-14 10:06:44 +08:00
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 }}
2025-08-26 10:14:34 +08:00
<img src="@/assets/images/homePage/toolsGuide/productCN.png" alt="">
<img src="@/assets/images/homePage/toolsGuide/productEN.png" alt=""> -->
2025-11-14 10:06:44 +08:00
<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}">
2025-04-09 14:09:19 +08:00
<span class="icon iconfont icon-xiala"></span>
</div> -->
2025-11-14 10:06:44 +08:00
<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>
2025-11-14 15:10:32 +08:00
<Tips v-model:showModal="showTips" />
2025-11-18 11:31:03 +08:00
<Product
v-model:showModal="showProductList"
:frameList="productFrameList"
:type="productType"
:key="productType"
@confirm="handleConfirmProduct"
/>
2025-11-14 10:06:44 +08:00
</div>
2025-04-01 15:25:15 +08:00
</template>
<script lang="ts">
2025-11-14 10:06:44 +08:00
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'
2025-04-01 15:25:15 +08:00
import { useI18n } from 'vue-i18n'
2025-11-14 10:06:44 +08:00
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'
2025-11-14 15:10:32 +08:00
import Tips from './Tips.vue'
2025-11-18 11:31:03 +08:00
import Product from './Product.vue'
2025-11-14 15:10:32 +08:00
import { getFirstFrame, getFirstAndLastFrame } from './prompt'
2025-04-01 15:25:15 +08:00
export default defineComponent({
2025-11-14 10:06:44 +08:00
components: {
generalDrag,
2025-11-14 15:10:32 +08:00
promptInput,
2025-11-18 11:31:03 +08:00
Tips,
Product,
2025-11-14 10:06:44 +08:00
},
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尾帧
2025-11-14 10:06:44 +08:00
},
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
2025-11-14 10:06:44 +08:00
})
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: ''
}
})
2025-11-14 15:10:32 +08:00
const showTips = ref(false)
2025-11-14 10:06:44 +08:00
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))
2025-11-14 10:06:44 +08:00
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()
}
}
2025-11-14 15:10:32 +08:00
// 存储视频元素的引用
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
})
}
2025-11-14 10:06:44 +08:00
}
2025-11-14 15:10:32 +08:00
// 检查视频是否正在播放
const isVideoPlaying = (id: number) => {
return videoPlayingStates.value[id] || false
2025-11-14 10:06:44 +08:00
}
2025-11-14 15:10:32 +08:00
// 播放视频
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()
}
}
}
2025-11-14 10:06:44 +08:00
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 () => {
2025-11-17 13:55:44 +08:00
if (videoType.value === 3 && !data.lastSelectImg.minioUrl) {
message.info(t('poseTransfer.NeedLastFrame'))
return
}
if (!data.selectImg.minioUrl) return message.info(t('poseTransfer.NeedFirstFrame'))
2025-11-14 10:06:44 +08:00
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()
}
})
})
2025-11-14 15:10:32 +08:00
2025-11-14 10:06:44 +08:00
data.isGenerate = true
2025-04-01 15:25:15 +08:00
2025-11-14 10:06:44 +08:00
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 = ''
}
2025-11-14 10:06:44 +08:00
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
}
2025-11-14 15:10:32 +08:00
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() {}
})
}
})
2025-11-14 10:06:44 +08:00
}
const setGenerate = (dataList: any) => {
let list: any = [dataList]
data.waitList = list
let state = true
data.generateTime = setInterval(() => {
2025-11-17 14:17:25 +08:00
if (!data.isGenerate) return
2025-11-14 10:06:44 +08:00
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')
}
})
2025-04-01 15:25:15 +08:00
}
2025-11-14 10:06:44 +08:00
})
.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'))
}
2025-11-17 13:42:57 +08:00
if (!route?.query?.id && !isSelectObject) {
2025-11-14 10:06:44 +08:00
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'
}
2025-11-14 10:06:44 +08:00
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)
}
2025-11-14 10:06:44 +08:00
} 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'
2025-11-14 16:34:09 +08:00
file.frameType = 'last'
2025-11-14 10:06:44 +08:00
// 取消尾帧列表选中状态
;(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)
2025-04-01 15:25:15 +08:00
}
2025-11-14 10:06:44 +08:00
}
})
}
}
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 : ''
2025-04-01 15:25:15 +08:00
}
2025-11-14 10:06:44 +08:00
if (props.isDesignPage) {
emit('unLike', item)
}
}
Https.axiosPost(Https.httpUrls.poselikeOrDisike, {}, { params: value })
.then(rv => {
if (str == 'like') {
item.newLike = true
2025-11-17 10:47:32 +08:00
// 从 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 = {
2025-11-14 10:06:44 +08:00
likedList: [
{
...item,
2025-11-17 10:47:32 +08:00
sort: rv?.sort ?? item.sort,
parentId: rv?.parentId ?? item.parentId
2025-11-14 10:06:44 +08:00
}
],
str: 'add',
index: -1
}
2025-11-17 10:47:32 +08:00
store.commit('setPoseTransfer', addValue)
2025-11-14 10:06:44 +08:00
} else {
2025-11-17 10:47:32 +08:00
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: [
2025-11-14 10:06:44 +08:00
{
...item
}
],
2025-11-17 10:47:32 +08:00
str: 'add',
index: -1
2025-04-01 15:25:15 +08:00
}
2025-11-17 10:47:32 +08:00
store.commit('setPoseTransfer', addValue)
2025-11-14 10:06:44 +08:00
}
})
2025-11-17 10:47:32 +08:00
.catch(res => {
console.error('likeFile error:', res)
})
2025-11-14 10:06:44 +08:00
}
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)
}
2025-11-14 16:34:09 +08:00
const setUploadDelete = (item: any, index: any, isLastFrame: boolean = false) => {
2025-11-18 11:31:03 +08:00
// 在 isDesignPage 模式且 videoType === 3 时,直接从列表中删除,并将图片重新添加到产品列表中以便重新选择
if (props.isDesignPage && videoType.value === 3) {
if (isLastFrame) {
// 从 lastFrameList 中删除
2025-11-18 14:49:45 +08:00
const deleteIndex = lastFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
2025-11-18 11:31:03 +08:00
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 中删除
2025-11-18 14:49:45 +08:00
const deleteIndex = firstFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
2025-11-18 11:31:03 +08:00
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
}
2025-11-18 14:49:45 +08:00
2025-11-18 11:31:03 +08:00
// 非 design 页面的原有逻辑
2025-11-14 10:06:44 +08:00
let value = {
id: item.id
}
Https.axiosPost(
Https.httpUrls.toProductImageElementDelete,
{},
{ params: value }
).then(rv => {
2025-11-14 16:34:09 +08:00
if (isLastFrame) {
// 如果删除的是当前选中的尾帧,清空选中状态
if (data.lastSelectImg?.id === item.id) {
data.lastSelectImg = {}
}
2025-11-14 16:34:42 +08:00
store.commit('setPoseTransferLastFrameList')
} else {
// 如果删除的是当前选中的首帧,清空选中状态
if (data.selectImg?.id === item.id) {
data.selectImg = {}
}
2025-11-14 16:34:09 +08:00
let storeData = {
str: 'delete',
index
}
store.commit('setUploadElement', storeData)
2025-04-01 15:25:15 +08:00
}
2025-11-14 10:06:44 +08:00
})
}
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(() => {
2025-11-18 11:31:03 +08:00
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
})
2025-11-18 11:31:03 +08:00
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
}
2025-11-14 10:06:44 +08:00
watch(
() => store.state.HomeStoreModule.uploadElement.length,
(newVal, oldVal) => {
2025-11-17 13:42:57 +08:00
if (props.isDesignPage || (!route.query?.id && !route.query?.history)) return
2025-11-14 15:20:46 +08:00
data.fileList = store.state.HomeStoreModule.uploadElement.filter(
item => !item.frameType
)
firstFrameList.value = store.state.HomeStoreModule.uploadElement.filter(
2025-11-14 15:20:46 +08:00
item => item.frameType === 'first'
)
2025-11-14 15:48:50 +08:00
lastFrameList.value = store.state.HomeStoreModule.uploadElement.filter(
2025-11-14 15:20:46 +08:00
item => item.frameType === 'last'
)
// 更新 showFirstFrameList 中项的选中状态
showFirstFrameList.value.forEach((listItem: any) => {
2025-11-14 10:06:44 +08:00
if (listItem.id == data.selectImg.id) {
listItem.isChecked = true
} else {
listItem.isChecked = false
}
})
2025-11-17 13:42:57 +08:00
},
{
2025-11-17 13:55:44 +08:00
immediate: true
2025-11-14 10:06:44 +08:00
}
)
2025-11-18 11:31:03 +08:00
// 监听 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 }
)
2025-11-14 10:06:44 +08:00
watch(
() => store.state.HomeStoreModule.lastFrameList,
val => {
if (props.isDesignPage) return
lastFrameList.value = val
if (val.length > 0) {
val[0].isChecked = true
}
},
{ immediate: true }
)
2025-11-12 15:42:08 +08:00
// 首帧图片的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 }
)
2025-11-14 10:06:44 +08:00
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 }
)
2025-11-12 15:42:08 +08:00
2025-11-14 10:06:44 +08:00
onBeforeUnmount(() => {
clearInterval(data.generateTime)
data.isGenerate = false
})
2025-11-12 15:42:08 +08:00
2025-11-14 10:06:44 +08:00
return {
...toRefs(speed),
...toRefs(dataDom),
...toRefs(data),
lastFrameList,
firstFrameList,
showFirstFrameList,
2025-11-14 10:06:44 +08:00
openSetData,
selectImgItem,
setSize,
setItemPosition,
getgenerate,
getUploadUrl,
beforeUpload,
fileUploadChange,
fileUploadChangeLast,
setRemoveGenerate,
likeSetBtn,
noLikeSetBtn,
selectPose,
openSpeed,
setSpeed,
setUploadDelete,
locale,
videoType,
options,
2025-11-14 15:10:32 +08:00
showMotion,
setVideoRef,
handlePlayMotion,
isVideoPlaying,
2025-11-18 11:31:03 +08:00
showTips,
showProductList,
handleClickDesignMask,
productType,
handleConfirmProduct,
productFrameList,
showFirstFrameUpload,
showLastFrameUpload,
inputPrompt
2025-11-14 10:06:44 +08:00
}
},
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 {}
}
2025-04-01 15:25:15 +08:00
})
</script>
<style lang="less" scoped>
2025-11-14 10:06:44 +08:00
.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;
2025-08-22 10:27:48 +08:00
2025-11-14 10:06:44 +08:00
> 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;
2025-11-18 11:31:03 +08:00
position: relative;
.design-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
2025-11-14 10:06:44 +08:00
}
.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;
2025-11-18 14:49:45 +08:00
align-items: center;
justify-content: flex-start;
2025-11-14 10:06:44 +08:00
.icon-list {
2025-11-18 14:49:45 +08:00
// height: 50%;
// width: calc(100% - 1.6rem);
2025-11-14 15:10:32 +08:00
// border-top: 1px solid #fff;
2025-11-18 14:49:45 +08:00
padding-left: 1rem;
2025-11-14 10:06:44 +08:00
display: flex;
box-sizing: border-box;
justify-content: flex-start;
align-items: center;
.play-icon {
2025-11-18 14:49:45 +08:00
width: 2rem;
height: 2rem;
2025-11-14 10:06:44 +08:00
}
}
}
}
> .head {
color: #000;
2025-11-18 14:44:38 +08:00
font-weight: 500;
2025-11-14 10:06:44 +08:00
display: flex;
align-items: center;
column-gap: 1rem;
.tips-icon {
cursor: pointer;
display: flex;
}
> .text {
// display: inline-block;
font-size: 1.8rem;
2025-11-14 10:06:44 +08:00
}
}
}
.prompt-input-container {
margin-top: 4rem;
.title {
display: flex;
align-items: center;
column-gap: 1rem;
i {
display: flex;
cursor: pointer;
}
}
2025-11-14 10:06:44 +08:00
}
.title {
font-size: 1.8rem;
color: #000;
margin-bottom: 1.4rem;
2025-11-18 14:44:38 +08:00
font-weight: 500;
2025-11-14 10:06:44 +08:00
}
> .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);
}
2025-04-09 14:09:19 +08:00
2025-11-14 10:06:44 +08:00
&.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;
}
}
}
}
2025-06-26 15:41:08 +08:00
2025-11-14 10:06:44 +08:00
> .designPage {
margin-right: 4rem;
}
> .noLikeBox {
padding-left: 2.3rem;
&.active {
flex: 0;
overflow: hidden;
}
}
2025-11-12 15:42:08 +08:00
2025-11-14 10:06:44 +08:00
.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;
2025-11-18 14:44:38 +08:00
font-weight: 500;
2025-11-14 10:06:44 +08:00
.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>