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 = {
designItemId:detailData.designDetail.designItemId,
designSingleItemDTOList:clothes,
isPreview:true,
isPreview:str == 'sub'?false:true,
// ifSubmit:designItemDetail.isPreview,
gender:workspace?.sex,
sketchString:'',
@@ -462,7 +462,8 @@ export default defineComponent({
const modelOnLoad = ()=>{
if(!detailData.isUndividedLayerWithSinglePrint)return
setTimeout(()=>{
previwe()
let data = getSubmitData('sub')
store.dispatch('DesignDetail/setSubmit',data)
})
}
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.frontBack_.back = detailData.frontBack_.back.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){
addSketch()
@@ -118,7 +117,7 @@ export default defineComponent({
const maxObj = detailData.designDetail.clothes.find(item => item.priority === maxValue);
store.commit('DesignDetail/setDesignColthes',maxObj.id)
}
emit('canvasReload')
emit('deleteItem')
}
const addSketch = ()=>{

File diff suppressed because it is too large Load Diff

View File

@@ -203,7 +203,10 @@ export default defineComponent({
})
}
const unLike = (item)=>{
data.unLikeList.push(item)
const hasSame = data.unLikeList.some((unlikeItem:any)=>unlikeItem?.id === item?.id)
if(!hasSame){
data.unLikeList.push(item)
}
}
let cleardata = async ()=>{
let list = []
@@ -220,10 +223,12 @@ export default defineComponent({
const { newLike, ...rest } = item; // 解构赋值移除 newLike
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 = {
status:'add',
addList:generateCourse,
deleteList:data.unLikeList,
deleteList,
}
emit('editToolsSuccess',emitData,'batch')
@@ -489,9 +494,6 @@ export default defineComponent({
// .ant-modal-mask{
// position: absolute;
// }
.ant-modal-wrap,.ant-modal-mask{
}
> .ant-modal-root{
> .ant-modal-centered{
> .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 class="imgBox" v-mousewheel>
<div
class="item"
class="item first-frame"
:class="{ active: item.id == selectImg.id }"
v-for="(item, index) in showFirstFrameList"
@click="selectImgItem(item)"
@@ -52,23 +52,26 @@
v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox"
>
<div :class="{ active: item.isChecked }">
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i>
</div>
<div
@click.stop="setUploadDelete(item, index)"
v-if="source != 'design'"
v-if="source != 'design' || (isDesignPage && videoType === 3)"
>
<i class="fi fi-rr-trash icon_delete"></i>
</div>
</div>
</div>
<div
class="upload_item item"
v-show="
!isDesignPage && !(videoType === 3 && showFirstFrameList.length > 0)
"
>
<div class="upload_item item first_frames" v-show="showFirstFrameUpload">
<div
class="design-mask"
v-if="isDesignPage && videoType === 3"
@click="handleClickDesignMask('first')"
/>
<div class="upload_file_item">
<a-upload
key="common"
@@ -93,9 +96,14 @@
</div>
<template v-if="videoType === 3">
<div
v-show="lastFrameList?.length < 1"
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">
<a-upload
key="lastframes"
@@ -142,12 +150,15 @@
v-show="item.designOutfitUrl || item.imgUrl || item.url"
class="btnBox"
>
<div :class="{ active: item.isChecked }">
<div
:class="{ active: item.isChecked }"
v-if="!(isDesignPage && videoType === 3)"
>
<i class="fi fi-br-check"></i>
</div>
<div
@click.stop="setUploadDelete(item, index, true)"
v-if="source != 'design'"
v-if="source != 'design' || (isDesignPage && videoType === 3)"
>
<i class="fi fi-rr-trash icon_delete"></i>
</div>
@@ -158,7 +169,7 @@
</div>
<div class="prompt-input-container" v-show="!showMotion">
<div class="title">{{ $t('ProductImg.Prompt') }}</div>
<promptInput :content="prompt" ref="promptInput" />
<promptInput :content="inputPrompt" ref="promptInput" />
</div>
<div class="poses" v-show="showMotion">
<div class="head">
@@ -180,19 +191,10 @@
<div class="control-container">
<div class="icon-list">
<SvgIcon
v-show="!isVideoPlaying(item.id)"
class="play-icon"
@click.stop="handlePlayMotion(item)"
name="CPlay"
size="10"
color="#fff"
/>
<SvgIcon
v-show="isVideoPlaying(item.id)"
class="play-icon pause"
@click.stop="handlePlayMotion(item)"
name="CPause"
size="10"
:name="isVideoPlaying(item.id) ? 'CPause' : 'CPlay'"
size="20"
color="#fff"
/>
</div>
@@ -213,6 +215,7 @@
@click="getgenerate()"
></i>
<div
v-show="showMotion"
class="icon iconfont icon-xiala"
:class="{ active: speedState }"
@click.stop="openSpeed"
@@ -277,6 +280,13 @@
</div>
</div>
<Tips v-model:showModal="showTips" />
<Product
v-model:showModal="showProductList"
:frameList="productFrameList"
:type="productType"
:key="productType"
@confirm="handleConfirmProduct"
/>
</div>
</template>
<script lang="ts">
@@ -304,13 +314,15 @@ import showViewVideo from '@/tool/mount'
import router from '@/router'
import promptInput from './promptInput.vue'
import Tips from './Tips.vue'
import Product from './Product.vue'
import { getFirstFrame, getFirstAndLastFrame } from './prompt'
export default defineComponent({
components: {
generalDrag,
promptInput,
Tips
Tips,
Product
},
props: {
isDesignPage: {
@@ -360,15 +372,7 @@ export default defineComponent({
removeGenerate: false,
generateTime: null as any,
poseList: [],
selectPose: null as any,
prompt: computed(() => {
if (videoType.value === 2) {
return getFirstFrame(t)
}
if (videoType.value === 3) {
return getFirstAndLastFrame(t)
}
})
selectPose: null as any
})
let speed = reactive({
speedList: [
@@ -515,7 +519,6 @@ export default defineComponent({
return
}
if (!data.selectImg.minioUrl) return message.info(t('poseTransfer.NeedFirstFrame'))
debugger
if (data.isGenerate) return
await new Promise((res, reject) => {
Modal.confirm({
@@ -546,6 +549,9 @@ export default defineComponent({
userLikeSortId: null, //是否进行排序
mode: videoType.value
}
if (!showMotion.value) {
value.modelName = ''
}
if (videoType.value === 2) {
const prompt = dataDom.promptInput.getFullText()
value.prompt = prompt
@@ -881,6 +887,61 @@ export default defineComponent({
document.removeEventListener('click', openSpeed)
}
const setUploadDelete = (item: any, index: any, isLastFrame: boolean = false) => {
// 在 isDesignPage 模式且 videoType === 3 时,直接从列表中删除,并将图片重新添加到产品列表中以便重新选择
if (props.isDesignPage && videoType.value === 3) {
if (isLastFrame) {
// 从 lastFrameList 中删除
const deleteIndex = lastFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
if (deleteIndex >= 0) {
lastFrameList.value.splice(deleteIndex, 1)
}
// 将删除的项重新添加到 lastFrameProductList 中,以便可以重新选择
const productItem = { ...item }
// 移除 frameType 和 isChecked 属性
delete productItem.frameType
delete productItem.isChecked
// 检查是否已存在,避免重复添加
const existingIndex = lastFrameProductList.value.findIndex(
(listItem: any) => listItem.id === productItem.id
)
if (existingIndex < 0) {
lastFrameProductList.value.unshift(productItem)
}
// 清空选中状态
if (data.lastSelectImg?.id === item.id) {
data.lastSelectImg = {}
}
} else {
// 从 firstFrameList 中删除
const deleteIndex = firstFrameList.value.findIndex(
(listItem: any) => listItem.id === item.id
)
if (deleteIndex >= 0) {
firstFrameList.value.splice(deleteIndex, 1)
}
// 将删除的项重新添加到 firstFrameProductList 中,以便可以重新选择
const productItem = { ...item }
// 移除 frameType 和 isChecked 属性
delete productItem.frameType
delete productItem.isChecked
// 检查是否已存在,避免重复添加
const existingIndex = firstFrameProductList.value.findIndex(
(listItem: any) => listItem.id === productItem.id
)
if (existingIndex < 0) {
firstFrameProductList.value.unshift(productItem)
}
// 清空选中状态
if (data.selectImg?.id === item.id) {
data.selectImg = {}
}
}
return
}
// 非 design 页面的原有逻辑
let value = {
id: item.id
}
@@ -911,6 +972,14 @@ export default defineComponent({
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') },
@@ -920,9 +989,148 @@ export default defineComponent({
const firstFrameList = ref([])
const lastFrameList = ref([])
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
})
const showProductList = ref(false)
const productType = ref('first')
// 首帧和尾帧的独立数据源
const firstFrameProductList = ref([])
const lastFrameProductList = ref([])
const showFirstFrameUpload = computed(() => {
// 只读依赖
const isDesign = props.isDesignPage
const isFirstAndLast = videoType.value === 3
const hasFirstFrames = Array.isArray(showFirstFrameList.value)
? showFirstFrameList.value.length > 0
: false
if (isDesign) {
// design 页面下,仅 videoType 为 3 时可展示上传,且没有首帧时显示上传按钮
return isFirstAndLast && !hasFirstFrames
}
// 非 design 页面下也是类似逻辑
return !(isFirstAndLast && hasFirstFrames)
})
const showLastFrameUpload = computed(() => {
if (videoType.value !== 3) return false
if (props.isDesignPage && lastFrameList.length > 0) return false
return true
})
// 根据 productType 返回对应的数据源
const productFrameList = computed(() => {
if (productType.value === 'first') {
return firstFrameProductList.value.length > 0
? firstFrameProductList.value
: data.fileList
} else {
return lastFrameProductList.value.length > 0
? lastFrameProductList.value
: data.fileList
}
})
const handleClickDesignMask = (type: 'first' | 'last') => {
productType.value = type
// 初始化独立的数据源,如果为空则从 fileList 复制
if (type === 'first') {
if (firstFrameProductList.value.length === 0) {
firstFrameProductList.value = JSON.parse(JSON.stringify(data.fileList || []))
}
} else {
if (lastFrameProductList.value.length === 0) {
lastFrameProductList.value = JSON.parse(JSON.stringify(data.fileList || []))
}
}
showProductList.value = true
}
const handleConfirmProduct = ({ data: productData }) => {
if (productType.value === 'first') {
// 准备图片数据
const imageItem: any = {
...productData,
isChecked: true,
type: 'ProductElement'
}
// 设置 minioUrl
imageItem.minioUrl =
productData.miniourl || getMinioUrl(imageItem.url || imageItem.imgUrl)
// 更新首帧产品列表,移除已选择的项
const productIndex = firstFrameProductList.value.findIndex(
(item: any) => item.id === productData.id
)
if (productIndex >= 0) {
firstFrameProductList.value.splice(productIndex, 1)
}
// 首尾帧模式,添加到 firstFrameList
// 先取消其他项的选中状态
firstFrameList.value.forEach((listItem: any) => {
listItem.isChecked = false
})
// 检查是否已存在,如果存在则更新,否则添加
const existingIndex = firstFrameList.value.findIndex(
(item: any) => item.id === productData.id
)
if (existingIndex >= 0) {
Object.assign(firstFrameList.value[existingIndex], imageItem)
} else {
imageItem.frameType = 'first'
firstFrameList.value.push(imageItem)
}
// 赋值给首帧选中对象
data.selectImg = { ...imageItem }
} else if (productType.value === 'last') {
// 准备图片数据
const imageItem: any = {
...productData,
isChecked: true,
type: 'ProductElement',
frameType: 'last'
}
// 设置 minioUrl
imageItem.minioUrl =
productData.miniourl || getMinioUrl(imageItem.url || imageItem.imgUrl)
// 更新尾帧产品列表,移除已选择的项
const productIndex = lastFrameProductList.value.findIndex(
(item: any) => item.id === productData.id
)
if (productIndex >= 0) {
lastFrameProductList.value.splice(productIndex, 1)
}
// 先取消其他项的选中状态
lastFrameList.value.forEach((listItem: any) => {
listItem.isChecked = false
})
// 检查是否已存在,如果存在则更新,否则添加
const existingIndex = lastFrameList.value.findIndex(
(item: any) => item.id === productData.id
)
if (existingIndex >= 0) {
Object.assign(lastFrameList.value[existingIndex], imageItem)
} else {
lastFrameList.value.push(imageItem)
}
// 赋值给尾帧选中对象
data.lastSelectImg = { ...imageItem }
}
showProductList.value = false
}
watch(
() => store.state.HomeStoreModule.uploadElement.length,
(newVal, oldVal) => {
@@ -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(
() => store.state.HomeStoreModule.lastFrameList,
val => {
@@ -1039,7 +1261,15 @@ export default defineComponent({
setVideoRef,
handlePlayMotion,
isVideoPlaying,
showTips
showTips,
showProductList,
handleClickDesignMask,
productType,
handleConfirmProduct,
productFrameList,
showFirstFrameUpload,
showLastFrameUpload,
inputPrompt
}
},
directives: {
@@ -1239,6 +1469,15 @@ export default defineComponent({
}
> .upload_item {
border: none;
position: relative;
.design-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
}
.control-container {
width: 100%;
@@ -1252,26 +1491,27 @@ export default defineComponent({
rgba(8, 9, 13, 0.27) 80.37%
);
display: flex;
align-items: flex-end;
justify-content: center;
align-items: center;
justify-content: flex-start;
.icon-list {
height: 50%;
width: calc(100% - 1.6rem);
// height: 50%;
// width: calc(100% - 1.6rem);
// border-top: 1px solid #fff;
padding-left: 1rem;
display: flex;
box-sizing: border-box;
justify-content: flex-start;
align-items: center;
.play-icon {
width: initial;
height: initial;
width: 2rem;
height: 2rem;
}
}
}
}
> .head {
color: #000;
font-weight: 600;
font-weight: 500;
display: flex;
align-items: center;
column-gap: 1rem;
@@ -1292,6 +1532,7 @@ export default defineComponent({
font-size: 1.8rem;
color: #000;
margin-bottom: 1.4rem;
font-weight: 500;
}
> .poses {
margin-top: 3rem;
@@ -1393,6 +1634,7 @@ export default defineComponent({
}
.video-type-container {
margin-bottom: 4rem;
font-weight: 500;
.title {
font-size: 1.8rem;
color: #000;

File diff suppressed because it is too large Load Diff

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

View File

@@ -283,9 +283,37 @@ export default {
Prompt: '提示词',
PromptAssit: '提示词助手',
AssitSubTitle: '您可以复制并使用以下提示词:',
Edited: '以下是编辑后的图像中需要注意的事项。',
CopySuccess: '已复制到剪贴板',
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: {
SelectDesign: '产品图',
@@ -295,8 +323,8 @@ export default {
GeneratedVideo: '生成的视频',
hint: '将这张图像转化为一张逼真、达到工作室水准的照片。在生活中真实存在的人',
jsContent1: '生成视频预计需要三分钟,请问是否继续',
NeedFirstFrame:'请选择首帧图片',
NeedLastFrame:'请选择尾帧图片',
NeedFirstFrame: '请选择首帧图片',
NeedLastFrame: '请选择尾帧图片',
SingleGarment:
'专业产品摄影:服装展示于隐形模特上,模特不可见。完整保留设计细节——所有图案、颜色、质地及细节特征。', // 单品样衣
SingleChildTryOn:
@@ -308,18 +336,18 @@ export default {
SeriesChildTryOn:
'将此图像转化为逼真的真实儿童模特,保持站立姿势并直面镜头,保留服装的原有图案、风格和色彩,呈现工作室级别的照片质量。', // 系列儿童试穿
UploadWithModel:
'创建模特穿着该服装的真实感摄影棚照片。若原有模特存在则保留,否则生成合适模特。采用站立姿势面向镜头。精确保留服装细节——图案、颜色、质地及装饰元素。', // 上传线稿,带模特
'生成真实模特穿着该服装的真摄影棚照片采用站立姿势面向镜头。保留服装所有细节——包括图案、颜色、纹理、装饰元素呈现摄影棚级别的8K分辨率照片支持HDR效果、景深控制、柔光效果及高细节呈现。请勿返还原始图像。', // 上传线稿,带模特
UploadWithoutModel:
'专业产品摄影:服装以自然形态展示,无模特。采用影棚灯光。保留精确细节——图案、色彩、质感、装饰元素。', // 上传线稿,不带模特
VideoType: '视频类型',
FirstFrame: '首帧',
LastFrame:'尾帧',
LastFrame: '尾帧',
FirstAndLastFrames: '首帧和尾帧',
FirstFrameAndSkeleton: '首帧和骨架',
Tips: '提示',
TipsStart: '你可以使用"',
TipsEnd: '编辑产品图"来生成首帧或尾帧图片',
PormptPlaceholder:'输入你想描述的场景...',
PormptPlaceholder: '输入你想描述的场景...',
firstAndLastFrameText1: '随着视频的进行,使用',
firstAndLastFramePlaceholder1: '[相机运动]',
firstAndLastFrameText2: '来跟随动作,在',

View File

@@ -293,9 +293,37 @@ export default {
Prompt: 'Prompt',
PromptAssit: 'Prompt Assist',
AssitSubTitle: 'You can copy following prompt and try:',
Edited: 'Heres what to notice in the edited image.',
CopySuccess: 'Copied to clipboard',
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: {
SelectDesign: 'Product image',
@@ -318,7 +346,7 @@ export default {
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.', // 系列儿童
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:
'Professional product photo: garment displayed with natural shape, no model. Studio lighting. Preserve exact details - patterns, colors, textures, embellishments.', // 上传线稿,不带模特
VideoType: 'Video Type',