Merge branch 'dev_vite' of http://18.167.251.121:10003/aidlab/aida_front into dev_vite

This commit is contained in:
李志鹏
2025-11-20 10:11:55 +08:00
29 changed files with 3671 additions and 1726 deletions

View File

@@ -0,0 +1 @@
<svg t="1763432312095" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4678" width="200" height="200"><path d="M509.92 176C325.504 176 176 325.504 176 509.92c0 184.416 149.504 333.92 333.92 333.92 184.416 0 333.92-149.504 333.92-333.92C843.84 325.504 694.32 176 509.92 176z m166.64 214.848a16 16 0 0 1 22.624 0l11.328 11.312a16 16 0 0 1 0 22.624l-254.08 254.08a16 16 0 0 1-22.624 0l-159.616-159.632a16 16 0 0 1 0-22.624l11.312-11.312a16 16 0 0 1 22.624 0l136.992 136.992z" fill="currentColor" p-id="4679"></path></svg>

After

Width:  |  Height:  |  Size: 562 B

View File

@@ -0,0 +1,3 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25 0C20.0555 0 15.222 1.46622 11.1108 4.21326C6.99953 6.9603 3.79521 10.8648 1.90302 15.4329C0.0108322 20.0011 -0.484251 25.0277 0.480379 29.8773C1.44501 34.7268 3.82603 39.1814 7.32234 42.6777C10.8187 46.174 15.2732 48.555 20.1228 49.5196C24.9723 50.4843 29.9989 49.9892 34.5671 48.097C39.1353 46.2048 43.0397 43.0005 45.7867 38.8893C48.5338 34.778 50 29.9445 50 25C50 18.3696 47.3661 12.0107 42.6777 7.32233C37.9893 2.63392 31.6304 0 25 0ZM34.8063 31.8604L31.8604 34.8063L25 27.9458L18.1396 34.8063L15.1938 31.8604L22.0542 25L15.1938 18.1396L18.1396 15.1938L25 22.0542L31.8604 15.1938L34.8063 18.1396L27.9458 25L34.8063 31.8604Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 760 B

View File

@@ -0,0 +1,3 @@
<svg width="50" height="50" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M25 0C11.2146 0 0 11.2146 0 25C0 38.7854 11.2146 50 25 50C38.7854 50 50 38.7854 50 25C50 11.2146 38.7854 0 25 0ZM24.8104 32.1229C24.0042 32.9292 22.9437 33.3312 21.8792 33.3312C20.8146 33.3312 19.7437 32.925 18.9292 32.1125L13.1333 26.4958L16.0354 23.5021L21.8542 29.1417L33.9562 17.2646L36.8812 20.2333L24.8104 32.1229Z" fill="black"/>
</svg>

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View File

@@ -388,7 +388,7 @@ export default defineComponent({
let data = { let data = {
designItemId:detailData.designDetail.designItemId, designItemId:detailData.designDetail.designItemId,
designSingleItemDTOList:clothes, designSingleItemDTOList:clothes,
isPreview:true, isPreview:str == 'sub'?false:true,
// ifSubmit:designItemDetail.isPreview, // ifSubmit:designItemDetail.isPreview,
gender:workspace?.sex, gender:workspace?.sex,
sketchString:'', sketchString:'',
@@ -462,7 +462,8 @@ export default defineComponent({
const modelOnLoad = ()=>{ const modelOnLoad = ()=>{
if(!detailData.isUndividedLayerWithSinglePrint)return if(!detailData.isUndividedLayerWithSinglePrint)return
setTimeout(()=>{ setTimeout(()=>{
previwe() let data = getSubmitData('sub')
store.dispatch('DesignDetail/setSubmit',data)
}) })
} }
const detailEdit = async (str:any)=>{ const detailEdit = async (str:any)=>{

View File

@@ -108,7 +108,6 @@ export default defineComponent({
detailData.designDetail.clothes = detailData.designDetail.clothes.filter((item:any)=>item.id != id) detailData.designDetail.clothes = detailData.designDetail.clothes.filter((item:any)=>item.id != id)
detailData.frontBack_.back = detailData.frontBack_.back.filter((item:any)=>item.id != id) detailData.frontBack_.back = detailData.frontBack_.back.filter((item:any)=>item.id != id)
detailData.frontBack_.front = detailData.frontBack_.front.filter((item:any)=>item.id != id) detailData.frontBack_.front = detailData.frontBack_.front.filter((item:any)=>item.id != id)
console.log(detailData.designDetail.clothes)
//判断删除后是否还有服装 //判断删除后是否还有服装
if(detailData.designDetail.clothes.length == 0){ if(detailData.designDetail.clothes.length == 0){
addSketch() addSketch()
@@ -118,7 +117,7 @@ export default defineComponent({
const maxObj = detailData.designDetail.clothes.find(item => item.priority === maxValue); const maxObj = detailData.designDetail.clothes.find(item => item.priority === maxValue);
store.commit('DesignDetail/setDesignColthes',maxObj.id) store.commit('DesignDetail/setDesignColthes',maxObj.id)
} }
emit('canvasReload')
emit('deleteItem') emit('deleteItem')
} }
const addSketch = ()=>{ const addSketch = ()=>{

File diff suppressed because it is too large Load Diff

View File

@@ -203,8 +203,11 @@ export default defineComponent({
}) })
} }
const unLike = (item)=>{ const unLike = (item)=>{
const hasSame = data.unLikeList.some((unlikeItem:any)=>unlikeItem?.id === item?.id)
if(!hasSame){
data.unLikeList.push(item) data.unLikeList.push(item)
} }
}
let cleardata = async ()=>{ let cleardata = async ()=>{
let list = [] let list = []
if(data.openType == 'toProduct'){ if(data.openType == 'toProduct'){
@@ -220,10 +223,12 @@ export default defineComponent({
const { newLike, ...rest } = item; // 解构赋值移除 newLike const { newLike, ...rest } = item; // 解构赋值移除 newLike
return item.oldSort ? { ...rest, sort: item.oldSort } : rest; return item.oldSort ? { ...rest, sort: item.oldSort } : rest;
}); });
const addIdSet = new Set(generateCourse.map((item:any)=>item?.id))
const deleteList = data.unLikeList.filter((item:any)=>!addIdSet.has(item?.id))
let emitData = { let emitData = {
status:'add', status:'add',
addList:generateCourse, addList:generateCourse,
deleteList:data.unLikeList, deleteList,
} }
emit('editToolsSuccess',emitData,'batch') emit('editToolsSuccess',emitData,'batch')
@@ -489,9 +494,6 @@ export default defineComponent({
// .ant-modal-mask{ // .ant-modal-mask{
// position: absolute; // position: absolute;
// } // }
.ant-modal-wrap,.ant-modal-mask{
}
> .ant-modal-root{ > .ant-modal-root{
> .ant-modal-centered{ > .ant-modal-centered{
> .fullScreen{ > .fullScreen{

View File

@@ -0,0 +1,248 @@
<template>
<a-modal
class="product-modal generalModel"
v-model:visible="showModal"
:footer="null"
width="78%"
:maskClosable="false"
:centered="true"
:closable="false"
wrapClassName="#app"
:keyboard="false"
>
<div class="generalModel_btn">
<div class="generalModel_closeIcon" @click.stop="handleClose()">
<svg
width="100%"
height="100%"
viewBox="0 0 46 46"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="23" cy="23" r="23" fill="#000" fill-opacity="0.3" />
<rect
x="32.5063"
y="12"
width="3"
height="29"
rx="1.5"
transform="rotate(45 32.5063 12)"
fill="white"
/>
<rect
x="34.6274"
y="32.5059"
width="3"
height="29"
rx="1.5"
transform="rotate(135 34.6274 32.5059)"
fill="white"
/>
</svg>
</div>
</div>
<div class="content-wrapper">
<div class="title-container">
<div class="title">{{ type === 'first' ? 'First Frame' : 'Last Frame' }}</div>
</div>
<div class="content-container">
<div class="video-frames-wrapper">
<div class="video-frames-scroll">
<div
v-for="(frame, index) in frameList"
:key="index"
class="frame-item"
@click="selectFrame(index)"
>
<SvgIcon
v-if="selectedFrameIndex === index"
class="check-icon"
name="CCheck"
color="#000000"
size="24"
/>
<img v-if="frame.url" :src="frame.url" alt="" />
<div v-else class="frame-placeholder"></div>
</div>
</div>
</div>
</div>
<div class="action-container">
<button class="confirm-btn" @click="handleConfirm">Confirm</button>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
const props = defineProps<{
frameList: Array<{ url?: string }>
type: String // first last
}>()
const showModal = defineModel<boolean>('showModal', { required: true })
const selectedFrameIndex = ref<number | null>(null)
const emits = defineEmits(['confirm'])
// 当 type 改变时,重置选中状态
watch(() => props.type, () => {
selectedFrameIndex.value = null
})
// 当弹窗关闭时,重置选中状态
watch(() => showModal.value, (newVal) => {
if (!newVal) {
selectedFrameIndex.value = null
}
})
const handleClose = () => {
showModal.value = false
}
const selectFrame = (index: number) => {
selectedFrameIndex.value = index
}
const handleConfirm = () => {
if (selectedFrameIndex.value !== null) {
// 触发确认事件或回调
const selected = props.frameList[selectedFrameIndex.value]
emits('confirm', { data: selected })
}
handleClose()
}
</script>
<style lang="less" scoped>
.product-modal {
.content-wrapper {
// padding: 0;
position: relative;
// margin-top: 1.36rem;
height: 100%;
display: flex;
flex-direction: column;
.title-container {
.title {
font-size: 2.4rem;
font-weight: 500;
color: rgba(0, 0, 0, 0.85);
}
}
.content-container {
margin-top: 5.8rem;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
.video-frames-wrapper {
background: #ffffff;
border-radius: 2rem;
padding: 1.2rem 2.88rem;
flex: 1;
height: 100%;
min-height: 0;
display: flex;
flex-direction: column;
overflow: hidden;
.video-frames-scroll {
display: flex;
flex-wrap: wrap;
gap: 3.1rem;
align-content: flex-start;
overflow-y: auto;
overflow-x: auto;
flex: 1;
min-height: 0;
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
&::-webkit-scrollbar {
width: 0.4rem;
height: 0.4rem;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 0.2rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
.frame-item {
flex-shrink: 0;
width: 16.47rem;
height: 24.6rem;
border-radius: 0;
border: 0.1rem solid #999999;
overflow: hidden;
cursor: pointer;
position: relative;
overflow: visible;
.check-icon {
position: absolute;
bottom: 0;
right: 0;
transform: translate(50%, 50%);
}
img {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
.frame-placeholder {
width: 100%;
height: 100%;
background: #f5f5f5;
}
}
}
}
}
}
.action-container {
display: flex;
justify-content: flex-end;
.confirm-btn {
width: 8.6rem;
height: 4rem;
background: #000000;
color: #ffffff;
border: none;
border-radius: 2rem;
font-size: 1.2rem;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
}
}
}
:deep(.generalModel .ant-modal-body) {
padding: 5rem 4.4rem 8rem 4.6rem;
}
.c-svg {
width: initial;
height: initial;
}
</style>

View File

@@ -31,7 +31,7 @@
</div> </div>
<div class="imgBox" v-mousewheel> <div class="imgBox" v-mousewheel>
<div <div
class="item" class="item first-frame"
:class="{ active: item.id == selectImg.id }" :class="{ active: item.id == selectImg.id }"
v-for="(item, index) in showFirstFrameList" v-for="(item, index) in showFirstFrameList"
@click="selectImgItem(item)" @click="selectImgItem(item)"
@@ -52,23 +52,26 @@
v-show="item.designOutfitUrl || item.imgUrl || item.url" v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox" class="btnBox"
> >
<div :class="{ active: item.isChecked }"> <div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i> <i class="fi fi-br-check"></i>
</div> </div>
<div <div
@click.stop="setUploadDelete(item, index)" @click.stop="setUploadDelete(item, index)"
v-if="source != 'design'" v-if="source != 'design' || (isDesignPage && videoType === 3)"
> >
<i class="fi fi-rr-trash icon_delete"></i> <i class="fi fi-rr-trash icon_delete"></i>
</div> </div>
</div> </div>
</div> </div>
<div class="upload_item item first_frames" v-show="showFirstFrameUpload">
<div <div
class="upload_item item" class="design-mask"
v-show=" v-if="isDesignPage && videoType === 3"
!isDesignPage && !(videoType === 3 && showFirstFrameList.length > 0) @click="handleClickDesignMask('first')"
" />
>
<div class="upload_file_item"> <div class="upload_file_item">
<a-upload <a-upload
key="common" key="common"
@@ -93,9 +96,14 @@
</div> </div>
<template v-if="videoType === 3"> <template v-if="videoType === 3">
<div <div
v-show="lastFrameList?.length < 1"
class="upload_item item last_frames" class="upload_item item last_frames"
v-show="!isDesignPage && lastFrameList?.length < 1"
> >
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('last')"
/>
<div class="upload_file_item"> <div class="upload_file_item">
<a-upload <a-upload
key="lastframes" key="lastframes"
@@ -142,12 +150,15 @@
v-show="item.designOutfitUrl || item.imgUrl || item.url" v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox" class="btnBox"
> >
<div :class="{ active: item.isChecked }"> <div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i> <i class="fi fi-br-check"></i>
</div> </div>
<div <div
@click.stop="setUploadDelete(item, index, true)" @click.stop="setUploadDelete(item, index, true)"
v-if="source != 'design'" v-if="source != 'design' || (isDesignPage && videoType === 3)"
> >
<i class="fi fi-rr-trash icon_delete"></i> <i class="fi fi-rr-trash icon_delete"></i>
</div> </div>
@@ -158,7 +169,7 @@
</div> </div>
<div class="prompt-input-container" v-show="!showMotion"> <div class="prompt-input-container" v-show="!showMotion">
<div class="title">{{ $t('ProductImg.Prompt') }}</div> <div class="title">{{ $t('ProductImg.Prompt') }}</div>
<promptInput :content="prompt" ref="promptInput" /> <promptInput :content="inputPrompt" ref="promptInput" />
</div> </div>
<div class="poses" v-show="showMotion"> <div class="poses" v-show="showMotion">
<div class="head"> <div class="head">
@@ -180,19 +191,10 @@
<div class="control-container"> <div class="control-container">
<div class="icon-list"> <div class="icon-list">
<SvgIcon <SvgIcon
v-show="!isVideoPlaying(item.id)"
class="play-icon" class="play-icon"
@click.stop="handlePlayMotion(item)" @click.stop="handlePlayMotion(item)"
name="CPlay" :name="isVideoPlaying(item.id) ? 'CPause' : 'CPlay'"
size="10" size="20"
color="#fff"
/>
<SvgIcon
v-show="isVideoPlaying(item.id)"
class="play-icon pause"
@click.stop="handlePlayMotion(item)"
name="CPause"
size="10"
color="#fff" color="#fff"
/> />
</div> </div>
@@ -213,6 +215,7 @@
@click="getgenerate()" @click="getgenerate()"
></i> ></i>
<div <div
v-show="showMotion"
class="icon iconfont icon-xiala" class="icon iconfont icon-xiala"
:class="{ active: speedState }" :class="{ active: speedState }"
@click.stop="openSpeed" @click.stop="openSpeed"
@@ -277,6 +280,13 @@
</div> </div>
</div> </div>
<Tips v-model:showModal="showTips" /> <Tips v-model:showModal="showTips" />
<Product
v-model:showModal="showProductList"
:frameList="productFrameList"
:type="productType"
:key="productType"
@confirm="handleConfirmProduct"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -304,13 +314,15 @@ import showViewVideo from '@/tool/mount'
import router from '@/router' import router from '@/router'
import promptInput from './promptInput.vue' import promptInput from './promptInput.vue'
import Tips from './Tips.vue' import Tips from './Tips.vue'
import Product from './Product.vue'
import { getFirstFrame, getFirstAndLastFrame } from './prompt' import { getFirstFrame, getFirstAndLastFrame } from './prompt'
export default defineComponent({ export default defineComponent({
components: { components: {
generalDrag, generalDrag,
promptInput, promptInput,
Tips Tips,
Product
}, },
props: { props: {
isDesignPage: { isDesignPage: {
@@ -360,15 +372,7 @@ export default defineComponent({
removeGenerate: false, removeGenerate: false,
generateTime: null as any, generateTime: null as any,
poseList: [], poseList: [],
selectPose: null as any, selectPose: null as any
prompt: computed(() => {
if (videoType.value === 2) {
return getFirstFrame(t)
}
if (videoType.value === 3) {
return getFirstAndLastFrame(t)
}
})
}) })
let speed = reactive({ let speed = reactive({
speedList: [ speedList: [
@@ -515,7 +519,6 @@ export default defineComponent({
return return
} }
if (!data.selectImg.minioUrl) return message.info(t('poseTransfer.NeedFirstFrame')) if (!data.selectImg.minioUrl) return message.info(t('poseTransfer.NeedFirstFrame'))
debugger
if (data.isGenerate) return if (data.isGenerate) return
await new Promise((res, reject) => { await new Promise((res, reject) => {
Modal.confirm({ Modal.confirm({
@@ -546,6 +549,9 @@ export default defineComponent({
userLikeSortId: null, //是否进行排序 userLikeSortId: null, //是否进行排序
mode: videoType.value mode: videoType.value
} }
if (!showMotion.value) {
value.modelName = ''
}
if (videoType.value === 2) { if (videoType.value === 2) {
const prompt = dataDom.promptInput.getFullText() const prompt = dataDom.promptInput.getFullText()
value.prompt = prompt value.prompt = prompt
@@ -881,6 +887,61 @@ export default defineComponent({
document.removeEventListener('click', openSpeed) document.removeEventListener('click', openSpeed)
} }
const setUploadDelete = (item: any, index: any, isLastFrame: boolean = false) => { 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 = { let value = {
id: item.id id: item.id
} }
@@ -911,6 +972,14 @@ export default defineComponent({
const videoType = ref(2) const videoType = ref(2)
const showMotion = computed(() => videoType.value === 1) 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([ const options = ref([
{ vlaue: 2, label: t('poseTransfer.FirstFrame') }, { vlaue: 2, label: t('poseTransfer.FirstFrame') },
{ value: 3, label: t('poseTransfer.FirstAndLastFrames') }, { value: 3, label: t('poseTransfer.FirstAndLastFrames') },
@@ -920,9 +989,148 @@ export default defineComponent({
const firstFrameList = ref([]) const firstFrameList = ref([])
const lastFrameList = ref([]) const lastFrameList = ref([])
const showFirstFrameList = computed(() => { const showFirstFrameList = computed(() => {
if(props.isDesignPage) return data.fileList 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 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( watch(
() => store.state.HomeStoreModule.uploadElement.length, () => store.state.HomeStoreModule.uploadElement.length,
(newVal, oldVal) => { (newVal, oldVal) => {
@@ -952,6 +1160,20 @@ export default defineComponent({
} }
) )
// 监听 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( watch(
() => store.state.HomeStoreModule.lastFrameList, () => store.state.HomeStoreModule.lastFrameList,
val => { val => {
@@ -1039,7 +1261,15 @@ export default defineComponent({
setVideoRef, setVideoRef,
handlePlayMotion, handlePlayMotion,
isVideoPlaying, isVideoPlaying,
showTips showTips,
showProductList,
handleClickDesignMask,
productType,
handleConfirmProduct,
productFrameList,
showFirstFrameUpload,
showLastFrameUpload,
inputPrompt
} }
}, },
directives: { directives: {
@@ -1239,6 +1469,15 @@ export default defineComponent({
} }
> .upload_item { > .upload_item {
border: none; border: none;
position: relative;
.design-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
} }
.control-container { .control-container {
width: 100%; width: 100%;
@@ -1252,26 +1491,27 @@ export default defineComponent({
rgba(8, 9, 13, 0.27) 80.37% rgba(8, 9, 13, 0.27) 80.37%
); );
display: flex; display: flex;
align-items: flex-end; align-items: center;
justify-content: center; justify-content: flex-start;
.icon-list { .icon-list {
height: 50%; // height: 50%;
width: calc(100% - 1.6rem); // width: calc(100% - 1.6rem);
// border-top: 1px solid #fff; // border-top: 1px solid #fff;
padding-left: 1rem;
display: flex; display: flex;
box-sizing: border-box; box-sizing: border-box;
justify-content: flex-start; justify-content: flex-start;
align-items: center; align-items: center;
.play-icon { .play-icon {
width: initial; width: 2rem;
height: initial; height: 2rem;
} }
} }
} }
} }
> .head { > .head {
color: #000; color: #000;
font-weight: 600; font-weight: 500;
display: flex; display: flex;
align-items: center; align-items: center;
column-gap: 1rem; column-gap: 1rem;
@@ -1292,6 +1532,7 @@ export default defineComponent({
font-size: 1.8rem; font-size: 1.8rem;
color: #000; color: #000;
margin-bottom: 1.4rem; margin-bottom: 1.4rem;
font-weight: 500;
} }
> .poses { > .poses {
margin-top: 3rem; margin-top: 3rem;
@@ -1393,6 +1634,7 @@ export default defineComponent({
} }
.video-type-container { .video-type-container {
margin-bottom: 4rem; margin-bottom: 4rem;
font-weight: 500;
.title { .title {
font-size: 1.8rem; font-size: 1.8rem;
color: #000; color: #000;

View File

@@ -1,11 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted, reactive, toRefs, nextTick, useTemplateRef, watch } from "vue"; import { ref, reactive, toRefs, nextTick, useTemplateRef, watch } from 'vue'
interface ContentItem { interface ContentItem {
id: string; id: string
type: 'text' | 'input'; type: 'text' | 'input'
value: string; value: string
placeholder?: string; placeholder?: string
} }
const props = defineProps({ const props = defineProps({
@@ -22,7 +22,7 @@ const data = reactive({
// { id: '3', type: 'text', value: '333333' }, // { id: '3', type: 'text', value: '333333' },
// { id: '4', type: 'input', value: '', placeholder: '[请输入内容]' } // { id: '4', type: 'input', value: '', placeholder: '[请输入内容]' }
// ] as ContentItem[] // ] as ContentItem[]
content: props.content content: [{ id: '1', type: 'text', value: '' }]
}) })
const editableArea = ref<HTMLElement>() const editableArea = ref<HTMLElement>()
@@ -32,11 +32,17 @@ const cursorState = ref({
isContainerClick: false isContainerClick: false
}) })
// 添加中文输入状态管理
const compositionState = reactive({
isComposing: false,
currentInputIndex: -1
})
// 检查并删除末尾的空文本框 // 检查并删除末尾的空文本框
const removeLastEmptyTextIfNeeded = () => { const removeLastEmptyTextIfNeeded = () => {
const lastItem = content.value[content.value.length - 1] const lastItem = data.content[data.content.length - 1]
if (lastItem && lastItem.type === 'text' && lastItem.value === '') { if (lastItem && lastItem.type === 'text' && lastItem.value === '') {
content.value.pop() data.content.pop()
return true return true
} }
return false return false
@@ -44,14 +50,14 @@ const removeLastEmptyTextIfNeeded = () => {
// 确保末尾有空文本框 // 确保末尾有空文本框
const ensureEmptyTextAtEnd = () => { const ensureEmptyTextAtEnd = () => {
const lastItem = content.value[content.value.length - 1] const lastItem = data.content[data.content.length - 1]
if (!lastItem || lastItem.type !== 'text' || lastItem.value !== '') { if (!lastItem || lastItem.type !== 'text' || lastItem.value !== '') {
const newItem: ContentItem = { const newItem: ContentItem = {
id: Date.now().toString(), id: Date.now().toString(),
type: 'text', type: 'text',
value: '' value: ''
} }
content.value.push(newItem) data.content.push(newItem)
return true return true
} }
return false return false
@@ -90,7 +96,7 @@ const getCurrentElementInfo = () => {
index = parseInt(parent?.getAttribute('data-index') || '-1') index = parseInt(parent?.getAttribute('data-index') || '-1')
type = 'input' type = 'input'
const item = content.value[index] const item = data.content[index]
if (element.classList.contains('has-placeholder')) { if (element.classList.contains('has-placeholder')) {
// placeholder状态下光标在任意位置都认为是"在元素内" // placeholder状态下光标在任意位置都认为是"在元素内"
isAtStart = range.startOffset === 0 isAtStart = range.startOffset === 0
@@ -111,7 +117,10 @@ const setCursorToElement = (element: HTMLElement, position: 'start' | 'end') =>
const selection = window.getSelection() const selection = window.getSelection()
const range = document.createRange() const range = document.createRange()
if (element.classList.contains('input-content') && element.classList.contains('has-placeholder')) { if (
element.classList.contains('input-content') &&
element.classList.contains('has-placeholder')
) {
// placeholder状态的特殊处理 // placeholder状态的特殊处理
range.selectNodeContents(element) range.selectNodeContents(element)
range.collapse(position === 'start') range.collapse(position === 'start')
@@ -146,7 +155,10 @@ const handleKeydown = (event: KeyboardEvent) => {
if (isAtStart && index > 0) { if (isAtStart && index > 0) {
event.preventDefault() event.preventDefault()
handleCrossElementDelete(index) handleCrossElementDelete(index)
} else if (type === 'input' && elementInfo.element?.classList.contains('has-placeholder')) { } else if (
type === 'input' &&
elementInfo.element?.classList.contains('has-placeholder')
) {
event.preventDefault() event.preventDefault()
} }
// 其他情况让浏览器正常处理删除 // 其他情况让浏览器正常处理删除
@@ -160,10 +172,10 @@ const handleKeydown = (event: KeyboardEvent) => {
break break
case 'ArrowRight': case 'ArrowRight':
if (isAtEnd && index < content.value.length - 1) { if (isAtEnd && index < data.content.length - 1) {
event.preventDefault() event.preventDefault()
navigateToElement(index + 1, 'start') navigateToElement(index + 1, 'start')
} else if (isAtEnd && index === content.value.length - 1) { } else if (isAtEnd && index === data.content.length - 1) {
// 在最后一个元素末尾按右箭头,确保有一个空文本框 // 在最后一个元素末尾按右箭头,确保有一个空文本框
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
nextTick(() => { nextTick(() => {
@@ -177,26 +189,26 @@ const handleKeydown = (event: KeyboardEvent) => {
// 跨元素删除逻辑 // 跨元素删除逻辑
const handleCrossElementDelete = (currentIndex: number) => { const handleCrossElementDelete = (currentIndex: number) => {
const prevIndex = currentIndex - 1 const prevIndex = currentIndex - 1
const prevItem = content.value[prevIndex] const prevItem = data.content[prevIndex]
if (prevItem.type === 'input') { if (prevItem.type === 'input') {
if (prevItem.value.trim() === '') { if (prevItem.value.trim() === '') {
// 删除空输入框 // 删除空输入框
content.value.splice(prevIndex, 1) data.content.splice(prevIndex, 1)
nextTick(() => { nextTick(() => {
// 删除输入框后,先删除末尾的空文本框 // 删除输入框后,先删除末尾的空文本框
removeLastEmptyTextIfNeeded() removeLastEmptyTextIfNeeded()
// 然后聚焦到正确的位置 // 然后聚焦到正确的位置
if (prevIndex < content.value.length) { if (prevIndex < data.content.length) {
focusElement(prevIndex, 'end') focusElement(prevIndex, 'end')
} else if (content.value.length > 0) { } else if (data.content.length > 0) {
focusElement(content.value.length - 1, 'end') focusElement(data.content.length - 1, 'end')
} }
}) })
} else { } else {
// 删除输入框最后一个字符,但保留输入框 // 删除输入框最后一个字符,但保留输入框
const newValue = prevItem.value.slice(0, -1) const newValue = prevItem.value.slice(0, -1)
content.value[prevIndex].value = newValue data.content[prevIndex].value = newValue
updateInputDisplay(prevIndex) updateInputDisplay(prevIndex)
nextTick(() => focusElement(prevIndex, 'end')) nextTick(() => focusElement(prevIndex, 'end'))
} }
@@ -210,27 +222,37 @@ const handleCrossElementDelete = (currentIndex: number) => {
// 导航到元素 // 导航到元素
const navigateToElement = (targetIndex: number, position: 'start' | 'end') => { const navigateToElement = (targetIndex: number, position: 'start' | 'end') => {
const targetItem = content.value[targetIndex] const targetItem = data.content[targetIndex]
const element = targetItem.type === 'text' const element =
? editableArea.value?.querySelector(`.text-field[data-index="${targetIndex}"]`) as HTMLElement targetItem.type === 'text'
: editableArea.value?.querySelector(`.input-field[data-index="${targetIndex}"] .input-content`) as HTMLElement ? (editableArea.value?.querySelector(
`.text-field[data-index="${targetIndex}"]`
) as HTMLElement)
: (editableArea.value?.querySelector(
`.input-field[data-index="${targetIndex}"] .input-content`
) as HTMLElement)
if (element) setCursorToElement(element, position) if (element) setCursorToElement(element, position)
} }
// 焦点设置 // 焦点设置
const focusElement = (index: number, position: 'start' | 'end') => { const focusElement = (index: number, position: 'start' | 'end') => {
const item = content.value[index] const item = data.content[index]
const element = item.type === 'text' const element =
? editableArea.value?.querySelector(`.text-field[data-index="${index}"]`) as HTMLElement item.type === 'text'
: editableArea.value?.querySelector(`.input-field[data-index="${index}"] .input-content`) as HTMLElement ? (editableArea.value?.querySelector(
`.text-field[data-index="${index}"]`
) as HTMLElement)
: (editableArea.value?.querySelector(
`.input-field[data-index="${index}"] .input-content`
) as HTMLElement)
if (element) setCursorToElement(element, position) if (element) setCursorToElement(element, position)
} }
// 输入框显示管理 // 输入框显示管理
const updateInputDisplay = (index: number) => { const updateInputDisplay = (index: number) => {
const item = content.value[index] const item = data.content[index]
if (item.type !== 'input') return if (item.type !== 'input') return
const inputElement = editableArea.value?.querySelector( const inputElement = editableArea.value?.querySelector(
@@ -250,15 +272,36 @@ const updateInputDisplay = (index: number) => {
} }
} }
// 输入框内容变化处理
const handleInputChange = (index: number, event: Event) => { const handleInputChange = (index: number, event: Event) => {
const target = event.target as HTMLSpanElement // 如果是中文输入过程中,不处理
const item = content.value[index] if (compositionState.isComposing && compositionState.currentInputIndex === index) {
return
}
// 如果当前显示placeholder不更新实际值 const target = event.target as HTMLSpanElement
const item = data.content[index]
// 如果当前显示placeholder但内容已改变比如通过粘贴清除placeholder
if (
target.classList.contains('has-placeholder') &&
target.textContent !== item.placeholder
) {
target.classList.remove('has-placeholder')
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
return
}
// 正常情况下的处理
if (!target.classList.contains('has-placeholder')) { if (!target.classList.contains('has-placeholder')) {
const newValue = target.textContent || '' const newValue = target.textContent || ''
content.value[index].value = newValue data.content[index].value = newValue
// 如果内容变空显示placeholder // 如果内容变空显示placeholder
if (newValue.trim() === '' && item.placeholder) { if (newValue.trim() === '' && item.placeholder) {
@@ -268,10 +311,55 @@ const handleInputChange = (index: number, event: Event) => {
} }
} }
// 输入框键盘事件 // 增强中文输入事件处理
const handleCompositionStart = (index: number, event: CompositionEvent) => {
compositionState.isComposing = true
compositionState.currentInputIndex = index
const target = event.target as HTMLSpanElement
// 如果是placeholder状态开始中文输入时清除placeholder
if (target.classList.contains('has-placeholder')) {
target.classList.remove('has-placeholder')
target.textContent = ''
data.content[index].value = ''
}
}
// 添加专门的粘贴事件处理
const handleInputPaste = (event: ClipboardEvent, index: number) => {
const target = event.target as HTMLSpanElement
const item = data.content[index]
// 如果当前显示 placeholder先清除它
if (target.classList.contains('has-placeholder')) {
event.preventDefault()
target.classList.remove('has-placeholder')
target.textContent = ''
// 获取粘贴的内容
const pasteData = event.clipboardData?.getData('text') || ''
document.execCommand('insertText', false, pasteData)
// 更新值
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
}
}
const handleInputKeydown = (event: KeyboardEvent, index: number) => { const handleInputKeydown = (event: KeyboardEvent, index: number) => {
const target = event.target as HTMLSpanElement const target = event.target as HTMLSpanElement
// 如果是中文输入过程中,不处理普通按键
if (compositionState.isComposing) {
return
}
if (event.key === 'Backspace') { if (event.key === 'Backspace') {
// 如果显示placeholder阻止删除 // 如果显示placeholder阻止删除
if (target.classList.contains('has-placeholder')) { if (target.classList.contains('has-placeholder')) {
@@ -291,12 +379,12 @@ const handleInputKeydown = (event: KeyboardEvent, index: number) => {
} }
} }
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) { } else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
// 普通字符输入 // 普通字符输入 - 只有在非中文输入状态下才处理
if (target.classList.contains('has-placeholder')) { if (target.classList.contains('has-placeholder')) {
event.preventDefault() event.preventDefault()
target.textContent = event.key target.textContent = event.key
target.classList.remove('has-placeholder') target.classList.remove('has-placeholder')
content.value[index].value = event.key data.content[index].value = event.key
// 移动光标到末尾 // 移动光标到末尾
nextTick(() => { nextTick(() => {
@@ -304,9 +392,79 @@ const handleInputKeydown = (event: KeyboardEvent, index: number) => {
}) })
} }
} }
// 添加对 Ctrl+V 的处理
else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
// 延迟处理,等待粘贴内容实际插入
setTimeout(() => {
handlePasteInInput(index, target)
}, 0)
}
} }
const handlePasteInInput = (index: number, element: HTMLElement) => {
const item = data.content[index]
// 如果当前显示 placeholder清除它
if (element.classList.contains('has-placeholder')) {
element.classList.remove('has-placeholder')
// 获取粘贴后的实际内容
const newValue = element.textContent || ''
data.content[index].value = newValue
// 如果粘贴后内容为空,重新显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
element.classList.add('has-placeholder')
element.textContent = item.placeholder
}
} else {
// 正常情况,直接更新值
const newValue = element.textContent || ''
data.content[index].value = newValue
// 检查是否需要显示 placeholder
if (newValue.trim() === '' && item.placeholder) {
element.classList.add('has-placeholder')
element.textContent = item.placeholder
}
}
}
const handleCompositionEnd = (index: number, event: CompositionEvent) => {
// 延迟设置 isComposing 为 false确保所有相关事件都处理完毕
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 0)
const target = event.target as HTMLSpanElement
const newValue = target.textContent || ''
data.content[index].value = newValue
// 如果中文输入后内容为空显示placeholder
const item = data.content[index]
if (newValue.trim() === '' && item.placeholder) {
// 延迟显示 placeholder确保光标位置正确
nextTick(() => {
if (target.textContent?.trim() === '') {
target.classList.add('has-placeholder')
target.textContent = item.placeholder
}
})
}
}
// 添加输入框焦点事件处理,确保中文输入状态正确重置
const handleInputFocus = (index: number) => {
compositionState.currentInputIndex = index
}
// 修改输入框的 blur 处理
const handleInputBlur = (index: number) => { const handleInputBlur = (index: number) => {
// 延迟重置状态,避免与 compositionend 冲突
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 100)
updateInputDisplay(index) updateInputDisplay(index)
} }
@@ -321,7 +479,7 @@ const handleContainerClick = (event: MouseEvent) => {
// 确保末尾有空文本框并聚焦到它 // 确保末尾有空文本框并聚焦到它
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
nextTick(() => { nextTick(() => {
focusElement(content.value.length - 1, 'start') focusElement(data.content.length - 1, 'start')
}) })
} }
} }
@@ -329,7 +487,7 @@ const handleContainerClick = (event: MouseEvent) => {
// 初始化 // 初始化
const initPlaceholders = () => { const initPlaceholders = () => {
nextTick(() => { nextTick(() => {
content.value.forEach((_, index) => updateInputDisplay(index)) data.content.forEach((_, index) => updateInputDisplay(index))
// 确保初始状态下有一个空文本框 // 确保初始状态下有一个空文本框
ensureEmptyTextAtEnd() ensureEmptyTextAtEnd()
}) })
@@ -337,7 +495,8 @@ const initPlaceholders = () => {
const getFullText = () => { const getFullText = () => {
if (assistModel.value) { if (assistModel.value) {
return content.value.map(item => { return data.content
.map(item => {
if (item.type === 'text') { if (item.type === 'text') {
return item.value return item.value
} else { } else {
@@ -354,45 +513,39 @@ const getFullText = () => {
} }
return '' return ''
} }
}).join('') })
.join('')
} }
return textareaValue.value return textareaValue.value
} }
const textareaValue = ref('') const textareaValue = ref('')
const assistModel = ref(false) const assistModel = ref(false)
const syncContentFromProps = (
newContent: ContentItem[] = props.content as ContentItem[]
) => {
data.content = JSON.parse(JSON.stringify(newContent || []))
if (assistModel.value) {
initPlaceholders()
}
}
const handleClickAssistBtn = () => { const handleClickAssistBtn = () => {
assistModel.value = !assistModel.value assistModel.value = !assistModel.value
if (assistModel.value) {
syncContentFromProps()
}
} }
// 监听 assistModel 变化,切换到 textarea 模式时调整高度 watch(
watch(assistModel, (newVal) => { () => props.content,
if (!newVal) { newVal => {
// 切换到 textarea 模式 syncContentFromProps(newVal as ContentItem[])
nextTick(() => { },
handleInputResize() { deep: true }
}) )
}
})
const textareaRef = useTemplateRef<HTMLTextAreaElement>('textareaRef') const textareaRef = useTemplateRef<HTMLTextAreaElement>('textareaRef')
const handleInputResize = () => {
const textarea = textareaRef.value
if (textarea) {
textarea.style.height = 'auto'
textarea.style.height = textarea.scrollHeight + 'px'
}
}
onMounted(() => {
initPlaceholders()
// 如果初始状态是 textarea 模式,设置初始高度
if (!assistModel.value) {
nextTick(() => {
handleInputResize()
})
}
})
defineExpose({ defineExpose({
getFullText, getFullText,
@@ -406,7 +559,6 @@ defineExpose({
class="area" class="area"
v-model="textareaValue" v-model="textareaValue"
ref="textareaRef" ref="textareaRef"
@input="handleInputResize"
:placeholder="$t('poseTransfer.PormptPlaceholder')" :placeholder="$t('poseTransfer.PormptPlaceholder')"
/> />
<div class="asistant-btn" @click="handleClickAssistBtn"> <div class="asistant-btn" @click="handleClickAssistBtn">
@@ -414,13 +566,22 @@ defineExpose({
<span>{{ $t('ProductImg.PromptAssit') }}</span> <span>{{ $t('ProductImg.PromptAssit') }}</span>
</div> </div>
</div> </div>
<div v-show="assistModel" ref="editableArea" class="promptInput" @keydown="handleKeydown" @click="handleContainerClick"> <!-- {{ data.content }} -->
<template v-for="(item, index) in content" :key="item.id"> <div
v-show="assistModel"
ref="editableArea"
class="promptInput"
@keydown="handleKeydown"
@click="handleContainerClick"
>
<div class="promptinput-wrapper">
<template v-for="(item, index) in data.content" :key="item.id">
<span <span
v-if="item.type === 'text'" v-if="item.type === 'text'"
class="text-field" class="text-field"
:data-index="index" :data-index="index"
contenteditable="plaintext-only"> contenteditable="plaintext-only"
>
{{ item.value }} {{ item.value }}
</span> </span>
@@ -428,11 +589,17 @@ defineExpose({
<span <span
class="input-content" class="input-content"
contenteditable="plaintext-only" contenteditable="plaintext-only"
@input="(e) => handleInputChange(index, e)" @input="e => handleInputChange(index, e)"
@keydown="(e) => handleInputKeydown(e, index)" @keydown="e => handleInputKeydown(e, index)"
@blur="() => handleInputBlur(index)"></span> @focus="() => handleInputFocus(index)"
@blur="() => handleInputBlur(index)"
@paste="e => handleInputPaste(e, index)"
@compositionstart="e => handleCompositionStart(index, e)"
@compositionend="e => handleCompositionEnd(index, e)"
></span>
</span> </span>
</template> </template>
</div>
<div class="asistant-btn" @click="handleClickAssistBtn"> <div class="asistant-btn" @click="handleClickAssistBtn">
<i class="fi fi-bs-magic-wand asistant-icon"></i> <i class="fi fi-bs-magic-wand asistant-icon"></i>
<span>{{ $t('ProductImg.PromptAssit') }}</span> <span>{{ $t('ProductImg.PromptAssit') }}</span>
@@ -447,7 +614,7 @@ defineExpose({
--promptInputPadding: 1.5rem; --promptInputPadding: 1.5rem;
width: 100%; width: 100%;
min-height: 12rem; font-weight: 500;
border-radius: var(--promptInputBorderRadius); border-radius: var(--promptInputBorderRadius);
border: var(--promptInputBorder); border: var(--promptInputBorder);
padding: var(--promptInputPadding); padding: var(--promptInputPadding);
@@ -459,23 +626,27 @@ defineExpose({
cursor: text; cursor: text;
position: relative; position: relative;
padding-bottom: 4rem; padding-bottom: 4rem;
box-sizing: content-box;
.promptinput-wrapper {
min-height: 12rem;
max-height: 14rem;
overflow-y: auto;
}
.text-field { .text-field {
display: inline; display: inline;
outline: none; outline: none;
padding: .2rem 0; padding: 0.2rem 0;
font-size: 1.8rem; font-size: 1.8rem;
min-width: 2px; min-width: 2px;
/* 确保空文本框也能点击 */ font-weight: 400;
} }
.input-field { .input-field {
display: inline-block; display: inline-block;
// background: #e3f2fd; margin: 0 0.2rem;
// border: 1px solid #bbdefb; padding: 0.2rem 1rem;
margin: 0 .2rem;
padding: .2rem 1rem;
font-size: 1.8rem; font-size: 1.8rem;
border-radius: 4px; border-radius: 4px;
@@ -486,7 +657,6 @@ defineExpose({
&.has-placeholder { &.has-placeholder {
color: #b9b9b9; color: #b9b9b9;
// font-style: italic;
} }
} }
} }
@@ -498,10 +668,11 @@ defineExpose({
border: 2px solid #dcdfe6; border: 2px solid #dcdfe6;
padding: 1.5rem 1.5rem 3rem; padding: 1.5rem 1.5rem 3rem;
height: auto; height: auto;
font-size: 1.8rem;
.area { .area {
width: 100%; width: 100%;
min-height: 12rem; min-height: 12rem;
height: auto; max-height: 14rem;
background: white; background: white;
line-height: 1.6; line-height: 1.6;
outline: none; outline: none;
@@ -509,9 +680,9 @@ defineExpose({
user-select: text; user-select: text;
cursor: text; cursor: text;
position: relative; position: relative;
// padding-bottom: 4rem;
resize: none; resize: none;
border: none; border: none;
overflow-y: auto;
} }
} }
@@ -536,7 +707,6 @@ defineExpose({
font-size: 1rem; font-size: 1rem;
margin-right: 0; margin-right: 0;
width: initial; width: initial;
// margin-top: -0.2rem;
} }
} }
</style> </style>

View File

@@ -0,0 +1,467 @@
<template>
<!-- <div ref="modalContainer"></div> -->
<a-modal
class="promptEditProduct-modal generalModel"
v-model:visible="showModal"
:footer="null"
width="78%"
:maskClosable="false"
:centered="true"
:closable="false"
wrapClassName="#app"
:keyboard="false"
>
<div class="generalModel_btn">
<div class="generalModel_closeIcon" @click.stop="handleClose()">
<svg
width="100%"
height="100%"
viewBox="0 0 46 46"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="23" cy="23" r="23" fill="#000" fill-opacity="0.3" />
<rect
x="32.5063"
y="12"
width="3"
height="29"
rx="1.5"
transform="rotate(45 32.5063 12)"
fill="white"
/>
<rect
x="34.6274"
y="32.5059"
width="3"
height="29"
rx="1.5"
transform="rotate(135 34.6274 32.5059)"
fill="white"
/>
</svg>
</div>
</div>
<div class="title-container">
<div class="title">{{ $t('ProductImg.PromptAssit') }}</div>
<div class="sub-title">{{ status == 0 ? $t('ProductImg.Edited') : $t('ProductImg.AssitSubTitle') }}</div>
</div>
<div class="example-content" v-show="status == 0" :class="{'active': status !== 0}">
<div
class="example-wrapper"
>
<div class="example-item"
v-for="item in editedList"
:key="item.text">
<div class="imgItem">
<img :src="item.src" />
<div class="icon">
<SvgIcon v-if="item.status === 1" name="promptEditProductYes" size="35" />
<SvgIcon v-else name="promptEditProductNo" size="35" />
</div>
</div>
<div class="text">
{{ item.text }}
</div>
</div>
</div>
</div>
<div class="example-content prompt" v-show="status == 1" :class="{'active': status == 0}">
<div
class="example-wrapper"
>
<div class="example-item">
<div class="imgItem">
<img :src="listImgOriginal" />
<div class="info">{{ $t('ProductImg.OriginalImage') }}</div>
</div>
<SvgIcon
class="download-icon"
name="CDownload"
size="20"
color="#8E8E8E"
@click.stop="handleDownload(item)"
/>
<div class="text">
{{ $t('ProductImg.EditGarmen') }}:
</div>
</div>
<div class="example-item"
v-for="item in presentList"
:key="item.text">
<div class="imgItem">
<img :src="item.src" />
<div class="info">{{ item.info }}</div>
</div>
<div class="textCopy">
{{ item.text }}
<SvgIcon
name="CCopy"
size="25"
color="#BCBCBC"
class="copy-icon"
@click.stop="handleCopy(item)"
/>
</div>
</div>
</div>
</div>
<!-- <div class="prompt-list">
<div v-for="item in promptList" :key="item" class="prompt-item">
<SvgIcon
name="CCopy"
size="25"
color="#BCBCBC"
class="copy-icon"
@click.stop="handleCopy(item)"
/>
{{ item }}
</div>
</div> -->
<div class="nextOrPrevious">
<div class="Previous" @click="setNextOrPrevious('Previous')">
<i class="fi fi-bs-angle-left"></i>
</div>
<div class="next" @click="setNextOrPrevious('next')">
<i class="fi fi-bs-angle-right"></i>
</div>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, useTemplateRef, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import listImgOriginal from '@/assets/images/editProduct/PromptEditProduct_listOriginal.png'
import edited1 from '@/assets/images/editProduct/PromptEditProduct_edited1.png'
import edited2 from '@/assets/images/editProduct/PromptEditProduct_edited2.png'
import edited3 from '@/assets/images/editProduct/PromptEditProduct_edited3.png'
import listImg1 from '@/assets/images/editProduct/PromptEditProduct_list1.png'
import listImg2 from '@/assets/images/editProduct/PromptEditProduct_list2.png'
import listImg3 from '@/assets/images/editProduct/PromptEditProduct_list3.png'
import listImg4 from '@/assets/images/editProduct/PromptEditProduct_list4.png'
import listImg5 from '@/assets/images/editProduct/PromptEditProduct_list5.png'
import listImg6 from '@/assets/images/editProduct/PromptEditProduct_list6.png'
import listImg7 from '@/assets/images/editProduct/PromptEditProduct_list7.png'
import listImg8 from '@/assets/images/editProduct/PromptEditProduct_list8.png'
import listImg9 from '@/assets/images/editProduct/PromptEditProduct_list9.png'
import listImg10 from '@/assets/images/editProduct/PromptEditProduct_list10.png'
import listImg11 from '@/assets/images/editProduct/PromptEditProduct_list11.png'
import { downloadIamge } from '@/tool/util'
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
const props = defineProps<{
}>()
const showModal = defineModel<boolean>('showModal', { required: true })
const status = ref(0)
const promptPage = ref(1)
const editedList = ref([
{
src:edited1,
text:t('ProductImg.Background'),
status:1,
},
{
src:edited2,
text:t('ProductImg.BackgroundColor'),
status:1,
},
{
src:edited3,
text:t('ProductImg.ComplexBackground'),
status:0,
}
])
const promptList = ref([
{
src:listImg1,
info:t('ProductImg.ChangeviewInfo'),
text:t('ProductImg.Changeview')
},{
src:listImg2,
info:t('ProductImg.ChangeposeInfo'),
text:t('ProductImg.Changepose')
},{
src:listImg3,
info:t('ProductImg.ChangehairInfo'),
text:t('ProductImg.Changehair')
},{
src:listImg4,
info:t('ProductImg.ChangeAsianInfo'),
text:t('ProductImg.ChangeAsian')
},{
src:listImg5,
info:t('ProductImg.ChangedeminInfo'),
text:t('ProductImg.Changedemin')
},{
src:listImg6,
info:t('ProductImg.ChaRemoveInfo'),
text:t('ProductImg.ChaRemove')
},{
src:listImg7,
info:t('ProductImg.ChangeSunglassesInfo'),
text:t('ProductImg.ChangeSunglasses')
},{
src:listImg8,
info:t('ProductImg.ChangeHeelsInfo'),
text:t('ProductImg.ChangeHeels')
},{
src:listImg9,
info:t('ProductImg.ChangeUrbanInfo'),
text:t('ProductImg.ChangeUrban')
},{
src:listImg10,
info:t('ProductImg.ChangeBarInfo'),
text:t('ProductImg.ChangeBar')
},{
src:listImg11,
info:t('ProductImg.ChangeGardenInfo'),
text:t('ProductImg.ChangeGarden')
},
])
const presentList = ref([
]) as any
const setPresentList = (pageSize = 4)=>{
const startIndex = (promptPage.value - 1) * pageSize;
const endIndex = startIndex + pageSize;
return promptList.value.slice(startIndex, endIndex);
}
const handleClose = () => {
showModal.value = false
}
const handleDownload = () => {
downloadIamge(listImgOriginal)
}
const handleCopy = async (item: any) => {
let value = item.text.replace(/\n/g, ' ');
try {
if (navigator.clipboard && navigator.clipboard.writeText) {
await navigator.clipboard.writeText(value)
message.success(t('ProductImg.CopySuccess'))
} else {
// 降级方案:使用传统的 document.execCommand 方法
const textArea = document.createElement('textarea')
textArea.value = value
textArea.style.position = 'fixed'
textArea.style.left = '-999999px'
textArea.style.top = '-999999px'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
document.execCommand('copy')
message.success(t('ProductImg.CopySuccess'))
} catch (err) {
message.error(t('ProductImg.CopyFailed'))
}
document.body.removeChild(textArea)
}
} catch (err) {
message.error(t('ProductImg.CopyFailed'))
}
}
const setNextOrPrevious = (type) => {
if(type === 'Previous'){
if(promptPage.value == 1){
status.value = 0
}else{
promptPage.value--
}
}else{
if(status.value == 0){
status.value = 1
}else{
promptPage.value++
}
}
if(promptPage.value > 0)presentList.value = setPresentList()
}
</script>
<style lang="less">
.promptEditProduct-modal{
&.generalModel{
.ant-modal-body{
padding: 5rem 4.7rem;
}
}
}
</style>
<style lang="less" scoped>
.promptEditProduct-modal {
.title-container {
.title {
font-size: 3.5rem;
color: #181818;
}
.sub-title {
font-size: 2rem;
font-weight: 400;
color: #000;
}
}
.nextOrPrevious{
position: absolute;
bottom: 2.2rem;
display: flex;
left: 0;
width: 100%;
justify-content: center;
gap: 3.6rem;
> div{
width: 3.6rem;
height: 3.6rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
border: 2px solid #E9E9E9;
&:hover{
background: #000000;
color: #fff;
}
> i{
display: flex;
}
}
}
.example-content {
margin-top: 4.7rem;
display: flex;
justify-content: space-between;
overflow-x: auto;
justify-content: center;
.example-wrapper {
display: flex;
gap: 6.4rem;
.example-item {
// width: 20.3rem;
position: relative;
> .imgItem{
position: relative;
height: 44.6rem;
border: 1px solid #EEEEEE;
img {
height: 100%;
}
> .icon{
position: absolute;
z-index: 2;
bottom: -.3rem;
right: -1.4rem;
background-color: #fff;
border-radius: 50%;
}
> .info{
}
}
> .text{
margin-top: 4rem;
font-size: 2rem;
font-weight: 400;
text-align: center;
}
.download-icon {
position: absolute;
top: 0.77rem;
right: 0.84rem;
cursor: pointer;
z-index: 2;
}
}
}
&.prompt{
margin-top: 2.5rem;
.example-wrapper {
gap: 4rem;
.example-item {
> .imgItem{
height: 45.7rem;
img {
height: 100%;
}
> .info{
position: absolute;
z-index: 2;
line-height: 2.2rem;
text-align: center;
background-color: #fff;
bottom: 0;
left: 0;
width: 100%;
font-size: 1.2rem;
}
}
> .textCopy{
margin-top: 3rem;
width: 22.8rem;
height: 6.6rem;
border-radius: 8px;
word-break: break-word;
white-space: pre-wrap;
border: 1px solid #000;
display: flex;
align-items: center;
color: #969696;
padding: 0 1.3rem;
position: relative;
> .copy-icon{
position: absolute;
right: .7rem;
top: .7rem;
cursor: pointer;
}
}
.download-icon {
}
}
}
}
}
// .prompt-list {
// margin-top: 3.5rem;
// display: flex;
// column-gap: 6.3rem;
// .prompt-item {
// height: 12.8rem;
// line-height: 2.3rem;
// padding: 1.8rem 3.5rem 1.8rem 2rem;
// color: #969696;
// font-size: 1.9rem;
// border: 1px solid #000;
// border-radius: 0.8rem;
// overflow-y: auto;
// position: relative;
// // &:first-child{
// // width: 53.6rem;
// // }
// .copy-icon {
// position: absolute;
// top: 1.1rem;
// right: 1rem;
// cursor: pointer;
// }
// }
// }
}
.c-svg {
width: initial;
height: initial;
}
</style>

View File

@@ -320,7 +320,8 @@
}" }"
:isProductimg="true" :isProductimg="true"
></scaleImage> ></scaleImage>
<Prompt v-model:showModal="showPromptAssist" :promptList="promptTextList" /> <Prompt v-if="productimgMenu.value == 'ToProductImage'" v-model:showModal="showPromptAssist" :promptList="promptTextList" />
<PromptEditProduct v-if="productimgMenu.value == 'Relight'" v-model:showModal="showPromptAssist"></PromptEditProduct>
</div> </div>
</template> </template>
@@ -353,6 +354,7 @@ import scaleImage from '@/component/HomePage/scaleImage.vue'
import generalMenu from '@/component/HomePage/generalMenu.vue' import generalMenu from '@/component/HomePage/generalMenu.vue'
import generalDrag from '@/component/modules/generalDrag.vue' import generalDrag from '@/component/modules/generalDrag.vue'
import Prompt from './Prompt.vue' import Prompt from './Prompt.vue'
import PromptEditProduct from './PromptEditProduct.vue'
import { List } from 'echarts' import { List } from 'echarts'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
@@ -361,7 +363,8 @@ export default defineComponent({
scaleImage, scaleImage,
generalMenu, generalMenu,
generalDrag, generalDrag,
Prompt Prompt,
PromptEditProduct
}, },
props: { props: {
setTask: { setTask: {
@@ -623,11 +626,12 @@ export default defineComponent({
} }
} }
let isSelectObject = false let isSelectObject = false
watch(() => route.query, watch(
() => route.query,
(query: any, oldQuery: any) => { (query: any, oldQuery: any) => {
isSelectObject = false isSelectObject = false
}, }
); )
let beforeUpload = async (file: any) => { let beforeUpload = async (file: any) => {
const isJpgOrPng = const isJpgOrPng =
file.type === 'image/jpeg' || file.type === 'image/jpeg' ||
@@ -650,10 +654,7 @@ export default defineComponent({
CollectionType: props.productimgMenu.value CollectionType: props.productimgMenu.value
} }
} }
return !!( return !!((isJpgOrPng && isLt2M && objectId) || Upload.LIST_IGNORE)
(isJpgOrPng && isLt2M && objectId) ||
Upload.LIST_IGNORE
)
} }
let setGenerate = (item: any) => { let setGenerate = (item: any) => {
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
@@ -1559,8 +1560,8 @@ export default defineComponent({
:deep(.ant-upload-picture-card-wrapper) { :deep(.ant-upload-picture-card-wrapper) {
.ant-upload-list-picture-card { .ant-upload-list-picture-card {
.ant-upload-select-picture-card { .ant-upload-select-picture-card {
width: 9.6rem; width: 12.7rem;
height: 13.4rem; height: 17.8rem;
} }
} }
} }
@@ -1579,9 +1580,14 @@ export default defineComponent({
.input_box { .input_box {
.input_box_btnBox { .input_box_btnBox {
position: relative; position: relative;
display: flex;
flex-direction: column;
align-items: flex-start;
.textarea { .textarea {
// flex: 1;
min-height: 12.7rem; min-height: 12.7rem;
max-height: none; max-height: none;
width: 100%;
} }
} }
} }
@@ -1592,9 +1598,9 @@ export default defineComponent({
font-size: 1rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
color: #313131; color: #313131;
position: absolute; // position: absolute;
bottom: 1.3rem; // bottom: 1.3rem;
left: 1.3rem; // left: 1.3rem;
display: flex; display: flex;
column-gap: 0.3rem; column-gap: 0.3rem;
justify-content: center; justify-content: center;

View File

@@ -283,9 +283,37 @@ export default {
Prompt: '提示词', Prompt: '提示词',
PromptAssit: '提示词助手', PromptAssit: '提示词助手',
AssitSubTitle: '您可以复制并使用以下提示词:', AssitSubTitle: '您可以复制并使用以下提示词:',
Edited: '以下是编辑后的图像中需要注意的事项。',
CopySuccess: '已复制到剪贴板', CopySuccess: '已复制到剪贴板',
CopyFiled: '复制失败', CopyFiled: '复制失败',
noPrompt: '请输入提示词' noPrompt: '请输入提示词',
OriginalImage: '原始图像',
EditGarmen: '您可以编辑这件服装',
Background: '白色背景',
BackgroundColor: '纯色背景',
ComplexBackground: '复杂背景',
Changeview: '改为背面视角。',
ChangeviewInfo: '切换视角',
Changepose: '将姿势改为坐下并交叉脚踝\n的姿势。',
ChangeposeInfo: '改变姿势',
Changehair: '将发型改为金色直发。',
ChangehairInfo: '改变发型',
ChangeAsian: '将模特改为亚洲人。',
ChangeAsianInfo: '改变面部特征',
Changedemin: '将黑色西装改为黄色牛仔...',
ChangedeminInfo: '替换服装纹理和颜色',
ChaRemove: '移除黑色西装。',
ChaRemoveInfo: '移除服装',
ChangeSunglasses: '为模特添加棕色牛仔帽和\n太阳镜',
ChangeSunglassesInfo: '添加配饰',
ChangeHeels: '将白色鞋子改为黑色高跟鞋。',
ChangeHeelsInfo: '更换鞋子',
ChangeUrban: '在城市街道,正午时分。',
ChangeUrbanInfo: '替换服装纹理和颜色',
ChangeBar: '在酒吧,傍晚时分。',
ChangeBarInfo: '移除服装',
ChangeGarden: '在花园,清晨时分。',
ChangeGardenInfo: '添加配饰',
}, },
poseTransfer: { poseTransfer: {
SelectDesign: '产品图', SelectDesign: '产品图',
@@ -308,7 +336,7 @@ export default {
SeriesChildTryOn: SeriesChildTryOn:
'将此图像转化为逼真的真实儿童模特,保持站立姿势并直面镜头,保留服装的原有图案、风格和色彩,呈现工作室级别的照片质量。', // 系列儿童试穿 '将此图像转化为逼真的真实儿童模特,保持站立姿势并直面镜头,保留服装的原有图案、风格和色彩,呈现工作室级别的照片质量。', // 系列儿童试穿
UploadWithModel: UploadWithModel:
'创建模特穿着该服装的真实感摄影棚照片。若原有模特存在则保留,否则生成合适模特。采用站立姿势面向镜头。精确保留服装细节——图案、颜色、质地及装饰元素。', // 上传线稿,带模特 '生成真实模特穿着该服装的真摄影棚照片采用站立姿势面向镜头。保留服装所有细节——包括图案、颜色、纹理、装饰元素呈现摄影棚级别的8K分辨率照片支持HDR效果、景深控制、柔光效果及高细节呈现。请勿返还原始图像。', // 上传线稿,带模特
UploadWithoutModel: UploadWithoutModel:
'专业产品摄影:服装以自然形态展示,无模特。采用影棚灯光。保留精确细节——图案、色彩、质感、装饰元素。', // 上传线稿,不带模特 '专业产品摄影:服装以自然形态展示,无模特。采用影棚灯光。保留精确细节——图案、色彩、质感、装饰元素。', // 上传线稿,不带模特
VideoType: '视频类型', VideoType: '视频类型',

View File

@@ -293,9 +293,37 @@ export default {
Prompt: 'Prompt', Prompt: 'Prompt',
PromptAssit: 'Prompt Assist', PromptAssit: 'Prompt Assist',
AssitSubTitle: 'You can copy following prompt and try:', AssitSubTitle: 'You can copy following prompt and try:',
Edited: 'Heres what to notice in the edited image.',
CopySuccess: 'Copied to clipboard', CopySuccess: 'Copied to clipboard',
CopyFiled: 'Failed to copy', CopyFiled: 'Failed to copy',
noPrompt: 'Please enter prompt' noPrompt: 'Please enter prompt',
OriginalImage: 'Original Image',
EditGarmen: 'You can edit the garmen',
Background: 'White Background',
BackgroundColor: 'Background with Pure Color',
ComplexBackground: 'with Complex Background',
Changeview: 'Change to back view.',
ChangeviewInfo: 'Alter View',
Changepose: 'Change the pose to sit\ndown and cross ankle pose.',
ChangeposeInfo: 'Change Pose',
Changehair: 'Change the haircut to\nblonde straight hair.',
ChangehairInfo: 'Change Haircut',
ChangeAsian: 'Change model to Asian.',
ChangeAsianInfo: 'Change Facial',
Changedemin: 'Change the black\nsuit to yellow demin...',
ChangedeminInfo: 'Replace Garment Texture and Color',
ChaRemove: 'ChaRemove the black suit.',
ChaRemoveInfo: 'Remove Garment',
ChangeSunglasses: 'Change this model with\nbrown cowboy hat and sunglasses',
ChangeSunglassesInfo: 'Add Accessories',
ChangeHeels: 'Change white shoes to\nblack high heels.',
ChangeHeelsInfo: 'Change Shoes',
ChangeUrban: 'In the urban street,\nat noon.',
ChangeUrbanInfo: 'Replace Garment Texture and Color',
ChangeBar: 'In the bar, in the evening.',
ChangeBarInfo: 'Remove Garment',
ChangeGarden: 'In the garden, in the morning.',
ChangeGardenInfo: 'Add Accessories',
}, },
poseTransfer: { poseTransfer: {
SelectDesign: 'Product image', SelectDesign: 'Product image',
@@ -318,7 +346,7 @@ export default {
SeriesChildTryOn: SeriesChildTryOn:
'Transform this image into a realistic, the same real child model , Maintain a standing posture and face the camera directly, keep the same pattern, style and colour of these garment, studio-quality photograph.', // 系列儿童 'Transform this image into a realistic, the same real child model , Maintain a standing posture and face the camera directly, keep the same pattern, style and colour of these garment, studio-quality photograph.', // 系列儿童
UploadWithModel: UploadWithModel:
'Create realistic studio photo with model wearing this garment. Keep original model if present, or generate appropriate model. Standing pose, facing camera. Preserve exact garment details - patterns, colors, textures, embellishments.', // 上传线稿,带模特 'Generate realistic studio photo of a real model wearing this garment, standing pose, facing camera. Preserve exact garment details - all patterns, colors, textures, embellishments, studio-quality photograph, 8K, HDR, DOF, soft lighting, high detail. Do not return the original image.', // 上传线稿,带模特
UploadWithoutModel: UploadWithoutModel:
'Professional product photo: garment displayed with natural shape, no model. Studio lighting. Preserve exact details - patterns, colors, textures, embellishments.', // 上传线稿,不带模特 'Professional product photo: garment displayed with natural shape, no model. Studio lighting. Preserve exact details - patterns, colors, textures, embellishments.', // 上传线稿,不带模特
VideoType: 'Video Type', VideoType: 'Video Type',