chore: 代码格式化统一(空格、换行、属性排序、LF规范化)
This commit is contained in:
parent
b964c826ce
commit
e98ff3a2c4
@ -7,7 +7,8 @@
|
|||||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/stores/display.js)",
|
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/stores/display.js)",
|
||||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/components/dialogBox/index.vue)",
|
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --oneline --all -- src/components/dialogBox/index.vue)",
|
||||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline --follow -p -- src/stores/display.js)",
|
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline --follow -p -- src/stores/display.js)",
|
||||||
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline -p -- src/components/dialogBox/index.vue)"
|
"Bash(git -C \"D:/WebUI/Kexue/操作平台/AI_Painting_V2.0\" log --all --oneline -p -- src/components/dialogBox/index.vue)",
|
||||||
|
"Bash(npx eslint *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,5 +43,5 @@ export const getUserInfo = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const checkUsertoken = () => {
|
export const checkUsertoken = () => {
|
||||||
return service.post(`/login/validateToken`)
|
return service.post('/login/validateToken')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="custom-popover" ref="popoverRef">
|
<div ref="popoverRef" class="custom-popover">
|
||||||
<div class="popover-trigger" ref="triggerRef" @click.stop="togglePopover">
|
<div ref="triggerRef" class="popover-trigger" @click.stop="togglePopover">
|
||||||
<slot name="reference" />
|
<slot name="reference" />
|
||||||
</div>
|
</div>
|
||||||
<Teleport to="body">
|
<Teleport to="body">
|
||||||
@ -143,10 +143,10 @@ const handleClickOutside = (e) => {
|
|||||||
const contentEl = contentRef.value
|
const contentEl = contentRef.value
|
||||||
|
|
||||||
if (
|
if (
|
||||||
triggerEl &&
|
triggerEl
|
||||||
!triggerEl.contains(e.target) &&
|
&& !triggerEl.contains(e.target)
|
||||||
contentEl &&
|
&& contentEl
|
||||||
!contentEl.contains(e.target)
|
&& !contentEl.contains(e.target)
|
||||||
) {
|
) {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
window.__currentOpenPopoverId__ = null
|
window.__currentOpenPopoverId__ = null
|
||||||
|
|||||||
@ -377,7 +377,7 @@ onBeforeUnmount(() => {
|
|||||||
|
|
||||||
.option-group {
|
.option-group {
|
||||||
/* margin-bottom: 10px; */
|
/* margin-bottom: 10px; */
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="close-btn" @click="handleClose">
|
<div class="close-btn" @click="handleClose">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 12 13" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 12 13" fill="none">
|
||||||
<path d="M1.28809 1.01469L10.9873 11.0052" stroke="#666666" stroke-width="2" stroke-linecap="round"/>
|
<path d="M1.28809 1.01469L10.9873 11.0052" stroke="#666666" stroke-width="2" stroke-linecap="round" />
|
||||||
<path d="M10.6846 1.02413L0.985354 11.0147" stroke="#666666" stroke-width="2" stroke-linecap="round"/>
|
<path d="M10.6846 1.02413L0.985354 11.0147" stroke="#666666" stroke-width="2" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -32,8 +32,8 @@
|
|||||||
ref="editableDivRef"
|
ref="editableDivRef"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
class="custom-textarea"
|
class="custom-textarea"
|
||||||
@input="handleInput"
|
|
||||||
:data-placeholder="!inputText ? '请输入提示词或使用圆形/矩形工具' : ''"
|
:data-placeholder="!inputText ? '请输入提示词或使用圆形/矩形工具' : ''"
|
||||||
|
@input="handleInput"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
@click="currentShape = 'rectangle'"
|
@click="currentShape = 'rectangle'"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||||
<rect x="1.84961" y="1.84998" width="14.3" height="14.3" rx="1.5" :stroke="currentShape === 'rectangle' ? '#000F33' : '#888888'"/>
|
<rect x="1.84961" y="1.84998" width="14.3" height="14.3" rx="1.5" :stroke="currentShape === 'rectangle' ? '#000F33' : '#888888'" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -59,7 +59,7 @@
|
|||||||
@click="currentShape = 'circle'"
|
@click="currentShape = 'circle'"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||||
<circle cx="9.00039" cy="9.00002" r="7.6" :stroke="currentShape === 'circle' ? '#000F33' : '#888888'"/>
|
<circle cx="9.00039" cy="9.00002" r="7.6" :stroke="currentShape === 'circle' ? '#000F33' : '#888888'" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -68,22 +68,22 @@
|
|||||||
<!-- 上一步 -->
|
<!-- 上一步 -->
|
||||||
<div class="shape-btn" @click="undo">
|
<div class="shape-btn" @click="undo">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||||
<path d="M1.64645 3.64645C1.45118 3.84171 1.45118 4.15829 1.64645 4.35355L4.82843 7.53553C5.02369 7.7308 5.34027 7.7308 5.53553 7.53553C5.7308 7.34027 5.7308 7.02369 5.53553 6.82843L2.70711 4L5.53553 1.17157C5.7308 0.976311 5.7308 0.659728 5.53553 0.464466C5.34027 0.269204 5.02369 0.269204 4.82843 0.464466L1.64645 3.64645ZM2 14.5C1.72386 14.5 1.5 14.7239 1.5 15C1.5 15.2761 1.72386 15.5 2 15.5V15V14.5ZM2 4V4.5H10.5V4V3.5H2V4ZM10.5 15V14.5H2V15V15.5H10.5V15ZM16 9.5H15.5C15.5 12.2614 13.2614 14.5 10.5 14.5V15V15.5C13.8137 15.5 16.5 12.8137 16.5 9.5H16ZM16 9.5H16.5C16.5 6.18629 13.8137 3.5 10.5 3.5V4V4.5C13.2614 4.5 15.5 6.73858 15.5 9.5H16Z" fill="#000F33"/>
|
<path d="M1.64645 3.64645C1.45118 3.84171 1.45118 4.15829 1.64645 4.35355L4.82843 7.53553C5.02369 7.7308 5.34027 7.7308 5.53553 7.53553C5.7308 7.34027 5.7308 7.02369 5.53553 6.82843L2.70711 4L5.53553 1.17157C5.7308 0.976311 5.7308 0.659728 5.53553 0.464466C5.34027 0.269204 5.02369 0.269204 4.82843 0.464466L1.64645 3.64645ZM2 14.5C1.72386 14.5 1.5 14.7239 1.5 15C1.5 15.2761 1.72386 15.5 2 15.5V15V14.5ZM2 4V4.5H10.5V4V3.5H2V4ZM10.5 15V14.5H2V15V15.5H10.5V15ZM16 9.5H15.5C15.5 12.2614 13.2614 14.5 10.5 14.5V15V15.5C13.8137 15.5 16.5 12.8137 16.5 9.5H16ZM16 9.5H16.5C16.5 6.18629 13.8137 3.5 10.5 3.5V4V4.5C13.2614 4.5 15.5 6.73858 15.5 9.5H16Z" fill="#000F33" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 下一步 -->
|
<!-- 下一步 -->
|
||||||
<div class="shape-btn" @click="redo">
|
<div class="shape-btn" @click="redo">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||||
<path d="M16.3536 3.64645C16.5488 3.84171 16.5488 4.15829 16.3536 4.35355L13.1716 7.53553C12.9763 7.7308 12.6597 7.7308 12.4645 7.53553C12.2692 7.34027 12.2692 7.02369 12.4645 6.82843L15.2929 4L12.4645 1.17157C12.2692 0.976311 12.2692 0.659728 12.4645 0.464466C12.6597 0.269204 12.9763 0.269204 13.1716 0.464466L16.3536 3.64645ZM16 13.5C16.2761 13.5 16.5 13.7239 16.5 14C16.5 14.2761 16.2761 14.5 16 14.5V14V13.5ZM16 4V4.5H8V4V3.5H16V4ZM8 14V13.5H16V14V14.5H8V14ZM3 9H3.5C3.5 11.4853 5.51472 13.5 8 13.5V14V14.5C4.96243 14.5 2.5 12.0376 2.5 9H3ZM3 9H2.5C2.5 5.96243 4.96243 3.5 8 3.5V4V4.5C5.51472 4.5 3.5 6.51472 3.5 9H3Z" fill="#000F33"/>
|
<path d="M16.3536 3.64645C16.5488 3.84171 16.5488 4.15829 16.3536 4.35355L13.1716 7.53553C12.9763 7.7308 12.6597 7.7308 12.4645 7.53553C12.2692 7.34027 12.2692 7.02369 12.4645 6.82843L15.2929 4L12.4645 1.17157C12.2692 0.976311 12.2692 0.659728 12.4645 0.464466C12.6597 0.269204 12.9763 0.269204 13.1716 0.464466L16.3536 3.64645ZM16 13.5C16.2761 13.5 16.5 13.7239 16.5 14C16.5 14.2761 16.2761 14.5 16 14.5V14V13.5ZM16 4V4.5H8V4V3.5H16V4ZM8 14V13.5H16V14V14.5H8V14ZM3 9H3.5C3.5 11.4853 5.51472 13.5 8 13.5V14V14.5C4.96243 14.5 2.5 12.0376 2.5 9H3ZM3 9H2.5C2.5 5.96243 4.96243 3.5 8 3.5V4V4.5C5.51472 4.5 3.5 6.51472 3.5 9H3Z" fill="#000F33" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 删除 -->
|
<!-- 删除 -->
|
||||||
<div class="shape-btn" @click="deleteShape">
|
<div class="shape-btn" @click="deleteShape">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||||
<path d="M1.7998 3.60002H4.4998M16.1998 3.60002H13.4998M13.4998 3.60002V2.90003C13.4998 1.79546 12.6044 0.900024 11.4998 0.900024H6.4998C5.39523 0.900024 4.4998 1.79545 4.4998 2.90002V3.60002M13.4998 3.60002H4.4998" stroke="#000F33" stroke-linecap="round"/>
|
<path d="M1.7998 3.60002H4.4998M16.1998 3.60002H13.4998M13.4998 3.60002V2.90003C13.4998 1.79546 12.6044 0.900024 11.4998 0.900024H6.4998C5.39523 0.900024 4.4998 1.79545 4.4998 2.90002V3.60002M13.4998 3.60002H4.4998" stroke="#000F33" stroke-linecap="round" />
|
||||||
<path d="M3.59961 6.29999V14.2C3.59961 15.3046 4.49504 16.2 5.59961 16.2H12.3996C13.5042 16.2 14.3996 15.3046 14.3996 14.2V6.29999M7.19961 7.19999V14.4M10.7996 7.19999V14.4" stroke="#000F33" stroke-linecap="round"/>
|
<path d="M3.59961 6.29999V14.2C3.59961 15.3046 4.49504 16.2 5.59961 16.2H12.3996C13.5042 16.2 14.3996 15.3046 14.3996 14.2V6.29999M7.19961 7.19999V14.4M10.7996 7.19999V14.4" stroke="#000F33" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -103,8 +103,8 @@
|
|||||||
<span class="brush-panel-title">请输入替换内容描述</span>
|
<span class="brush-panel-title">请输入替换内容描述</span>
|
||||||
<div class="brush-panel-close" @click="closeBrushPanel">
|
<div class="brush-panel-close" @click="closeBrushPanel">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 12 13" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="13" viewBox="0 0 12 13" fill="none">
|
||||||
<path d="M1.28809 1.01468L10.9873 11.0052" stroke="#666666" stroke-width="2" stroke-linecap="round"/>
|
<path d="M1.28809 1.01468L10.9873 11.0052" stroke="#666666" stroke-width="2" stroke-linecap="round" />
|
||||||
<path d="M10.6846 1.02411L0.985354 11.0147" stroke="#666666" stroke-width="2" stroke-linecap="round"/>
|
<path d="M10.6846 1.02411L0.985354 11.0147" stroke="#666666" stroke-width="2" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -114,8 +114,8 @@
|
|||||||
ref="brushTextareaRef"
|
ref="brushTextareaRef"
|
||||||
contenteditable="true"
|
contenteditable="true"
|
||||||
class="brush-textarea"
|
class="brush-textarea"
|
||||||
@input="handleBrushInput"
|
|
||||||
:data-placeholder="!currentShapeDescription ? '请输入描述...' : ''"
|
:data-placeholder="!currentShapeDescription ? '请输入描述...' : ''"
|
||||||
|
@input="handleBrushInput"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -135,7 +135,7 @@
|
|||||||
<img :src="img" alt="参考图" />
|
<img :src="img" alt="参考图" />
|
||||||
<div class="reference-image-delete" @click.stop="removeReferenceImage(index)">
|
<div class="reference-image-delete" @click.stop="removeReferenceImage(index)">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="7" viewBox="0 0 7 7" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="7" height="7" viewBox="0 0 7 7" fill="none">
|
||||||
<path d="M0.5 0.5L6.5 6.5M6.5 0.5L0.5 6.5" stroke="white" stroke-linecap="round"/>
|
<path d="M0.5 0.5L6.5 6.5M6.5 0.5L0.5 6.5" stroke="white" stroke-linecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -145,7 +145,7 @@
|
|||||||
@click="handleUploadReference"
|
@click="handleUploadReference"
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15" fill="none">
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15" fill="none">
|
||||||
<path d="M7.49316 0C7.76931 3.42279e-07 7.99316 0.223857 7.99316 0.5V6.99316H14.5C14.7761 6.99316 15 7.21702 15 7.49316C14.9999 7.76925 14.7761 7.99316 14.5 7.99316H7.99316V14.5C7.99316 14.7761 7.76931 15 7.49316 15C7.21702 15 6.99316 14.7761 6.99316 14.5V7.99316H0.5C0.223898 7.99316 6.59601e-05 7.76925 0 7.49316C0 7.21702 0.223858 6.99316 0.5 6.99316H6.99316V0.5C6.99316 0.223857 7.21702 1.20706e-08 7.49316 0Z" fill="#000F33"/>
|
<path d="M7.49316 0C7.76931 3.42279e-07 7.99316 0.223857 7.99316 0.5V6.99316H14.5C14.7761 6.99316 15 7.21702 15 7.49316C14.9999 7.76925 14.7761 7.99316 14.5 7.99316H7.99316V14.5C7.99316 14.7761 7.76931 15 7.49316 15C7.21702 15 6.99316 14.7761 6.99316 14.5V7.99316H0.5C0.223898 7.99316 6.59601e-05 7.76925 0 7.49316C0 7.21702 0.223858 6.99316 0.5 6.99316H6.99316V0.5C6.99316 0.223857 7.21702 1.20706e-08 7.49316 0Z" fill="#000F33" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -160,10 +160,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { generate } from '@/utils/taskPolling'
|
|
||||||
import { useDisplayStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
import request from '@/utils/request'
|
|
||||||
import { getModelId } from '@/utils/modelApi'
|
import { getModelId } from '@/utils/modelApi'
|
||||||
|
import request from '@/utils/request'
|
||||||
|
import { generate } from '@/utils/taskPolling'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
visible: {
|
visible: {
|
||||||
@ -240,11 +240,11 @@ const updateCurrentEditingContent = (description) => {
|
|||||||
const colorIndex = currentEditingShapeIndex.value >= 0 ? currentEditingShapeIndex.value : 0
|
const colorIndex = currentEditingShapeIndex.value >= 0 ? currentEditingShapeIndex.value : 0
|
||||||
const colorName = colorNames[colorIndex % colorNames.length]
|
const colorName = colorNames[colorIndex % colorNames.length]
|
||||||
const isFirstShape = shapes.value.length === 0 || (currentEditingShapeIndex.value === 0 && shapes.value.length === 1)
|
const isFirstShape = shapes.value.length === 0 || (currentEditingShapeIndex.value === 0 && shapes.value.length === 1)
|
||||||
|
|
||||||
const currentShape = currentEditingShapeIndex.value >= 0 ? shapes.value[currentEditingShapeIndex.value] : null
|
const currentShape = currentEditingShapeIndex.value >= 0 ? shapes.value[currentEditingShapeIndex.value] : null
|
||||||
const shapeType = currentShape ? currentShape.type : 'rectangle'
|
const shapeType = currentShape ? currentShape.type : 'rectangle'
|
||||||
const shapeWord = shapeType === 'circle' ? '圈' : '框'
|
const shapeWord = shapeType === 'circle' ? '圈' : '框'
|
||||||
|
|
||||||
if (selectedReferenceImages.value.length > 0) {
|
if (selectedReferenceImages.value.length > 0) {
|
||||||
const imageIndex = allReferenceImages.value.indexOf(selectedReferenceImages.value[0]) + 2
|
const imageIndex = allReferenceImages.value.indexOf(selectedReferenceImages.value[0]) + 2
|
||||||
const prefix = isFirstShape ? '' : ','
|
const prefix = isFirstShape ? '' : ','
|
||||||
@ -268,7 +268,7 @@ watch(() => props.visible, (newVal) => {
|
|||||||
historyIndex.value = -1
|
historyIndex.value = -1
|
||||||
promptHistory.value = []
|
promptHistory.value = []
|
||||||
promptHistoryIndex.value = -1
|
promptHistoryIndex.value = -1
|
||||||
allReferenceImages.value = props.referenceImages.map(img => img.url || img)
|
allReferenceImages.value = props.referenceImages.map((img) => img.url || img)
|
||||||
brushPanelVisible.value = false
|
brushPanelVisible.value = false
|
||||||
isPanelOpen.value = false
|
isPanelOpen.value = false
|
||||||
currentShapeDescription.value = ''
|
currentShapeDescription.value = ''
|
||||||
@ -284,19 +284,19 @@ watch(() => props.visible, (newVal) => {
|
|||||||
const initCanvas = () => {
|
const initCanvas = () => {
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
|
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
const container = canvas.parentElement
|
const container = canvas.parentElement
|
||||||
const containerWidth = container.clientWidth
|
const containerWidth = container.clientWidth
|
||||||
const containerHeight = container.clientHeight
|
const containerHeight = container.clientHeight
|
||||||
|
|
||||||
if (currentImage.value) {
|
if (currentImage.value) {
|
||||||
let imageUrl = currentImage.value
|
let imageUrl = currentImage.value
|
||||||
|
|
||||||
if (!imageUrl.startsWith('data:')) {
|
if (!imageUrl.startsWith('data:')) {
|
||||||
imageUrl = imageUrl.replace('https://sxwz.xueai.art', 'https://talkingdraw.xueai.art')
|
imageUrl = imageUrl.replace('https://sxwz.xueai.art', 'https://talkingdraw.xueai.art')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageUrl.startsWith('data:')) {
|
if (imageUrl.startsWith('data:')) {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
@ -310,8 +310,8 @@ const initCanvas = () => {
|
|||||||
img.src = imageUrl
|
img.src = imageUrl
|
||||||
} else {
|
} else {
|
||||||
fetch(imageUrl)
|
fetch(imageUrl)
|
||||||
.then(res => res.blob())
|
.then((res) => res.blob())
|
||||||
.then(blob => {
|
.then((blob) => {
|
||||||
const img = new Image()
|
const img = new Image()
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
const imgScale = Math.min(containerWidth / img.width, containerHeight / img.height)
|
const imgScale = Math.min(containerWidth / img.width, containerHeight / img.height)
|
||||||
@ -363,7 +363,7 @@ const handleMouseDown = (e) => {
|
|||||||
|
|
||||||
const handleMouseMove = (e) => {
|
const handleMouseMove = (e) => {
|
||||||
if (!isDrawing.value) return
|
if (!isDrawing.value) return
|
||||||
|
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
const ratio = canvas.width / canvas.offsetWidth
|
const ratio = canvas.width / canvas.offsetWidth
|
||||||
@ -373,15 +373,15 @@ const handleMouseMove = (e) => {
|
|||||||
const savedStartY = startY.value
|
const savedStartY = startY.value
|
||||||
const savedCurrentX = currentX
|
const savedCurrentX = currentX
|
||||||
const savedCurrentY = currentY
|
const savedCurrentY = currentY
|
||||||
|
|
||||||
if (bgImage.value) {
|
if (bgImage.value) {
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
ctx.drawImage(bgImage.value, 0, 0)
|
ctx.drawImage(bgImage.value, 0, 0)
|
||||||
|
|
||||||
shapes.value.forEach(shape => {
|
shapes.value.forEach((shape) => {
|
||||||
drawShape(ctx, shape)
|
drawShape(ctx, shape)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentShapeData = {
|
const currentShapeData = {
|
||||||
type: currentShape.value,
|
type: currentShape.value,
|
||||||
startX: savedStartX,
|
startX: savedStartX,
|
||||||
@ -396,10 +396,10 @@ const handleMouseMove = (e) => {
|
|||||||
ctx.fillStyle = '#fff'
|
ctx.fillStyle = '#fff'
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
shapes.value.forEach(shape => {
|
shapes.value.forEach((shape) => {
|
||||||
drawShape(ctx, shape)
|
drawShape(ctx, shape)
|
||||||
})
|
})
|
||||||
|
|
||||||
const currentShapeData = {
|
const currentShapeData = {
|
||||||
type: currentShape.value,
|
type: currentShape.value,
|
||||||
startX: startX.value,
|
startX: startX.value,
|
||||||
@ -414,34 +414,34 @@ const handleMouseMove = (e) => {
|
|||||||
|
|
||||||
const handleMouseUp = (e) => {
|
const handleMouseUp = (e) => {
|
||||||
if (!isDrawing.value) return
|
if (!isDrawing.value) return
|
||||||
|
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
const ratio = canvas.width / canvas.offsetWidth
|
const ratio = canvas.width / canvas.offsetWidth
|
||||||
const endX = e.offsetX * ratio
|
const endX = e.offsetX * ratio
|
||||||
const endY = e.offsetY * ratio
|
const endY = e.offsetY * ratio
|
||||||
|
|
||||||
if (shapes.value.length >= maxShapes) {
|
if (shapes.value.length >= maxShapes) {
|
||||||
ElMessage.warning('最多只能画5笔')
|
ElMessage.warning('最多只能画5笔')
|
||||||
isDrawing.value = false
|
isDrawing.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorIndex = shapes.value.length
|
const colorIndex = shapes.value.length
|
||||||
const newShape = {
|
const newShape = {
|
||||||
type: currentShape.value,
|
type: currentShape.value,
|
||||||
startX: startX.value,
|
startX: startX.value,
|
||||||
startY: startY.value,
|
startY: startY.value,
|
||||||
endX: endX,
|
endX,
|
||||||
endY: endY,
|
endY,
|
||||||
color: shapeColors[colorIndex],
|
color: shapeColors[colorIndex],
|
||||||
description: '',
|
description: '',
|
||||||
referenceImages: []
|
referenceImages: []
|
||||||
}
|
}
|
||||||
shapes.value.push(newShape)
|
shapes.value.push(newShape)
|
||||||
currentEditingShapeIndex.value = shapes.value.length - 1
|
currentEditingShapeIndex.value = shapes.value.length - 1
|
||||||
|
|
||||||
isDrawing.value = false
|
isDrawing.value = false
|
||||||
|
|
||||||
brushPanelVisible.value = true
|
brushPanelVisible.value = true
|
||||||
isPanelOpen.value = true
|
isPanelOpen.value = true
|
||||||
currentShapeDescription.value = ''
|
currentShapeDescription.value = ''
|
||||||
@ -452,7 +452,7 @@ const saveHistory = () => {
|
|||||||
history.value = history.value.slice(0, historyIndex.value + 1)
|
history.value = history.value.slice(0, historyIndex.value + 1)
|
||||||
history.value.push([...shapes.value])
|
history.value.push([...shapes.value])
|
||||||
historyIndex.value = history.value.length - 1
|
historyIndex.value = history.value.length - 1
|
||||||
|
|
||||||
promptHistory.value = promptHistory.value.slice(0, promptHistoryIndex.value + 1)
|
promptHistory.value = promptHistory.value.slice(0, promptHistoryIndex.value + 1)
|
||||||
promptHistory.value.push(inputText.value)
|
promptHistory.value.push(inputText.value)
|
||||||
promptHistoryIndex.value = promptHistory.value.length - 1
|
promptHistoryIndex.value = promptHistory.value.length - 1
|
||||||
@ -463,7 +463,7 @@ const undo = () => {
|
|||||||
historyIndex.value--
|
historyIndex.value--
|
||||||
shapes.value = historyIndex.value >= 0 ? [...history.value[historyIndex.value]] : []
|
shapes.value = historyIndex.value >= 0 ? [...history.value[historyIndex.value]] : []
|
||||||
redrawCanvas()
|
redrawCanvas()
|
||||||
|
|
||||||
promptHistoryIndex.value--
|
promptHistoryIndex.value--
|
||||||
inputText.value = promptHistoryIndex.value >= 0 ? promptHistory.value[promptHistoryIndex.value] : ''
|
inputText.value = promptHistoryIndex.value >= 0 ? promptHistory.value[promptHistoryIndex.value] : ''
|
||||||
if (editableDivRef.value) {
|
if (editableDivRef.value) {
|
||||||
@ -477,7 +477,7 @@ const redo = () => {
|
|||||||
historyIndex.value++
|
historyIndex.value++
|
||||||
shapes.value = [...history.value[historyIndex.value]]
|
shapes.value = [...history.value[historyIndex.value]]
|
||||||
redrawCanvas()
|
redrawCanvas()
|
||||||
|
|
||||||
promptHistoryIndex.value++
|
promptHistoryIndex.value++
|
||||||
inputText.value = promptHistory.value[promptHistoryIndex.value]
|
inputText.value = promptHistory.value[promptHistoryIndex.value]
|
||||||
if (editableDivRef.value) {
|
if (editableDivRef.value) {
|
||||||
@ -493,11 +493,11 @@ const deleteShape = () => {
|
|||||||
promptHistoryIndex.value = -1
|
promptHistoryIndex.value = -1
|
||||||
history.value = []
|
history.value = []
|
||||||
historyIndex.value = -1
|
historyIndex.value = -1
|
||||||
|
|
||||||
if (editableDivRef.value) {
|
if (editableDivRef.value) {
|
||||||
editableDivRef.value.innerHTML = ''
|
editableDivRef.value.innerHTML = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
redrawCanvas()
|
redrawCanvas()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,17 +505,17 @@ const redrawCanvas = () => {
|
|||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
if (!canvas) return
|
if (!canvas) return
|
||||||
const ctx = canvas.getContext('2d')
|
const ctx = canvas.getContext('2d')
|
||||||
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||||
|
|
||||||
if (bgImage.value) {
|
if (bgImage.value) {
|
||||||
ctx.drawImage(bgImage.value, 0, 0)
|
ctx.drawImage(bgImage.value, 0, 0)
|
||||||
} else {
|
} else {
|
||||||
ctx.fillStyle = '#fff'
|
ctx.fillStyle = '#fff'
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
shapes.value.forEach(shape => {
|
shapes.value.forEach((shape) => {
|
||||||
drawShape(ctx, shape)
|
drawShape(ctx, shape)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -523,14 +523,14 @@ const redrawCanvas = () => {
|
|||||||
const drawShape = (ctx, shape) => {
|
const drawShape = (ctx, shape) => {
|
||||||
ctx.strokeStyle = shape.color || '#ff0000'
|
ctx.strokeStyle = shape.color || '#ff0000'
|
||||||
ctx.lineWidth = 2
|
ctx.lineWidth = 2
|
||||||
|
|
||||||
if (shape.type === 'rectangle') {
|
if (shape.type === 'rectangle') {
|
||||||
const width = shape.endX - shape.startX
|
const width = shape.endX - shape.startX
|
||||||
const height = shape.endY - shape.startY
|
const height = shape.endY - shape.startY
|
||||||
ctx.strokeRect(shape.startX, shape.startY, width, height)
|
ctx.strokeRect(shape.startX, shape.startY, width, height)
|
||||||
} else if (shape.type === 'circle') {
|
} else if (shape.type === 'circle') {
|
||||||
const radius = Math.sqrt(
|
const radius = Math.sqrt(
|
||||||
Math.pow(shape.endX - shape.startX, 2) + Math.pow(shape.endY - shape.startY, 2)
|
(shape.endX - shape.startX) ** 2 + (shape.endY - shape.startY) ** 2
|
||||||
)
|
)
|
||||||
ctx.beginPath()
|
ctx.beginPath()
|
||||||
ctx.arc(shape.startX, shape.startY, radius, 0, Math.PI * 2)
|
ctx.arc(shape.startX, shape.startY, radius, 0, Math.PI * 2)
|
||||||
@ -553,7 +553,7 @@ const removeReferenceImage = (index) => {
|
|||||||
const selectReferenceImage = (index) => {
|
const selectReferenceImage = (index) => {
|
||||||
const img = allReferenceImages.value[index]
|
const img = allReferenceImages.value[index]
|
||||||
const existingIndex = selectedReferenceImages.value.indexOf(img)
|
const existingIndex = selectedReferenceImages.value.indexOf(img)
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
selectedReferenceImages.value = []
|
selectedReferenceImages.value = []
|
||||||
} else {
|
} else {
|
||||||
@ -569,14 +569,14 @@ const handleUploadReference = () => {
|
|||||||
input.onchange = async (e) => {
|
input.onchange = async (e) => {
|
||||||
const files = Array.from(e.target.files)
|
const files = Array.from(e.target.files)
|
||||||
const remainingSlots = 5 - allReferenceImages.value.length
|
const remainingSlots = 5 - allReferenceImages.value.length
|
||||||
|
|
||||||
if (remainingSlots <= 0) {
|
if (remainingSlots <= 0) {
|
||||||
ElMessage.warning('最多只能上传5张图片')
|
ElMessage.warning('最多只能上传5张图片')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesToUpload = files.slice(0, remainingSlots)
|
const filesToUpload = files.slice(0, remainingSlots)
|
||||||
|
|
||||||
const readFileAsDataURL = (file) => {
|
const readFileAsDataURL = (file) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
@ -585,7 +585,7 @@ const handleUploadReference = () => {
|
|||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageDataUrls = await Promise.all(filesToUpload.map(readFileAsDataURL))
|
const imageDataUrls = await Promise.all(filesToUpload.map(readFileAsDataURL))
|
||||||
allReferenceImages.value.push(...imageDataUrls)
|
allReferenceImages.value.push(...imageDataUrls)
|
||||||
}
|
}
|
||||||
@ -597,7 +597,7 @@ const closeBrushPanel = () => {
|
|||||||
shapes.value.splice(currentEditingShapeIndex.value, 1)
|
shapes.value.splice(currentEditingShapeIndex.value, 1)
|
||||||
redrawCanvas()
|
redrawCanvas()
|
||||||
}
|
}
|
||||||
|
|
||||||
brushPanelVisible.value = false
|
brushPanelVisible.value = false
|
||||||
isPanelOpen.value = false
|
isPanelOpen.value = false
|
||||||
currentShapeDescription.value = ''
|
currentShapeDescription.value = ''
|
||||||
@ -611,7 +611,7 @@ const handleBrushConfirm = () => {
|
|||||||
shapes.value[currentEditingShapeIndex.value].description = currentShapeDescription.value
|
shapes.value[currentEditingShapeIndex.value].description = currentShapeDescription.value
|
||||||
shapes.value[currentEditingShapeIndex.value].referenceImages = [...selectedReferenceImages.value]
|
shapes.value[currentEditingShapeIndex.value].referenceImages = [...selectedReferenceImages.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentEditingContent.value) {
|
if (currentEditingContent.value) {
|
||||||
if (editableDivRef.value) {
|
if (editableDivRef.value) {
|
||||||
const currentContent = editableDivRef.value.innerHTML
|
const currentContent = editableDivRef.value.innerHTML
|
||||||
@ -621,9 +621,9 @@ const handleBrushConfirm = () => {
|
|||||||
inputText.value += currentEditingContent.value
|
inputText.value += currentEditingContent.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
saveHistory()
|
saveHistory()
|
||||||
|
|
||||||
brushPanelVisible.value = false
|
brushPanelVisible.value = false
|
||||||
isPanelOpen.value = false
|
isPanelOpen.value = false
|
||||||
currentShapeDescription.value = ''
|
currentShapeDescription.value = ''
|
||||||
@ -635,10 +635,10 @@ const getImageAspectRatio = () => {
|
|||||||
if (!bgImage.value) {
|
if (!bgImage.value) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const width = bgImage.value.width
|
const width = bgImage.value.width
|
||||||
const height = bgImage.value.height
|
const height = bgImage.value.height
|
||||||
|
|
||||||
const aspectRatios = [
|
const aspectRatios = [
|
||||||
{ ratio: '4:3', value: 4 / 3 },
|
{ ratio: '4:3', value: 4 / 3 },
|
||||||
{ ratio: '16:9', value: 16 / 9 },
|
{ ratio: '16:9', value: 16 / 9 },
|
||||||
@ -648,12 +648,12 @@ const getImageAspectRatio = () => {
|
|||||||
{ ratio: '2:3', value: 2 / 3 },
|
{ ratio: '2:3', value: 2 / 3 },
|
||||||
{ ratio: '3:2', value: 3 / 2 }
|
{ ratio: '3:2', value: 3 / 2 }
|
||||||
]
|
]
|
||||||
|
|
||||||
const currentRatio = width / height
|
const currentRatio = width / height
|
||||||
|
|
||||||
let closest = aspectRatios[0]
|
let closest = aspectRatios[0]
|
||||||
let minDiff = Math.abs(currentRatio - closest.value)
|
let minDiff = Math.abs(currentRatio - closest.value)
|
||||||
|
|
||||||
for (const item of aspectRatios) {
|
for (const item of aspectRatios) {
|
||||||
const diff = Math.abs(currentRatio - item.value)
|
const diff = Math.abs(currentRatio - item.value)
|
||||||
if (diff < minDiff) {
|
if (diff < minDiff) {
|
||||||
@ -661,7 +661,7 @@ const getImageAspectRatio = () => {
|
|||||||
closest = item
|
closest = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -675,34 +675,34 @@ const handleSend = async () => {
|
|||||||
ElMessage.error('请输入提示词')
|
ElMessage.error('请输入提示词')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isSending.value = true
|
isSending.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const canvas = canvasRef.value
|
const canvas = canvasRef.value
|
||||||
const imageData = canvas.toDataURL('image/png')
|
const imageData = canvas.toDataURL('image/png')
|
||||||
|
|
||||||
const imgs = []
|
const imgs = []
|
||||||
if (imageData) {
|
if (imageData) {
|
||||||
imgs.push({ name: 'image_1', url: imageData })
|
imgs.push({ name: 'image_1', url: imageData })
|
||||||
}
|
}
|
||||||
|
|
||||||
allReferenceImages.value.forEach((img, index) => {
|
allReferenceImages.value.forEach((img, index) => {
|
||||||
imgs.push({ name: `image_${index + 2}`, url: img })
|
imgs.push({ name: `image_${index + 2}`, url: img })
|
||||||
})
|
})
|
||||||
|
|
||||||
const uploadImg = async (imgItem) => {
|
const uploadImg = async (imgItem) => {
|
||||||
if (!imgItem.url.startsWith('data:') && !imgItem.url.startsWith('blob:')) {
|
if (!imgItem.url.startsWith('data:') && !imgItem.url.startsWith('blob:')) {
|
||||||
return imgItem
|
return imgItem
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(imgItem.url)
|
const response = await fetch(imgItem.url)
|
||||||
const blob = await response.blob()
|
const blob = await response.blob()
|
||||||
const file = new File([blob], `${imgItem.name}.png`, { type: 'image/png' })
|
const file = new File([blob], `${imgItem.name}.png`, { type: 'image/png' })
|
||||||
|
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await request({
|
const result = await request({
|
||||||
url: import.meta.env.VITE_API_WORKFLOW_UPLOAD,
|
url: import.meta.env.VITE_API_WORKFLOW_UPLOAD,
|
||||||
@ -721,11 +721,11 @@ const handleSend = async () => {
|
|||||||
return imgItem
|
return imgItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const uploadedImgs = await Promise.all(imgs.map(uploadImg))
|
const uploadedImgs = await Promise.all(imgs.map(uploadImg))
|
||||||
|
|
||||||
const proportion = getImageAspectRatio()
|
const proportion = getImageAspectRatio()
|
||||||
|
|
||||||
const modelId = await getModelId(props.type, 'GPT')
|
const modelId = await getModelId(props.type, 'GPT')
|
||||||
|
|
||||||
const generateData = {
|
const generateData = {
|
||||||
@ -743,20 +743,20 @@ const handleSend = async () => {
|
|||||||
modelId,
|
modelId,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
params: [
|
params: [
|
||||||
{ name: 'prompt', data: inputText.value + '并且去除掉图1中的框' },
|
{ name: 'prompt', data: `${inputText.value}并且去除掉图1中的框` },
|
||||||
{ name: 'index', data: 1 },
|
{ name: 'index', data: 1 },
|
||||||
{ name: 'proportion', data: proportion?.aspectRatio || '4:3' },
|
{ name: 'proportion', data: proportion?.aspectRatio || '4:3' }
|
||||||
],
|
],
|
||||||
imgs: uploadedImgs,
|
imgs: uploadedImgs,
|
||||||
request: JSON.stringify(generateData)
|
request: JSON.stringify(generateData)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('send', {
|
emit('send', {
|
||||||
image: imageData,
|
image: imageData,
|
||||||
text: inputText.value,
|
text: inputText.value,
|
||||||
shapes: shapes.value
|
shapes: shapes.value
|
||||||
})
|
})
|
||||||
|
|
||||||
await generate(data, generateData)
|
await generate(data, generateData)
|
||||||
handleClose()
|
handleClose()
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Transition name="slide-up">
|
<Transition name="slide-up">
|
||||||
<div class="input-container" :class="{ generate : !props.isGenerate }" @click="handleContainerClick">
|
<div class="input-container" :class="{ generate: !props.isGenerate }" @click="handleContainerClick">
|
||||||
<div v-if="!props.isGenerate" class="title">{{ platform.label }}</div>
|
<div v-if="!props.isGenerate" class="title">{{ platform.label }}</div>
|
||||||
|
|
||||||
<div class="sender-top">
|
<div class="sender-top">
|
||||||
@ -11,8 +11,8 @@
|
|||||||
<div v-show="showUploader" class="upload-img-container">
|
<div v-show="showUploader" class="upload-img-container">
|
||||||
<div class="reference-diagram">
|
<div class="reference-diagram">
|
||||||
<component
|
<component
|
||||||
v-if="platform.ImageUploader"
|
|
||||||
:is="platform.ImageUploader"
|
:is="platform.ImageUploader"
|
||||||
|
v-if="platform.ImageUploader"
|
||||||
ref="referenceDiagramRef"
|
ref="referenceDiagramRef"
|
||||||
v-model="referenceImages"
|
v-model="referenceImages"
|
||||||
v-bind="uploaderBindings"
|
v-bind="uploaderBindings"
|
||||||
@ -34,11 +34,11 @@
|
|||||||
<div class="prefix-self-wrap">
|
<div class="prefix-self-wrap">
|
||||||
<component
|
<component
|
||||||
:is="platform.ModelSelector"
|
:is="platform.ModelSelector"
|
||||||
:modelValue="platform.model.value"
|
:model-value="platform.model.value"
|
||||||
@update:modelValue="platform.model.value = $event"
|
:type-value="platform.modelType.value"
|
||||||
:typeValue="platform.modelType.value"
|
|
||||||
@update:typeValue="platform.modelType.value = $event"
|
|
||||||
v-bind="(platform.modelSelectorProps && platform.modelSelectorProps()) || {}"
|
v-bind="(platform.modelSelectorProps && platform.modelSelectorProps()) || {}"
|
||||||
|
@update:model-value="platform.model.value = $event"
|
||||||
|
@update:type-value="platform.modelType.value = $event"
|
||||||
/>
|
/>
|
||||||
<template v-for="ctrl in visibleControls" :key="ctrl.name">
|
<template v-for="ctrl in visibleControls" :key="ctrl.name">
|
||||||
<component
|
<component
|
||||||
@ -62,18 +62,17 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Sender>
|
</Sender>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Sender } from 'vue-element-plus-x'
|
import { Sender } from 'vue-element-plus-x'
|
||||||
import { useDisplayStore } from '@/stores'
|
|
||||||
import { generate } from '@/utils/taskPolling'
|
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { createPlatform } from '@/platforms/registry.js'
|
import { createPlatform } from '@/platforms/registry.js'
|
||||||
|
import { useDisplayStore } from '@/stores'
|
||||||
import { getModelId } from '@/utils/modelApi'
|
import { getModelId } from '@/utils/modelApi'
|
||||||
|
import { generate } from '@/utils/taskPolling'
|
||||||
|
|
||||||
// 确保平台包被加载(触发自注册)
|
// 确保平台包被加载(触发自注册)
|
||||||
import '@/platforms/painting/index.js'
|
import '@/platforms/painting/index.js'
|
||||||
@ -82,7 +81,7 @@ import '@/platforms/video/index.js'
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
isGenerate: { type: Boolean, default: false },
|
isGenerate: { type: Boolean, default: false },
|
||||||
generate: { type: Boolean, default: false },
|
generate: { type: Boolean, default: false },
|
||||||
type: { type: String, default: 'Painting' },
|
type: { type: String, default: 'Painting' }
|
||||||
})
|
})
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -99,7 +98,7 @@ const getCurrentConfig = () => {
|
|||||||
|
|
||||||
const visibleControls = computed(() => {
|
const visibleControls = computed(() => {
|
||||||
const config = getCurrentConfig()
|
const config = getCurrentConfig()
|
||||||
return platform.value.controls.filter(c => c.show(config))
|
return platform.value.controls.filter((c) => c.show(config))
|
||||||
})
|
})
|
||||||
|
|
||||||
const showUploader = computed(() => {
|
const showUploader = computed(() => {
|
||||||
@ -148,7 +147,7 @@ const handleStart = async () => {
|
|||||||
modelType: p.modelType.value,
|
modelType: p.modelType.value,
|
||||||
prompt: prompt.value,
|
prompt: prompt.value,
|
||||||
referenceImages: referenceImages.value,
|
referenceImages: referenceImages.value,
|
||||||
modelParams: body,
|
modelParams: body
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@ -157,7 +156,7 @@ const handleStart = async () => {
|
|||||||
modelName: p.model.value,
|
modelName: p.model.value,
|
||||||
modelId: modelId || '',
|
modelId: modelId || '',
|
||||||
body,
|
body,
|
||||||
request: JSON.stringify(generateData),
|
request: JSON.stringify(generateData)
|
||||||
}
|
}
|
||||||
|
|
||||||
await generate(data, generateData)
|
await generate(data, generateData)
|
||||||
@ -190,7 +189,7 @@ watch(
|
|||||||
async ([newModel, newModelType]) => {
|
async ([newModel, newModelType]) => {
|
||||||
if (!newModel) return
|
if (!newModel) return
|
||||||
await platform.value.loadConfig(newModel, newModelType)
|
await platform.value.loadConfig(newModel, newModelType)
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// 平台切换 → 设置默认模型 + 预加载模型列表
|
// 平台切换 → 设置默认模型 + 预加载模型列表
|
||||||
|
|||||||
@ -100,6 +100,8 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
|
||||||
|
|
||||||
const computedData = computed(() => {
|
const computedData = computed(() => {
|
||||||
return props.data.length > 0 ? props.data : props.items
|
return props.data.length > 0 ? props.data : props.items
|
||||||
})
|
})
|
||||||
@ -114,8 +116,6 @@ const computedBuffer = computed(() => {
|
|||||||
return props.buffer !== 3 ? props.buffer : props.bufferSize
|
return props.buffer !== 3 ? props.buffer : props.bufferSize
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
|
|
||||||
|
|
||||||
const containerRef = ref(null)
|
const containerRef = ref(null)
|
||||||
const wrapperRef = ref(null)
|
const wrapperRef = ref(null)
|
||||||
const renderContainerRef = ref(null)
|
const renderContainerRef = ref(null)
|
||||||
@ -140,27 +140,27 @@ const containerStyle = computed(() => {
|
|||||||
const totalHeight = computed(() => {
|
const totalHeight = computed(() => {
|
||||||
let height = 0
|
let height = 0
|
||||||
const len = computedData.value.length
|
const len = computedData.value.length
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
height += props.bottomPlaceholderHeight
|
height += props.bottomPlaceholderHeight
|
||||||
|
|
||||||
return height
|
return height
|
||||||
})
|
})
|
||||||
|
|
||||||
const getItemPosition = (index) => {
|
const getItemPosition = (index) => {
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
for (let i = 0; i < index; i++) {
|
for (let i = 0; i < index; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = itemHeights.value.get(index) ?? props.estimatedHeight
|
const height = itemHeights.value.get(index) ?? props.estimatedHeight
|
||||||
|
|
||||||
return { offset, height }
|
return { offset, height }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,73 +173,73 @@ const visibleRange = computed(() => {
|
|||||||
if (!renderContainerRef.value || computedData.value.length === 0) {
|
if (!renderContainerRef.value || computedData.value.length === 0) {
|
||||||
return { start: 0, end: 0, offset: 0 }
|
return { start: 0, end: 0, offset: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewportHeight = containerHeight.value
|
const viewportHeight = containerHeight.value
|
||||||
const currentScrollTop = scrollTop.value
|
const currentScrollTop = scrollTop.value
|
||||||
const bufferCount = computedBuffer.value
|
const bufferCount = computedBuffer.value
|
||||||
|
|
||||||
let startIndex = 0
|
let startIndex = 0
|
||||||
let endIndex = computedData.value.length - 1
|
let endIndex = computedData.value.length - 1
|
||||||
let startOffset = 0
|
let startOffset = 0
|
||||||
|
|
||||||
let offset = 0
|
let offset = 0
|
||||||
for (let i = 0; i < computedData.value.length; i++) {
|
for (let i = 0; i < computedData.value.length; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
if (offset + height > currentScrollTop) {
|
if (offset + height > currentScrollTop) {
|
||||||
startIndex = Math.max(0, i - bufferCount)
|
startIndex = Math.max(0, i - bufferCount)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
offset += height
|
offset += height
|
||||||
}
|
}
|
||||||
|
|
||||||
startOffset = 0
|
startOffset = 0
|
||||||
for (let i = 0; i < startIndex; i++) {
|
for (let i = 0; i < startIndex; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
startOffset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
startOffset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
offset = startOffset
|
offset = startOffset
|
||||||
endIndex = startIndex
|
endIndex = startIndex
|
||||||
for (let i = startIndex; i < computedData.value.length; i++) {
|
for (let i = startIndex; i < computedData.value.length; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
offset += height
|
offset += height
|
||||||
|
|
||||||
if (offset > currentScrollTop + viewportHeight) {
|
if (offset > currentScrollTop + viewportHeight) {
|
||||||
endIndex = Math.min(computedData.value.length - 1, i + bufferCount)
|
endIndex = Math.min(computedData.value.length - 1, i + bufferCount)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
endIndex = i
|
endIndex = i
|
||||||
}
|
}
|
||||||
|
|
||||||
return { start: Math.min(startIndex, endIndex), end: Math.max(startIndex, endIndex), offset: startOffset }
|
return { start: Math.min(startIndex, endIndex), end: Math.max(startIndex, endIndex), offset: startOffset }
|
||||||
})
|
})
|
||||||
|
|
||||||
const visibleItems = computed(() => {
|
const visibleItems = computed(() => {
|
||||||
const { start, end, offset } = visibleRange.value
|
const { start, end, offset } = visibleRange.value
|
||||||
const items = []
|
const items = []
|
||||||
|
|
||||||
let currentOffset = offset
|
let currentOffset = offset
|
||||||
|
|
||||||
for (let i = start; i <= end && i < computedData.value.length; i++) {
|
for (let i = start; i <= end && i < computedData.value.length; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
item: computedData.value[i],
|
item: computedData.value[i],
|
||||||
index: i,
|
index: i,
|
||||||
offset: currentOffset + props.bottomPlaceholderHeight,
|
offset: currentOffset + props.bottomPlaceholderHeight,
|
||||||
height
|
height
|
||||||
})
|
})
|
||||||
|
|
||||||
currentOffset += height
|
currentOffset += height
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ const bottomPlaceholderStyle = computed(() => ({
|
|||||||
top: 0,
|
top: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: `${props.bottomPlaceholderHeight}px`,
|
height: `${props.bottomPlaceholderHeight}px`,
|
||||||
transform: `translateY(0px)`,
|
transform: 'translateY(0px)',
|
||||||
zIndex: 1
|
zIndex: 1
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -312,12 +312,12 @@ const setItemRef = (el, index) => {
|
|||||||
|
|
||||||
const measureItem = (index, element) => {
|
const measureItem = (index, element) => {
|
||||||
if (!element) return
|
if (!element) return
|
||||||
|
|
||||||
const firstChild = element.firstElementChild
|
const firstChild = element.firstElementChild
|
||||||
const targetElement = firstChild || element
|
const targetElement = firstChild || element
|
||||||
|
|
||||||
const height = Math.ceil(targetElement.offsetHeight)
|
const height = Math.ceil(targetElement.offsetHeight)
|
||||||
|
|
||||||
if (height > 0) {
|
if (height > 0) {
|
||||||
const cachedHeight = itemHeights.value.get(index)
|
const cachedHeight = itemHeights.value.get(index)
|
||||||
if (cachedHeight !== height) {
|
if (cachedHeight !== height) {
|
||||||
@ -332,10 +332,10 @@ const setupResizeObserver = () => {
|
|||||||
if (resizeObserver.value) {
|
if (resizeObserver.value) {
|
||||||
resizeObserver.value.disconnect()
|
resizeObserver.value.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeObserver.value = new ResizeObserver((entries) => {
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const index = parseInt(entry.target.dataset.index, 10)
|
const index = Number.parseInt(entry.target.dataset.index, 10)
|
||||||
if (!isNaN(index)) {
|
if (!isNaN(index)) {
|
||||||
measureItem(index, entry.target)
|
measureItem(index, entry.target)
|
||||||
}
|
}
|
||||||
@ -345,15 +345,15 @@ const setupResizeObserver = () => {
|
|||||||
|
|
||||||
const handleWheel = (event) => {
|
const handleWheel = (event) => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
const { deltaY } = event
|
const { deltaY } = event
|
||||||
const el = renderContainerRef.value
|
const el = renderContainerRef.value
|
||||||
|
|
||||||
el.scrollBy({
|
el.scrollBy({
|
||||||
top: -deltaY,
|
top: -deltaY,
|
||||||
behavior: 'instant'
|
behavior: 'instant'
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,15 +363,15 @@ const handleScroll = (event) => {
|
|||||||
const target = event.target
|
const target = event.target
|
||||||
scrollTop.value = target.scrollTop
|
scrollTop.value = target.scrollTop
|
||||||
isScrolling.value = true
|
isScrolling.value = true
|
||||||
|
|
||||||
if (scrollTimeout.value) {
|
if (scrollTimeout.value) {
|
||||||
clearTimeout(scrollTimeout.value)
|
clearTimeout(scrollTimeout.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollTimeout.value = setTimeout(() => {
|
scrollTimeout.value = setTimeout(() => {
|
||||||
isScrolling.value = false
|
isScrolling.value = false
|
||||||
}, 150)
|
}, 150)
|
||||||
|
|
||||||
// 滚动时添加防抖清理,每100ms最多执行一次
|
// 滚动时添加防抖清理,每100ms最多执行一次
|
||||||
if (scrollCleanupTimeout.value) {
|
if (scrollCleanupTimeout.value) {
|
||||||
clearTimeout(scrollCleanupTimeout.value)
|
clearTimeout(scrollCleanupTimeout.value)
|
||||||
@ -379,19 +379,19 @@ const handleScroll = (event) => {
|
|||||||
scrollCleanupTimeout.value = setTimeout(() => {
|
scrollCleanupTimeout.value = setTimeout(() => {
|
||||||
cleanupExtraItems(visibleItems.value)
|
cleanupExtraItems(visibleItems.value)
|
||||||
}, 300)
|
}, 300)
|
||||||
|
|
||||||
const st = target.scrollTop
|
const st = target.scrollTop
|
||||||
const scrollHeight = target.scrollHeight
|
const scrollHeight = target.scrollHeight
|
||||||
const clientHeight = target.clientHeight
|
const clientHeight = target.clientHeight
|
||||||
|
|
||||||
const distanceToContainerTop = st
|
const distanceToContainerTop = st
|
||||||
const distanceToContainerBottom = scrollHeight - st - clientHeight
|
const distanceToContainerBottom = scrollHeight - st - clientHeight
|
||||||
|
|
||||||
const distanceToPageTop = distanceToContainerBottom
|
const distanceToPageTop = distanceToContainerBottom
|
||||||
const distanceToPageBottom = distanceToContainerTop
|
const distanceToPageBottom = distanceToContainerTop
|
||||||
const isAtPageTop = distanceToPageTop <= 0
|
const isAtPageTop = distanceToPageTop <= 0
|
||||||
const isAtPageBottom = distanceToPageBottom <= 0
|
const isAtPageBottom = distanceToPageBottom <= 0
|
||||||
|
|
||||||
emit('scroll', {
|
emit('scroll', {
|
||||||
target,
|
target,
|
||||||
scrollTop: st,
|
scrollTop: st,
|
||||||
@ -402,11 +402,11 @@ const handleScroll = (event) => {
|
|||||||
isAtPageTop,
|
isAtPageTop,
|
||||||
isAtPageBottom
|
isAtPageBottom
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isAtPageTop) {
|
if (isAtPageTop) {
|
||||||
emit('scroll-start')
|
emit('scroll-start')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAtPageBottom) {
|
if (isAtPageBottom) {
|
||||||
emit('scroll-end')
|
emit('scroll-end')
|
||||||
}
|
}
|
||||||
@ -414,9 +414,9 @@ const handleScroll = (event) => {
|
|||||||
|
|
||||||
const scrollToIndex = (index, behavior = 'auto') => {
|
const scrollToIndex = (index, behavior = 'auto') => {
|
||||||
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
|
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
|
||||||
|
|
||||||
const position = getItemPosition(index)
|
const position = getItemPosition(index)
|
||||||
|
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: position.offset,
|
top: position.offset,
|
||||||
behavior
|
behavior
|
||||||
@ -428,10 +428,10 @@ const scrollToBottom = (behavior = 'smooth') => {
|
|||||||
pendingScrollToBottom.value = true
|
pendingScrollToBottom.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior
|
behavior
|
||||||
@ -441,10 +441,10 @@ const scrollToBottom = (behavior = 'smooth') => {
|
|||||||
|
|
||||||
const scrollToTop = (behavior = 'smooth') => {
|
const scrollToTop = (behavior = 'smooth') => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
const scrollHeight = renderContainerRef.value.scrollHeight
|
const scrollHeight = renderContainerRef.value.scrollHeight
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: scrollHeight,
|
top: scrollHeight,
|
||||||
@ -479,9 +479,9 @@ const isAtPageTop = () => {
|
|||||||
|
|
||||||
const observeVisibleItems = () => {
|
const observeVisibleItems = () => {
|
||||||
if (!resizeObserver.value) return
|
if (!resizeObserver.value) return
|
||||||
|
|
||||||
resizeObserver.value.disconnect()
|
resizeObserver.value.disconnect()
|
||||||
|
|
||||||
for (const [index, element] of itemRefs) {
|
for (const [index, element] of itemRefs) {
|
||||||
if (element) {
|
if (element) {
|
||||||
resizeObserver.value.observe(element)
|
resizeObserver.value.observe(element)
|
||||||
@ -492,24 +492,24 @@ const observeVisibleItems = () => {
|
|||||||
watch(() => computedData.value, (newData, oldData) => {
|
watch(() => computedData.value, (newData, oldData) => {
|
||||||
const oldLength = oldData?.length || 0
|
const oldLength = oldData?.length || 0
|
||||||
const newLength = newData.length
|
const newLength = newData.length
|
||||||
|
|
||||||
if (newLength !== oldLength) {
|
if (newLength !== oldLength) {
|
||||||
const newHeights = new Map()
|
const newHeights = new Map()
|
||||||
|
|
||||||
const minLen = Math.min(oldLength, newLength)
|
const minLen = Math.min(oldLength, newLength)
|
||||||
for (let i = 0; i < minLen; i++) {
|
for (let i = 0; i < minLen; i++) {
|
||||||
if (itemHeights.value.has(i)) {
|
if (itemHeights.value.has(i)) {
|
||||||
newHeights.set(i, itemHeights.value.get(i))
|
newHeights.set(i, itemHeights.value.get(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemHeights.value = newHeights
|
itemHeights.value = newHeights
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
observeVisibleItems()
|
observeVisibleItems()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
previousDataLength.value = newLength
|
previousDataLength.value = newLength
|
||||||
}, { deep: false })
|
}, { deep: false })
|
||||||
|
|
||||||
@ -527,36 +527,36 @@ watch(visibleItems, (newItems) => {
|
|||||||
|
|
||||||
const cleanupExtraItems = (currentVisibleItems) => {
|
const cleanupExtraItems = (currentVisibleItems) => {
|
||||||
if (!renderContainerRef.value || !currentVisibleItems.length) return
|
if (!renderContainerRef.value || !currentVisibleItems.length) return
|
||||||
|
|
||||||
// 构建当前应该可见的索引集合
|
// 构建当前应该可见的索引集合
|
||||||
const visibleIndices = new Set(currentVisibleItems.map(item => item.index))
|
const visibleIndices = new Set(currentVisibleItems.map((item) => item.index))
|
||||||
|
|
||||||
// 直接获取 render-container 内所有实际渲染的 .virtual-scroller-item 元素
|
// 直接获取 render-container 内所有实际渲染的 .virtual-scroller-item 元素
|
||||||
const renderedItems = renderContainerRef.value.querySelectorAll('.virtual-scroller-item')
|
const renderedItems = renderContainerRef.value.querySelectorAll('.virtual-scroller-item')
|
||||||
|
|
||||||
const toRemove = []
|
const toRemove = []
|
||||||
|
|
||||||
for (const el of renderedItems) {
|
for (const el of renderedItems) {
|
||||||
const dataIndex = parseInt(el.getAttribute('data-index'), 10)
|
const dataIndex = Number.parseInt(el.getAttribute('data-index'), 10)
|
||||||
|
|
||||||
// 如果元素的 data-index 不在可见范围内,标记为删除
|
// 如果元素的 data-index 不在可见范围内,标记为删除
|
||||||
if (!isNaN(dataIndex) && !visibleIndices.has(dataIndex)) {
|
if (!isNaN(dataIndex) && !visibleIndices.has(dataIndex)) {
|
||||||
toRemove.push(el)
|
toRemove.push(el)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 DOM 中删除多余元素
|
// 从 DOM 中删除多余元素
|
||||||
for (const el of toRemove) {
|
for (const el of toRemove) {
|
||||||
if (el.parentNode) {
|
if (el.parentNode) {
|
||||||
el.parentNode.removeChild(el)
|
el.parentNode.removeChild(el)
|
||||||
}
|
}
|
||||||
// 同步清理 itemRefs Map
|
// 同步清理 itemRefs Map
|
||||||
const index = parseInt(el.getAttribute('data-index'), 10)
|
const index = Number.parseInt(el.getAttribute('data-index'), 10)
|
||||||
if (!isNaN(index)) {
|
if (!isNaN(index)) {
|
||||||
itemRefs.delete(index)
|
itemRefs.delete(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toRemove.length > 0) {
|
if (toRemove.length > 0) {
|
||||||
console.log(`[VirtualScroller] 清理了 ${toRemove.length} 个多余DOM元素`)
|
console.log(`[VirtualScroller] 清理了 ${toRemove.length} 个多余DOM元素`)
|
||||||
}
|
}
|
||||||
@ -566,13 +566,13 @@ onMounted(() => {
|
|||||||
setupResizeObserver()
|
setupResizeObserver()
|
||||||
isInitialized.value = true
|
isInitialized.value = true
|
||||||
previousDataLength.value = computedData.value.length
|
previousDataLength.value = computedData.value.length
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
if (pendingScrollToBottom.value) {
|
if (pendingScrollToBottom.value) {
|
||||||
pendingScrollToBottom.value = false
|
pendingScrollToBottom.value = false
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
}
|
}
|
||||||
|
|
||||||
observeVisibleItems()
|
observeVisibleItems()
|
||||||
cleanupExtraItems(visibleItems.value)
|
cleanupExtraItems(visibleItems.value)
|
||||||
})
|
})
|
||||||
@ -582,15 +582,15 @@ onBeforeUnmount(() => {
|
|||||||
if (resizeObserver.value) {
|
if (resizeObserver.value) {
|
||||||
resizeObserver.value.disconnect()
|
resizeObserver.value.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollTimeout.value) {
|
if (scrollTimeout.value) {
|
||||||
clearTimeout(scrollTimeout.value)
|
clearTimeout(scrollTimeout.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scrollCleanupTimeout.value) {
|
if (scrollCleanupTimeout.value) {
|
||||||
clearTimeout(scrollCleanupTimeout.value)
|
clearTimeout(scrollCleanupTimeout.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
itemRefs.clear()
|
itemRefs.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -611,56 +611,56 @@ defineExpose({
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.virtual-scroller {
|
.virtual-scroller {
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-wrapper {
|
.virtual-scroller-wrapper {
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-spacer {
|
.virtual-scroller-spacer {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-placeholder {
|
.virtual-scroller-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-render-container {
|
.virtual-scroller-render-container {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-item {
|
.virtual-scroller-item {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
perspective: 1000px;
|
perspective: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-bottom-placeholder {
|
.virtual-scroller-bottom-placeholder {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -99,6 +99,8 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
|
||||||
|
|
||||||
const computedData = computed(() => {
|
const computedData = computed(() => {
|
||||||
return props.data.length > 0 ? props.data : props.items
|
return props.data.length > 0 ? props.data : props.items
|
||||||
})
|
})
|
||||||
@ -113,8 +115,6 @@ const computedBuffer = computed(() => {
|
|||||||
return props.buffer !== 3 ? props.buffer : props.bufferSize
|
return props.buffer !== 3 ? props.buffer : props.bufferSize
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
|
|
||||||
|
|
||||||
const containerRef = ref(null)
|
const containerRef = ref(null)
|
||||||
const wrapperRef = ref(null)
|
const wrapperRef = ref(null)
|
||||||
const renderContainerRef = ref(null)
|
const renderContainerRef = ref(null)
|
||||||
@ -136,27 +136,27 @@ const totalHeight = computed(() => {
|
|||||||
heightVersion.value // 依赖追踪:measureItem 直接 mutate Map 时通过此版本号触发重算
|
heightVersion.value // 依赖追踪:measureItem 直接 mutate Map 时通过此版本号触发重算
|
||||||
let height = 0
|
let height = 0
|
||||||
const len = computedData.value.length
|
const len = computedData.value.length
|
||||||
|
|
||||||
for (let i = 0; i < len; i++) {
|
for (let i = 0; i < len; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
height += props.bottomPlaceholderHeight
|
height += props.bottomPlaceholderHeight
|
||||||
|
|
||||||
return height
|
return height
|
||||||
})
|
})
|
||||||
|
|
||||||
const getItemPosition = (index) => {
|
const getItemPosition = (index) => {
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
|
||||||
for (let i = 0; i < index; i++) {
|
for (let i = 0; i < index; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
const height = itemHeights.value.get(index) ?? props.estimatedHeight
|
const height = itemHeights.value.get(index) ?? props.estimatedHeight
|
||||||
|
|
||||||
return { offset, height }
|
return { offset, height }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,23 +218,23 @@ const visibleRange = computed(() => {
|
|||||||
const visibleItems = computed(() => {
|
const visibleItems = computed(() => {
|
||||||
const { start, end, offset } = visibleRange.value
|
const { start, end, offset } = visibleRange.value
|
||||||
const items = []
|
const items = []
|
||||||
|
|
||||||
let currentOffset = offset
|
let currentOffset = offset
|
||||||
|
|
||||||
for (let i = start; i <= end && i < computedData.value.length; i++) {
|
for (let i = start; i <= end && i < computedData.value.length; i++) {
|
||||||
const cachedHeight = itemHeights.value.get(i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
item: computedData.value[i],
|
item: computedData.value[i],
|
||||||
index: i,
|
index: i,
|
||||||
offset: currentOffset + props.bottomPlaceholderHeight,
|
offset: currentOffset + props.bottomPlaceholderHeight,
|
||||||
height
|
height
|
||||||
})
|
})
|
||||||
|
|
||||||
currentOffset += height
|
currentOffset += height
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -269,7 +269,7 @@ const bottomPlaceholderStyle = computed(() => ({
|
|||||||
top: 0,
|
top: 0,
|
||||||
width: '100%',
|
width: '100%',
|
||||||
height: `${props.bottomPlaceholderHeight}px`,
|
height: `${props.bottomPlaceholderHeight}px`,
|
||||||
transform: `translateY(0px)`,
|
transform: 'translateY(0px)',
|
||||||
zIndex: 1
|
zIndex: 1
|
||||||
}))
|
}))
|
||||||
|
|
||||||
@ -344,10 +344,10 @@ const setupResizeObserver = () => {
|
|||||||
if (resizeObserver.value) {
|
if (resizeObserver.value) {
|
||||||
resizeObserver.value.disconnect()
|
resizeObserver.value.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
resizeObserver.value = new ResizeObserver((entries) => {
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const index = parseInt(entry.target.dataset.index, 10)
|
const index = Number.parseInt(entry.target.dataset.index, 10)
|
||||||
if (!isNaN(index)) {
|
if (!isNaN(index)) {
|
||||||
measureItem(index, entry.target)
|
measureItem(index, entry.target)
|
||||||
}
|
}
|
||||||
@ -357,15 +357,15 @@ const setupResizeObserver = () => {
|
|||||||
|
|
||||||
const handleWheel = (event) => {
|
const handleWheel = (event) => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
const { deltaY } = event
|
const { deltaY } = event
|
||||||
const el = renderContainerRef.value
|
const el = renderContainerRef.value
|
||||||
|
|
||||||
el.scrollBy({
|
el.scrollBy({
|
||||||
top: -deltaY,
|
top: -deltaY,
|
||||||
behavior: 'instant'
|
behavior: 'instant'
|
||||||
})
|
})
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,15 +376,15 @@ const handleScroll = (event) => {
|
|||||||
const st = target.scrollTop
|
const st = target.scrollTop
|
||||||
const scrollHeight = target.scrollHeight
|
const scrollHeight = target.scrollHeight
|
||||||
const clientHeight = target.clientHeight
|
const clientHeight = target.clientHeight
|
||||||
|
|
||||||
const distanceToContainerTop = st
|
const distanceToContainerTop = st
|
||||||
const distanceToContainerBottom = scrollHeight - st - clientHeight
|
const distanceToContainerBottom = scrollHeight - st - clientHeight
|
||||||
|
|
||||||
const distanceToPageTop = distanceToContainerBottom
|
const distanceToPageTop = distanceToContainerBottom
|
||||||
const distanceToPageBottom = distanceToContainerTop
|
const distanceToPageBottom = distanceToContainerTop
|
||||||
const isAtPageTop = distanceToPageTop <= 0
|
const isAtPageTop = distanceToPageTop <= 0
|
||||||
const isAtPageBottom = distanceToPageBottom <= 0
|
const isAtPageBottom = distanceToPageBottom <= 0
|
||||||
|
|
||||||
emit('scroll', {
|
emit('scroll', {
|
||||||
target,
|
target,
|
||||||
scrollTop: st,
|
scrollTop: st,
|
||||||
@ -395,11 +395,11 @@ const handleScroll = (event) => {
|
|||||||
isAtPageTop,
|
isAtPageTop,
|
||||||
isAtPageBottom
|
isAtPageBottom
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isAtPageTop) {
|
if (isAtPageTop) {
|
||||||
emit('scroll-start')
|
emit('scroll-start')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAtPageBottom) {
|
if (isAtPageBottom) {
|
||||||
emit('scroll-end')
|
emit('scroll-end')
|
||||||
}
|
}
|
||||||
@ -407,9 +407,9 @@ const handleScroll = (event) => {
|
|||||||
|
|
||||||
const scrollToIndex = (index, behavior = 'auto') => {
|
const scrollToIndex = (index, behavior = 'auto') => {
|
||||||
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
|
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
|
||||||
|
|
||||||
const position = getItemPosition(index)
|
const position = getItemPosition(index)
|
||||||
|
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: position.offset,
|
top: position.offset,
|
||||||
behavior
|
behavior
|
||||||
@ -421,10 +421,10 @@ const scrollToBottom = (behavior = 'smooth') => {
|
|||||||
pendingScrollToBottom.value = true
|
pendingScrollToBottom.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: 0,
|
top: 0,
|
||||||
behavior
|
behavior
|
||||||
@ -434,10 +434,10 @@ const scrollToBottom = (behavior = 'smooth') => {
|
|||||||
|
|
||||||
const scrollToTop = (behavior = 'smooth') => {
|
const scrollToTop = (behavior = 'smooth') => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
if (!renderContainerRef.value) return
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
const scrollHeight = renderContainerRef.value.scrollHeight
|
const scrollHeight = renderContainerRef.value.scrollHeight
|
||||||
renderContainerRef.value.scrollTo({
|
renderContainerRef.value.scrollTo({
|
||||||
top: scrollHeight,
|
top: scrollHeight,
|
||||||
@ -552,56 +552,56 @@ defineExpose({
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.virtual-scroller {
|
.virtual-scroller {
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
&::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
&::-webkit-scrollbar-thumb {
|
||||||
background: rgba(0, 0, 0, 0.2);
|
background: rgba(0, 0, 0, 0.2);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.3);
|
background: rgba(0, 0, 0, 0.3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-wrapper {
|
.virtual-scroller-wrapper {
|
||||||
contain: content;
|
contain: content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-spacer {
|
.virtual-scroller-spacer {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-placeholder {
|
.virtual-scroller-placeholder {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-render-container {
|
.virtual-scroller-render-container {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-item {
|
.virtual-scroller-item {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
perspective: 1000px;
|
perspective: 1000px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.virtual-scroller-bottom-placeholder {
|
.virtual-scroller-bottom-placeholder {
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,194 +1,194 @@
|
|||||||
window.TEST_DATA = [
|
window.TEST_DATA = [
|
||||||
{
|
{
|
||||||
"id": "839217090555557410",
|
id: '839217090555557410',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "839211834861958673",
|
id: '839211834861958673',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "BananaPro"
|
model: 'BananaPro'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "839209605929121287",
|
id: '839209605929121287',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "837370053866304564",
|
id: '837370053866304564',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "837360015437214709",
|
id: '837360015437214709',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "836534979084169461",
|
id: '836534979084169461',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/20/69e581e93888d39e4ed1d025.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/20/69e581e93888d39e4ed1d025.png',
|
||||||
"prompt": "<div data-v-43afc57f=\"\" class=\"prompt-container\" style=\"width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255); height: 39px;\"><div data-v-43afc57f=\"\" class=\"prompt-wrapper\" style=\"width: 1118px;\"><div data-v-43afc57f=\"\" class=\"prompt expanded\" style=\"max-height: none; overflow: visible;\"><span data-v-43afc57f=\"\" class=\"prompt-text\">将图1红色框内的【苹果】替换为【火龙果】</span></div><div><span data-v-43afc57f=\"\" class=\"prompt-text\"><br></span></div></div></div><div data-v-43afc57f=\"\" class=\"box success-box\" style=\"width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255);\"><div data-v-43afc57f=\"\" class=\"one-box\"></div></div>",
|
prompt: '<div data-v-43afc57f="" class="prompt-container" style="width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255); height: 39px;"><div data-v-43afc57f="" class="prompt-wrapper" style="width: 1118px;"><div data-v-43afc57f="" class="prompt expanded" style="max-height: none; overflow: visible;"><span data-v-43afc57f="" class="prompt-text">将图1红色框内的【苹果】替换为【火龙果】</span></div><div><span data-v-43afc57f="" class="prompt-text"><br></span></div></div></div><div data-v-43afc57f="" class="box success-box" style="width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255);"><div data-v-43afc57f="" class="one-box"></div></div>',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835464458670191734",
|
id: '835464458670191734',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835463648116749398",
|
id: '835463648116749398',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835463392293565520",
|
id: '835463392293565520',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "832562717234575283",
|
id: '832562717234575283',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf66.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf67.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf68.png,http://test.xueai.art/file/2026/4/9/69d70e743888d39e4ed1cf69.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf66.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf67.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf68.png,http://test.xueai.art/file/2026/4/9/69d70e743888d39e4ed1cf69.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "839217090555557410",
|
id: '839217090555557410',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "839211834861958673",
|
id: '839211834861958673',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "BananaPro"
|
model: 'BananaPro'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "839209605929121287",
|
id: '839209605929121287',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "837370053866304564",
|
id: '837370053866304564',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "837360015437214709",
|
id: '837360015437214709',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "836534979084169461",
|
id: '836534979084169461',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/20/69e581e93888d39e4ed1d025.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/20/69e581e93888d39e4ed1d025.png',
|
||||||
"prompt": "<div data-v-43afc57f=\"\" class=\"prompt-container\" style=\"width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255); height: 39px;\"><div data-v-43afc57f=\"\" class=\"prompt-wrapper\" style=\"width: 1118px;\"><div data-v-43afc57f=\"\" class=\"prompt expanded\" style=\"max-height: none; overflow: visible;\"><span data-v-43afc57f=\"\" class=\"prompt-text\">将图1红色框内的【苹果】替换为【火龙果】</span></div><div><span data-v-43afc57f=\"\" class=\"prompt-text\"><br></span></div></div></div><div data-v-43afc57f=\"\" class=\"box success-box\" style=\"width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255);\"><div data-v-43afc57f=\"\" class=\"one-box\"></div></div>",
|
prompt: '<div data-v-43afc57f="" class="prompt-container" style="width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255); height: 39px;"><div data-v-43afc57f="" class="prompt-wrapper" style="width: 1118px;"><div data-v-43afc57f="" class="prompt expanded" style="max-height: none; overflow: visible;"><span data-v-43afc57f="" class="prompt-text">将图1红色框内的【苹果】替换为【火龙果】</span></div><div><span data-v-43afc57f="" class="prompt-text"><br></span></div></div></div><div data-v-43afc57f="" class="box success-box" style="width: 1118px; font-size: 14px; background-color: rgb(255, 255, 255);"><div data-v-43afc57f="" class="one-box"></div></div>',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835464458670191734",
|
id: '835464458670191734',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835463648116749398",
|
id: '835463648116749398',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "835463392293565520",
|
id: '835463392293565520',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "832562717234575283",
|
id: '832562717234575283',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf66.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf67.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf68.png,http://test.xueai.art/file/2026/4/9/69d70e743888d39e4ed1cf69.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf66.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf67.png,http://test.xueai.art/file/2026/4/9/69d70e733888d39e4ed1cf68.png,http://test.xueai.art/file/2026/4/9/69d70e743888d39e4ed1cf69.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "830138209152283808",
|
id: '830138209152283808',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce3c743888d39e4ed1cf4f.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce3c743888d39e4ed1cf4f.png',
|
||||||
"prompt": "将图1红色框内的【苹果】替换为【火龙果】",
|
prompt: '将图1红色框内的【苹果】替换为【火龙果】',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "830136945106498711",
|
id: '830136945106498711',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce3b463888d39e4ed1cf4e.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce3b463888d39e4ed1cf4e.png',
|
||||||
"prompt": "一个女孩在树下吃苹果",
|
prompt: '一个女孩在树下吃苹果',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "830083758811001839",
|
id: '830083758811001839',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce09be3888d39e4ed1cf48.png",
|
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce09be3888d39e4ed1cf48.png',
|
||||||
"prompt": "一个女孩在树下吃苹果",
|
prompt: '一个女孩在树下吃苹果',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829393290267734386",
|
id: '829393290267734386',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb86b13888d39e4ed1cf32.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb86b13888d39e4ed1cf32.png',
|
||||||
"prompt": "将图1红色框内的【女孩】替换为【图2中的女孩】",
|
prompt: '将图1红色框内的【女孩】替换为【图2中的女孩】',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829389466203337022",
|
id: '829389466203337022',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb83223888d39e4ed1cf31.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb83223888d39e4ed1cf31.png',
|
||||||
"prompt": "将图1红色框内的【<span style=\"font-size: 14px;\">女孩</span>】替换为【图2中的女孩】",
|
prompt: '将图1红色框内的【<span style="font-size: 14px;">女孩</span>】替换为【图2中的女孩】',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829388114303660338",
|
id: '829388114303660338',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb81df3888d39e4ed1cf30.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb81df3888d39e4ed1cf30.png',
|
||||||
"prompt": "将图1红色框内的【女孩】替换为【男孩】",
|
prompt: '将图1红色框内的【女孩】替换为【男孩】',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829381253919682782",
|
id: '829381253919682782',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb7b7c3888d39e4ed1cf2d.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb7b7c3888d39e4ed1cf2d.png',
|
||||||
"prompt": "将图1红色框内的【女孩】替换为【图2中的男孩】",
|
prompt: '将图1红色框内的【女孩】替换为【图2中的男孩】',
|
||||||
"model": "banana"
|
model: 'banana'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829324561060212843",
|
id: '829324561060212843',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb46af3888d39e4ed1cf29.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb46af3888d39e4ed1cf29.png',
|
||||||
"prompt": "一个女孩在树下吃苹果",
|
prompt: '一个女孩在树下吃苹果',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829319226454978647",
|
id: '829319226454978647',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf25.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf26.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf27.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf28.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf25.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf26.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf27.png,http://test.xueai.art/file/2026/3/31/69cb41b73888d39e4ed1cf28.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829317957644464188",
|
id: '829317957644464188',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb40893888d39e4ed1cf20.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb40893888d39e4ed1cf20.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829305227994738709",
|
id: '829305227994738709',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb34ae3888d39e4ed1cf1e.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb34ae3888d39e4ed1cf1e.png',
|
||||||
"prompt": "这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
|
prompt: '这是一幅治愈写实风格的插画,画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶,空调外机挂在墙边,金属防护栏环绕四周,外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅,旁边还有小凳。彩色水桶,一个是红色,一个是蓝色,放置在角落,花盆里栽种着金桔,水仙花、蝴蝶兰,整齐排列,大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树,绿意浓郁,与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼,整个场景被绿意环绕,明亮柔和的光线洒下,充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
|
||||||
"model": "flux"
|
model: 'flux'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "829068575099597628",
|
id: '829068575099597628',
|
||||||
"fileUrl": "http://test.xueai.art/file/2026/3/30/69ca58473888d39e0bb8728b.png",
|
fileUrl: 'http://test.xueai.art/file/2026/3/30/69ca58473888d39e0bb8728b.png',
|
||||||
"prompt": "",
|
prompt: '',
|
||||||
"model": ""
|
model: ''
|
||||||
}
|
}
|
||||||
];
|
]
|
||||||
|
|||||||
@ -7,8 +7,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>W</label>
|
<label>W</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
|
||||||
v-model.number="localWidth"
|
v-model.number="localWidth"
|
||||||
|
type="number"
|
||||||
:min="minW"
|
:min="minW"
|
||||||
:max="maxW"
|
:max="maxW"
|
||||||
@input="onWidthChange"
|
@input="onWidthChange"
|
||||||
@ -21,8 +21,8 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>H</label>
|
<label>H</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
|
||||||
v-model.number="localHeight"
|
v-model.number="localHeight"
|
||||||
|
type="number"
|
||||||
:min="minH"
|
:min="minH"
|
||||||
:max="maxH"
|
:max="maxH"
|
||||||
@input="onHeightChange"
|
@input="onHeightChange"
|
||||||
@ -41,9 +41,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Popover from '@/components/Popover/index.vue'
|
|
||||||
import lockIcon from '@/assets/dialog/lock.svg'
|
import lockIcon from '@/assets/dialog/lock.svg'
|
||||||
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
||||||
|
import Popover from '@/components/Popover/index.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
width: { type: Number, default: 1024 },
|
width: { type: Number, default: 1024 },
|
||||||
@ -51,7 +51,7 @@ const props = defineProps({
|
|||||||
minW: { type: Number, default: 256 },
|
minW: { type: Number, default: 256 },
|
||||||
maxW: { type: Number, default: 6197 },
|
maxW: { type: Number, default: 6197 },
|
||||||
minH: { type: Number, default: 256 },
|
minH: { type: Number, default: 256 },
|
||||||
maxH: { type: Number, default: 4096 },
|
maxH: { type: Number, default: 4096 }
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:width', 'update:height'])
|
const emit = defineEmits(['update:width', 'update:height'])
|
||||||
|
|||||||
@ -37,7 +37,7 @@
|
|||||||
<div class="size-inputs">
|
<div class="size-inputs">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>W</label>
|
<label>W</label>
|
||||||
<input type="number" v-model.number="width" @input="updateWidth" :disabled="isLocked">
|
<input v-model.number="width" type="number" :disabled="isLocked" @input="updateWidth">
|
||||||
</div>
|
</div>
|
||||||
<div class="lock-icon" :class="{ locked: isLocked }" @click="toggleLock">
|
<div class="lock-icon" :class="{ locked: isLocked }" @click="toggleLock">
|
||||||
<img :src="isLocked ? lockIcon : lockNoIcon" alt="约束比例">
|
<img :src="isLocked ? lockIcon : lockNoIcon" alt="约束比例">
|
||||||
@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<label>H</label>
|
<label>H</label>
|
||||||
<input type="number" v-model.number="height" @input="updateHeight" :disabled="isLocked">
|
<input v-model.number="height" type="number" :disabled="isLocked" @input="updateHeight">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -60,9 +60,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Popover from '@/components/Popover/index.vue'
|
|
||||||
import lockIcon from '@/assets/dialog/lock.svg'
|
import lockIcon from '@/assets/dialog/lock.svg'
|
||||||
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
import lockNoIcon from '@/assets/dialog/lockNo.svg'
|
||||||
|
import Popover from '@/components/Popover/index.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -95,7 +95,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
allowCustom: {
|
allowCustom: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: true,
|
default: true
|
||||||
},
|
},
|
||||||
resolutionOptions: {
|
resolutionOptions: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@ -461,5 +461,4 @@ watch(() => [props.modelValue, props.resolution], () => {
|
|||||||
background: #f5f6f7;
|
background: #f5f6f7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -19,8 +19,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-upload
|
<el-upload
|
||||||
ref="uploadRef"
|
|
||||||
v-show="false"
|
v-show="false"
|
||||||
|
ref="uploadRef"
|
||||||
:action="uploadurl"
|
:action="uploadurl"
|
||||||
multiple
|
multiple
|
||||||
:limit="limit"
|
:limit="limit"
|
||||||
|
|||||||
@ -15,10 +15,9 @@
|
|||||||
import Select from '@/components/Select/index.vue'
|
import Select from '@/components/Select/index.vue'
|
||||||
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: { type: String, default: 'Flux 2' },
|
modelValue: { type: String, default: 'Flux 2' },
|
||||||
typeValue: { type: String, default: 'text' },
|
typeValue: { type: String, default: 'text' }
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'update:typeValue'])
|
const emit = defineEmits(['update:modelValue', 'update:typeValue'])
|
||||||
@ -28,7 +27,7 @@ const platformModels = ref([])
|
|||||||
const categoryMap = [
|
const categoryMap = [
|
||||||
{ tag: 'text', label: '生成模型', inputType: 'text' },
|
{ tag: 'text', label: '生成模型', inputType: 'text' },
|
||||||
{ tag: 'edit', label: '编辑模型', inputType: 'image' },
|
{ tag: 'edit', label: '编辑模型', inputType: 'image' },
|
||||||
{ tag: 'vision', label: '视觉理解模型', inputType: 'vision' },
|
{ tag: 'vision', label: '视觉理解模型', inputType: 'vision' }
|
||||||
]
|
]
|
||||||
|
|
||||||
function parseValue(encoded) {
|
function parseValue(encoded) {
|
||||||
@ -44,14 +43,14 @@ function encodeValue(tag, modelName) {
|
|||||||
|
|
||||||
function findTagForModel(modelName) {
|
function findTagForModel(modelName) {
|
||||||
for (const cat of categoryMap) {
|
for (const cat of categoryMap) {
|
||||||
const model = platformModels.value.find(m => (m.display_name || m.name) === modelName && m.tags?.includes(cat.tag))
|
const model = platformModels.value.find((m) => (m.display_name || m.name) === modelName && m.tags?.includes(cat.tag))
|
||||||
if (model) return cat.tag
|
if (model) return cat.tag
|
||||||
}
|
}
|
||||||
return 'text'
|
return 'text'
|
||||||
}
|
}
|
||||||
|
|
||||||
function tagToInputType(tag) {
|
function tagToInputType(tag) {
|
||||||
const cat = categoryMap.find(c => c.tag === tag)
|
const cat = categoryMap.find((c) => c.tag === tag)
|
||||||
return cat?.inputType || 'text'
|
return cat?.inputType || 'text'
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +66,7 @@ const selectValue = computed({
|
|||||||
if (!parsed) return
|
if (!parsed) return
|
||||||
emit('update:modelValue', parsed.modelName)
|
emit('update:modelValue', parsed.modelName)
|
||||||
emit('update:typeValue', tagToInputType(parsed.tag))
|
emit('update:typeValue', tagToInputType(parsed.tag))
|
||||||
},
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 从 API 加载模型列表
|
// 从 API 加载模型列表
|
||||||
@ -89,26 +88,26 @@ const modelGroups = computed(() => {
|
|||||||
if (models.length === 0) return []
|
if (models.length === 0) return []
|
||||||
|
|
||||||
return categoryMap
|
return categoryMap
|
||||||
.filter(cat => models.some(m => m.tags?.includes(cat.tag)))
|
.filter((cat) => models.some((m) => m.tags?.includes(cat.tag)))
|
||||||
.map(cat => ({
|
.map((cat) => ({
|
||||||
label: cat.label,
|
label: cat.label,
|
||||||
options: models
|
options: models
|
||||||
.filter(m => m.tags?.includes(cat.tag))
|
.filter((m) => m.tags?.includes(cat.tag))
|
||||||
.map(m => ({
|
.map((m) => ({
|
||||||
value: `${cat.tag}::${m.display_name || m.name}`,
|
value: `${cat.tag}::${m.display_name || m.name}`,
|
||||||
label: m.display_name || m.name,
|
label: m.display_name || m.name,
|
||||||
disabled: m.disabled || false,
|
disabled: m.disabled || false
|
||||||
}))
|
}))
|
||||||
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' })),
|
.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: 'base' }))
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 模型列表加载后自动纠正不可用模型
|
// 模型列表加载后自动纠正不可用模型
|
||||||
watch(platformModels, (models) => {
|
watch(platformModels, (models) => {
|
||||||
if (models.length === 0) return
|
if (models.length === 0) return
|
||||||
const currentModel = models.find(m => (m.display_name || m.name) === props.modelValue || m.id === props.modelValue)
|
const currentModel = models.find((m) => (m.display_name || m.name) === props.modelValue || m.id === props.modelValue)
|
||||||
if (!currentModel || currentModel.disabled) {
|
if (!currentModel || currentModel.disabled) {
|
||||||
const firstEnabled = models.find(m => !m.disabled)
|
const firstEnabled = models.find((m) => !m.disabled)
|
||||||
if (firstEnabled) {
|
if (firstEnabled) {
|
||||||
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
||||||
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
||||||
@ -121,9 +120,9 @@ watch(() => props.modelValue, (newValue) => {
|
|||||||
if (!newValue) return
|
if (!newValue) return
|
||||||
const models = platformModels.value
|
const models = platformModels.value
|
||||||
if (models.length === 0) return
|
if (models.length === 0) return
|
||||||
const currentModel = models.find(m => (m.display_name || m.name) === newValue)
|
const currentModel = models.find((m) => (m.display_name || m.name) === newValue)
|
||||||
if (currentModel && currentModel.disabled) {
|
if (currentModel && currentModel.disabled) {
|
||||||
const firstEnabled = models.find(m => !m.disabled)
|
const firstEnabled = models.find((m) => !m.disabled)
|
||||||
if (firstEnabled) {
|
if (firstEnabled) {
|
||||||
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
|
||||||
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))
|
||||||
|
|||||||
@ -18,12 +18,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Select from '@/components/Select/index.vue'
|
|
||||||
|
|
||||||
import videoPattern2 from '@/assets/dialog/videoPattern2.svg'
|
import videoPattern2 from '@/assets/dialog/videoPattern2.svg'
|
||||||
|
|
||||||
import videoPattern4 from '@/assets/dialog/videoPattern4.svg'
|
import videoPattern4 from '@/assets/dialog/videoPattern4.svg'
|
||||||
import videoPattern5 from '@/assets/dialog/videoPattern5.svg'
|
import videoPattern5 from '@/assets/dialog/videoPattern5.svg'
|
||||||
import videoPattern6 from '@/assets/dialog/videoPattern6.svg'
|
import videoPattern6 from '@/assets/dialog/videoPattern6.svg'
|
||||||
|
import Select from '@/components/Select/index.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
@ -44,12 +44,12 @@ const quantityOptions = [
|
|||||||
{ value: '首尾帧', label: '首尾帧', labelText: '首尾帧', icon: videoPattern2 },
|
{ value: '首尾帧', label: '首尾帧', labelText: '首尾帧', icon: videoPattern2 },
|
||||||
{ value: '数字人', label: '数字人', labelText: '数字人', icon: videoPattern2 },
|
{ value: '数字人', label: '数字人', labelText: '数字人', icon: videoPattern2 },
|
||||||
{ value: '全能参考', label: '全能参考', labelText: '全能参考', icon: videoPattern4 },
|
{ value: '全能参考', label: '全能参考', labelText: '全能参考', icon: videoPattern4 },
|
||||||
{ value: '智能多帧', label: '智能多帧', labelText: '智能多帧', icon: videoPattern5 },
|
{ value: '智能多帧', label: '智能多帧', labelText: '智能多帧', icon: videoPattern5 },
|
||||||
{ value: '主体参考', label: '主体参考', labelText: '主体参考', icon: videoPattern6 }
|
{ value: '主体参考', label: '主体参考', labelText: '主体参考', icon: videoPattern6 }
|
||||||
]
|
]
|
||||||
|
|
||||||
const selectedIcon = computed(() => {
|
const selectedIcon = computed(() => {
|
||||||
const option = quantityOptions.find(opt => opt.value === quantity.value)
|
const option = quantityOptions.find((opt) => opt.value === quantity.value)
|
||||||
return option ? option.icon : videoPattern1
|
return option ? option.icon : videoPattern1
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@ -62,20 +62,20 @@ const selectedIcon = computed(() => {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #E8E9EB;
|
border: 1px solid #E8E9EB;
|
||||||
background: #f5f6f7;
|
background: #f5f6f7;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #e9eaeb;
|
background: #e9eaeb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.select-text) {
|
:deep(.select-text) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
:deep(.dropdown-menu) {
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
:deep(.dropdown-item) {
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
|
|||||||
@ -4,8 +4,8 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>选择比例</h3>
|
<h3>选择比例</h3>
|
||||||
<div class="proportion-options" :style="{ marginBottom: props.type === 'Video' ? '0px' : '20px' }">
|
<div class="proportion-options" :style="{ marginBottom: props.type === 'Video' ? '0px' : '20px' }">
|
||||||
<div
|
<div
|
||||||
v-for="item in proportionOptions"
|
v-for="item in proportionOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
class="proportion-item"
|
class="proportion-item"
|
||||||
:class="{ active: proportion === item.value }"
|
:class="{ active: proportion === item.value }"
|
||||||
@ -20,8 +20,8 @@
|
|||||||
<div class="section">
|
<div class="section">
|
||||||
<h3>选择分辨率</h3>
|
<h3>选择分辨率</h3>
|
||||||
<div class="resolution-options">
|
<div class="resolution-options">
|
||||||
<div
|
<div
|
||||||
v-for="item in resolutionOptions"
|
v-for="item in resolutionOptions"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
class="resolution-item"
|
class="resolution-item"
|
||||||
:class="{ active: resolution === item.value }"
|
:class="{ active: resolution === item.value }"
|
||||||
@ -107,7 +107,7 @@ const getProportionStyle = (value) => {
|
|||||||
const [w, h] = value.split(':').map(Number)
|
const [w, h] = value.split(':').map(Number)
|
||||||
const aspectRatio = w / h
|
const aspectRatio = w / h
|
||||||
const baseSize = 20
|
const baseSize = 20
|
||||||
|
|
||||||
if (aspectRatio > 1) {
|
if (aspectRatio > 1) {
|
||||||
return {
|
return {
|
||||||
'--width': `${baseSize}px`,
|
'--width': `${baseSize}px`,
|
||||||
@ -148,11 +148,11 @@ const getProportionStyle = (value) => {
|
|||||||
.section{
|
.section{
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
|
|
||||||
&:last-child{
|
&:last-child{
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
h3{
|
h3{
|
||||||
font-family: "Microsoft YaHei";
|
font-family: "Microsoft YaHei";
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -185,7 +185,7 @@ const getProportionStyle = (value) => {
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
text-align: bottom;
|
text-align: bottom;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
||||||
&::before{
|
&::before{
|
||||||
content: '';
|
content: '';
|
||||||
width: var(--width, 20px);
|
width: var(--width, 20px);
|
||||||
@ -195,11 +195,11 @@ const getProportionStyle = (value) => {
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
border: 2px solid #999;
|
border: 2px solid #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
background: #e0e0e0;
|
background: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active{
|
&.active{
|
||||||
color: #000F33;
|
color: #000F33;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
@ -228,11 +228,11 @@ const getProportionStyle = (value) => {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
color: #666;
|
color: #666;
|
||||||
|
|
||||||
&:hover{
|
&:hover{
|
||||||
background: #e0e0e0;
|
background: #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active{
|
&.active{
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
color: #000000;
|
color: #000000;
|
||||||
|
|||||||
@ -53,20 +53,20 @@ const quantityOptions = computed(() => props.options)
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #E8E9EB;
|
border: 1px solid #E8E9EB;
|
||||||
background: #f5f6f7;
|
background: #f5f6f7;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #e9eaeb;
|
background: #e9eaeb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.select-text) {
|
:deep(.select-text) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
:deep(.dropdown-menu) {
|
||||||
min-width: 136px;
|
min-width: 136px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
:deep(.dropdown-item) {
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|||||||
@ -25,9 +25,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<el-upload
|
<el-upload
|
||||||
v-for="i in maxImages"
|
v-for="i in maxImages"
|
||||||
|
v-show="false"
|
||||||
:key="i"
|
:key="i"
|
||||||
:ref="el => setUploadRef(el, i - 1)"
|
:ref="el => setUploadRef(el, i - 1)"
|
||||||
v-show="false"
|
|
||||||
:action="uploadurl"
|
:action="uploadurl"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
:before-upload="beforeUpload"
|
:before-upload="beforeUpload"
|
||||||
@ -90,9 +90,9 @@ watch(() => props.modelValue, async (newVal) => {
|
|||||||
if (isUploading.value) {
|
if (isUploading.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imageList.value = [...newVal]
|
imageList.value = [...newVal]
|
||||||
|
|
||||||
const newPreviewList = []
|
const newPreviewList = []
|
||||||
for (const img of newVal) {
|
for (const img of newVal) {
|
||||||
let previewImg = { ...img }
|
let previewImg = { ...img }
|
||||||
@ -140,16 +140,16 @@ const beforeUpload = (rawFile) => {
|
|||||||
|
|
||||||
const handleSuccess = (response, uploadFile, index) => {
|
const handleSuccess = (response, uploadFile, index) => {
|
||||||
ElMessage.success('上传成功')
|
ElMessage.success('上传成功')
|
||||||
|
|
||||||
isUploading.value = true
|
isUploading.value = true
|
||||||
|
|
||||||
const localUrl = URL.createObjectURL(uploadFile.raw)
|
const localUrl = URL.createObjectURL(uploadFile.raw)
|
||||||
|
|
||||||
const newImage = {
|
const newImage = {
|
||||||
uid: uploadFile.uid,
|
uid: uploadFile.uid,
|
||||||
url: response.url
|
url: response.url
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageList.value[index]) {
|
if (imageList.value[index]) {
|
||||||
const previewItem = localPreviewList.value[index]
|
const previewItem = localPreviewList.value[index]
|
||||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
||||||
@ -169,9 +169,9 @@ const handleSuccess = (response, uploadFile, index) => {
|
|||||||
serverUrl: response.url
|
serverUrl: response.url
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('update:modelValue', [...imageList.value])
|
emit('update:modelValue', [...imageList.value])
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
isUploading.value = false
|
isUploading.value = false
|
||||||
})
|
})
|
||||||
@ -186,7 +186,7 @@ const handleDelete = (index) => {
|
|||||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
||||||
URL.revokeObjectURL(previewItem.url)
|
URL.revokeObjectURL(previewItem.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
localPreviewList.value.splice(index, 1)
|
localPreviewList.value.splice(index, 1)
|
||||||
imageList.value.splice(index, 1)
|
imageList.value.splice(index, 1)
|
||||||
emit('update:modelValue', [...imageList.value])
|
emit('update:modelValue', [...imageList.value])
|
||||||
|
|||||||
@ -44,15 +44,14 @@ const fetchConfig = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fetchConfig()
|
fetchConfig()
|
||||||
|
|
||||||
watch(() => videoConfig.value, (newConfig) => {
|
watch(() => videoConfig.value, (newConfig) => {
|
||||||
const models = newConfig[props.videoPattern] || []
|
const models = newConfig[props.videoPattern] || []
|
||||||
if (models.length > 0) {
|
if (models.length > 0) {
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
const enabledModels = models.filter((m) => !m.disabled)
|
||||||
if (enabledModels.length > 0) {
|
if (enabledModels.length > 0) {
|
||||||
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
|
const currentModelExists = enabledModels.find((m) => m.value === props.modelValue)
|
||||||
if (!currentModelExists) {
|
if (!currentModelExists) {
|
||||||
model.value = enabledModels[0].value
|
model.value = enabledModels[0].value
|
||||||
}
|
}
|
||||||
@ -60,7 +59,6 @@ watch(() => videoConfig.value, (newConfig) => {
|
|||||||
}
|
}
|
||||||
}, { deep: true })
|
}, { deep: true })
|
||||||
|
|
||||||
|
|
||||||
const model = computed({
|
const model = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (value) => {
|
set: (value) => {
|
||||||
@ -91,9 +89,9 @@ const getModelType = (value) => {
|
|||||||
watch(() => props.videoPattern, (newPattern) => {
|
watch(() => props.videoPattern, (newPattern) => {
|
||||||
const models = videoConfig.value[newPattern] || []
|
const models = videoConfig.value[newPattern] || []
|
||||||
if (models.length > 0) {
|
if (models.length > 0) {
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
const enabledModels = models.filter((m) => !m.disabled)
|
||||||
if (enabledModels.length > 0) {
|
if (enabledModels.length > 0) {
|
||||||
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
|
const currentModelExists = enabledModels.find((m) => m.value === props.modelValue)
|
||||||
if (!currentModelExists) {
|
if (!currentModelExists) {
|
||||||
model.value = enabledModels[0].value
|
model.value = enabledModels[0].value
|
||||||
}
|
}
|
||||||
@ -103,9 +101,9 @@ watch(() => props.videoPattern, (newPattern) => {
|
|||||||
|
|
||||||
watch(() => props.modelValue, (newValue) => {
|
watch(() => props.modelValue, (newValue) => {
|
||||||
const models = videoConfig.value[props.videoPattern] || []
|
const models = videoConfig.value[props.videoPattern] || []
|
||||||
const currentModel = models.find(m => m.value === newValue)
|
const currentModel = models.find((m) => m.value === newValue)
|
||||||
if (currentModel && currentModel.disabled) {
|
if (currentModel && currentModel.disabled) {
|
||||||
const enabledModels = models.filter(m => !m.disabled)
|
const enabledModels = models.filter((m) => !m.disabled)
|
||||||
if (enabledModels.length > 0) {
|
if (enabledModels.length > 0) {
|
||||||
model.value = enabledModels[0].value
|
model.value = enabledModels[0].value
|
||||||
}
|
}
|
||||||
@ -121,24 +119,24 @@ watch(() => props.modelValue, (newValue) => {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #E8E9EB;
|
border: 1px solid #E8E9EB;
|
||||||
background: #f5f6f7;
|
background: #f5f6f7;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: #e9eaeb;
|
background: #e9eaeb;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.select-text) {
|
:deep(.select-text) {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-menu) {
|
:deep(.dropdown-menu) {
|
||||||
max-height: 510px;
|
max-height: 510px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.dropdown-item) {
|
:deep(.dropdown-item) {
|
||||||
min-width: 120px;
|
min-width: 120px;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background: rgba(0, 15, 51, 0.10);
|
background: rgba(0, 15, 51, 0.10);
|
||||||
color: #000F33;
|
color: #000F33;
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import { useDisplayStore, useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
import { getToken, setToken } from '@/utils/auth'
|
import { getToken, setToken } from '@/utils/auth'
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -30,7 +30,7 @@ const router = createRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
router.beforeEach(async (to, from) => {
|
router.beforeEach(async (to, from) => {
|
||||||
if(to.query.token){
|
if (to.query.token) {
|
||||||
setToken(to.query.token)
|
setToken(to.query.token)
|
||||||
} else {
|
} else {
|
||||||
// 检查是否有 token
|
// 检查是否有 token
|
||||||
|
|||||||
@ -8,12 +8,12 @@ const DisplayStoreSetup = () => {
|
|||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const currentResultData = ref(null)
|
const currentResultData = ref(null)
|
||||||
const dialogBoxRef = ref(null)
|
const dialogBoxRef = ref(null)
|
||||||
|
|
||||||
const canvasVisible = ref(false)
|
const canvasVisible = ref(false)
|
||||||
const canvasImage = ref('')
|
const canvasImage = ref('')
|
||||||
const canvasReferenceImages = ref([])
|
const canvasReferenceImages = ref([])
|
||||||
const canvasSource = ref('')
|
const canvasSource = ref('')
|
||||||
|
|
||||||
const addGeneratingItem = (item) => {
|
const addGeneratingItem = (item) => {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
id: item.taskId || crypto.randomUUID(),
|
id: item.taskId || crypto.randomUUID(),
|
||||||
@ -26,29 +26,29 @@ const DisplayStoreSetup = () => {
|
|||||||
tempList.value.unshift(newItem)
|
tempList.value.unshift(newItem)
|
||||||
return newItem
|
return newItem
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItemToSuccess = (taskId, fileUrls) => {
|
const updateItemToSuccess = (taskId, fileUrls) => {
|
||||||
const index = tempList.value.findIndex(item => item.id === taskId)
|
const index = tempList.value.findIndex((item) => item.id === taskId)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
tempList.value[index].status = 'success'
|
tempList.value[index].status = 'success'
|
||||||
tempList.value[index].files = Array.isArray(fileUrls) ? fileUrls : [fileUrls]
|
tempList.value[index].files = Array.isArray(fileUrls) ? fileUrls : [fileUrls]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initHistoryList = (historyList) => {
|
const initHistoryList = (historyList) => {
|
||||||
tempList.value = historyList
|
tempList.value = historyList
|
||||||
currentPage.value = 1
|
currentPage.value = 1
|
||||||
hasMoreData.value = true
|
hasMoreData.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const prependHistoryList = (historyList) => {
|
const prependHistoryList = (historyList) => {
|
||||||
tempList.value = [...historyList, ...tempList.value]
|
tempList.value = [...historyList, ...tempList.value]
|
||||||
}
|
}
|
||||||
|
|
||||||
const appendHistoryList = (historyList) => {
|
const appendHistoryList = (historyList) => {
|
||||||
tempList.value = [...tempList.value, ...historyList]
|
tempList.value = [...tempList.value, ...historyList]
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetPagination = () => {
|
const resetPagination = () => {
|
||||||
currentPage.value = 0
|
currentPage.value = 0
|
||||||
hasMoreData.value = true
|
hasMoreData.value = true
|
||||||
@ -56,26 +56,26 @@ const DisplayStoreSetup = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteHistoryItem = (id) => {
|
const deleteHistoryItem = (id) => {
|
||||||
const index = tempList.value.findIndex(item => item.id === id)
|
const index = tempList.value.findIndex((item) => item.id === id)
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
tempList.value.splice(index, 1)
|
tempList.value.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollToBottom = async () => {
|
const scrollToBottom = async () => {
|
||||||
const refValue = scrollerRef.value
|
const refValue = scrollerRef.value
|
||||||
|
|
||||||
if (!refValue) {
|
if (!refValue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof refValue.scrollToBottom === 'function') {
|
if (typeof refValue.scrollToBottom === 'function') {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
refValue.scrollToBottom()
|
refValue.scrollToBottom()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const scrollerEl = refValue.$el
|
const scrollerEl = refValue.$el
|
||||||
if (scrollerEl) {
|
if (scrollerEl) {
|
||||||
const viewport = scrollerEl.querySelector('.vue-recycle-scroller__viewport')
|
const viewport = scrollerEl.querySelector('.vue-recycle-scroller__viewport')
|
||||||
@ -83,7 +83,7 @@ const DisplayStoreSetup = () => {
|
|||||||
viewport.scrollTop = viewport.scrollHeight
|
viewport.scrollTop = viewport.scrollHeight
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof refValue.scrollToItem === 'function' && tempList.value && tempList.value.length > 0) {
|
if (typeof refValue.scrollToItem === 'function' && tempList.value && tempList.value.length > 0) {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
refValue.scrollToItem(tempList.value.length - 1)
|
refValue.scrollToItem(tempList.value.length - 1)
|
||||||
@ -92,28 +92,28 @@ const DisplayStoreSetup = () => {
|
|||||||
console.error('滚动出错:', error)
|
console.error('滚动出错:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setResultData = (data) => {
|
const setResultData = (data) => {
|
||||||
currentResultData.value = data
|
currentResultData.value = data
|
||||||
}
|
}
|
||||||
|
|
||||||
const setDialogBoxRef = (ref) => {
|
const setDialogBoxRef = (ref) => {
|
||||||
dialogBoxRef.value = ref
|
dialogBoxRef.value = ref
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerGenerateWithResult = async () => {
|
const triggerGenerateWithResult = async () => {
|
||||||
if (dialogBoxRef.value && currentResultData.value) {
|
if (dialogBoxRef.value && currentResultData.value) {
|
||||||
await dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
|
await dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
|
||||||
await dialogBoxRef.value.handleStart()
|
await dialogBoxRef.value.handleStart()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fillParamsForEdit = () => {
|
const fillParamsForEdit = () => {
|
||||||
if (dialogBoxRef.value && currentResultData.value) {
|
if (dialogBoxRef.value && currentResultData.value) {
|
||||||
dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
|
dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openCanvas = (data) => {
|
const openCanvas = (data) => {
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
canvasImage.value = data
|
canvasImage.value = data
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
const ParamStoreSetup = () => {
|
const ParamStoreSetup = () => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export async function generateFilename(url, prefix = 'image') {
|
|||||||
|
|
||||||
// 如果URL中没有文件名或扩展名,根据类型生成
|
// 如果URL中没有文件名或扩展名,根据类型生成
|
||||||
if (!filename || !filename.includes('.')) {
|
if (!filename || !filename.includes('.')) {
|
||||||
const timestamp = new Date().getTime()
|
const timestamp = Date.now()
|
||||||
// 根据URL内容推断文件类型,否则默认为png
|
// 根据URL内容推断文件类型,否则默认为png
|
||||||
const extension = url.includes('.jpg') || url.includes('.jpeg')
|
const extension = url.includes('.jpg') || url.includes('.jpeg')
|
||||||
? '.jpg'
|
? '.jpg'
|
||||||
@ -21,7 +21,7 @@ export async function generateFilename(url, prefix = 'image') {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('URL解析失败:', error)
|
console.error('URL解析失败:', error)
|
||||||
// 如果URL解析失败,生成默认文件名
|
// 如果URL解析失败,生成默认文件名
|
||||||
const timestamp = new Date().getTime()
|
const timestamp = Date.now()
|
||||||
return `${prefix}_${timestamp}.png`
|
return `${prefix}_${timestamp}.png`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,21 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div style="width: 100%;display: flex;justify-content: center;align-items: center;transform: rotate(180deg);">
|
<div style="width: 100%;display: flex;justify-content: center;align-items: center;transform: rotate(180deg);">
|
||||||
<div class="primary-box" :class="{ 'none-primary-box': props.item.status === 'none' }">
|
<div class="primary-box" :class="{ 'none-primary-box': props.item.status === 'none' }">
|
||||||
|
<div ref="promptContainerRef" class="prompt-container">
|
||||||
<div class="prompt-container" ref="promptContainerRef">
|
<div ref="promptWrapperRef" class="prompt-wrapper">
|
||||||
<div class="prompt-wrapper" ref="promptWrapperRef">
|
<div ref="promptRef" class="prompt" :class="{ expanded: isHovering }" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
||||||
<div class="prompt" ref="promptRef" :class="{ 'expanded': isHovering }" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
|
||||||
<span class="prompt-text">
|
<span class="prompt-text">
|
||||||
{{ props.item.generateData.prompt || '生成图片' }}
|
{{ props.item.generateData.prompt || '生成图片' }}
|
||||||
<i-ep-DocumentCopy class="Copy" @click.stop="copyPrompt"/>
|
<i-ep-DocumentCopy class="Copy" @click.stop="copyPrompt" />
|
||||||
</span>
|
</span>
|
||||||
<div class="generate-data internal" v-show="!isHovering && !showExternalGenerateData">
|
<div v-show="!isHovering && !showExternalGenerateData" class="generate-data internal">
|
||||||
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
||||||
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="generate-data external" v-show="!isHovering && showExternalGenerateData">
|
<div v-show="!isHovering && showExternalGenerateData" class="generate-data external">
|
||||||
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
||||||
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -49,7 +48,7 @@
|
|||||||
|
|
||||||
<!-- 已完成 图片 -->
|
<!-- 已完成 图片 -->
|
||||||
<div v-if="props.item.status === 'success' && props.item.type === 'Painting'" class="box success-box">
|
<div v-if="props.item.status === 'success' && props.item.type === 'Painting'" class="box success-box">
|
||||||
<div v-for="(file, index) in props.item.files" :key="index" class="one-box" :class="{ 'collected': isCollected(file) }" @mouseenter="hoverIndex = index" @mouseleave="hoverIndex = -1">
|
<div v-for="(file, index) in props.item.files" :key="index" class="one-box" :class="{ collected: isCollected(file) }" @mouseenter="hoverIndex = index" @mouseleave="hoverIndex = -1">
|
||||||
<!-- <img :src="file" alt="index" class="img" /> -->
|
<!-- <img :src="file" alt="index" class="img" /> -->
|
||||||
<Img :src="file" alt="index" class="img" />
|
<Img :src="file" alt="index" class="img" />
|
||||||
|
|
||||||
@ -58,14 +57,14 @@
|
|||||||
<span v-if="hoverIndex === index" class="line" />
|
<span v-if="hoverIndex === index" class="line" />
|
||||||
<div class="left-top-btn collect-btn" @click="addCollection(file)"><img :src="isCollected(file) ? collectionActiveIcon : collectionIcon" /></div>
|
<div class="left-top-btn collect-btn" @click="addCollection(file)"><img :src="isCollected(file) ? collectionActiveIcon : collectionIcon" /></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-tooltip
|
<el-tooltip
|
||||||
effect="dark"
|
effect="dark"
|
||||||
content="画笔"
|
content="画笔"
|
||||||
placement="top"
|
placement="top"
|
||||||
:hide-after="0"
|
:hide-after="0"
|
||||||
>
|
>
|
||||||
<div @click.stop="AIbrush(file, index)" class="bottom-brush">
|
<div class="bottom-brush" @click.stop="AIbrush(file, index)">
|
||||||
<img :src="brush" />
|
<img :src="brush" />
|
||||||
</div>
|
</div>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
@ -74,7 +73,7 @@
|
|||||||
|
|
||||||
<!-- 已完成 视频 -->
|
<!-- 已完成 视频 -->
|
||||||
<div v-if="props.item.status === 'success' && props.item.type === 'Video'" class="box success-box">
|
<div v-if="props.item.status === 'success' && props.item.type === 'Video'" class="box success-box">
|
||||||
<div class="one-box" :class="{ 'collected': isCollected(props.item.files[0]) }" @mouseenter="hoverIndex = 0" @mouseleave="hoverIndex = -1">
|
<div class="one-box" :class="{ collected: isCollected(props.item.files[0]) }" @mouseenter="hoverIndex = 0" @mouseleave="hoverIndex = -1">
|
||||||
<!-- <img :src="file" alt="index" class="img" /> -->
|
<!-- <img :src="file" alt="index" class="img" /> -->
|
||||||
<video :src="props.item.files[0]" class="video" controls playsinline />
|
<video :src="props.item.files[0]" class="video" controls playsinline />
|
||||||
|
|
||||||
@ -97,16 +96,16 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { cancelOrCollect, deleteGenerateHistory } from '@/apis/display'
|
||||||
|
import againGenerateIcon from '@/assets/display/againGenerate.svg'
|
||||||
import brush from '@/assets/display/brush.svg'
|
import brush from '@/assets/display/brush.svg'
|
||||||
import collectionIcon from '@/assets/display/collection.svg'
|
|
||||||
import collectionActiveIcon from '@/assets/display/collection-active.svg'
|
import collectionActiveIcon from '@/assets/display/collection-active.svg'
|
||||||
|
import collectionIcon from '@/assets/display/collection.svg'
|
||||||
|
import deleteImageIcon from '@/assets/display/deleteImage.svg'
|
||||||
|
import reEditIcon from '@/assets/display/reEdit.svg'
|
||||||
|
import Img from '@/components/Img/index.vue'
|
||||||
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
|
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
|
||||||
import { downloadImage } from '@/utils/downloadImage.js'
|
import { downloadImage } from '@/utils/downloadImage.js'
|
||||||
import reEditIcon from '@/assets/display/reEdit.svg'
|
|
||||||
import againGenerateIcon from '@/assets/display/againGenerate.svg'
|
|
||||||
import deleteImageIcon from '@/assets/display/deleteImage.svg'
|
|
||||||
import Img from '@/components/Img/index.vue'
|
|
||||||
import { cancelOrCollect, deleteGenerateHistory } from '@/apis/display'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
@ -134,22 +133,22 @@ const checkTextOverflow = () => {
|
|||||||
const lineHeight = 22.5
|
const lineHeight = 22.5
|
||||||
const padding = 8
|
const padding = 8
|
||||||
const twoLineHeight = lineHeight * 2 + padding
|
const twoLineHeight = lineHeight * 2 + padding
|
||||||
|
|
||||||
promptRef.value.style.maxHeight = 'none'
|
promptRef.value.style.maxHeight = 'none'
|
||||||
promptRef.value.style.overflow = 'hidden'
|
promptRef.value.style.overflow = 'hidden'
|
||||||
|
|
||||||
const actualHeight = promptRef.value.scrollHeight
|
const actualHeight = promptRef.value.scrollHeight
|
||||||
const lineCount = Math.ceil((actualHeight - padding) / lineHeight)
|
const lineCount = Math.ceil((actualHeight - padding) / lineHeight)
|
||||||
|
|
||||||
if (!isHovering.value) {
|
if (!isHovering.value) {
|
||||||
promptRef.value.style.maxHeight = `${twoLineHeight}px`
|
promptRef.value.style.maxHeight = `${twoLineHeight}px`
|
||||||
promptRef.value.style.overflow = 'hidden'
|
promptRef.value.style.overflow = 'hidden'
|
||||||
}
|
}
|
||||||
|
|
||||||
showExternalGenerateData.value = lineCount >= 3
|
showExternalGenerateData.value = lineCount >= 3
|
||||||
|
|
||||||
if (!isHovering.value) {
|
if (!isHovering.value) {
|
||||||
if(lineCount >= 3){
|
if (lineCount >= 3) {
|
||||||
promptContainerRef.value.style.height = `${twoLineHeight}px`
|
promptContainerRef.value.style.height = `${twoLineHeight}px`
|
||||||
} else {
|
} else {
|
||||||
promptContainerRef.value.style.height = `${actualHeight}px`
|
promptContainerRef.value.style.height = `${actualHeight}px`
|
||||||
@ -163,8 +162,8 @@ watch(isHovering, (newVal) => {
|
|||||||
if (promptRef.value) {
|
if (promptRef.value) {
|
||||||
const lineHeight = 22.5
|
const lineHeight = 22.5
|
||||||
const padding = 8
|
const padding = 8
|
||||||
const twoLineHeight = lineHeight * 2 + padding
|
const twoLineHeight = lineHeight * 2 + padding
|
||||||
|
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
promptRef.value.style.maxHeight = 'none'
|
promptRef.value.style.maxHeight = 'none'
|
||||||
promptRef.value.style.overflow = 'visible'
|
promptRef.value.style.overflow = 'visible'
|
||||||
@ -208,7 +207,7 @@ const AIbrush = (file, index) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const reEdit = () => {
|
const reEdit = () => {
|
||||||
if(props.item.generateData?.modelType === 'edit'){
|
if (props.item.generateData?.modelType === 'edit') {
|
||||||
ElMessage.error('画笔生成的任务不能重新编辑')
|
ElMessage.error('画笔生成的任务不能重新编辑')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -217,7 +216,7 @@ const reEdit = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const againGenerate = () => {
|
const againGenerate = () => {
|
||||||
if(props.item.generateData?.modelType === 'edit'){
|
if (props.item.generateData?.modelType === 'edit') {
|
||||||
ElMessage.error('画笔生成的任务不能再次生成')
|
ElMessage.error('画笔生成的任务不能再次生成')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -276,7 +275,7 @@ const addCollection = async (url) => {
|
|||||||
const res = await cancelOrCollect({
|
const res = await cancelOrCollect({
|
||||||
taskId: props.item.id,
|
taskId: props.item.id,
|
||||||
userId: useUser.userInfo.id,
|
userId: useUser.userInfo.id,
|
||||||
url: url,
|
url
|
||||||
})
|
})
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
ElMessage.success(res.message || '操作成功')
|
ElMessage.success(res.message || '操作成功')
|
||||||
@ -342,7 +341,7 @@ const copyPrompt = async () => {
|
|||||||
width: 102%;
|
width: 102%;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
|
||||||
&.expanded{
|
&.expanded{
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -381,12 +380,12 @@ const copyPrompt = async () => {
|
|||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
|
|
||||||
&.internal{
|
&.internal{
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.external{
|
&.external{
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -16px;
|
right: -16px;
|
||||||
|
|||||||
@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="line"></span>
|
<span class="line"></span>
|
||||||
<div class="btn">
|
<div class="btn">
|
||||||
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto" >
|
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto">
|
||||||
<template #prefix>
|
<template #prefix>
|
||||||
<i-ep-Star />
|
<i-ep-Star />
|
||||||
</template>
|
</template>
|
||||||
@ -43,8 +43,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<VirtualScroller
|
<VirtualScroller
|
||||||
ref="scrollerRef"
|
|
||||||
v-if="props.if"
|
v-if="props.if"
|
||||||
|
ref="scrollerRef"
|
||||||
:items="list"
|
:items="list"
|
||||||
key-field="id"
|
key-field="id"
|
||||||
:estimated-height="300"
|
:estimated-height="300"
|
||||||
@ -66,17 +66,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
|
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import Set from './components/set.vue'
|
import { useRouter } from 'vue-router'
|
||||||
import RefreshOverlay from './components/RefreshOverlay.vue'
|
import { requestTaskHistory } from '@/apis/display'
|
||||||
|
import Canvas from '@/components/canvas/index.vue'
|
||||||
import Select from '@/components/Select/index.vue'
|
import Select from '@/components/Select/index.vue'
|
||||||
import { VirtualScroller } from '@/components/virtual-scroller'
|
import { VirtualScroller } from '@/components/virtual-scroller'
|
||||||
import Canvas from '@/components/canvas/index.vue'
|
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
|
||||||
import { requestTaskHistory } from '@/apis/display'
|
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { getChargeType } from '@/utils/taskPolling'
|
|
||||||
import { getPlatformCode } from '@/utils/modelApi'
|
import { getPlatformCode } from '@/utils/modelApi'
|
||||||
|
import { getChargeType } from '@/utils/taskPolling'
|
||||||
|
import RefreshOverlay from './components/RefreshOverlay.vue'
|
||||||
|
import Set from './components/set.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
if: {
|
if: {
|
||||||
@ -138,7 +138,7 @@ const toggleDisplay = (newValue, oldValue) => {
|
|||||||
const conversion = (newlist) => {
|
const conversion = (newlist) => {
|
||||||
const temp = newlist.map((item) => {
|
const temp = newlist.map((item) => {
|
||||||
// 从 outputs 扁平数组提取 URL
|
// 从 outputs 扁平数组提取 URL
|
||||||
const files = item.outputs?.map(o => o.url) || []
|
const files = item.outputs?.map((o) => o.url) || []
|
||||||
const request = item.request || {}
|
const request = item.request || {}
|
||||||
const generateData = {
|
const generateData = {
|
||||||
model: item.model_name || '',
|
model: item.model_name || '',
|
||||||
@ -152,7 +152,7 @@ const conversion = (newlist) => {
|
|||||||
customHight: request.customHight,
|
customHight: request.customHight,
|
||||||
duration: request.duration || '',
|
duration: request.duration || '',
|
||||||
videoPattern: request.videoPattern || '',
|
videoPattern: request.videoPattern || '',
|
||||||
modelParams: { ...request },
|
modelParams: { ...request }
|
||||||
}
|
}
|
||||||
// 将 API status 映射为 UI 展示状态
|
// 将 API status 映射为 UI 展示状态
|
||||||
let uiStatus = 'success'
|
let uiStatus = 'success'
|
||||||
@ -168,7 +168,7 @@ const conversion = (newlist) => {
|
|||||||
generateData,
|
generateData,
|
||||||
time: item.created_at || '',
|
time: item.created_at || '',
|
||||||
files,
|
files,
|
||||||
collectStatus: item.collectStatus || {},
|
collectStatus: item.collectStatus || {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return temp
|
return temp
|
||||||
@ -235,7 +235,6 @@ const fetchHistory = async (isLoadMore = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hasMoreData.value = dataList.length === 10
|
hasMoreData.value = dataList.length === 10
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取历史失败:', error)
|
console.error('获取历史失败:', error)
|
||||||
ElMessage({
|
ElMessage({
|
||||||
@ -380,5 +379,4 @@ onBeforeUnmount(() => {
|
|||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted, watch } from 'vue'
|
import { computed, onMounted, ref, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import display from './display/index.vue'
|
|
||||||
import { useDisplayStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
|
import display from './display/index.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const useDisplay = useDisplayStore()
|
const useDisplay = useDisplayStore()
|
||||||
|
|||||||
@ -4,14 +4,14 @@
|
|||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<div class="lock-icon">
|
<div class="lock-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 2C9.243 2 7 4.243 7 7V10H6C4.897 10 4 10.897 4 12V20C4 21.103 4.897 22 6 22H18C19.103 22 20 21.103 20 20V12C20 10.897 19.103 10 18 10H17V7C17 4.243 14.757 2 12 2ZM12 4C13.654 4 15 5.346 15 7V10H9V7C9 5.346 10.346 4 12 4ZM6 12H18V20H6V12Z" fill="currentColor"/>
|
<path d="M12 2C9.243 2 7 4.243 7 7V10H6C4.897 10 4 10.897 4 12V20C4 21.103 4.897 22 6 22H18C19.103 22 20 21.103 20 20V12C20 10.897 19.103 10 18 10H17V7C17 4.243 14.757 2 12 2ZM12 4C13.654 4 15 5.346 15 7V10H9V7C9 5.346 10.346 4 12 4ZM6 12H18V20H6V12Z" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 class="title">账号未登录</h1>
|
<h1 class="title">账号未登录</h1>
|
||||||
<p class="description">请先登录以访问完整功能</p>
|
<p class="description">请先登录以访问完整功能</p>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<button class="btn-primary" @click="handleLogin">
|
<button class="btn-primary" @click="handleLogin">
|
||||||
立即登录
|
立即登录
|
||||||
@ -20,7 +20,7 @@
|
|||||||
注册账号
|
注册账号
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="features">
|
<div class="features">
|
||||||
<div class="feature-item">
|
<div class="feature-item">
|
||||||
<div class="feature-icon">✨</div>
|
<div class="feature-icon">✨</div>
|
||||||
@ -36,7 +36,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="background-decoration">
|
<div class="background-decoration">
|
||||||
<div class="circle circle-1"></div>
|
<div class="circle circle-1"></div>
|
||||||
<div class="circle circle-2"></div>
|
<div class="circle circle-2"></div>
|
||||||
@ -311,15 +311,15 @@ const handleRegister = () => {
|
|||||||
.login-content {
|
.login-content {
|
||||||
padding: 40px 20px;
|
padding: 40px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.features {
|
.features {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 15px;
|
gap: 15px;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user