绘画初步完成所有功能逻辑,右上角时间与收藏筛选待完成
This commit is contained in:
parent
727ecb378b
commit
5a7dab7dc7
|
|
@ -11,7 +11,9 @@ export {}
|
|||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
2: typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default']
|
||||
Canvas: typeof import('./src/components/canvas/index.vue')['default']
|
||||
copy: typeof import('./src/components/virtual-scroller/VirtualScroller copy.vue')['default']
|
||||
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
|
|
|
|||
|
|
@ -159,6 +159,7 @@
|
|||
<script setup>
|
||||
import { generate } from '@/utils/websocket'
|
||||
import { useDisplayStore } from '@/stores'
|
||||
import request from '@/utils/request'
|
||||
|
||||
const props = defineProps({
|
||||
visible: {
|
||||
|
|
@ -176,6 +177,10 @@ const props = defineProps({
|
|||
source: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -236,15 +241,15 @@ const updateCurrentEditingContent = (description) => {
|
|||
const shapeWord = shapeType === 'circle' ? '圈' : '框'
|
||||
|
||||
if (selectedReferenceImages.value.length > 0) {
|
||||
const imageIndex = allReferenceImages.value.indexOf(selectedReferenceImages.value[0]) + 1
|
||||
const imageIndex = allReferenceImages.value.indexOf(selectedReferenceImages.value[0]) + 2
|
||||
const prefix = isFirstShape ? '' : ','
|
||||
currentEditingContent.value = `${prefix}将${colorName}${shapeWord}内的【XXX】替换为【图${imageIndex}中的${description}】`
|
||||
currentEditingContent.value = `${prefix}将图1${colorName}${shapeWord}内的【XXX】替换为【图${imageIndex}中的${description}】`
|
||||
} else if (description) {
|
||||
const prefix = isFirstShape ? '' : ','
|
||||
currentEditingContent.value = `${prefix}将${colorName}${shapeWord}内的【XXX】替换为【${description}】`
|
||||
currentEditingContent.value = `${prefix}将图1${colorName}${shapeWord}内的【XXX】替换为【${description}】`
|
||||
} else {
|
||||
const prefix = isFirstShape ? '' : ','
|
||||
currentEditingContent.value = `${prefix}将${colorName}${shapeWord}内的【XXX】替换为【XXX】或【图X中的XXX】`
|
||||
currentEditingContent.value = `${prefix}将图1${colorName}${shapeWord}内的【XXX】替换为【XXX】或【图X中的XXX】`
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +286,13 @@ const initCanvas = () => {
|
|||
const containerHeight = container.clientHeight
|
||||
|
||||
if (currentImage.value) {
|
||||
if (currentImage.value.startsWith('data:')) {
|
||||
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 = () => {
|
||||
const imgScale = Math.min(containerWidth / img.width, containerHeight / img.height)
|
||||
|
|
@ -291,9 +302,9 @@ const initCanvas = () => {
|
|||
bgImage.value = img
|
||||
scale.value = imgScale
|
||||
}
|
||||
img.src = currentImage.value
|
||||
img.src = imageUrl
|
||||
} else {
|
||||
fetch(currentImage.value)
|
||||
fetch(imageUrl)
|
||||
.then(res => res.blob())
|
||||
.then(blob => {
|
||||
const img = new Image()
|
||||
|
|
@ -548,18 +559,55 @@ const handleSend = async () => {
|
|||
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,
|
||||
method: 'POST',
|
||||
data: formData,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
if (result.success || result.code === 0) {
|
||||
return { name: imgItem.name, url: result.url }
|
||||
}
|
||||
return imgItem
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error)
|
||||
return imgItem
|
||||
}
|
||||
}
|
||||
|
||||
const uploadedImgs = await Promise.all(imgs.map(uploadImg))
|
||||
|
||||
const generateData = {
|
||||
model: 'banana',
|
||||
modelType: 'edit',
|
||||
prompt: inputText.value,
|
||||
proportion: '比例自动'
|
||||
}
|
||||
const data = {
|
||||
AIGC: 'Painting',
|
||||
platform: 'runninghub',
|
||||
file_type: 'image',
|
||||
modelName: 'flux',
|
||||
modelName: 'banana',
|
||||
params: [
|
||||
{ name: 'prompt', data: inputText.value },
|
||||
{ name: 'quantity', data: 1 },
|
||||
{ name: 'aspect_ratio', data: '16:9' },
|
||||
{ name: 'resolution', data: '1k' }
|
||||
{ name: 'prompt', data: inputText.value + '并且去除掉图1中的框' },
|
||||
{ name: 'index', data: 1 },
|
||||
],
|
||||
imgs
|
||||
imgs: uploadedImgs,
|
||||
result: JSON.stringify(generateData)
|
||||
}
|
||||
|
||||
emit('send', {
|
||||
|
|
@ -568,7 +616,7 @@ const handleSend = async () => {
|
|||
shapes: shapes.value
|
||||
})
|
||||
|
||||
await generate('text', data)
|
||||
await generate('edit', data , generateData, props.type)
|
||||
handleClose()
|
||||
}
|
||||
|
||||
|
|
@ -592,7 +640,7 @@ const handleUploadReference = () => {
|
|||
input.type = 'file'
|
||||
input.accept = 'image/*'
|
||||
input.multiple = true
|
||||
input.onchange = (e) => {
|
||||
input.onchange = async (e) => {
|
||||
const files = Array.from(e.target.files)
|
||||
const remainingSlots = 5 - allReferenceImages.value.length
|
||||
|
||||
|
|
@ -603,13 +651,17 @@ const handleUploadReference = () => {
|
|||
|
||||
const filesToUpload = files.slice(0, remainingSlots)
|
||||
|
||||
filesToUpload.forEach(file => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => {
|
||||
allReferenceImages.value.push(event.target.result)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
const readFileAsDataURL = (file) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader()
|
||||
reader.onload = (event) => resolve(event.target.result)
|
||||
reader.onerror = (error) => reject(error)
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
const imageDataUrls = await Promise.all(filesToUpload.map(readFileAsDataURL))
|
||||
allReferenceImages.value.push(...imageDataUrls)
|
||||
}
|
||||
input.click()
|
||||
}
|
||||
|
|
@ -818,11 +870,6 @@ const handleBrushConfirm = () => {
|
|||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
// border-color: #409eff;
|
||||
// color: #409eff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #fff;
|
||||
|
||||
|
|
@ -847,10 +894,6 @@ const handleBrushConfirm = () => {
|
|||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
// border-color: #409eff;
|
||||
// color: #409eff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -867,6 +910,7 @@ const handleBrushConfirm = () => {
|
|||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.brush-panel {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ const props = defineProps({
|
|||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
/**
|
||||
* 图片列表,每个元素包含 url 和 uid 属性
|
||||
*/
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
|
|
@ -52,13 +55,39 @@ const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
|
|||
const uploadRef = ref(null)
|
||||
const imageList = ref([...props.modelValue])
|
||||
const localPreviewList = ref([...props.modelValue])
|
||||
const isUploading = ref(false)
|
||||
|
||||
watch(() => props.modelValue, (newVal) => {
|
||||
imageList.value = [...newVal]
|
||||
if (newVal.length === 0) {
|
||||
localPreviewList.value = []
|
||||
watch(() => props.modelValue, async (newVal) => {
|
||||
if (isUploading.value) {
|
||||
return
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
imageList.value = [...newVal]
|
||||
|
||||
const newPreviewList = []
|
||||
for (const img of newVal) {
|
||||
let previewImg = { ...img }
|
||||
if (img.url && !img.url.startsWith('blob:')) {
|
||||
try {
|
||||
const response = await fetch(img.url)
|
||||
const blob = await response.blob()
|
||||
previewImg = {
|
||||
...img,
|
||||
url: URL.createObjectURL(blob),
|
||||
serverUrl: img.url
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create blob URL:', error)
|
||||
previewImg = {
|
||||
...img,
|
||||
serverUrl: img.url
|
||||
}
|
||||
}
|
||||
}
|
||||
newPreviewList.push(previewImg)
|
||||
}
|
||||
localPreviewList.value = newPreviewList
|
||||
}, { deep: true, immediate: true })
|
||||
|
||||
const triggerUpload = () => {
|
||||
uploadRef.value.$el.querySelector('input').click()
|
||||
|
|
@ -81,6 +110,8 @@ const beforeUpload = (rawFile) => {
|
|||
const handleSuccess = (response, uploadFile) => {
|
||||
ElMessage.success('上传成功')
|
||||
|
||||
isUploading.value = true
|
||||
|
||||
const localUrl = URL.createObjectURL(uploadFile.raw)
|
||||
|
||||
const newImage = {
|
||||
|
|
@ -92,9 +123,14 @@ const handleSuccess = (response, uploadFile) => {
|
|||
|
||||
const newPreview = {
|
||||
uid: uploadFile.uid,
|
||||
url: localUrl
|
||||
url: localUrl,
|
||||
serverUrl: response.url
|
||||
}
|
||||
localPreviewList.value.push(newPreview)
|
||||
|
||||
nextTick(() => {
|
||||
isUploading.value = false
|
||||
})
|
||||
}
|
||||
|
||||
const handleError = () => {
|
||||
|
|
@ -107,7 +143,7 @@ const handleExceed = () => {
|
|||
|
||||
const handleDelete = (index) => {
|
||||
const previewItem = localPreviewList.value[index]
|
||||
if (previewItem && previewItem.url.startsWith('blob:')) {
|
||||
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
|
||||
URL.revokeObjectURL(previewItem.url)
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +153,10 @@ const handleDelete = (index) => {
|
|||
}
|
||||
|
||||
const handleImageClick = (clickedIndex) => {
|
||||
const clickedImage = imageList.value[clickedIndex]
|
||||
const clickedImage = localPreviewList.value[clickedIndex]
|
||||
if (!clickedImage) return
|
||||
|
||||
const otherImages = imageList.value
|
||||
const otherImages = localPreviewList.value
|
||||
.filter((_, index) => index !== clickedIndex)
|
||||
.map((img, index) => ({
|
||||
...img,
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ const autoSizeConfig = computed(() => {
|
|||
|
||||
const handleStart = async () => {
|
||||
const currentType = props.type
|
||||
let currentModelType = modelType.value
|
||||
|
||||
if (!props.isGenerate) {
|
||||
router.push({ name: 'home', query: { loading: false, Generate: true, type: currentType } })
|
||||
|
|
@ -121,11 +122,19 @@ const handleStart = async () => {
|
|||
referenceImages.value.forEach((img, index) => {
|
||||
imgs.push({ name: `image_${index + 1}`, url: img.url })
|
||||
})
|
||||
console.log('imgs', imgs)
|
||||
// console.log('imgs', imgs)
|
||||
// 判断视频模式下是否文生视频
|
||||
if (currentType === 'video') {
|
||||
if (imgs.length > 0) {
|
||||
currentModelType = 'image'
|
||||
} else {
|
||||
currentModelType = 'text'
|
||||
}
|
||||
}
|
||||
|
||||
const generateData = {
|
||||
model: model.value,
|
||||
modelType: modelType.value,
|
||||
modelType: currentModelType,
|
||||
prompt: prompt.value,
|
||||
proportion: proportion.value,
|
||||
referenceImages: referenceImages.value,
|
||||
|
|
@ -138,7 +147,6 @@ const handleStart = async () => {
|
|||
const data = {
|
||||
AIGC: 'Painting',
|
||||
platform: 'runninghub',
|
||||
file_type: 'image',
|
||||
modelName: model.value,
|
||||
params: [
|
||||
{ name: 'prompt', data: prompt.value},
|
||||
|
|
@ -149,7 +157,7 @@ const handleStart = async () => {
|
|||
imgs,
|
||||
result: JSON.stringify(generateData)
|
||||
}
|
||||
await generate(modelType.value, data, generateData, props.type)
|
||||
await generate(currentModelType, data, generateData, currentType)
|
||||
console.log('生成中', isgerenate.value)
|
||||
}
|
||||
|
||||
|
|
@ -236,11 +244,12 @@ onMounted(() => {
|
|||
position: relative;
|
||||
|
||||
.scroll-to-bottom-text {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
padding: 10px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
border-radius: 10px;
|
||||
// box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
background-color: #F8F9FA;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
|
|
@ -248,6 +257,7 @@ onMounted(() => {
|
|||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
export async function Playload(data,type) {
|
||||
export async function Playload(data,modelType) {
|
||||
// data = getWidthHeight(data)
|
||||
try {
|
||||
const response = await fetch(`https://resources.xueai.art/AIGC/static/public/Platform/Painting/workflows/${type}/${data.modelName}.json`)
|
||||
const response = await fetch(`https://resources.xueai.art/AIGC/static/public/Platform/Painting/workflows/${modelType}/${data.modelName}.json`)
|
||||
const json = await response.json()
|
||||
|
||||
const nodeInfoList = []
|
||||
|
||||
if (Array.isArray(data.imgs) && data.imgs.length > 0 && type === 'image') {
|
||||
if (Array.isArray(data.imgs) && data.imgs.length > 0 && (modelType === 'image' || modelType === 'edit')) {
|
||||
for (const key of data.imgs) {
|
||||
if (json.nodeInfoList[key.name]) {
|
||||
console.log(key)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import outPlatform from '@/config/index'
|
||||
|
||||
// 处理音频生成任务的数据并返回
|
||||
export async function createTask(data, type, taskId, token) {
|
||||
console.log(data, type)
|
||||
const payload = await outPlatform[data.platform].Playload(data, type)
|
||||
export async function createTask(data, modelType, taskId, token) {
|
||||
console.log(data, modelType)
|
||||
const payload = await outPlatform[data.platform].Playload(data, modelType)
|
||||
|
||||
return {
|
||||
AIGC: data.AIGC,
|
||||
platform: data.platform,
|
||||
prompt: data.prompt,
|
||||
taskType: type === 'text' ? 1 : 2,
|
||||
taskType: modelType === 'text' ? 1 : 2,
|
||||
modelName: data.modelName,
|
||||
payload,
|
||||
taskId,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
<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="title" ref="titleRef" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
||||
<div class="prompt-wrapper">
|
||||
<div class="prompt" ref="nameRef" :class="{ 'expanded': isHovering }">{{ props.item.generateData.prompt || '生成图片' }}</div>
|
||||
<div class="generate-data" v-show="!isHovering" ref="generateDa taRef" :class="{ 'second-line': shouldShowOnSecondLine }">
|
||||
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
||||
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
||||
|
||||
<div class="prompt-container" ref="promptContainerRef" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
||||
<div class="prompt-wrapper" ref="promptWrapperRef">
|
||||
<div class="prompt" ref="promptRef" :class="{ 'expanded': isHovering }">
|
||||
<span class="prompt-text">{{ props.item.generateData.prompt || '生成图片' }}</span>
|
||||
<div class="generate-data internal" v-show="!isHovering && !showExternalGenerateData">
|
||||
<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 class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
||||
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载中 -->
|
||||
|
|
@ -114,45 +120,36 @@ const useUser = useUserStore()
|
|||
const localCollectStatus = ref({ ...props.item.collectStatus })
|
||||
const hoverIndex = ref(-1)
|
||||
const isHovering = ref(false)
|
||||
const shouldShowOnSecondLine = ref(false)
|
||||
const nameRef = ref(null)
|
||||
const titleRef = ref(null)
|
||||
const generateDataRef = ref(null)
|
||||
const wrapperHeight = ref(38.5)
|
||||
const showExternalGenerateData = ref(false)
|
||||
const promptContainerRef = ref(null)
|
||||
const promptWrapperRef = ref(null)
|
||||
const promptRef = ref(null)
|
||||
|
||||
const checkTextOverflow = () => {
|
||||
nextTick(() => {
|
||||
if (nameRef.value && titleRef.value && generateDataRef.value) {
|
||||
if (promptRef.value && promptWrapperRef.value && promptContainerRef.value) {
|
||||
const lineHeight = 22.5
|
||||
const padding = 8
|
||||
const twoLineHeight = 55
|
||||
const generateDataWidth = generateDataRef.value.offsetWidth + 20
|
||||
const twoLineHeight = lineHeight * 2 + padding
|
||||
|
||||
nameRef.value.style.maxHeight = 'none'
|
||||
nameRef.value.style.overflow = 'visible'
|
||||
promptRef.value.style.maxHeight = 'none'
|
||||
promptRef.value.style.overflow = 'hidden'
|
||||
|
||||
const actualHeight = nameRef.value.scrollHeight
|
||||
const lineCount = Math.ceil((actualHeight - padding * 2) / lineHeight)
|
||||
const actualHeight = promptRef.value.scrollHeight
|
||||
const lineCount = Math.ceil((actualHeight - padding) / lineHeight)
|
||||
|
||||
if (!isHovering.value) {
|
||||
nameRef.value.style.maxHeight = '55px'
|
||||
nameRef.value.style.overflow = 'hidden'
|
||||
promptRef.value.style.maxHeight = `${twoLineHeight}px`
|
||||
promptRef.value.style.overflow = 'hidden'
|
||||
}
|
||||
|
||||
if (lineCount > 1) {
|
||||
shouldShowOnSecondLine.value = true
|
||||
wrapperHeight.value = twoLineHeight
|
||||
} else {
|
||||
const containerWidth = titleRef.value.offsetWidth
|
||||
const promptWidth = nameRef.value.scrollWidth
|
||||
const firstLineRemaining = containerWidth - promptWidth
|
||||
|
||||
if (firstLineRemaining < generateDataWidth) {
|
||||
shouldShowOnSecondLine.value = true
|
||||
wrapperHeight.value = twoLineHeight
|
||||
showExternalGenerateData.value = lineCount >= 3
|
||||
|
||||
if (!isHovering.value) {
|
||||
if(lineCount >= 3){
|
||||
promptContainerRef.value.style.height = `${twoLineHeight}px`
|
||||
} else {
|
||||
shouldShowOnSecondLine.value = false
|
||||
wrapperHeight.value = Math.max(lineHeight + padding * 2, actualHeight)
|
||||
promptContainerRef.value.style.height = `${actualHeight}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -160,13 +157,17 @@ const checkTextOverflow = () => {
|
|||
}
|
||||
|
||||
watch(isHovering, (newVal) => {
|
||||
if (nameRef.value) {
|
||||
if (promptRef.value) {
|
||||
const lineHeight = 22.5
|
||||
const padding = 8
|
||||
const twoLineHeight = lineHeight * 2 + padding
|
||||
|
||||
if (newVal) {
|
||||
nameRef.value.style.maxHeight = 'none'
|
||||
nameRef.value.style.overflow = 'visible'
|
||||
promptRef.value.style.maxHeight = 'none'
|
||||
promptRef.value.style.overflow = 'visible'
|
||||
} else {
|
||||
nameRef.value.style.maxHeight = '55px'
|
||||
nameRef.value.style.overflow = 'hidden'
|
||||
promptRef.value.style.maxHeight = `${twoLineHeight}px`
|
||||
promptRef.value.style.overflow = 'hidden'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
@ -204,11 +205,19 @@ const AIbrush = (file, index) => {
|
|||
}
|
||||
|
||||
const reEdit = () => {
|
||||
if(props.item.generateData?.modelType === 'edit'){
|
||||
ElMessage.error('画笔生成的任务不能重新编辑')
|
||||
return
|
||||
}
|
||||
useDisplay.setResultData(props.item.generateData)
|
||||
useDisplay.fillParamsForEdit()
|
||||
}
|
||||
|
||||
const againGenerate = () => {
|
||||
if(props.item.generateData?.modelType === 'edit'){
|
||||
ElMessage.error('画笔生成的任务不能再次生成')
|
||||
return
|
||||
}
|
||||
useDisplay.setResultData(props.item.generateData)
|
||||
useDisplay.triggerGenerateWithResult()
|
||||
}
|
||||
|
|
@ -293,77 +302,97 @@ const addCollection = async (url) => {
|
|||
height: 350px;
|
||||
}
|
||||
|
||||
.title{
|
||||
.prompt-container{
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.prompt-wrapper{
|
||||
position: relative;
|
||||
min-height: 28.5px;
|
||||
overflow: visible;
|
||||
.prompt-wrapper{
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prompt{
|
||||
color: #333;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22.5px;
|
||||
word-break: break-all;
|
||||
padding: 8px;
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
left: -8px;
|
||||
top: 0;
|
||||
width: 102%;
|
||||
z-index: 5;
|
||||
|
||||
&.expanded{
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
color: #333;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 22.5px;
|
||||
word-break: break-all;
|
||||
max-height: 60px;
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
padding: 8px;
|
||||
background-color: #fff;
|
||||
|
||||
&.expanded{
|
||||
max-height: none;
|
||||
overflow: visible;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
.prompt-text{
|
||||
display: inline;
|
||||
color: #333;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.generate-data{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
.generate-data{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
height: 22.5px;
|
||||
text-align: center;
|
||||
padding: 0 12px;
|
||||
|
||||
color: #999;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
|
||||
&.internal{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
padding-right: 20px;
|
||||
|
||||
&.second-line{
|
||||
top: auto;
|
||||
bottom: 0;
|
||||
}
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&.external{
|
||||
position: absolute;
|
||||
right: -16px;
|
||||
bottom: 0;
|
||||
z-index: 6;
|
||||
// padding-right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-data{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
padding: 0 10px;
|
||||
border-left: 1px solid #999;
|
||||
white-space: nowrap;
|
||||
height: 12px;
|
||||
}
|
||||
.detailed-data{
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #999;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: normal;
|
||||
padding: 0 10px;
|
||||
border-left: 1px solid #999;
|
||||
white-space: nowrap;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.first-detailed-data{
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
.first-detailed-data{
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.box{
|
||||
|
|
|
|||
|
|
@ -1,391 +0,0 @@
|
|||
<template>
|
||||
<div id="display" class="content-area">
|
||||
<RefreshOverlay :visible="refreshing" />
|
||||
|
||||
<div class="back">
|
||||
<img src="@/assets/display/back.svg" alt="">
|
||||
<span class="title-text">退出</span>
|
||||
</div>
|
||||
|
||||
<div v-if="props.if" class="btn-container">
|
||||
<div class="btn">
|
||||
<!-- <span class="btn-text">全部</span> -->
|
||||
<img src="@/assets/display/search.svg" alt="">
|
||||
</div>
|
||||
<span class="line"></span>
|
||||
<div class="btn">
|
||||
<Select v-model="selectedTime" :options="timeOptions" width="auto">
|
||||
<template #prefix>
|
||||
<i-ep-Calendar />
|
||||
</template>
|
||||
|
||||
<template #header>
|
||||
<div class="header">
|
||||
<el-date-picker
|
||||
v-model="value1"
|
||||
type="daterange"
|
||||
start-placeholder="Start date"
|
||||
end-placeholder="End date"
|
||||
:size="size"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</Select>
|
||||
</div>
|
||||
<span class="line"></span>
|
||||
<div class="btn">
|
||||
<Select v-model="selectedFavorite" :options="favoriteOptions" width="auto" >
|
||||
<template #prefix>
|
||||
<i-ep-Star />
|
||||
</template>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DynamicScroller
|
||||
ref="scrollerRef"
|
||||
v-if="props.if"
|
||||
:items="list"
|
||||
:min-item-size="800"
|
||||
class="scroller"
|
||||
:buffer="50"
|
||||
@scroll="handleScroll"
|
||||
>
|
||||
<template #default="{ item, index, active }">
|
||||
<DynamicScrollerItem
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
:active="active"
|
||||
:index="index"
|
||||
data-index="index"
|
||||
>
|
||||
<Set :key="`${item.id}`" :item="item" />
|
||||
</DynamicScrollerItem>
|
||||
</template>
|
||||
</DynamicScroller>
|
||||
</div>
|
||||
</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 Select from '@/components/Select/index.vue'
|
||||
import { getGenerateHistoryList } from '@/apis/display'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const props = defineProps({
|
||||
if: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const useDisplay = useDisplayStore()
|
||||
const useParams = useParamStore()
|
||||
const userStore = useUserStore()
|
||||
const router = useRouter()
|
||||
const refreshing = ref(false)
|
||||
const scrollerRef = ref(null)
|
||||
const isLoadingMore = ref(false)
|
||||
const activeTab = ref('all')
|
||||
const isInitializing = ref(true)
|
||||
let total = 0
|
||||
|
||||
const timeOptions = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '最近一周', value: 'week' },
|
||||
{ label: '最近一个月', value: 'month' },
|
||||
{ label: '最近三个月', value: 'quarter' }
|
||||
]
|
||||
|
||||
const favoriteOptions = [
|
||||
{ label: '全部', value: 'all' },
|
||||
{ label: '已收藏', value: 'favorite' }
|
||||
]
|
||||
|
||||
const selectedTime = ref('all')
|
||||
const selectedFavorite = ref('all')
|
||||
const { tempList } = storeToRefs(useDisplay)
|
||||
|
||||
// const tempList = ref([
|
||||
// { id: 0, type: 'image', status: 'none', name: '局部重绘', time: '2025-12-01 18:26', files: [] },
|
||||
// { id: 1, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
|
||||
// { id: 2, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
|
||||
// { id: 3, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] },
|
||||
// { id: 4, type: 'image', status: 'success', name: '局部重绘', time: '2025-12-01 18:26', parentName: '2', parentTime: '2025-12-01 12:26', files: ['https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png', 'https://sxwz.xueai.art/static/png/image4-1-1-BZWQeEAk.png'] }
|
||||
|
||||
|
||||
// ])
|
||||
const activeFilter = ref('all')
|
||||
const list = computed(() => {
|
||||
const data = tempList.value || []
|
||||
if (activeFilter.value === 'all') {
|
||||
return data
|
||||
}
|
||||
return data.filter((item) => item.type === activeFilter.value)
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
// 筛选列表里的不同生成类型: 图片,视频
|
||||
const toggleDisplay = (newValue, oldValue) => {
|
||||
activeFilter.value = newValue
|
||||
}
|
||||
|
||||
// 转换数据
|
||||
const conversion = (newlist) => {
|
||||
const temp = newlist.data.records.map((item) => {
|
||||
return {
|
||||
id: item.taskId,
|
||||
collection: item.collection,
|
||||
status: 'success',
|
||||
prompt: item.prompt,
|
||||
params: item.params,
|
||||
time: item.createTime,
|
||||
files: [item.fileUrl]
|
||||
}
|
||||
})
|
||||
return temp
|
||||
}
|
||||
|
||||
// 获取历史列表
|
||||
const fetchHistory= async (isScrollTopLoad = false) => {
|
||||
try {
|
||||
if (isScrollTopLoad) {
|
||||
return
|
||||
}
|
||||
|
||||
const result = await getGenerateHistoryList({ userId: userStore.userInfo.id, chargeType: 1 })
|
||||
total = result.data ? result.data.length : 0
|
||||
if (total === 0) {
|
||||
useDisplay.Sender_variant = 'updown'
|
||||
router.push({ name: 'home' })
|
||||
}
|
||||
|
||||
const wrappedData = {
|
||||
data: {
|
||||
records: result.data
|
||||
}
|
||||
}
|
||||
const convertedList = conversion(wrappedData)
|
||||
|
||||
const adaptedList = convertedList.map((item, index) => {
|
||||
const originalItem = result.data[index]
|
||||
return {
|
||||
...item,
|
||||
text: originalItem?.title || item.prompt || '生成图片',
|
||||
name: originalItem?.title || item.prompt || '生成图片',
|
||||
type: 'image',
|
||||
title: originalItem?.title || '生成图片'
|
||||
}
|
||||
})
|
||||
|
||||
if (!isScrollTopLoad && adaptedList.length > 0) {
|
||||
useDisplay.initHistoryList(adaptedList)
|
||||
|
||||
await nextTick()
|
||||
|
||||
const scrollToBottomDirect = (force = false) => {
|
||||
if (scrollerRef.value) {
|
||||
const el = scrollerRef.value.$el
|
||||
if (el) {
|
||||
const viewport = el.querySelector('.vue-recycle-scroller__viewport')
|
||||
if (viewport) {
|
||||
console.log('直接滚动 - scrollHeight:', viewport.scrollHeight, 'force:', force)
|
||||
if (force) {
|
||||
viewport.scrollTop = viewport.scrollHeight + 1000
|
||||
} else {
|
||||
viewport.scrollTop = viewport.scrollHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof scrollerRef.value.scrollToItem === 'function') {
|
||||
console.log('直接 scrollToItem')
|
||||
scrollerRef.value.scrollToItem(list.value.length - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < 20; i++) {
|
||||
setTimeout(() => {
|
||||
scrollToBottomDirect(i >= 15)
|
||||
}, 60 * i)
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
scrollToBottomDirect(true)
|
||||
setTimeout(() => {
|
||||
refreshing.value = false
|
||||
isInitializing.value = false
|
||||
useDisplay.scrollToBottom()
|
||||
}, 600)
|
||||
}, 1500)
|
||||
} else {
|
||||
useDisplay.initHistoryList(adaptedList)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取历史失败:', error)
|
||||
ElMessage({
|
||||
message: '获取历史失败',
|
||||
type: 'warning'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 获取下一页数据
|
||||
const getList = async () => {
|
||||
if (isLoadingMore.value) return
|
||||
isLoadingMore.value = true
|
||||
try {
|
||||
await fetchHistory(true)
|
||||
} finally {
|
||||
isLoadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理滚动事件
|
||||
const handleScroll = (event) => {
|
||||
if (isInitializing.value) return
|
||||
|
||||
const { scrollTop, scrollHeight, clientHeight } = event.target
|
||||
const distanceToBottom = scrollHeight - scrollTop - clientHeight
|
||||
|
||||
// 临时禁用滚动到顶部获取历史记录
|
||||
// if (scrollTop <= 50 && !isLoadingMore.value) {
|
||||
// getList()
|
||||
// }
|
||||
|
||||
if (distanceToBottom <= 50) {
|
||||
useDisplay.Sender_variant = 'updown'
|
||||
} else if (distanceToBottom >= 350) {
|
||||
useDisplay.Sender_variant = 'default'
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log('display 组件已挂载')
|
||||
if (!props.loading) return
|
||||
refreshing.value = true
|
||||
|
||||
nextTick(() => {
|
||||
console.log('设置 scrollerRef 到 store')
|
||||
useDisplay.scrollerRef = scrollerRef.value
|
||||
fetchHistory()
|
||||
})
|
||||
|
||||
page.value++
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.content-area {
|
||||
width: 100%;
|
||||
min-width: 750px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
.back{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: 36px;
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
top: 22px;
|
||||
z-index: 3;
|
||||
cursor: pointer;
|
||||
border-radius: 10px;
|
||||
// background-color: #FAFBFC;
|
||||
}
|
||||
|
||||
.back:hover{
|
||||
background-color: #e4e7ed;
|
||||
}
|
||||
|
||||
.btn-container{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
height: auto;
|
||||
padding: 4px;
|
||||
right: 30px;
|
||||
top: 22px;
|
||||
z-index: 3;
|
||||
|
||||
border-radius: 10px;
|
||||
background-color: #FAFBFC;
|
||||
position: absolute;
|
||||
|
||||
.btn{
|
||||
display: flex;
|
||||
padding: 5px;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn-text{
|
||||
color: #000;
|
||||
font-family: "Microsoft YaHei";
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.line{
|
||||
width: 1px;
|
||||
height: 8px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.scroller {
|
||||
height: 100%;
|
||||
padding: 30px 0px 350px 0px;
|
||||
will-change: scroll-position;
|
||||
-webkit-overflow-scrolling: touch; /* iOS Safari */
|
||||
scroll-behavior: smooth; /* 平滑滚动 */
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent; /* 轨道透明 */
|
||||
}
|
||||
|
||||
:deep(.option-item) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 20px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
:deep(.option-item:hover) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.option-item.selected) {
|
||||
color: #000F33;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.option-text) {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
:deep(.option-check) {
|
||||
margin-left: 10px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div id="display" class="content-area">
|
||||
<RefreshOverlay :visible="refreshing" />
|
||||
<Canvas v-model:visible="canvasVisible" :image="canvasImage" :reference-images="canvasReferenceImages" :source="canvasSource" @send="handleCanvasSend" />
|
||||
<Canvas v-model:visible="canvasVisible" :image="canvasImage" :reference-images="canvasReferenceImages" :source="canvasSource" :type="props.type" @send="handleCanvasSend" />
|
||||
|
||||
<div class="back" @click="handleExit">
|
||||
<img src="@/assets/display/back.svg" alt="">
|
||||
|
|
|
|||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"nodeInfoList": {
|
||||
"value_1":{ "nodeId":"20", "fieldName":"value", "fieldValue":"512" },
|
||||
"value_2":{ "nodeId":"21", "fieldName":"value", "fieldValue":"512" },
|
||||
"model_type":{ "nodeId":"18", "fieldName":"model_type", "fieldValue":"pro" },
|
||||
"audio":{ "nodeId":"16", "fieldName":"audio", "fieldValue":"dce37fba29e596ddcd927c4660b4fb47bd3ecdbef4ad242fed72bd860e032b1f.flac" },
|
||||
"image":{ "nodeId":"17", "fieldName":"image", "fieldValue":"d3ee810ce387739e7a99cb3ba87a104e34b0a955149b8525a600867edbab1138.png" }
|
||||
},
|
||||
"workflowId": "2036266399357739009"
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"nodeInfoList": {
|
||||
"text":{ "nodeId":"40", "fieldName":"text", "fieldValue":"深夜,一个美丽的中年中国女人在一边弹吉他一边歌唱,环绕镜头,半身特写,逆光,月光,海风吹拂。场景是海边。" },
|
||||
"audio":{ "nodeId":"39", "fieldName":"audio", "fieldValue":"62e5c0b15854bcac34e9aa0bf9f449767bda9f66047a60abeddbcd47c712ee8d.mp3" },
|
||||
"start_index":{ "nodeId":"58", "fieldName":"start_index", "fieldValue":0 },
|
||||
"duration":{ "nodeId":"58", "fieldName":"duration", "fieldValue":25 },
|
||||
"value_1":{ "nodeId":"55", "fieldName":"value", "fieldValue":1280 },
|
||||
"value_2":{ "nodeId":"56", "fieldName":"value", "fieldValue":720 }
|
||||
},
|
||||
"workflowId": "2036343285949665282"
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"nodeInfoList": {
|
||||
"image":{ "nodeId":"2", "fieldName":"image", "fieldValue":"67bbf03a4ce453557b8c9acf85bd83d3519d3374ef35c54da1084d03f9ac111f.png" },
|
||||
"prompt":{ "nodeId":"3", "fieldName":"prompt", "fieldValue":"一个小女孩在树下吃苹果" },
|
||||
"resolution":{ "nodeId":"3", "fieldName":"resolution", "fieldValue":"540p" },
|
||||
"duration":{ "nodeId":"3", "fieldName":"duration", "fieldValue":5 },
|
||||
"audio":{ "nodeId":"3", "fieldName":"audio", "fieldValue":true }
|
||||
},
|
||||
"workflowId": "2036354451904139265"
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"nodeInfoList": {
|
||||
"style":{ "nodeId":"2", "fieldName":"style", "fieldValue":"general" },
|
||||
"prompt":{ "nodeId":"2", "fieldName":"prompt", "fieldValue":"一个小女孩在树下吃苹果" },
|
||||
"resolution":{ "nodeId":"2", "fieldName":"resolution", "fieldValue":"540p" },
|
||||
"aspect_ratio":{ "nodeId":"2", "fieldName":"aspect_ratio", "fieldValue":"4:3" },
|
||||
"duration":{ "nodeId":"2", "fieldName":"duration", "fieldValue":5 },
|
||||
"audio":{ "nodeId":"2", "fieldName":"audio", "fieldValue":true }
|
||||
},
|
||||
"workflowId": "2036349280088231938"
|
||||
}
|
||||
Loading…
Reference in New Issue