feat: 提示词助手弹窗

This commit is contained in:
zhangyh
2025-11-11 14:20:04 +08:00
parent f67af2c2a8
commit cb5b4f9b65
11 changed files with 285 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="m16,18.5v1c0,2.481-2.019,4.5-4.5,4.5h-7c-2.481,0-4.5-2.019-4.5-4.5v-7c0-2.481,2.019-4.5,4.5-4.5h1c.276,0,.5.224.5.5s-.224.5-.5.5h-1c-1.93,0-3.5,1.57-3.5,3.5v7c0,1.93,1.57,3.5,3.5,3.5h7c1.93,0,3.5-1.57,3.5-3.5v-1c0-.276.224-.5.5-.5s.5.224.5.5Zm8-14v7c0,2.481-2.019,4.5-4.5,4.5h-7c-2.481,0-4.5-2.019-4.5-4.5v-7c0-2.481,2.019-4.5,4.5-4.5h7c2.481,0,4.5,2.019,4.5,4.5Zm-1,0c0-1.93-1.57-3.5-3.5-3.5h-7c-1.93,0-3.5,1.57-3.5,3.5v7c0,1.93,1.57,3.5,3.5,3.5h7c1.93,0,3.5-1.57,3.5-3.5v-7Z"/></svg>

After

Width:  |  Height:  |  Size: 652 B

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" data-name="Layer 1" viewBox="0 0 24 24" width="512" height="512"><path d="M23.5,24H.5c-.28,0-.5-.22-.5-.5s.22-.5,.5-.5H23.5c.28,0,.5,.22,.5,.5s-.22,.5-.5,.5Zm-8.83-4.09l6.56-7.35c.77-.77,.98-1.88,.57-2.88-.42-1.01-1.36-1.65-2.45-1.65h-2.34V3.47c0-1.92-1.57-3.47-3.51-3.47h-2.99c-1.93,0-3.51,1.56-3.51,3.47v4.55s-2.33,0-2.33,0c-1.1,0-2.04,.64-2.45,1.65s-.19,2.12,.56,2.88l6.59,7.38c.73,.73,1.68,1.09,2.64,1.09s1.93-.37,2.67-1.11ZM7.5,9.03c.13,0,.26-.05,.35-.15s.15-.22,.15-.35V3.47c0-1.36,1.12-2.47,2.5-2.47h2.99c1.38,0,2.51,1.11,2.51,2.47v5.05c0,.28,.22,.5,.5,.5h2.84c.82,0,1.33,.54,1.53,1.03,.2,.5,.21,1.23-.36,1.81,0,0-.01,.01-.02,.02l-6.55,7.34c-1.07,1.07-2.81,1.07-3.86,.02L3.49,11.86c-.58-.58-.57-1.31-.36-1.81,.2-.5,.71-1.03,1.53-1.03h2.84Z"/></svg>

After

Width:  |  Height:  |  Size: 848 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -0,0 +1,261 @@
<template>
<div ref="modalContainer"></div>
<a-modal
class="prompt-modal generalModel"
v-model:visible="showModal"
:footer="null"
:get-container="() => $refs.modalContainer"
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">{{ $t('ProductImg.AssitSubTitle') }}</div>
</div>
<div class="example-content">
<div
class="example-wrapper"
v-for="item in Object.keys(exampleList)"
:key="item"
:class="item"
>
<div class="example-item" v-for="value in exampleList[item]" :key="value.title">
<img :src="value.src" />
<SvgIcon
v-if="value.isOriginal"
class="download-icon"
name="CDownload"
size="20"
color="#8E8E8E"
@click.stop="handleDownload(value)"
/>
</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>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, useTemplateRef } from 'vue'
import { message } from 'ant-design-vue'
import originalDress from '@/assets/images/product/original_dress.png'
import generatedDress from '@/assets/images/product/geneated_dress.png'
import originalModel from '@/assets/images/product/original_model.png'
import generatedModel from '@/assets/images/product/generated_model.png'
import generatedSketch from '@/assets/images/product/generated_sketch.png'
import { downloadIamge } from '@/tool/util'
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
defineProps<{
promptList: string[]
}>()
const modalContainer = useTemplateRef('modalContainer')
const showModal = defineModel<boolean>('showModal', { required: true })
const garmentExample = [
{
title: 'originalDress',
src: originalDress,
isOriginal: true
},
{
title: 'generatedDress',
src: generatedDress
}
]
const sketchExample = [
{
title: 'origianlDress',
src: originalDress,
isOriginal: true
},
{
title: 'generatedSketch',
src: generatedSketch
}
]
const modelExample = [
{
title: 'originalModel',
src: originalModel,
isOriginal: true
},
{
title: 'generatedModel',
src: generatedModel
}
]
const exampleList = {
garmentExample,
sketchExample,
modelExample
}
const handleClose = () => {
showModal.value = false
}
const handleDownload = value => {
const { title, src } = value
downloadIamge(src, title)
}
const handleCopy = async (value: string) => {
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'))
}
}
</script>
<style lang="less" scoped>
.prompt-modal {
.title-container {
.title {
font-size: 3.5rem;
color: #181818;
}
.sub-title {
font-size: 2rem;
font-weight: 400;
color: #000;
}
}
.example-content {
margin-top: 4.3rem;
padding-left: 7.4rem;
display: flex;
.example-wrapper {
display: flex;
gap: 0;
&.garmentExample {
margin-right: 9.6rem;
}
&.sketchExample {
margin-right: 1.6rem;
}
.example-item {
width: 20.3rem;
height: 38.4rem;
margin-left: -2px;
position: relative;
img {
width: 100%;
display: block;
}
.download-icon {
position: absolute;
top: 0.77rem;
right: 0.84rem;
cursor: pointer;
}
}
}
}
.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;
}
}
}
}
:deep(.generalModel .ant-modal-body) {
padding: 5rem 4.4rem 8rem 4.6rem;
}
.c-svg {
width: initial;
height: initial;
}
</style>

View File

@@ -150,7 +150,7 @@
/> />
</div> </div>
<div class="prompt-container"> <div class="prompt-container">
<div class="prompt-title">Prompt</div> <div class="prompt-title">{{ $t('ProductImg.Prompt') }}</div>
<div class="input_border productImg_content_item_generate"> <div class="input_border productImg_content_item_generate">
<div class="input_box"> <div class="input_box">
<div class="input_box_btnBox"> <div class="input_box_btnBox">
@@ -171,30 +171,8 @@
</div> </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>Prompt Assist</span> <span>{{ $t('ProductImg.PromptAssit') }}</span>
</div> </div>
<!-- <div
class="selectText"
v-show="productimgMenu.value == 'ToProductImage' && speedData.value"
>
<a-tooltip
v-for="(promptText, index) in promptTextList"
:key="index"
placement="bottom"
>
<template #title>{{ promptText }}</template>
<div
@click="
() => {
searchName[productimgMenu.value] = promptText
ifMaximumLength()
}
"
>
{{ promptText }}
</div>
</a-tooltip>
</div> -->
</div> </div>
<div class="productImg_content_item_generate_btn input_border"> <div class="productImg_content_item_generate_btn input_border">
<div class="generage_btn_box"> <div class="generage_btn_box">
@@ -342,8 +320,8 @@
}" }"
:isProductimg="true" :isProductimg="true"
></scaleImage> ></scaleImage>
<Prompt v-model:showModal="showPromptAssist" :promptList="promptTextList" />
</div> </div>
<Prompt />
</template> </template>
<script lang="ts"> <script lang="ts">
@@ -374,6 +352,7 @@ import { useStore } from 'vuex'
import scaleImage from '@/component/HomePage/scaleImage.vue' 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 { List } from 'echarts' import { List } from 'echarts'
import { useRouter, useRoute } from 'vue-router' import { useRouter, useRoute } from 'vue-router'
@@ -382,6 +361,7 @@ export default defineComponent({
scaleImage, scaleImage,
generalMenu, generalMenu,
generalDrag, generalDrag,
Prompt
}, },
props: { props: {
setTask: { setTask: {
@@ -1122,7 +1102,6 @@ export default defineComponent({
}) })
const showPromptAssist = ref(false) const showPromptAssist = ref(false)
const showPrompt = ref(false)
const handleClickAssistBtn = () => { const handleClickAssistBtn = () => {
showPromptAssist.value = true showPromptAssist.value = true
} }
@@ -1139,7 +1118,6 @@ export default defineComponent({
RelightDirection, RelightDirection,
promptTextList, promptTextList,
showPromptAssist, showPromptAssist,
showPrompt,
setproduct, setproduct,
fileUploadChange, fileUploadChange,
@@ -1330,7 +1308,7 @@ export default defineComponent({
} }
> .head { > .head {
color: #000; color: #000;
font-weight: 600; // font-weight: 600;
margin-bottom: 2rem; margin-bottom: 2rem;
> .text { > .text {
display: inline-block; display: inline-block;
@@ -1544,18 +1522,7 @@ export default defineComponent({
.designPage { .designPage {
margin-right: 4rem; margin-right: 4rem;
} }
.upload_file_item {
// height: 13.4rem;
// margin-top: 2rem;
:deep(.ant-upload-picture-card-wrapper) {
.ant-upload-list-picture-card {
.ant-upload-select-picture-card {
width: 9.6rem;
height: 13.4rem;
}
}
}
}
.prompt-container { .prompt-container {
margin-top: 4rem; margin-top: 4rem;

View File

@@ -279,7 +279,12 @@ export default {
Clear: '清空', Clear: '清空',
jsContent1: '如果您离开此页,您的更改将会丢失。您确定要离开这一页吗?', jsContent1: '如果您离开此页,您的更改将会丢失。您确定要离开这一页吗?',
jsContent2: '请至少选择一张图片', jsContent2: '请至少选择一张图片',
jsContent3: '您有一张图生成失败,请重试。' jsContent3: '您有一张图生成失败,请重试。',
Prompt: '提示词',
PromptAssit: '提示词助手',
AssitSubTitle: '您可以复制并使用以下提示词:',
CopySuccess: '已复制到剪贴板',
CopyFiled: '复制失败'
}, },
poseTransfer: { poseTransfer: {
SelectDesign: '产品图', SelectDesign: '产品图',

View File

@@ -289,7 +289,12 @@ export default {
jsContent1: jsContent1:
'Your changes will be lost if you navigate away from this page. Are you sure you want to leave this page?', 'Your changes will be lost if you navigate away from this page. Are you sure you want to leave this page?',
jsContent2: 'Please select at least one picture', jsContent2: 'Please select at least one picture',
jsContent3: 'One of your images failed to generate. Please try again.' jsContent3: 'One of your images failed to generate. Please try again.',
Prompt: 'Prompt',
PromptAssit: 'Prompt Assit',
AssitSubTitle: 'You can copy following prompt and try:',
CopySuccess: 'Copied to clipboard',
CopyFiled: 'Failed to copy'
}, },
poseTransfer: { poseTransfer: {
SelectDesign: 'Product image', SelectDesign: 'Product image',
@@ -312,7 +317,7 @@ export default {
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.', // 上传线稿,带模特 '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.', // 上传线稿,带模特
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.' // 上传线稿,不带模特
}, },
LibraryPage: { LibraryPage: {
library: 'Library', library: 'Library',