relight提示词相关内容

This commit is contained in:
X1627315083
2025-11-19 17:15:19 +08:00
parent f0702702a5
commit 3b72e61a68
22 changed files with 582 additions and 25 deletions

View File

@@ -308,6 +308,20 @@ 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
@@ -338,6 +352,11 @@ const handleInputPaste = (event: ClipboardEvent, index: number) => {
const handleInputKeydown = (event: KeyboardEvent, index: number) => {
const target = event.target as HTMLSpanElement
// 如果是中文输入过程中,不处理普通按键
if (compositionState.isComposing) {
return
}
if (event.key === 'Backspace') {
// 如果显示placeholder阻止删除
if (target.classList.contains('has-placeholder')) {
@@ -357,7 +376,7 @@ const handleInputKeydown = (event: KeyboardEvent, index: number) => {
}
}
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey) {
// 普通字符输入
// 普通字符输入 - 只有在非中文输入状态下才处理
if (target.classList.contains('has-placeholder')) {
event.preventDefault()
target.textContent = event.key
@@ -407,23 +426,13 @@ const handlePasteInInput = (index: number, element: HTMLElement) => {
}
}
// 添加中文输入事件处理
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 handleCompositionEnd = (index: number, event: CompositionEvent) => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
// 延迟设置 isComposing 为 false确保所有相关事件都处理完毕
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 0)
const target = event.target as HTMLSpanElement
const newValue = target.textContent || ''
@@ -432,14 +441,28 @@ const handleCompositionEnd = (index: number, event: CompositionEvent) => {
// 如果中文输入后内容为空显示placeholder
const item = data.content[index]
if (newValue.trim() === '' && item.placeholder) {
target.classList.add('has-placeholder')
target.textContent = 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) => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
// 延迟重置状态,避免与 compositionend 冲突
setTimeout(() => {
compositionState.isComposing = false
compositionState.currentInputIndex = -1
}, 100)
updateInputDisplay(index)
}
@@ -528,6 +551,7 @@ defineExpose({
<span>{{ $t('ProductImg.PromptAssit') }}</span>
</div>
</div>
<!-- {{ data.content }} -->
<div
v-show="assistModel"
ref="editableArea"
@@ -547,11 +571,12 @@ defineExpose({
</span>
<span v-else class="input-field" :data-index="index">
<span
<span
class="input-content"
contenteditable="plaintext-only"
@input="e => handleInputChange(index, e)"
@keydown="e => handleInputKeydown(e, index)"
@focus="() => handleInputFocus(index)"
@blur="() => handleInputBlur(index)"
@paste="e => handleInputPaste(e, index)"
@compositionstart="e => handleCompositionStart(index, e)"

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: {