chore: 代码格式化统一(空格、换行、属性排序、LF规范化)

This commit is contained in:
王佑琳 2026-06-09 18:26:37 +08:00
parent b964c826ce
commit e98ff3a2c4
26 changed files with 501 additions and 509 deletions

View File

@ -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/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 -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 *)"
]
}
}

View File

@ -43,5 +43,5 @@ export const getUserInfo = () => {
}
export const checkUsertoken = () => {
return service.post(`/login/validateToken`)
return service.post('/login/validateToken')
}

View File

@ -1,6 +1,6 @@
<template>
<div class="custom-popover" ref="popoverRef">
<div class="popover-trigger" ref="triggerRef" @click.stop="togglePopover">
<div ref="popoverRef" class="custom-popover">
<div ref="triggerRef" class="popover-trigger" @click.stop="togglePopover">
<slot name="reference" />
</div>
<Teleport to="body">
@ -143,10 +143,10 @@ const handleClickOutside = (e) => {
const contentEl = contentRef.value
if (
triggerEl &&
!triggerEl.contains(e.target) &&
contentEl &&
!contentEl.contains(e.target)
triggerEl
&& !triggerEl.contains(e.target)
&& contentEl
&& !contentEl.contains(e.target)
) {
visible.value = false
window.__currentOpenPopoverId__ = null

View File

@ -377,7 +377,7 @@ onBeforeUnmount(() => {
.option-group {
/* margin-bottom: 10px; */
&:last-child {
margin-bottom: 0;
}

View File

@ -9,8 +9,8 @@
</div>
<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">
<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="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" />
</svg>
</div>
</div>
@ -32,8 +32,8 @@
ref="editableDivRef"
contenteditable="true"
class="custom-textarea"
@input="handleInput"
:data-placeholder="!inputText ? '请输入提示词或使用圆形/矩形工具' : ''"
@input="handleInput"
></div>
</div>
</div>
@ -48,7 +48,7 @@
@click="currentShape = 'rectangle'"
>
<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>
</div>
@ -59,7 +59,7 @@
@click="currentShape = 'circle'"
>
<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>
</div>
</div>
@ -68,22 +68,22 @@
<!-- 上一步 -->
<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">
<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>
</div>
<!-- 下一步 -->
<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">
<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>
</div>
<!-- 删除 -->
<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">
<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="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" />
</svg>
</div>
</div>
@ -103,8 +103,8 @@
<span class="brush-panel-title">请输入替换内容描述</span>
<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">
<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="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" />
</svg>
</div>
</div>
@ -114,8 +114,8 @@
ref="brushTextareaRef"
contenteditable="true"
class="brush-textarea"
@input="handleBrushInput"
:data-placeholder="!currentShapeDescription ? '请输入描述...' : ''"
@input="handleBrushInput"
></div>
</div>
@ -135,7 +135,7 @@
<img :src="img" alt="参考图" />
<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">
<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>
</div>
</div>
@ -145,7 +145,7 @@
@click="handleUploadReference"
>
<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>
</div>
</div>
@ -160,10 +160,10 @@
</template>
<script setup>
import { generate } from '@/utils/taskPolling'
import { useDisplayStore } from '@/stores'
import request from '@/utils/request'
import { getModelId } from '@/utils/modelApi'
import request from '@/utils/request'
import { generate } from '@/utils/taskPolling'
const props = defineProps({
visible: {
@ -240,11 +240,11 @@ const updateCurrentEditingContent = (description) => {
const colorIndex = currentEditingShapeIndex.value >= 0 ? currentEditingShapeIndex.value : 0
const colorName = colorNames[colorIndex % colorNames.length]
const isFirstShape = shapes.value.length === 0 || (currentEditingShapeIndex.value === 0 && shapes.value.length === 1)
const currentShape = currentEditingShapeIndex.value >= 0 ? shapes.value[currentEditingShapeIndex.value] : null
const shapeType = currentShape ? currentShape.type : 'rectangle'
const shapeWord = shapeType === 'circle' ? '圈' : '框'
if (selectedReferenceImages.value.length > 0) {
const imageIndex = allReferenceImages.value.indexOf(selectedReferenceImages.value[0]) + 2
const prefix = isFirstShape ? '' : ''
@ -268,7 +268,7 @@ watch(() => props.visible, (newVal) => {
historyIndex.value = -1
promptHistory.value = []
promptHistoryIndex.value = -1
allReferenceImages.value = props.referenceImages.map(img => img.url || img)
allReferenceImages.value = props.referenceImages.map((img) => img.url || img)
brushPanelVisible.value = false
isPanelOpen.value = false
currentShapeDescription.value = ''
@ -284,19 +284,19 @@ watch(() => props.visible, (newVal) => {
const initCanvas = () => {
const canvas = canvasRef.value
if (!canvas) return
const ctx = canvas.getContext('2d')
const container = canvas.parentElement
const containerWidth = container.clientWidth
const containerHeight = container.clientHeight
if (currentImage.value) {
let imageUrl = currentImage.value
if (!imageUrl.startsWith('data:')) {
imageUrl = imageUrl.replace('https://sxwz.xueai.art', 'https://talkingdraw.xueai.art')
}
if (imageUrl.startsWith('data:')) {
const img = new Image()
img.onload = () => {
@ -310,8 +310,8 @@ const initCanvas = () => {
img.src = imageUrl
} else {
fetch(imageUrl)
.then(res => res.blob())
.then(blob => {
.then((res) => res.blob())
.then((blob) => {
const img = new Image()
img.onload = () => {
const imgScale = Math.min(containerWidth / img.width, containerHeight / img.height)
@ -363,7 +363,7 @@ const handleMouseDown = (e) => {
const handleMouseMove = (e) => {
if (!isDrawing.value) return
const canvas = canvasRef.value
const ctx = canvas.getContext('2d')
const ratio = canvas.width / canvas.offsetWidth
@ -373,15 +373,15 @@ const handleMouseMove = (e) => {
const savedStartY = startY.value
const savedCurrentX = currentX
const savedCurrentY = currentY
if (bgImage.value) {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(bgImage.value, 0, 0)
shapes.value.forEach(shape => {
shapes.value.forEach((shape) => {
drawShape(ctx, shape)
})
const currentShapeData = {
type: currentShape.value,
startX: savedStartX,
@ -396,10 +396,10 @@ const handleMouseMove = (e) => {
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
shapes.value.forEach(shape => {
shapes.value.forEach((shape) => {
drawShape(ctx, shape)
})
const currentShapeData = {
type: currentShape.value,
startX: startX.value,
@ -414,34 +414,34 @@ const handleMouseMove = (e) => {
const handleMouseUp = (e) => {
if (!isDrawing.value) return
const canvas = canvasRef.value
const ratio = canvas.width / canvas.offsetWidth
const endX = e.offsetX * ratio
const endY = e.offsetY * ratio
if (shapes.value.length >= maxShapes) {
ElMessage.warning('最多只能画5笔')
isDrawing.value = false
return
}
const colorIndex = shapes.value.length
const newShape = {
type: currentShape.value,
startX: startX.value,
startY: startY.value,
endX: endX,
endY: endY,
endX,
endY,
color: shapeColors[colorIndex],
description: '',
referenceImages: []
}
shapes.value.push(newShape)
currentEditingShapeIndex.value = shapes.value.length - 1
isDrawing.value = false
brushPanelVisible.value = true
isPanelOpen.value = true
currentShapeDescription.value = ''
@ -452,7 +452,7 @@ const saveHistory = () => {
history.value = history.value.slice(0, historyIndex.value + 1)
history.value.push([...shapes.value])
historyIndex.value = history.value.length - 1
promptHistory.value = promptHistory.value.slice(0, promptHistoryIndex.value + 1)
promptHistory.value.push(inputText.value)
promptHistoryIndex.value = promptHistory.value.length - 1
@ -463,7 +463,7 @@ const undo = () => {
historyIndex.value--
shapes.value = historyIndex.value >= 0 ? [...history.value[historyIndex.value]] : []
redrawCanvas()
promptHistoryIndex.value--
inputText.value = promptHistoryIndex.value >= 0 ? promptHistory.value[promptHistoryIndex.value] : ''
if (editableDivRef.value) {
@ -477,7 +477,7 @@ const redo = () => {
historyIndex.value++
shapes.value = [...history.value[historyIndex.value]]
redrawCanvas()
promptHistoryIndex.value++
inputText.value = promptHistory.value[promptHistoryIndex.value]
if (editableDivRef.value) {
@ -493,11 +493,11 @@ const deleteShape = () => {
promptHistoryIndex.value = -1
history.value = []
historyIndex.value = -1
if (editableDivRef.value) {
editableDivRef.value.innerHTML = ''
}
redrawCanvas()
}
@ -505,17 +505,17 @@ const redrawCanvas = () => {
const canvas = canvasRef.value
if (!canvas) return
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, canvas.width, canvas.height)
if (bgImage.value) {
ctx.drawImage(bgImage.value, 0, 0)
} else {
ctx.fillStyle = '#fff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
}
shapes.value.forEach(shape => {
shapes.value.forEach((shape) => {
drawShape(ctx, shape)
})
}
@ -523,14 +523,14 @@ const redrawCanvas = () => {
const drawShape = (ctx, shape) => {
ctx.strokeStyle = shape.color || '#ff0000'
ctx.lineWidth = 2
if (shape.type === 'rectangle') {
const width = shape.endX - shape.startX
const height = shape.endY - shape.startY
ctx.strokeRect(shape.startX, shape.startY, width, height)
} else if (shape.type === 'circle') {
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.arc(shape.startX, shape.startY, radius, 0, Math.PI * 2)
@ -553,7 +553,7 @@ const removeReferenceImage = (index) => {
const selectReferenceImage = (index) => {
const img = allReferenceImages.value[index]
const existingIndex = selectedReferenceImages.value.indexOf(img)
if (existingIndex !== -1) {
selectedReferenceImages.value = []
} else {
@ -569,14 +569,14 @@ const handleUploadReference = () => {
input.onchange = async (e) => {
const files = Array.from(e.target.files)
const remainingSlots = 5 - allReferenceImages.value.length
if (remainingSlots <= 0) {
ElMessage.warning('最多只能上传5张图片')
return
}
const filesToUpload = files.slice(0, remainingSlots)
const readFileAsDataURL = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
@ -585,7 +585,7 @@ const handleUploadReference = () => {
reader.readAsDataURL(file)
})
}
const imageDataUrls = await Promise.all(filesToUpload.map(readFileAsDataURL))
allReferenceImages.value.push(...imageDataUrls)
}
@ -597,7 +597,7 @@ const closeBrushPanel = () => {
shapes.value.splice(currentEditingShapeIndex.value, 1)
redrawCanvas()
}
brushPanelVisible.value = false
isPanelOpen.value = false
currentShapeDescription.value = ''
@ -611,7 +611,7 @@ const handleBrushConfirm = () => {
shapes.value[currentEditingShapeIndex.value].description = currentShapeDescription.value
shapes.value[currentEditingShapeIndex.value].referenceImages = [...selectedReferenceImages.value]
}
if (currentEditingContent.value) {
if (editableDivRef.value) {
const currentContent = editableDivRef.value.innerHTML
@ -621,9 +621,9 @@ const handleBrushConfirm = () => {
inputText.value += currentEditingContent.value
}
}
saveHistory()
brushPanelVisible.value = false
isPanelOpen.value = false
currentShapeDescription.value = ''
@ -635,10 +635,10 @@ const getImageAspectRatio = () => {
if (!bgImage.value) {
return null
}
const width = bgImage.value.width
const height = bgImage.value.height
const aspectRatios = [
{ ratio: '4:3', value: 4 / 3 },
{ ratio: '16:9', value: 16 / 9 },
@ -648,12 +648,12 @@ const getImageAspectRatio = () => {
{ ratio: '2:3', value: 2 / 3 },
{ ratio: '3:2', value: 3 / 2 }
]
const currentRatio = width / height
let closest = aspectRatios[0]
let minDiff = Math.abs(currentRatio - closest.value)
for (const item of aspectRatios) {
const diff = Math.abs(currentRatio - item.value)
if (diff < minDiff) {
@ -661,7 +661,7 @@ const getImageAspectRatio = () => {
closest = item
}
}
return {
width,
height,
@ -675,34 +675,34 @@ const handleSend = async () => {
ElMessage.error('请输入提示词')
return
}
isSending.value = true
try {
const canvas = canvasRef.value
const imageData = canvas.toDataURL('image/png')
const imgs = []
if (imageData) {
imgs.push({ name: 'image_1', url: imageData })
}
allReferenceImages.value.forEach((img, index) => {
imgs.push({ name: `image_${index + 2}`, url: img })
})
const uploadImg = async (imgItem) => {
if (!imgItem.url.startsWith('data:') && !imgItem.url.startsWith('blob:')) {
return imgItem
}
const response = await fetch(imgItem.url)
const blob = await response.blob()
const file = new File([blob], `${imgItem.name}.png`, { type: 'image/png' })
const formData = new FormData()
formData.append('file', file)
try {
const result = await request({
url: import.meta.env.VITE_API_WORKFLOW_UPLOAD,
@ -721,11 +721,11 @@ const handleSend = async () => {
return imgItem
}
}
const uploadedImgs = await Promise.all(imgs.map(uploadImg))
const proportion = getImageAspectRatio()
const modelId = await getModelId(props.type, 'GPT')
const generateData = {
@ -743,20 +743,20 @@ const handleSend = async () => {
modelId,
quantity: 1,
params: [
{ name: 'prompt', data: inputText.value + '并且去除掉图1中的框' },
{ name: 'prompt', data: `${inputText.value}并且去除掉图1中的框` },
{ name: 'index', data: 1 },
{ name: 'proportion', data: proportion?.aspectRatio || '4:3' },
{ name: 'proportion', data: proportion?.aspectRatio || '4:3' }
],
imgs: uploadedImgs,
request: JSON.stringify(generateData)
}
emit('send', {
image: imageData,
text: inputText.value,
shapes: shapes.value
})
await generate(data, generateData)
handleClose()
} finally {

View File

@ -1,6 +1,6 @@
<template>
<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 class="sender-top">
@ -11,8 +11,8 @@
<div v-show="showUploader" class="upload-img-container">
<div class="reference-diagram">
<component
v-if="platform.ImageUploader"
:is="platform.ImageUploader"
v-if="platform.ImageUploader"
ref="referenceDiagramRef"
v-model="referenceImages"
v-bind="uploaderBindings"
@ -34,11 +34,11 @@
<div class="prefix-self-wrap">
<component
:is="platform.ModelSelector"
:modelValue="platform.model.value"
@update:modelValue="platform.model.value = $event"
:typeValue="platform.modelType.value"
@update:typeValue="platform.modelType.value = $event"
:model-value="platform.model.value"
:type-value="platform.modelType.value"
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">
<component
@ -62,18 +62,17 @@
</div>
</template>
</Sender>
</div>
</div>
</Transition>
</template>
<script setup>
import { Sender } from 'vue-element-plus-x'
import { useDisplayStore } from '@/stores'
import { generate } from '@/utils/taskPolling'
import { useRouter } from 'vue-router'
import { createPlatform } from '@/platforms/registry.js'
import { useDisplayStore } from '@/stores'
import { getModelId } from '@/utils/modelApi'
import { generate } from '@/utils/taskPolling'
//
import '@/platforms/painting/index.js'
@ -82,7 +81,7 @@ import '@/platforms/video/index.js'
const props = defineProps({
isGenerate: { type: Boolean, default: false },
generate: { type: Boolean, default: false },
type: { type: String, default: 'Painting' },
type: { type: String, default: 'Painting' }
})
const router = useRouter()
@ -99,7 +98,7 @@ const getCurrentConfig = () => {
const visibleControls = computed(() => {
const config = getCurrentConfig()
return platform.value.controls.filter(c => c.show(config))
return platform.value.controls.filter((c) => c.show(config))
})
const showUploader = computed(() => {
@ -148,7 +147,7 @@ const handleStart = async () => {
modelType: p.modelType.value,
prompt: prompt.value,
referenceImages: referenceImages.value,
modelParams: body,
modelParams: body
}
const data = {
@ -157,7 +156,7 @@ const handleStart = async () => {
modelName: p.model.value,
modelId: modelId || '',
body,
request: JSON.stringify(generateData),
request: JSON.stringify(generateData)
}
await generate(data, generateData)
@ -190,7 +189,7 @@ watch(
async ([newModel, newModelType]) => {
if (!newModel) return
await platform.value.loadConfig(newModel, newModelType)
},
}
)
// +

View File

@ -100,6 +100,8 @@ const props = defineProps({
}
})
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
const computedData = computed(() => {
return props.data.length > 0 ? props.data : props.items
})
@ -114,8 +116,6 @@ const computedBuffer = computed(() => {
return props.buffer !== 3 ? props.buffer : props.bufferSize
})
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
const containerRef = ref(null)
const wrapperRef = ref(null)
const renderContainerRef = ref(null)
@ -140,27 +140,27 @@ const containerStyle = computed(() => {
const totalHeight = computed(() => {
let height = 0
const len = computedData.value.length
for (let i = 0; i < len; i++) {
const cachedHeight = itemHeights.value.get(i)
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
}
height += props.bottomPlaceholderHeight
return height
})
const getItemPosition = (index) => {
let offset = 0
for (let i = 0; i < index; i++) {
const cachedHeight = itemHeights.value.get(i)
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
}
const height = itemHeights.value.get(index) ?? props.estimatedHeight
return { offset, height }
}
@ -173,73 +173,73 @@ const visibleRange = computed(() => {
if (!renderContainerRef.value || computedData.value.length === 0) {
return { start: 0, end: 0, offset: 0 }
}
const viewportHeight = containerHeight.value
const currentScrollTop = scrollTop.value
const bufferCount = computedBuffer.value
let startIndex = 0
let endIndex = computedData.value.length - 1
let startOffset = 0
let offset = 0
for (let i = 0; i < computedData.value.length; i++) {
const cachedHeight = itemHeights.value.get(i)
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
if (offset + height > currentScrollTop) {
startIndex = Math.max(0, i - bufferCount)
break
}
offset += height
}
startOffset = 0
for (let i = 0; i < startIndex; i++) {
const cachedHeight = itemHeights.value.get(i)
startOffset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
}
offset = startOffset
endIndex = startIndex
for (let i = startIndex; i < computedData.value.length; i++) {
const cachedHeight = itemHeights.value.get(i)
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
offset += height
if (offset > currentScrollTop + viewportHeight) {
endIndex = Math.min(computedData.value.length - 1, i + bufferCount)
break
}
endIndex = i
}
return { start: Math.min(startIndex, endIndex), end: Math.max(startIndex, endIndex), offset: startOffset }
})
const visibleItems = computed(() => {
const { start, end, offset } = visibleRange.value
const items = []
let currentOffset = offset
for (let i = start; i <= end && i < computedData.value.length; i++) {
const cachedHeight = itemHeights.value.get(i)
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
items.push({
item: computedData.value[i],
index: i,
offset: currentOffset + props.bottomPlaceholderHeight,
height
})
currentOffset += height
}
return items
})
@ -275,7 +275,7 @@ const bottomPlaceholderStyle = computed(() => ({
top: 0,
width: '100%',
height: `${props.bottomPlaceholderHeight}px`,
transform: `translateY(0px)`,
transform: 'translateY(0px)',
zIndex: 1
}))
@ -312,12 +312,12 @@ const setItemRef = (el, index) => {
const measureItem = (index, element) => {
if (!element) return
const firstChild = element.firstElementChild
const targetElement = firstChild || element
const height = Math.ceil(targetElement.offsetHeight)
if (height > 0) {
const cachedHeight = itemHeights.value.get(index)
if (cachedHeight !== height) {
@ -332,10 +332,10 @@ const setupResizeObserver = () => {
if (resizeObserver.value) {
resizeObserver.value.disconnect()
}
resizeObserver.value = new ResizeObserver((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)) {
measureItem(index, entry.target)
}
@ -345,15 +345,15 @@ const setupResizeObserver = () => {
const handleWheel = (event) => {
if (!renderContainerRef.value) return
const { deltaY } = event
const el = renderContainerRef.value
el.scrollBy({
top: -deltaY,
behavior: 'instant'
})
event.preventDefault()
}
@ -363,15 +363,15 @@ const handleScroll = (event) => {
const target = event.target
scrollTop.value = target.scrollTop
isScrolling.value = true
if (scrollTimeout.value) {
clearTimeout(scrollTimeout.value)
}
scrollTimeout.value = setTimeout(() => {
isScrolling.value = false
}, 150)
// ,100ms
if (scrollCleanupTimeout.value) {
clearTimeout(scrollCleanupTimeout.value)
@ -379,19 +379,19 @@ const handleScroll = (event) => {
scrollCleanupTimeout.value = setTimeout(() => {
cleanupExtraItems(visibleItems.value)
}, 300)
const st = target.scrollTop
const scrollHeight = target.scrollHeight
const clientHeight = target.clientHeight
const distanceToContainerTop = st
const distanceToContainerBottom = scrollHeight - st - clientHeight
const distanceToPageTop = distanceToContainerBottom
const distanceToPageBottom = distanceToContainerTop
const isAtPageTop = distanceToPageTop <= 0
const isAtPageBottom = distanceToPageBottom <= 0
emit('scroll', {
target,
scrollTop: st,
@ -402,11 +402,11 @@ const handleScroll = (event) => {
isAtPageTop,
isAtPageBottom
})
if (isAtPageTop) {
emit('scroll-start')
}
if (isAtPageBottom) {
emit('scroll-end')
}
@ -414,9 +414,9 @@ const handleScroll = (event) => {
const scrollToIndex = (index, behavior = 'auto') => {
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
const position = getItemPosition(index)
renderContainerRef.value.scrollTo({
top: position.offset,
behavior
@ -428,10 +428,10 @@ const scrollToBottom = (behavior = 'smooth') => {
pendingScrollToBottom.value = true
return
}
requestAnimationFrame(() => {
if (!renderContainerRef.value) return
renderContainerRef.value.scrollTo({
top: 0,
behavior
@ -441,10 +441,10 @@ const scrollToBottom = (behavior = 'smooth') => {
const scrollToTop = (behavior = 'smooth') => {
if (!renderContainerRef.value) return
requestAnimationFrame(() => {
if (!renderContainerRef.value) return
const scrollHeight = renderContainerRef.value.scrollHeight
renderContainerRef.value.scrollTo({
top: scrollHeight,
@ -479,9 +479,9 @@ const isAtPageTop = () => {
const observeVisibleItems = () => {
if (!resizeObserver.value) return
resizeObserver.value.disconnect()
for (const [index, element] of itemRefs) {
if (element) {
resizeObserver.value.observe(element)
@ -492,24 +492,24 @@ const observeVisibleItems = () => {
watch(() => computedData.value, (newData, oldData) => {
const oldLength = oldData?.length || 0
const newLength = newData.length
if (newLength !== oldLength) {
const newHeights = new Map()
const minLen = Math.min(oldLength, newLength)
for (let i = 0; i < minLen; i++) {
if (itemHeights.value.has(i)) {
newHeights.set(i, itemHeights.value.get(i))
}
}
itemHeights.value = newHeights
nextTick(() => {
observeVisibleItems()
})
}
previousDataLength.value = newLength
}, { deep: false })
@ -527,36 +527,36 @@ watch(visibleItems, (newItems) => {
const cleanupExtraItems = (currentVisibleItems) => {
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
const renderedItems = renderContainerRef.value.querySelectorAll('.virtual-scroller-item')
const toRemove = []
for (const el of renderedItems) {
const dataIndex = parseInt(el.getAttribute('data-index'), 10)
const dataIndex = Number.parseInt(el.getAttribute('data-index'), 10)
// data-index ,
if (!isNaN(dataIndex) && !visibleIndices.has(dataIndex)) {
toRemove.push(el)
}
}
// DOM
for (const el of toRemove) {
if (el.parentNode) {
el.parentNode.removeChild(el)
}
// itemRefs Map
const index = parseInt(el.getAttribute('data-index'), 10)
const index = Number.parseInt(el.getAttribute('data-index'), 10)
if (!isNaN(index)) {
itemRefs.delete(index)
}
}
if (toRemove.length > 0) {
console.log(`[VirtualScroller] 清理了 ${toRemove.length} 个多余DOM元素`)
}
@ -566,13 +566,13 @@ onMounted(() => {
setupResizeObserver()
isInitialized.value = true
previousDataLength.value = computedData.value.length
nextTick(() => {
if (pendingScrollToBottom.value) {
pendingScrollToBottom.value = false
scrollToBottom()
}
observeVisibleItems()
cleanupExtraItems(visibleItems.value)
})
@ -582,15 +582,15 @@ onBeforeUnmount(() => {
if (resizeObserver.value) {
resizeObserver.value.disconnect()
}
if (scrollTimeout.value) {
clearTimeout(scrollTimeout.value)
}
if (scrollCleanupTimeout.value) {
clearTimeout(scrollCleanupTimeout.value)
}
itemRefs.clear()
})
@ -611,56 +611,56 @@ defineExpose({
<style lang="less" scoped>
.virtual-scroller {
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
.virtual-scroller-wrapper {
contain: content;
}
.virtual-scroller-spacer {
flex-shrink: 0;
width: 100%;
}
.virtual-scroller-placeholder {
width: 100%;
}
.virtual-scroller-render-container {
contain: layout style;
&::-webkit-scrollbar {
display: none;
width: 6px;
height: 6px;
}
}
.virtual-scroller-item {
contain: layout style;
backface-visibility: hidden;
perspective: 1000px;
}
.virtual-scroller-bottom-placeholder {
contain: layout style;
}

View File

@ -99,6 +99,8 @@ const props = defineProps({
}
})
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
const computedData = computed(() => {
return props.data.length > 0 ? props.data : props.items
})
@ -113,8 +115,6 @@ const computedBuffer = computed(() => {
return props.buffer !== 3 ? props.buffer : props.bufferSize
})
const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-change'])
const containerRef = ref(null)
const wrapperRef = ref(null)
const renderContainerRef = ref(null)
@ -136,27 +136,27 @@ const totalHeight = computed(() => {
heightVersion.value // measureItem mutate Map
let height = 0
const len = computedData.value.length
for (let i = 0; i < len; i++) {
const cachedHeight = itemHeights.value.get(i)
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
}
height += props.bottomPlaceholderHeight
return height
})
const getItemPosition = (index) => {
let offset = 0
for (let i = 0; i < index; i++) {
const cachedHeight = itemHeights.value.get(i)
offset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
}
const height = itemHeights.value.get(index) ?? props.estimatedHeight
return { offset, height }
}
@ -218,23 +218,23 @@ const visibleRange = computed(() => {
const visibleItems = computed(() => {
const { start, end, offset } = visibleRange.value
const items = []
let currentOffset = offset
for (let i = start; i <= end && i < computedData.value.length; i++) {
const cachedHeight = itemHeights.value.get(i)
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
items.push({
item: computedData.value[i],
index: i,
offset: currentOffset + props.bottomPlaceholderHeight,
height
})
currentOffset += height
}
return items
})
@ -269,7 +269,7 @@ const bottomPlaceholderStyle = computed(() => ({
top: 0,
width: '100%',
height: `${props.bottomPlaceholderHeight}px`,
transform: `translateY(0px)`,
transform: 'translateY(0px)',
zIndex: 1
}))
@ -344,10 +344,10 @@ const setupResizeObserver = () => {
if (resizeObserver.value) {
resizeObserver.value.disconnect()
}
resizeObserver.value = new ResizeObserver((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)) {
measureItem(index, entry.target)
}
@ -357,15 +357,15 @@ const setupResizeObserver = () => {
const handleWheel = (event) => {
if (!renderContainerRef.value) return
const { deltaY } = event
const el = renderContainerRef.value
el.scrollBy({
top: -deltaY,
behavior: 'instant'
})
event.preventDefault()
}
@ -376,15 +376,15 @@ const handleScroll = (event) => {
const st = target.scrollTop
const scrollHeight = target.scrollHeight
const clientHeight = target.clientHeight
const distanceToContainerTop = st
const distanceToContainerBottom = scrollHeight - st - clientHeight
const distanceToPageTop = distanceToContainerBottom
const distanceToPageBottom = distanceToContainerTop
const isAtPageTop = distanceToPageTop <= 0
const isAtPageBottom = distanceToPageBottom <= 0
emit('scroll', {
target,
scrollTop: st,
@ -395,11 +395,11 @@ const handleScroll = (event) => {
isAtPageTop,
isAtPageBottom
})
if (isAtPageTop) {
emit('scroll-start')
}
if (isAtPageBottom) {
emit('scroll-end')
}
@ -407,9 +407,9 @@ const handleScroll = (event) => {
const scrollToIndex = (index, behavior = 'auto') => {
if (!renderContainerRef.value || index < 0 || index >= computedData.value.length) return
const position = getItemPosition(index)
renderContainerRef.value.scrollTo({
top: position.offset,
behavior
@ -421,10 +421,10 @@ const scrollToBottom = (behavior = 'smooth') => {
pendingScrollToBottom.value = true
return
}
requestAnimationFrame(() => {
if (!renderContainerRef.value) return
renderContainerRef.value.scrollTo({
top: 0,
behavior
@ -434,10 +434,10 @@ const scrollToBottom = (behavior = 'smooth') => {
const scrollToTop = (behavior = 'smooth') => {
if (!renderContainerRef.value) return
requestAnimationFrame(() => {
if (!renderContainerRef.value) return
const scrollHeight = renderContainerRef.value.scrollHeight
renderContainerRef.value.scrollTo({
top: scrollHeight,
@ -552,56 +552,56 @@ defineExpose({
<style lang="less" scoped>
.virtual-scroller {
-webkit-overflow-scrolling: touch;
&::-webkit-scrollbar {
display: none;
width: 6px;
height: 6px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.2);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.3);
}
}
.virtual-scroller-wrapper {
contain: content;
}
.virtual-scroller-spacer {
flex-shrink: 0;
width: 100%;
}
.virtual-scroller-placeholder {
width: 100%;
}
.virtual-scroller-render-container {
contain: layout style;
&::-webkit-scrollbar {
display: none;
width: 6px;
height: 6px;
}
}
.virtual-scroller-item {
contain: layout style;
backface-visibility: hidden;
perspective: 1000px;
}
.virtual-scroller-bottom-placeholder {
contain: layout style;
}

View File

@ -1,194 +1,194 @@
window.TEST_DATA = [
{
"id": "839217090555557410",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '839217090555557410',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "839211834861958673",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "BananaPro"
id: '839211834861958673',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'BananaPro'
},
{
"id": "839209605929121287",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '839209605929121287',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "837370053866304564",
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '837370053866304564',
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "837360015437214709",
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '837360015437214709',
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "836534979084169461",
"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>",
"model": "banana"
id: '836534979084169461',
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>',
model: 'banana'
},
{
"id": "835464458670191734",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835464458670191734',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "835463648116749398",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835463648116749398',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "835463392293565520",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835463392293565520',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"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",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
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',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "839217090555557410",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '839217090555557410',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef43d23888d39e4ed1d062.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "839211834861958673",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "BananaPro"
id: '839211834861958673',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3eed3888d39e4ed1d061.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'BananaPro'
},
{
"id": "839209605929121287",
"fileUrl": "http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '839209605929121287',
fileUrl: 'http://test.xueai.art/file/2026/4/27/69ef3cd93888d39e4ed1d060.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "837370053866304564",
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '837370053866304564',
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e88ba23888d39e4ed1d05c.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "837360015437214709",
"fileUrl": "http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '837360015437214709',
fileUrl: 'http://test.xueai.art/file/2026/4/22/69e882493888d39e4ed1d05b.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "836534979084169461",
"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>",
"model": "banana"
id: '836534979084169461',
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>',
model: 'banana'
},
{
"id": "835464458670191734",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835464458670191734',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19ce93888d39e4ed1cfaf.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "835463648116749398",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835463648116749398',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19c273888d39e4ed1cfac.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "835463392293565520",
"fileUrl": "http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '835463392293565520',
fileUrl: 'http://test.xueai.art/file/2026/4/17/69e19bea3888d39e4ed1cfab.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"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",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
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',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "830138209152283808",
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce3c743888d39e4ed1cf4f.png",
"prompt": "将图1红色框内的【苹果】替换为【火龙果】",
"model": "banana"
id: '830138209152283808',
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce3c743888d39e4ed1cf4f.png',
prompt: '将图1红色框内的【苹果】替换为【火龙果】',
model: 'banana'
},
{
"id": "830136945106498711",
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce3b463888d39e4ed1cf4e.png",
"prompt": "一个女孩在树下吃苹果",
"model": "flux"
id: '830136945106498711',
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce3b463888d39e4ed1cf4e.png',
prompt: '一个女孩在树下吃苹果',
model: 'flux'
},
{
"id": "830083758811001839",
"fileUrl": "http://test.xueai.art/file/2026/4/2/69ce09be3888d39e4ed1cf48.png",
"prompt": "一个女孩在树下吃苹果",
"model": "flux"
id: '830083758811001839',
fileUrl: 'http://test.xueai.art/file/2026/4/2/69ce09be3888d39e4ed1cf48.png',
prompt: '一个女孩在树下吃苹果',
model: 'flux'
},
{
"id": "829393290267734386",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb86b13888d39e4ed1cf32.png",
"prompt": "将图1红色框内的【女孩】替换为【图2中的女孩】",
"model": "banana"
id: '829393290267734386',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb86b13888d39e4ed1cf32.png',
prompt: '将图1红色框内的【女孩】替换为【图2中的女孩】',
model: 'banana'
},
{
"id": "829389466203337022",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb83223888d39e4ed1cf31.png",
"prompt": "将图1红色框内的【<span style=\"font-size: 14px;\">女孩</span>】替换为【图2中的女孩】",
"model": "banana"
id: '829389466203337022',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb83223888d39e4ed1cf31.png',
prompt: '将图1红色框内的【<span style="font-size: 14px;">女孩</span>】替换为【图2中的女孩】',
model: 'banana'
},
{
"id": "829388114303660338",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb81df3888d39e4ed1cf30.png",
"prompt": "将图1红色框内的【女孩】替换为【男孩】",
"model": "banana"
id: '829388114303660338',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb81df3888d39e4ed1cf30.png',
prompt: '将图1红色框内的【女孩】替换为【男孩】',
model: 'banana'
},
{
"id": "829381253919682782",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb7b7c3888d39e4ed1cf2d.png",
"prompt": "将图1红色框内的【女孩】替换为【图2中的男孩】",
"model": "banana"
id: '829381253919682782',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb7b7c3888d39e4ed1cf2d.png',
prompt: '将图1红色框内的【女孩】替换为【图2中的男孩】',
model: 'banana'
},
{
"id": "829324561060212843",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb46af3888d39e4ed1cf29.png",
"prompt": "一个女孩在树下吃苹果",
"model": "flux"
id: '829324561060212843',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb46af3888d39e4ed1cf29.png',
prompt: '一个女孩在树下吃苹果',
model: 'flux'
},
{
"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",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
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',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "829317957644464188",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb40893888d39e4ed1cf20.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '829317957644464188',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb40893888d39e4ed1cf20.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "829305227994738709",
"fileUrl": "http://test.xueai.art/file/2026/3/31/69cb34ae3888d39e4ed1cf1e.png",
"prompt": "这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格",
"model": "flux"
id: '829305227994738709',
fileUrl: 'http://test.xueai.art/file/2026/3/31/69cb34ae3888d39e4ed1cf1e.png',
prompt: '这是一幅治愈写实风格的插画画面中展现了一处城市石砌民居的顶层露台。石砌民居带有木质门、铁栅栏窗台、锈色金属屋顶空调外机挂在墙边金属防护栏环绕四周外置楼梯延伸下来。露台上摆放着竹制躺椅和木质桌椅旁边还有小凳。彩色水桶一个是红色一个是蓝色放置在角落花盆里栽种着金桔水仙花、蝴蝶兰整齐排列大量花卉在露台上盛放。露台旁有一棵枝叶繁茂、遮天蔽日的大树绿意浓郁与建筑共生。周围还摆放着一些绿植盆栽。远处是城市的高楼整个场景被绿意环绕明亮柔和的光线洒下充满了都市生活与自然融合感的顶层露台风情。Moebius (Jean Giraud)风格',
model: 'flux'
},
{
"id": "829068575099597628",
"fileUrl": "http://test.xueai.art/file/2026/3/30/69ca58473888d39e0bb8728b.png",
"prompt": "",
"model": ""
id: '829068575099597628',
fileUrl: 'http://test.xueai.art/file/2026/3/30/69ca58473888d39e0bb8728b.png',
prompt: '',
model: ''
}
];
]

View File

@ -7,8 +7,8 @@
<div class="input-group">
<label>W</label>
<input
type="number"
v-model.number="localWidth"
type="number"
:min="minW"
:max="maxW"
@input="onWidthChange"
@ -21,8 +21,8 @@
<div class="input-group">
<label>H</label>
<input
type="number"
v-model.number="localHeight"
type="number"
:min="minH"
:max="maxH"
@input="onHeightChange"
@ -41,9 +41,9 @@
</template>
<script setup>
import Popover from '@/components/Popover/index.vue'
import lockIcon from '@/assets/dialog/lock.svg'
import lockNoIcon from '@/assets/dialog/lockNo.svg'
import Popover from '@/components/Popover/index.vue'
const props = defineProps({
width: { type: Number, default: 1024 },
@ -51,7 +51,7 @@ const props = defineProps({
minW: { type: Number, default: 256 },
maxW: { type: Number, default: 6197 },
minH: { type: Number, default: 256 },
maxH: { type: Number, default: 4096 },
maxH: { type: Number, default: 4096 }
})
const emit = defineEmits(['update:width', 'update:height'])

View File

@ -37,7 +37,7 @@
<div class="size-inputs">
<div class="input-group">
<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 class="lock-icon" :class="{ locked: isLocked }" @click="toggleLock">
<img :src="isLocked ? lockIcon : lockNoIcon" alt="约束比例">
@ -45,7 +45,7 @@
</div>
<div class="input-group">
<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>
@ -60,9 +60,9 @@
</template>
<script setup>
import Popover from '@/components/Popover/index.vue'
import lockIcon from '@/assets/dialog/lock.svg'
import lockNoIcon from '@/assets/dialog/lockNo.svg'
import Popover from '@/components/Popover/index.vue'
const props = defineProps({
modelValue: {
@ -95,7 +95,7 @@ const props = defineProps({
},
allowCustom: {
type: Boolean,
default: true,
default: true
},
resolutionOptions: {
type: Array,
@ -461,5 +461,4 @@ watch(() => [props.modelValue, props.resolution], () => {
background: #f5f6f7;
}
}
</style>

View File

@ -19,8 +19,8 @@
</div>
</div>
<el-upload
ref="uploadRef"
v-show="false"
ref="uploadRef"
:action="uploadurl"
multiple
:limit="limit"

View File

@ -15,10 +15,9 @@
import Select from '@/components/Select/index.vue'
import { fetchPlatformModels, getPlatformCode } from '@/utils/modelApi'
const props = defineProps({
modelValue: { type: String, default: 'Flux 2' },
typeValue: { type: String, default: 'text' },
typeValue: { type: String, default: 'text' }
})
const emit = defineEmits(['update:modelValue', 'update:typeValue'])
@ -28,7 +27,7 @@ const platformModels = ref([])
const categoryMap = [
{ tag: 'text', label: '生成模型', inputType: 'text' },
{ tag: 'edit', label: '编辑模型', inputType: 'image' },
{ tag: 'vision', label: '视觉理解模型', inputType: 'vision' },
{ tag: 'vision', label: '视觉理解模型', inputType: 'vision' }
]
function parseValue(encoded) {
@ -44,14 +43,14 @@ function encodeValue(tag, modelName) {
function findTagForModel(modelName) {
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
}
return 'text'
}
function tagToInputType(tag) {
const cat = categoryMap.find(c => c.tag === tag)
const cat = categoryMap.find((c) => c.tag === tag)
return cat?.inputType || 'text'
}
@ -67,7 +66,7 @@ const selectValue = computed({
if (!parsed) return
emit('update:modelValue', parsed.modelName)
emit('update:typeValue', tagToInputType(parsed.tag))
},
}
})
// API
@ -89,26 +88,26 @@ const modelGroups = computed(() => {
if (models.length === 0) return []
return categoryMap
.filter(cat => models.some(m => m.tags?.includes(cat.tag)))
.map(cat => ({
.filter((cat) => models.some((m) => m.tags?.includes(cat.tag)))
.map((cat) => ({
label: cat.label,
options: models
.filter(m => m.tags?.includes(cat.tag))
.map(m => ({
.filter((m) => m.tags?.includes(cat.tag))
.map((m) => ({
value: `${cat.tag}::${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) => {
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) {
const firstEnabled = models.find(m => !m.disabled)
const firstEnabled = models.find((m) => !m.disabled)
if (firstEnabled) {
emit('update:modelValue', 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
const models = platformModels.value
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) {
const firstEnabled = models.find(m => !m.disabled)
const firstEnabled = models.find((m) => !m.disabled)
if (firstEnabled) {
emit('update:modelValue', firstEnabled.display_name || firstEnabled.name)
emit('update:typeValue', tagToInputType(findTagForModel(firstEnabled.display_name || firstEnabled.name)))

View File

@ -18,12 +18,12 @@
</template>
<script setup>
import Select from '@/components/Select/index.vue'
import videoPattern2 from '@/assets/dialog/videoPattern2.svg'
import videoPattern4 from '@/assets/dialog/videoPattern4.svg'
import videoPattern5 from '@/assets/dialog/videoPattern5.svg'
import videoPattern6 from '@/assets/dialog/videoPattern6.svg'
import Select from '@/components/Select/index.vue'
const props = defineProps({
modelValue: {
@ -44,12 +44,12 @@ const quantityOptions = [
{ value: '首尾帧', label: '首尾帧', labelText: '首尾帧', icon: videoPattern2 },
{ value: '数字人', label: '数字人', labelText: '数字人', icon: videoPattern2 },
{ value: '全能参考', label: '全能参考', labelText: '全能参考', icon: videoPattern4 },
{ value: '智能多帧', label: '智能多帧', labelText: '智能多帧', icon: videoPattern5 },
{ value: '智能多帧', label: '智能多帧', labelText: '智能多帧', icon: videoPattern5 },
{ value: '主体参考', label: '主体参考', labelText: '主体参考', icon: videoPattern6 }
]
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
})
</script>
@ -62,20 +62,20 @@ const selectedIcon = computed(() => {
border-radius: 10px;
border: 1px solid #E8E9EB;
background: #f5f6f7;
&:hover {
background: #e9eaeb;
}
}
:deep(.select-text) {
font-size: 14px;
}
:deep(.dropdown-menu) {
min-width: 140px;
}
:deep(.dropdown-item) {
min-width: 80px;
justify-content: start;

View File

@ -4,8 +4,8 @@
<div class="section">
<h3>选择比例</h3>
<div class="proportion-options" :style="{ marginBottom: props.type === 'Video' ? '0px' : '20px' }">
<div
v-for="item in proportionOptions"
<div
v-for="item in proportionOptions"
:key="item.value"
class="proportion-item"
:class="{ active: proportion === item.value }"
@ -20,8 +20,8 @@
<div class="section">
<h3>选择分辨率</h3>
<div class="resolution-options">
<div
v-for="item in resolutionOptions"
<div
v-for="item in resolutionOptions"
:key="item.value"
class="resolution-item"
:class="{ active: resolution === item.value }"
@ -107,7 +107,7 @@ const getProportionStyle = (value) => {
const [w, h] = value.split(':').map(Number)
const aspectRatio = w / h
const baseSize = 20
if (aspectRatio > 1) {
return {
'--width': `${baseSize}px`,
@ -148,11 +148,11 @@ const getProportionStyle = (value) => {
.section{
margin-bottom: 20px;
border-radius: 20px;
&:last-child{
margin-bottom: 0;
}
h3{
font-family: "Microsoft YaHei";
font-size: 12px;
@ -185,7 +185,7 @@ const getProportionStyle = (value) => {
border-radius: 5px;
text-align: bottom;
color: #999;
&::before{
content: '';
width: var(--width, 20px);
@ -195,11 +195,11 @@ const getProportionStyle = (value) => {
transition: all 0.2s ease;
border: 2px solid #999;
}
&:hover{
background: #e0e0e0;
}
&.active{
color: #000F33;
background: #ffffff;
@ -228,11 +228,11 @@ const getProportionStyle = (value) => {
text-align: center;
transition: all 0.2s ease;
color: #666;
&:hover{
background: #e0e0e0;
}
&.active{
background: #ffffff;
color: #000000;

View File

@ -53,20 +53,20 @@ const quantityOptions = computed(() => props.options)
border-radius: 10px;
border: 1px solid #E8E9EB;
background: #f5f6f7;
&:hover {
background: #e9eaeb;
}
}
:deep(.select-text) {
font-size: 14px;
}
:deep(.dropdown-menu) {
min-width: 136px;
}
:deep(.dropdown-item) {
min-width: 80px;
justify-content: center;

View File

@ -25,9 +25,9 @@
</div>
<el-upload
v-for="i in maxImages"
v-show="false"
:key="i"
:ref="el => setUploadRef(el, i - 1)"
v-show="false"
:action="uploadurl"
:limit="1"
:before-upload="beforeUpload"
@ -90,9 +90,9 @@ watch(() => props.modelValue, async (newVal) => {
if (isUploading.value) {
return
}
imageList.value = [...newVal]
const newPreviewList = []
for (const img of newVal) {
let previewImg = { ...img }
@ -140,16 +140,16 @@ const beforeUpload = (rawFile) => {
const handleSuccess = (response, uploadFile, index) => {
ElMessage.success('上传成功')
isUploading.value = true
const localUrl = URL.createObjectURL(uploadFile.raw)
const newImage = {
uid: uploadFile.uid,
url: response.url
}
if (imageList.value[index]) {
const previewItem = localPreviewList.value[index]
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
@ -169,9 +169,9 @@ const handleSuccess = (response, uploadFile, index) => {
serverUrl: response.url
}
}
emit('update:modelValue', [...imageList.value])
nextTick(() => {
isUploading.value = false
})
@ -186,7 +186,7 @@ const handleDelete = (index) => {
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
URL.revokeObjectURL(previewItem.url)
}
localPreviewList.value.splice(index, 1)
imageList.value.splice(index, 1)
emit('update:modelValue', [...imageList.value])

View File

@ -44,15 +44,14 @@ const fetchConfig = async () => {
}
}
fetchConfig()
watch(() => videoConfig.value, (newConfig) => {
const models = newConfig[props.videoPattern] || []
if (models.length > 0) {
const enabledModels = models.filter(m => !m.disabled)
const enabledModels = models.filter((m) => !m.disabled)
if (enabledModels.length > 0) {
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
const currentModelExists = enabledModels.find((m) => m.value === props.modelValue)
if (!currentModelExists) {
model.value = enabledModels[0].value
}
@ -60,7 +59,6 @@ watch(() => videoConfig.value, (newConfig) => {
}
}, { deep: true })
const model = computed({
get: () => props.modelValue,
set: (value) => {
@ -91,9 +89,9 @@ const getModelType = (value) => {
watch(() => props.videoPattern, (newPattern) => {
const models = videoConfig.value[newPattern] || []
if (models.length > 0) {
const enabledModels = models.filter(m => !m.disabled)
const enabledModels = models.filter((m) => !m.disabled)
if (enabledModels.length > 0) {
const currentModelExists = enabledModels.find(m => m.value === props.modelValue)
const currentModelExists = enabledModels.find((m) => m.value === props.modelValue)
if (!currentModelExists) {
model.value = enabledModels[0].value
}
@ -103,9 +101,9 @@ watch(() => props.videoPattern, (newPattern) => {
watch(() => props.modelValue, (newValue) => {
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) {
const enabledModels = models.filter(m => !m.disabled)
const enabledModels = models.filter((m) => !m.disabled)
if (enabledModels.length > 0) {
model.value = enabledModels[0].value
}
@ -121,24 +119,24 @@ watch(() => props.modelValue, (newValue) => {
border-radius: 10px;
border: 1px solid #E8E9EB;
background: #f5f6f7;
&:hover {
background: #e9eaeb;
}
}
:deep(.select-text) {
font-size: 14px;
}
:deep(.dropdown-menu) {
max-height: 510px;
overflow-y: auto;
}
:deep(.dropdown-item) {
min-width: 120px;
&.active {
background: rgba(0, 15, 51, 0.10);
color: #000F33;

View File

@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router'
import { useDisplayStore, useUserStore } from '@/stores'
import { useUserStore } from '@/stores'
import { getToken, setToken } from '@/utils/auth'
const routes = [
@ -30,7 +30,7 @@ const router = createRouter({
})
router.beforeEach(async (to, from) => {
if(to.query.token){
if (to.query.token) {
setToken(to.query.token)
} else {
// 检查是否有 token

View File

@ -8,12 +8,12 @@ const DisplayStoreSetup = () => {
const isLoading = ref(false)
const currentResultData = ref(null)
const dialogBoxRef = ref(null)
const canvasVisible = ref(false)
const canvasImage = ref('')
const canvasReferenceImages = ref([])
const canvasSource = ref('')
const addGeneratingItem = (item) => {
const newItem = {
id: item.taskId || crypto.randomUUID(),
@ -26,29 +26,29 @@ const DisplayStoreSetup = () => {
tempList.value.unshift(newItem)
return newItem
}
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) {
tempList.value[index].status = 'success'
tempList.value[index].files = Array.isArray(fileUrls) ? fileUrls : [fileUrls]
}
}
const initHistoryList = (historyList) => {
tempList.value = historyList
currentPage.value = 1
hasMoreData.value = true
}
const prependHistoryList = (historyList) => {
tempList.value = [...historyList, ...tempList.value]
}
const appendHistoryList = (historyList) => {
tempList.value = [...tempList.value, ...historyList]
}
const resetPagination = () => {
currentPage.value = 0
hasMoreData.value = true
@ -56,26 +56,26 @@ const DisplayStoreSetup = () => {
}
const deleteHistoryItem = (id) => {
const index = tempList.value.findIndex(item => item.id === id)
const index = tempList.value.findIndex((item) => item.id === id)
if (index !== -1) {
tempList.value.splice(index, 1)
}
}
const scrollToBottom = async () => {
const refValue = scrollerRef.value
if (!refValue) {
return
}
try {
if (typeof refValue.scrollToBottom === 'function') {
await nextTick()
refValue.scrollToBottom()
return
}
const scrollerEl = refValue.$el
if (scrollerEl) {
const viewport = scrollerEl.querySelector('.vue-recycle-scroller__viewport')
@ -83,7 +83,7 @@ const DisplayStoreSetup = () => {
viewport.scrollTop = viewport.scrollHeight
}
}
if (typeof refValue.scrollToItem === 'function' && tempList.value && tempList.value.length > 0) {
await nextTick()
refValue.scrollToItem(tempList.value.length - 1)
@ -92,28 +92,28 @@ const DisplayStoreSetup = () => {
console.error('滚动出错:', error)
}
}
const setResultData = (data) => {
currentResultData.value = data
}
const setDialogBoxRef = (ref) => {
dialogBoxRef.value = ref
}
const triggerGenerateWithResult = async () => {
if (dialogBoxRef.value && currentResultData.value) {
await dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
await dialogBoxRef.value.handleStart()
}
}
const fillParamsForEdit = () => {
if (dialogBoxRef.value && currentResultData.value) {
dialogBoxRef.value.fillParamsFromResult(currentResultData.value)
}
}
const openCanvas = (data) => {
if (typeof data === 'string') {
canvasImage.value = data

View File

@ -1,5 +1,4 @@
const ParamStoreSetup = () => {
return {
}
}

View File

@ -7,7 +7,7 @@ export async function generateFilename(url, prefix = 'image') {
// 如果URL中没有文件名或扩展名根据类型生成
if (!filename || !filename.includes('.')) {
const timestamp = new Date().getTime()
const timestamp = Date.now()
// 根据URL内容推断文件类型否则默认为png
const extension = url.includes('.jpg') || url.includes('.jpeg')
? '.jpg'
@ -21,7 +21,7 @@ export async function generateFilename(url, prefix = 'image') {
} catch (error) {
console.error('URL解析失败:', error)
// 如果URL解析失败生成默认文件名
const timestamp = new Date().getTime()
const timestamp = Date.now()
return `${prefix}_${timestamp}.png`
}
}

View File

@ -1,21 +1,20 @@
<template>
<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="prompt-container" ref="promptContainerRef">
<div class="prompt-wrapper" ref="promptWrapperRef">
<div class="prompt" ref="promptRef" :class="{ 'expanded': isHovering }" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
<div ref="promptContainerRef" class="prompt-container">
<div ref="promptWrapperRef" class="prompt-wrapper">
<div ref="promptRef" class="prompt" :class="{ expanded: isHovering }" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
<span class="prompt-text">
{{ props.item.generateData.prompt || '生成图片' }}
<i-ep-DocumentCopy class="Copy" @click.stop="copyPrompt"/>
<i-ep-DocumentCopy class="Copy" @click.stop="copyPrompt" />
</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">{{ props.item.generateData.proportion }}</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">{{ props.item.generateData.proportion }}</div>
</div>
@ -49,7 +48,7 @@
<!-- 已完成 图片 -->
<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" />
@ -58,14 +57,14 @@
<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>
<el-tooltip
effect="dark"
content="画笔"
placement="top"
:hide-after="0"
>
<div @click.stop="AIbrush(file, index)" class="bottom-brush">
<div class="bottom-brush" @click.stop="AIbrush(file, index)">
<img :src="brush" />
</div>
</el-tooltip>
@ -74,7 +73,7 @@
<!-- 已完成 视频 -->
<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" /> -->
<video :src="props.item.files[0]" class="video" controls playsinline />
@ -97,16 +96,16 @@
</template>
<script setup>
import { cancelOrCollect, deleteGenerateHistory } from '@/apis/display'
import againGenerateIcon from '@/assets/display/againGenerate.svg'
import brush from '@/assets/display/brush.svg'
import collectionIcon from '@/assets/display/collection.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 { 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({
item: {
@ -134,22 +133,22 @@ const checkTextOverflow = () => {
const lineHeight = 22.5
const padding = 8
const twoLineHeight = lineHeight * 2 + padding
promptRef.value.style.maxHeight = 'none'
promptRef.value.style.overflow = 'hidden'
const actualHeight = promptRef.value.scrollHeight
const lineCount = Math.ceil((actualHeight - padding) / lineHeight)
if (!isHovering.value) {
promptRef.value.style.maxHeight = `${twoLineHeight}px`
promptRef.value.style.overflow = 'hidden'
}
showExternalGenerateData.value = lineCount >= 3
if (!isHovering.value) {
if(lineCount >= 3){
if (lineCount >= 3) {
promptContainerRef.value.style.height = `${twoLineHeight}px`
} else {
promptContainerRef.value.style.height = `${actualHeight}px`
@ -163,8 +162,8 @@ watch(isHovering, (newVal) => {
if (promptRef.value) {
const lineHeight = 22.5
const padding = 8
const twoLineHeight = lineHeight * 2 + padding
const twoLineHeight = lineHeight * 2 + padding
if (newVal) {
promptRef.value.style.maxHeight = 'none'
promptRef.value.style.overflow = 'visible'
@ -208,7 +207,7 @@ const AIbrush = (file, index) => {
}
const reEdit = () => {
if(props.item.generateData?.modelType === 'edit'){
if (props.item.generateData?.modelType === 'edit') {
ElMessage.error('画笔生成的任务不能重新编辑')
return
}
@ -217,7 +216,7 @@ const reEdit = () => {
}
const againGenerate = () => {
if(props.item.generateData?.modelType === 'edit'){
if (props.item.generateData?.modelType === 'edit') {
ElMessage.error('画笔生成的任务不能再次生成')
return
}
@ -276,7 +275,7 @@ const addCollection = async (url) => {
const res = await cancelOrCollect({
taskId: props.item.id,
userId: useUser.userInfo.id,
url: url,
url
})
if (res.success) {
ElMessage.success(res.message || '操作成功')
@ -342,7 +341,7 @@ const copyPrompt = async () => {
width: 102%;
z-index: 5;
width: auto;
&.expanded{
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-radius: 4px;
@ -381,12 +380,12 @@ const copyPrompt = async () => {
font-style: normal;
font-weight: 400;
line-height: normal;
&.internal{
display: inline-flex;
margin-left: 10px;
}
&.external{
position: absolute;
right: -16px;

View File

@ -34,7 +34,7 @@
</div>
<span class="line"></span>
<div class="btn">
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto" >
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto">
<template #prefix>
<i-ep-Star />
</template>
@ -43,8 +43,8 @@
</div>
<VirtualScroller
ref="scrollerRef"
v-if="props.if"
ref="scrollerRef"
:items="list"
key-field="id"
:estimated-height="300"
@ -66,17 +66,17 @@
</template>
<script setup>
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
import { storeToRefs } from 'pinia'
import Set from './components/set.vue'
import RefreshOverlay from './components/RefreshOverlay.vue'
import { useRouter } from 'vue-router'
import { requestTaskHistory } from '@/apis/display'
import Canvas from '@/components/canvas/index.vue'
import Select from '@/components/Select/index.vue'
import { VirtualScroller } from '@/components/virtual-scroller'
import Canvas from '@/components/canvas/index.vue'
import { requestTaskHistory } from '@/apis/display'
import { useRouter } from 'vue-router'
import { getChargeType } from '@/utils/taskPolling'
import { useDisplayStore, useParamStore, useUserStore } from '@/stores'
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({
if: {
@ -138,7 +138,7 @@ const toggleDisplay = (newValue, oldValue) => {
const conversion = (newlist) => {
const temp = newlist.map((item) => {
// outputs URL
const files = item.outputs?.map(o => o.url) || []
const files = item.outputs?.map((o) => o.url) || []
const request = item.request || {}
const generateData = {
model: item.model_name || '',
@ -152,7 +152,7 @@ const conversion = (newlist) => {
customHight: request.customHight,
duration: request.duration || '',
videoPattern: request.videoPattern || '',
modelParams: { ...request },
modelParams: { ...request }
}
// API status UI
let uiStatus = 'success'
@ -168,7 +168,7 @@ const conversion = (newlist) => {
generateData,
time: item.created_at || '',
files,
collectStatus: item.collectStatus || {},
collectStatus: item.collectStatus || {}
}
})
return temp
@ -235,7 +235,6 @@ const fetchHistory = async (isLoadMore = false) => {
}
hasMoreData.value = dataList.length === 10
} catch (error) {
console.error('获取历史失败:', error)
ElMessage({
@ -380,5 +379,4 @@ onBeforeUnmount(() => {
background-color: #ccc;
}
}
</style>

View File

@ -1,8 +1,8 @@
<script setup>
import { computed, ref, onMounted, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import display from './display/index.vue'
import { useDisplayStore } from '@/stores'
import display from './display/index.vue'
const route = useRoute()
const useDisplay = useDisplayStore()

View File

@ -4,14 +4,14 @@
<div class="icon-wrapper">
<div class="lock-icon">
<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>
</div>
</div>
<h1 class="title">账号未登录</h1>
<p class="description">请先登录以访问完整功能</p>
<div class="action-buttons">
<button class="btn-primary" @click="handleLogin">
立即登录
@ -20,7 +20,7 @@
注册账号
</button>
</div>
<div class="features">
<div class="feature-item">
<div class="feature-icon"></div>
@ -36,7 +36,7 @@
</div>
</div>
</div>
<div class="background-decoration">
<div class="circle circle-1"></div>
<div class="circle circle-2"></div>
@ -311,15 +311,15 @@ const handleRegister = () => {
.login-content {
padding: 40px 20px;
}
.title {
font-size: 24px;
}
.action-buttons {
flex-direction: column;
}
.features {
flex-direction: column;
gap: 15px;