优化set组件 里的prompt的显示逻辑
This commit is contained in:
parent
ff4ae2bdc8
commit
727ecb378b
|
|
@ -11,9 +11,7 @@ export {}
|
||||||
/* prettier-ignore */
|
/* prettier-ignore */
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
2: typeof import('./src/components/virtual-scroller/VirtualScroller copy 2.vue')['default']
|
|
||||||
Canvas: typeof import('./src/components/canvas/index.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']
|
DialogBox: typeof import('./src/components/dialogBox/index.vue')['default']
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,6 @@ const isgerenate = ref(false)
|
||||||
const model = ref('flux')
|
const model = ref('flux')
|
||||||
const modelType = ref('text')
|
const modelType = ref('text')
|
||||||
|
|
||||||
// 公用参数
|
|
||||||
const prompt = ref('一个女孩在树下吃苹果')
|
const prompt = ref('一个女孩在树下吃苹果')
|
||||||
const promptPlaceholder = '描述你想生成的画面和动作。'
|
const promptPlaceholder = '描述你想生成的画面和动作。'
|
||||||
const proportion = ref('16:9')
|
const proportion = ref('16:9')
|
||||||
|
|
@ -106,8 +105,10 @@ const autoSizeConfig = computed(() => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const handleStart = async () => {
|
const handleStart = async () => {
|
||||||
|
const currentType = props.type
|
||||||
|
|
||||||
if (!props.isGenerate) {
|
if (!props.isGenerate) {
|
||||||
router.push({ name: 'home', query: { loading: false, Generate: true } })
|
router.push({ name: 'home', query: { loading: false, Generate: true, type: currentType } })
|
||||||
}
|
}
|
||||||
if (!prompt.value) {
|
if (!prompt.value) {
|
||||||
// eslint-disable-next-line no-undef
|
// eslint-disable-next-line no-undef
|
||||||
|
|
@ -122,8 +123,7 @@ const handleStart = async () => {
|
||||||
})
|
})
|
||||||
console.log('imgs', imgs)
|
console.log('imgs', imgs)
|
||||||
|
|
||||||
const result = {
|
const generateData = {
|
||||||
type: props.type,
|
|
||||||
model: model.value,
|
model: model.value,
|
||||||
modelType: modelType.value,
|
modelType: modelType.value,
|
||||||
prompt: prompt.value,
|
prompt: prompt.value,
|
||||||
|
|
@ -147,15 +147,15 @@ const handleStart = async () => {
|
||||||
{ name: 'resolution', data: resolution.value},
|
{ name: 'resolution', data: resolution.value},
|
||||||
],
|
],
|
||||||
imgs,
|
imgs,
|
||||||
result
|
result: JSON.stringify(generateData)
|
||||||
}
|
}
|
||||||
await generate(modelType.value, data)
|
await generate(modelType.value, data, generateData, props.type)
|
||||||
console.log('生成中', isgerenate.value)
|
console.log('生成中', isgerenate.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
const fillParamsFromResult = (resultData) => {
|
const fillParamsFromResult = (resultData) => {
|
||||||
if (!resultData) return
|
if (!resultData) return
|
||||||
|
|
||||||
if (resultData.model !== undefined) model.value = resultData.model
|
if (resultData.model !== undefined) model.value = resultData.model
|
||||||
if (resultData.modelType !== undefined) modelType.value = resultData.modelType
|
if (resultData.modelType !== undefined) modelType.value = resultData.modelType
|
||||||
if (resultData.prompt !== undefined) prompt.value = resultData.prompt
|
if (resultData.prompt !== undefined) prompt.value = resultData.prompt
|
||||||
|
|
@ -196,6 +196,15 @@ watch(() => useDisplay.isSubGerenate, (newValue) => {
|
||||||
watch(() => modelType.value, (newValue) => {
|
watch(() => modelType.value, (newValue) => {
|
||||||
console.log('modelType.value', newValue)
|
console.log('modelType.value', newValue)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(() => props.type, (newType) => {
|
||||||
|
if (newType === 'video') {
|
||||||
|
model.value = 'Vidu Q3-T2V'
|
||||||
|
} else {
|
||||||
|
model.value = 'flux'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if(props.type === 'video'){
|
if(props.type === 'video'){
|
||||||
model.value = 'Vidu Q3-T2V'
|
model.value = 'Vidu Q3-T2V'
|
||||||
|
|
|
||||||
|
|
@ -1,379 +1,613 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="virtual-scroller" :style="containerStyle">
|
<div
|
||||||
|
ref="containerRef"
|
||||||
|
class="virtual-scroller"
|
||||||
|
:style="containerStyle"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
ref="scrollContainerRef"
|
ref="wrapperRef"
|
||||||
class="virtual-scroller-container"
|
class="virtual-scroller-wrapper"
|
||||||
:style="scrollContainerStyle"
|
:style="wrapperStyle"
|
||||||
@scroll.passive="handleScroll"
|
|
||||||
>
|
>
|
||||||
<div class="virtual-scroller-spacer" :style="spacerStyle"></div>
|
|
||||||
<div class="virtual-scroller-placeholder" :style="placeholderStyle">
|
|
||||||
<slot name="bottom-placeholder" />
|
|
||||||
</div>
|
|
||||||
<div
|
<div
|
||||||
v-for="item in visibleItems"
|
ref="renderContainerRef"
|
||||||
:key="item.key"
|
class="virtual-scroller-render-container"
|
||||||
:ref="el => setItemRef(el, item.key)"
|
:style="renderContainerStyle"
|
||||||
class="virtual-scroller-item"
|
@scroll.passive="handleScroll"
|
||||||
:style="getItemStyle(item)"
|
@wheel="handleWheel"
|
||||||
:data-index="item.index"
|
|
||||||
:data-key="item.key"
|
|
||||||
>
|
>
|
||||||
<slot name="default" :item="item.data" :index="item.index" />
|
<div class="virtual-scroller-spacer" :style="{ height: `${totalHeight}px` }"></div>
|
||||||
|
<div
|
||||||
|
class="virtual-scroller-bottom-placeholder"
|
||||||
|
:style="bottomPlaceholderStyle"
|
||||||
|
>
|
||||||
|
<slot name="bottom-placeholder" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="renderItem in visibleItems"
|
||||||
|
:key="getItemKey(renderItem.item, renderItem.index)"
|
||||||
|
:ref="el => setItemRef(el, renderItem.index)"
|
||||||
|
class="virtual-scroller-item"
|
||||||
|
:style="getItemStyle(renderItem)"
|
||||||
|
:data-index="renderItem.index"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="default"
|
||||||
|
:item="renderItem.item"
|
||||||
|
:index="renderItem.index"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, onUnmounted, nextTick, type CSSProperties } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
interface VirtualScrollerProps {
|
const props = defineProps({
|
||||||
items: any[]
|
data: {
|
||||||
estimatedHeight?: number
|
type: Array,
|
||||||
bufferSize?: number
|
required: false,
|
||||||
keyField?: string
|
default: () => []
|
||||||
height?: string | number
|
},
|
||||||
direction?: 'normal' | 'reverse'
|
items: {
|
||||||
}
|
type: Array,
|
||||||
|
required: false,
|
||||||
const props = withDefaults(defineProps<VirtualScrollerProps>(), {
|
default: () => []
|
||||||
estimatedHeight: 100,
|
},
|
||||||
bufferSize: 3,
|
itemKey: {
|
||||||
keyField: 'id',
|
type: [String, Function],
|
||||||
height: '100%',
|
default: 'id'
|
||||||
direction: 'reverse'
|
},
|
||||||
|
keyField: {
|
||||||
|
type: String,
|
||||||
|
default: 'id'
|
||||||
|
},
|
||||||
|
estimatedHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 100
|
||||||
|
},
|
||||||
|
buffer: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
bufferSize: {
|
||||||
|
type: Number,
|
||||||
|
default: 3
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: '100%'
|
||||||
|
},
|
||||||
|
renderMode: {
|
||||||
|
type: String,
|
||||||
|
default: 'default',
|
||||||
|
validator: (value) => ['default', 'top'].includes(value)
|
||||||
|
},
|
||||||
|
direction: {
|
||||||
|
type: String,
|
||||||
|
default: 'reverse',
|
||||||
|
validator: (value) => ['normal', 'reverse'].includes(value)
|
||||||
|
},
|
||||||
|
bottomPlaceholderHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: 350
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const computedData = computed(() => {
|
||||||
scroll: [scrollTop: number, scrollInfo: {
|
return props.data.length > 0 ? props.data : props.items
|
||||||
isAtTop: boolean
|
})
|
||||||
isAtBottom: boolean
|
|
||||||
distanceToTop: number
|
|
||||||
distanceToBottom: number
|
|
||||||
}]
|
|
||||||
'visible-change': [startIndex: number, endIndex: number]
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const scrollContainerRef = ref<HTMLElement | null>(null)
|
const computedItemKey = computed(() => {
|
||||||
|
if (typeof props.itemKey === 'function') return props.itemKey
|
||||||
|
if (props.itemKey !== 'id') return props.itemKey
|
||||||
|
return props.keyField
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
const itemRefs = new Map()
|
||||||
|
const itemHeights = ref(new Map())
|
||||||
|
const resizeObserver = ref(null)
|
||||||
const scrollTop = ref(0)
|
const scrollTop = ref(0)
|
||||||
const itemRefs = new Map<string, HTMLElement>()
|
const isScrolling = ref(false)
|
||||||
const isReverseMode = computed(() => props.direction === 'reverse')
|
const scrollTimeout = ref(null)
|
||||||
const isInitializing = ref(true)
|
const isInitialized = ref(false)
|
||||||
|
const pendingScrollToBottom = ref(false)
|
||||||
|
const previousDataLength = ref(0)
|
||||||
|
|
||||||
const itemHeights = ref(new Map<string | number, number>())
|
const containerStyle = computed(() => {
|
||||||
const itemOffsets = ref(new Map<string | number, number>())
|
return {
|
||||||
let resizeObserver: ResizeObserver | null = null
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
function getItemKey(item: any, index: number): string | number {
|
position: 'relative'
|
||||||
return item[props.keyField] ?? index
|
|
||||||
}
|
|
||||||
|
|
||||||
function getItemHeight(key: string | number): number {
|
|
||||||
return itemHeights.value.get(key) ?? props.estimatedHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
function calculateOffsets() {
|
|
||||||
let offset = 0
|
|
||||||
const newOffsets = new Map<string | number, number>()
|
|
||||||
|
|
||||||
for (let i = 0; i < props.items.length; i++) {
|
|
||||||
const key = getItemKey(props.items[i], i)
|
|
||||||
newOffsets.set(key, offset)
|
|
||||||
offset += getItemHeight(key)
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
itemOffsets.value = newOffsets
|
|
||||||
}
|
|
||||||
|
|
||||||
const totalHeight = computed(() => {
|
const totalHeight = computed(() => {
|
||||||
let height = 0
|
let height = 0
|
||||||
for (let i = 0; i < props.items.length; i++) {
|
const len = computedData.value.length
|
||||||
const key = getItemKey(props.items[i], i)
|
|
||||||
height += getItemHeight(key)
|
for (let i = 0; i < len; i++) {
|
||||||
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
|
height += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
height += props.bottomPlaceholderHeight
|
||||||
|
|
||||||
return height
|
return height
|
||||||
})
|
})
|
||||||
|
|
||||||
const containerStyle = computed<CSSProperties>(() => ({
|
const getItemPosition = (index) => {
|
||||||
height: typeof props.height === 'number' ? `${props.height}px` : props.height,
|
let offset = 0
|
||||||
overflow: 'hidden'
|
|
||||||
}))
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
const scrollContainerStyle = computed<CSSProperties>(() => ({
|
const containerHeight = computed(() => {
|
||||||
height: '100%',
|
if (!renderContainerRef.value) return 0
|
||||||
overflowY: 'auto',
|
return renderContainerRef.value.clientHeight
|
||||||
overflowX: 'hidden',
|
})
|
||||||
transform: 'rotate(180deg)',
|
|
||||||
position: 'relative'
|
|
||||||
}))
|
|
||||||
|
|
||||||
const spacerStyle = computed<CSSProperties>(() => ({
|
|
||||||
height: `${totalHeight.value}px`,
|
|
||||||
width: '1px',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
const placeholderStyle = computed<CSSProperties>(() => ({
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
pointerEvents: 'none'
|
|
||||||
}))
|
|
||||||
|
|
||||||
const visibleRange = computed(() => {
|
const visibleRange = computed(() => {
|
||||||
if (!scrollContainerRef.value || props.items.length === 0) {
|
if (!renderContainerRef.value || computedData.value.length === 0) {
|
||||||
return { start: 0, end: Math.min(props.bufferSize * 2, props.items.length - 1) }
|
return { start: 0, end: 0, offset: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerHeight = scrollContainerRef.value.clientHeight
|
const viewportHeight = containerHeight.value
|
||||||
const scrollTopValue = scrollTop.value
|
const currentScrollTop = scrollTop.value
|
||||||
|
const bufferCount = computedBuffer.value
|
||||||
|
|
||||||
let startIndex = 0
|
let startIndex = 0
|
||||||
let endIndex = props.items.length - 1
|
let endIndex = computedData.value.length - 1
|
||||||
let currentOffset = 0
|
let startOffset = 0
|
||||||
|
|
||||||
for (let i = 0; i < props.items.length; i++) {
|
let offset = 0
|
||||||
const key = getItemKey(props.items[i], i)
|
for (let i = 0; i < computedData.value.length; i++) {
|
||||||
const itemHeight = getItemHeight(key)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
if (currentOffset + itemHeight > scrollTopValue - props.bufferSize * props.estimatedHeight) {
|
if (offset + height > currentScrollTop) {
|
||||||
startIndex = Math.max(0, i - props.bufferSize)
|
startIndex = Math.max(0, i - bufferCount)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
currentOffset += itemHeight
|
|
||||||
|
offset += height
|
||||||
}
|
}
|
||||||
|
|
||||||
currentOffset = 0
|
startOffset = 0
|
||||||
for (let i = 0; i < props.items.length; i++) {
|
for (let i = 0; i < startIndex; i++) {
|
||||||
const key = getItemKey(props.items[i], i)
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
const itemHeight = getItemHeight(key)
|
startOffset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = startOffset
|
||||||
|
for (let i = startIndex; i < computedData.value.length; i++) {
|
||||||
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
|
|
||||||
if (currentOffset > scrollTopValue + containerHeight + props.bufferSize * props.estimatedHeight) {
|
if (offset >= currentScrollTop + viewportHeight) {
|
||||||
endIndex = Math.min(props.items.length - 1, i + props.bufferSize)
|
endIndex = Math.min(computedData.value.length - 1, i + bufferCount)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
currentOffset += itemHeight
|
|
||||||
|
offset += height
|
||||||
|
endIndex = i
|
||||||
}
|
}
|
||||||
|
|
||||||
return { start: startIndex, end: endIndex }
|
return { start: startIndex, end: endIndex, offset: startOffset }
|
||||||
})
|
})
|
||||||
|
|
||||||
const visibleItems = computed(() => {
|
const visibleItems = computed(() => {
|
||||||
const { start, end } = visibleRange.value
|
const { start, end, offset } = visibleRange.value
|
||||||
const items: Array<{ key: string | number; data: any; index: number; offset: number }> = []
|
const items = []
|
||||||
const currentRenderedKeys = new Set<string | number>()
|
|
||||||
|
let currentOffset = offset
|
||||||
for (let i = start; i <= end; i++) {
|
|
||||||
const item = props.items[i]
|
for (let i = start; i <= end && i < computedData.value.length; i++) {
|
||||||
if (!item) continue
|
const cachedHeight = itemHeights.value.get(i)
|
||||||
|
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||||
const key = getItemKey(item, i)
|
|
||||||
|
|
||||||
if (currentRenderedKeys.has(key)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
currentRenderedKeys.add(key)
|
|
||||||
|
|
||||||
const offset = itemOffsets.value.get(key) ?? i * props.estimatedHeight
|
|
||||||
|
|
||||||
items.push({
|
items.push({
|
||||||
key: String(key),
|
item: computedData.value[i],
|
||||||
data: item,
|
|
||||||
index: i,
|
index: i,
|
||||||
offset
|
offset: currentOffset + props.bottomPlaceholderHeight,
|
||||||
|
height
|
||||||
})
|
})
|
||||||
|
|
||||||
|
currentOffset += height
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|
||||||
function getItemStyle(item: { offset: number }): CSSProperties {
|
const wrapperStyle = computed(() => ({
|
||||||
|
direction: 'rtl',
|
||||||
|
height: '100%',
|
||||||
|
position: 'relative',
|
||||||
|
scrollbarWidth: 'auto',
|
||||||
|
overflow: 'hidden',
|
||||||
|
transform: 'rotate(180deg)',
|
||||||
|
width: '100%'
|
||||||
|
}))
|
||||||
|
|
||||||
|
const renderContainerStyle = computed(() => ({
|
||||||
|
direction: 'ltr',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
overflowX: 'hidden',
|
||||||
|
overflowY: 'auto',
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%'
|
||||||
|
}))
|
||||||
|
|
||||||
|
const bottomPlaceholderStyle = computed(() => ({
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: `${props.bottomPlaceholderHeight}px`,
|
||||||
|
transform: `translateY(0px)`,
|
||||||
|
zIndex: 1
|
||||||
|
}))
|
||||||
|
|
||||||
|
const getItemKey = (item, index) => {
|
||||||
|
const keyField = computedItemKey.value
|
||||||
|
if (typeof keyField === 'function') {
|
||||||
|
return keyField(item, index)
|
||||||
|
}
|
||||||
|
if (typeof keyField === 'string' && item && typeof item === 'object') {
|
||||||
|
return item[keyField] ?? index
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
const getItemStyle = (renderItem) => {
|
||||||
return {
|
return {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: `${item.offset}px`,
|
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: '100%',
|
||||||
|
transform: `translateY(${renderItem.offset}px)`,
|
||||||
|
willChange: 'transform'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setItemRef(el: any, key: string) {
|
const setItemRef = (el, index) => {
|
||||||
if (el) {
|
if (el) {
|
||||||
itemRefs.set(key, el as HTMLElement)
|
itemRefs.set(index, el)
|
||||||
} else {
|
} else {
|
||||||
itemRefs.delete(key)
|
itemRefs.delete(index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function measureItems() {
|
const measureItem = (index, element) => {
|
||||||
if (!resizeObserver) return
|
if (!element) return
|
||||||
|
|
||||||
itemRefs.forEach((el, key) => {
|
const firstChild = element.firstElementChild
|
||||||
resizeObserver!.observe(el)
|
const targetElement = firstChild || element
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateItemHeight(key: string | number, height: number) {
|
|
||||||
const oldHeight = itemHeights.value.get(key)
|
|
||||||
if (oldHeight !== height) {
|
|
||||||
itemHeights.value.set(key, height)
|
|
||||||
calculateOffsets()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleScroll(event: Event) {
|
|
||||||
const target = event.target as HTMLElement
|
|
||||||
const currentScrollTop = target.scrollTop
|
|
||||||
scrollTop.value = currentScrollTop
|
|
||||||
|
|
||||||
if (!scrollContainerRef.value) {
|
const height = targetElement.getBoundingClientRect().height
|
||||||
emit('scroll', currentScrollTop)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const containerHeight = scrollContainerRef.value.clientHeight
|
if (height > 0) {
|
||||||
const maxScroll = Math.max(0, totalHeight.value - containerHeight)
|
const cachedHeight = itemHeights.value.get(index)
|
||||||
|
if (cachedHeight !== height) {
|
||||||
const distanceToTop = currentScrollTop
|
const newHeights = new Map(itemHeights.value)
|
||||||
const distanceToBottom = maxScroll - currentScrollTop
|
newHeights.set(index, height)
|
||||||
|
itemHeights.value = newHeights
|
||||||
const isAtTop = currentScrollTop >= maxScroll - 10
|
|
||||||
const isAtBottom = currentScrollTop <= 10
|
|
||||||
|
|
||||||
emit('scroll', currentScrollTop, {
|
|
||||||
isAtTop,
|
|
||||||
isAtBottom,
|
|
||||||
distanceToTop,
|
|
||||||
distanceToBottom
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToIndex(index: number) {
|
|
||||||
if (!scrollContainerRef.value) return
|
|
||||||
|
|
||||||
let targetTop = 0
|
|
||||||
for (let i = 0; i < index; i++) {
|
|
||||||
const key = getItemKey(props.items[i], i)
|
|
||||||
targetTop += getItemHeight(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollContainerRef.value.scrollTop = targetTop
|
|
||||||
scrollTop.value = targetTop
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToTop() {
|
|
||||||
if (!scrollContainerRef.value) return
|
|
||||||
const containerHeight = scrollContainerRef.value.clientHeight
|
|
||||||
const maxScroll = Math.max(0, totalHeight.value - containerHeight)
|
|
||||||
scrollContainerRef.value.scrollTop = maxScroll
|
|
||||||
scrollTop.value = maxScroll
|
|
||||||
}
|
|
||||||
|
|
||||||
function scrollToBottom() {
|
|
||||||
if (!scrollContainerRef.value) return
|
|
||||||
scrollContainerRef.value.scrollTop = 0
|
|
||||||
scrollTop.value = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => visibleRange.value,
|
|
||||||
(newRange) => {
|
|
||||||
emit('visible-change', newRange.start, newRange.end)
|
|
||||||
nextTick(() => {
|
|
||||||
measureItems()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.items,
|
|
||||||
() => {
|
|
||||||
calculateOffsets()
|
|
||||||
nextTick(() => {
|
|
||||||
measureItems()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.items.length,
|
|
||||||
async (newLength, oldLength) => {
|
|
||||||
if (isReverseMode.value && newLength > (oldLength || 0)) {
|
|
||||||
await nextTick()
|
|
||||||
scrollToBottom()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
const setupResizeObserver = () => {
|
||||||
resizeObserver = new ResizeObserver((entries) => {
|
if (resizeObserver.value) {
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeObserver.value = new ResizeObserver((entries) => {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const el = entry.target as HTMLElement
|
const index = parseInt(entry.target.dataset.index, 10)
|
||||||
const key = el.dataset.key
|
if (!isNaN(index)) {
|
||||||
if (key) {
|
measureItem(index, entry.target)
|
||||||
updateItemHeight(key, entry.contentRect.height)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleWheel = (event) => {
|
||||||
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
if (scrollContainerRef.value && isReverseMode.value) {
|
const { deltaY } = event
|
||||||
nextTick(() => {
|
const el = renderContainerRef.value
|
||||||
scrollToBottom()
|
|
||||||
setTimeout(() => {
|
el.scrollBy({
|
||||||
isInitializing.value = false
|
top: -deltaY,
|
||||||
}, 100)
|
behavior: 'instant'
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
isInitializing.value = false
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScroll = (event) => {
|
||||||
|
const target = event.target
|
||||||
|
scrollTop.value = target.scrollTop
|
||||||
|
isScrolling.value = true
|
||||||
|
|
||||||
|
if (scrollTimeout.value) {
|
||||||
|
clearTimeout(scrollTimeout.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
measureItems()
|
scrollTimeout.value = setTimeout(() => {
|
||||||
|
isScrolling.value = false
|
||||||
|
}, 150)
|
||||||
|
|
||||||
|
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,
|
||||||
|
scrollHeight,
|
||||||
|
clientHeight,
|
||||||
|
distanceToPageTop,
|
||||||
|
distanceToPageBottom,
|
||||||
|
isAtPageTop,
|
||||||
|
isAtPageBottom
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isAtPageTop) {
|
||||||
|
emit('scroll-start')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAtPageBottom) {
|
||||||
|
emit('scroll-end')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToBottom = (behavior = 'smooth') => {
|
||||||
|
if (!renderContainerRef.value) {
|
||||||
|
pendingScrollToBottom.value = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
|
renderContainerRef.value.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollToTop = (behavior = 'smooth') => {
|
||||||
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
if (!renderContainerRef.value) return
|
||||||
|
|
||||||
|
const scrollHeight = renderContainerRef.value.scrollHeight
|
||||||
|
renderContainerRef.value.scrollTo({
|
||||||
|
top: scrollHeight,
|
||||||
|
behavior
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getScrollElement = () => renderContainerRef.value
|
||||||
|
|
||||||
|
const getVisibleIndices = () => {
|
||||||
|
const { start, end } = visibleRange.value
|
||||||
|
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetMeasurements = () => {
|
||||||
|
itemHeights.value = new Map()
|
||||||
|
itemRefs.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAtPageBottom = () => {
|
||||||
|
if (!renderContainerRef.value) return false
|
||||||
|
const { scrollTop } = renderContainerRef.value
|
||||||
|
return scrollTop <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAtPageTop = () => {
|
||||||
|
if (!renderContainerRef.value) return false
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = renderContainerRef.value
|
||||||
|
return scrollHeight - scrollTop - clientHeight <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const observeVisibleItems = () => {
|
||||||
|
if (!resizeObserver.value) return
|
||||||
|
|
||||||
|
resizeObserver.value.disconnect()
|
||||||
|
|
||||||
|
for (const [index, element] of itemRefs) {
|
||||||
|
if (element) {
|
||||||
|
resizeObserver.value.observe(element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 })
|
||||||
|
|
||||||
|
watch(visibleItems, (newItems) => {
|
||||||
|
nextTick(() => {
|
||||||
|
observeVisibleItems()
|
||||||
|
})
|
||||||
|
if (newItems.length > 0) {
|
||||||
|
const firstItem = newItems[0]
|
||||||
|
const lastItem = newItems[newItems.length - 1]
|
||||||
|
emit('visible-change', firstItem.index, lastItem.index)
|
||||||
|
}
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setupResizeObserver()
|
||||||
|
isInitialized.value = true
|
||||||
|
previousDataLength.value = computedData.value.length
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (pendingScrollToBottom.value) {
|
||||||
|
pendingScrollToBottom.value = false
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
|
||||||
|
observeVisibleItems()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onBeforeUnmount(() => {
|
||||||
if (resizeObserver) {
|
if (resizeObserver.value) {
|
||||||
resizeObserver.disconnect()
|
resizeObserver.value.disconnect()
|
||||||
resizeObserver = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scrollTimeout.value) {
|
||||||
|
clearTimeout(scrollTimeout.value)
|
||||||
|
}
|
||||||
|
|
||||||
itemRefs.clear()
|
itemRefs.clear()
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
scrollToIndex,
|
scrollToIndex,
|
||||||
scrollToTop,
|
scrollToItem: scrollToIndex,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
getScrollTop: () => scrollTop.value,
|
scrollToTop,
|
||||||
getVisibleRange: () => visibleRange.value,
|
getScrollElement,
|
||||||
updateLayout: () => {
|
getVisibleIndices,
|
||||||
calculateOffsets()
|
resetMeasurements,
|
||||||
measureItems()
|
containerRef,
|
||||||
}
|
isAtPageBottom,
|
||||||
|
isAtPageTop
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style lang="less" scoped>
|
||||||
.virtual-scroller {
|
.virtual-scroller {
|
||||||
position: relative;
|
-webkit-overflow-scrolling: touch;
|
||||||
width: 100%;
|
|
||||||
}
|
&::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
.virtual-scroller-container {
|
width: 6px;
|
||||||
position: relative;
|
height: 6px;
|
||||||
width: 100%;
|
}
|
||||||
}
|
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
.virtual-scroller-spacer {
|
background: transparent;
|
||||||
flex-shrink: 0;
|
}
|
||||||
}
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
.virtual-scroller-item {
|
background: rgba(0, 0, 0, 0.2);
|
||||||
will-change: transform;
|
border-radius: 3px;
|
||||||
contain: layout style;
|
|
||||||
|
&: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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,10 @@ const DisplayStoreSetup = () => {
|
||||||
const newItem = {
|
const newItem = {
|
||||||
id: item.taskId || crypto.randomUUID(),
|
id: item.taskId || crypto.randomUUID(),
|
||||||
status: 'generate',
|
status: 'generate',
|
||||||
text: item.text || '生成中...',
|
|
||||||
name: item.name || '生成中...',
|
|
||||||
type: item.type || 'image',
|
type: item.type || 'image',
|
||||||
time: item.time || new Date().toLocaleString(),
|
time: item.time || new Date().toLocaleString(),
|
||||||
files: [],
|
files: [],
|
||||||
...item
|
generateData: item.generateData || {}
|
||||||
}
|
}
|
||||||
tempList.value.unshift(newItem)
|
tempList.value.unshift(newItem)
|
||||||
return newItem
|
return newItem
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ export async function createTask(data, type, taskId, token) {
|
||||||
modelName: data.modelName,
|
modelName: data.modelName,
|
||||||
payload,
|
payload,
|
||||||
taskId,
|
taskId,
|
||||||
token
|
token,
|
||||||
|
result: data.result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export function getChargeType(chargeType) {
|
||||||
case 'painting':
|
case 'painting':
|
||||||
return 1
|
return 1
|
||||||
case 'video':
|
case 'video':
|
||||||
return 2
|
return 4
|
||||||
default:
|
default:
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ export function websocketSuccess() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function generate(type, data) {
|
export async function generate(modelType, data, generateData, type) {
|
||||||
const progress_text = ref('')
|
const progress_text = ref('')
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
const useDisplay = useDisplayStore()
|
const useDisplay = useDisplayStore()
|
||||||
|
|
@ -68,7 +68,7 @@ export async function generate(type, data) {
|
||||||
|
|
||||||
useDisplay.isSubGerenate = true
|
useDisplay.isSubGerenate = true
|
||||||
|
|
||||||
const result = await createTask(data, type, taskId, token)
|
const result = await createTask(data, modelType, taskId, token)
|
||||||
console.log(result)
|
console.log(result)
|
||||||
// const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjY0NDEwODAyMjk1OTgzNzIzMCwicm5TdHIiOiJiWkVwS2JLWFJyZmRIaFFHWXZKTkdzOGdGM0JSRmxQOCJ9.5eQ2GtVdrDntQDe2tnF8vl_DhTfd2uW-KNqzvl1imc0'
|
// const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOjY0NDEwODAyMjk1OTgzNzIzMCwicm5TdHIiOiJiWkVwS2JLWFJyZmRIaFFHWXZKTkdzOGdGM0JSRmxQOCJ9.5eQ2GtVdrDntQDe2tnF8vl_DhTfd2uW-KNqzvl1imc0'
|
||||||
const wsURL = `${import.meta.env.VITE_API_WORKFLOW_WS}/?token=Bearer ${token}`
|
const wsURL = `${import.meta.env.VITE_API_WORKFLOW_WS}/?token=Bearer ${token}`
|
||||||
|
|
@ -102,10 +102,8 @@ export async function generate(type, data) {
|
||||||
|
|
||||||
useDisplay.addGeneratingItem({
|
useDisplay.addGeneratingItem({
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
text: data.prompt || '生成中...',
|
|
||||||
name: data.prompt || '生成中...',
|
|
||||||
type: type,
|
type: type,
|
||||||
result: data.result
|
generateData: generateData
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
useDisplay.scrollToBottom()
|
useDisplay.scrollToBottom()
|
||||||
|
|
|
||||||
|
|
@ -2,25 +2,14 @@
|
||||||
<div style="width: 100%;display: flex;justify-content: center;align-items: center;transform: rotate(180deg);">
|
<div style="width: 100%;display: flex;justify-content: center;align-items: center;transform: rotate(180deg);">
|
||||||
<div class="primary-box" :class="{ 'none-primary-box': props.item.status === 'none' }">
|
<div class="primary-box" :class="{ 'none-primary-box': props.item.status === 'none' }">
|
||||||
<!-- 标题 -->
|
<!-- 标题 -->
|
||||||
<div class="title">
|
<div class="title" ref="titleRef" @mouseenter="isHovering = true" @mouseleave="isHovering = false">
|
||||||
<div class="style">
|
<div class="prompt-wrapper">
|
||||||
<div class="name" ref="nameRef">{{ props.item.text }}</div>
|
<div class="prompt" ref="nameRef" :class="{ 'expanded': isHovering }">{{ props.item.generateData.prompt || '生成图片' }}</div>
|
||||||
<div class="generate-data" ref="generateDataRef">
|
<div class="generate-data" v-show="!isHovering" ref="generateDa taRef" :class="{ 'second-line': shouldShowOnSecondLine }">
|
||||||
<div class="detailed-data first-detailed-data">{{ props.item.model }}</div>
|
<div class="detailed-data first-detailed-data">{{ props.item.generateData.model }}</div>
|
||||||
<!-- <div class="detailed-data">多少秒</div> -->
|
<div class="detailed-data">{{ props.item.generateData.proportion }}</div>
|
||||||
<div class="detailed-data">{{ props.item.proportion }}</div>
|
|
||||||
<div class="detailed-data">{{ props.item.resolution }}</div>
|
|
||||||
<div class="detailed-data">{{ props.item.quantity }}张</div>
|
|
||||||
<!-- <div class="detailed-data">多少k</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.item.parentIndex" class="style">
|
|
||||||
<span class="time">源自 {{ props.item.parentTime }}</span>
|
|
||||||
<div class="dividing-line"></div>
|
|
||||||
<span class="time">源自 {{ props.item.parentTime }}</span>
|
|
||||||
<div v-if="props.item.parentIndex" class="dividing-line"></div>
|
|
||||||
<span class="time"> 第 {{ props.item.parentIndex }} 张图片</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 加载中 -->
|
<!-- 加载中 -->
|
||||||
|
|
@ -49,8 +38,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 已完成 -->
|
<!-- 已完成 图片 -->
|
||||||
<div v-if="props.item.status === 'success'" class="box success-box">
|
<div v-if="props.item.status === 'success' && props.item.type === 'painting'" class="box success-box">
|
||||||
<div v-for="(file, index) in props.item.files" :key="index" class="one-box" :class="{ 'collected': isCollected(file) }" @mouseenter="hoverIndex = index" @mouseleave="hoverIndex = -1">
|
<div v-for="(file, index) in props.item.files" :key="index" class="one-box" :class="{ 'collected': isCollected(file) }" @mouseenter="hoverIndex = index" @mouseleave="hoverIndex = -1">
|
||||||
<!-- <img :src="file" alt="index" class="img" /> -->
|
<!-- <img :src="file" alt="index" class="img" /> -->
|
||||||
<Img :src="file" alt="index" class="img" />
|
<Img :src="file" alt="index" class="img" />
|
||||||
|
|
@ -74,7 +63,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.item.status === 'success'" class="bottom-btn-group">
|
<!-- 已完成 视频 -->
|
||||||
|
<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">
|
||||||
|
<!-- <img :src="file" alt="index" class="img" /> -->
|
||||||
|
<video :src="props.item.files[0]" alt="index" class="video" />
|
||||||
|
|
||||||
|
<div class="left-top">
|
||||||
|
<div v-show="hoverIndex === 0" class="left-top-btn download-btn" @click="downloadImage(props.item.files[0], 'video')"><img src="@/assets/display/download.svg" /></div>
|
||||||
|
<span v-if="hoverIndex === 0" class="line" />
|
||||||
|
<div class="left-top-btn collect-btn" @click="addCollection(props.item.files[0])"><img :src="isCollected(props.item.files[0]) ? collectionActiveIcon : collectionIcon" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="props.item.status === 'success'" class="bottom-btn-group" style="margin-top: 8px;">
|
||||||
<div v-for="(item, index) in bottomBtnGroup" :key="index" class="bottom-btn" @click="item.click()">
|
<div v-for="(item, index) in bottomBtnGroup" :key="index" class="bottom-btn" @click="item.click()">
|
||||||
<img :src="item.icon" />
|
<img :src="item.icon" />
|
||||||
<span>{{ item.name }}</span>
|
<span>{{ item.name }}</span>
|
||||||
|
|
@ -102,7 +105,6 @@ const props = defineProps({
|
||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['open-canvas', 'delete-success'])
|
const emit = defineEmits(['open-canvas', 'delete-success'])
|
||||||
|
|
||||||
const useDisplay = useDisplayStore()
|
const useDisplay = useDisplayStore()
|
||||||
|
|
@ -111,6 +113,76 @@ const useUser = useUserStore()
|
||||||
|
|
||||||
const localCollectStatus = ref({ ...props.item.collectStatus })
|
const localCollectStatus = ref({ ...props.item.collectStatus })
|
||||||
const hoverIndex = ref(-1)
|
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 checkTextOverflow = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
if (nameRef.value && titleRef.value && generateDataRef.value) {
|
||||||
|
const lineHeight = 22.5
|
||||||
|
const padding = 8
|
||||||
|
const twoLineHeight = 55
|
||||||
|
const generateDataWidth = generateDataRef.value.offsetWidth + 20
|
||||||
|
|
||||||
|
nameRef.value.style.maxHeight = 'none'
|
||||||
|
nameRef.value.style.overflow = 'visible'
|
||||||
|
|
||||||
|
const actualHeight = nameRef.value.scrollHeight
|
||||||
|
const lineCount = Math.ceil((actualHeight - padding * 2) / lineHeight)
|
||||||
|
|
||||||
|
if (!isHovering.value) {
|
||||||
|
nameRef.value.style.maxHeight = '55px'
|
||||||
|
nameRef.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
|
||||||
|
} else {
|
||||||
|
shouldShowOnSecondLine.value = false
|
||||||
|
wrapperHeight.value = Math.max(lineHeight + padding * 2, actualHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(isHovering, (newVal) => {
|
||||||
|
if (nameRef.value) {
|
||||||
|
if (newVal) {
|
||||||
|
nameRef.value.style.maxHeight = 'none'
|
||||||
|
nameRef.value.style.overflow = 'visible'
|
||||||
|
} else {
|
||||||
|
nameRef.value.style.maxHeight = '55px'
|
||||||
|
nameRef.value.style.overflow = 'hidden'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.item.generateData.prompt, () => {
|
||||||
|
checkTextOverflow()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
checkTextOverflow()
|
||||||
|
window.addEventListener('resize', checkTextOverflow)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', checkTextOverflow)
|
||||||
|
})
|
||||||
|
|
||||||
const isCollected = (url) => {
|
const isCollected = (url) => {
|
||||||
return localCollectStatus.value[url] === true
|
return localCollectStatus.value[url] === true
|
||||||
|
|
@ -132,12 +204,12 @@ const AIbrush = (file, index) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const reEdit = () => {
|
const reEdit = () => {
|
||||||
useDisplay.setResultData(props.item.result)
|
useDisplay.setResultData(props.item.generateData)
|
||||||
useDisplay.fillParamsForEdit()
|
useDisplay.fillParamsForEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
const againGenerate = () => {
|
const againGenerate = () => {
|
||||||
useDisplay.setResultData(props.item.result)
|
useDisplay.setResultData(props.item.generateData)
|
||||||
useDisplay.triggerGenerateWithResult()
|
useDisplay.triggerGenerateWithResult()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -214,40 +286,62 @@ const addCollection = async (url) => {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 20px;
|
gap: 12px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
.none-primary-box{
|
.none-primary-box{
|
||||||
height: 350px;
|
height: 350px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title{
|
.title{
|
||||||
// height: 25px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
position: relative;
|
||||||
align-items: center;
|
|
||||||
gap: 20px;
|
|
||||||
|
|
||||||
.style{
|
.prompt-wrapper{
|
||||||
width: 100%;
|
position: relative;
|
||||||
display: inline;
|
min-height: 28.5px;
|
||||||
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.name{
|
.prompt{
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
color: #333;
|
color: #333;
|
||||||
font-family: "Microsoft YaHei";
|
font-family: "Microsoft YaHei";
|
||||||
font-size: 15px;
|
font-size: 14px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
line-height: 1.5;
|
line-height: 22.5px;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
display: inline;
|
max-height: 60px;
|
||||||
margin-right: 20px;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.generate-data{
|
.generate-data{
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: #fff;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
|
&.second-line{
|
||||||
|
top: auto;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.detailed-data{
|
.detailed-data{
|
||||||
|
|
@ -270,28 +364,6 @@ const addCollection = async (url) => {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time{
|
|
||||||
color: #333;
|
|
||||||
font-family: "Microsoft YaHei";
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dividing-line{
|
|
||||||
height: 100%;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn{
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-btn:hover{
|
|
||||||
color: #ff4949;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.box{
|
.box{
|
||||||
|
|
@ -399,6 +471,13 @@ const addCollection = async (url) => {
|
||||||
object-fit: contain; /* 确保图片完整显示,不被裁剪 */
|
object-fit: contain; /* 确保图片完整显示,不被裁剪 */
|
||||||
border-radius: 8px; /* 可选:给图片添加圆角 */
|
border-radius: 8px; /* 可选:给图片添加圆角 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video{
|
||||||
|
width: 380px;
|
||||||
|
height: auto; /* 保持宽高比 */
|
||||||
|
object-fit: contain; /* 确保图片完整显示,不被裁剪 */
|
||||||
|
border-radius: 8px; /* 可选:给图片添加圆角 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-top,.bottom-brush{
|
.left-top,.bottom-brush{
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@
|
||||||
:estimated-height="300"
|
:estimated-height="300"
|
||||||
:buffer-size="3"
|
:buffer-size="3"
|
||||||
direction="reverse"
|
direction="reverse"
|
||||||
|
:bottom-placeholder-height="350"
|
||||||
class="scroller"
|
class="scroller"
|
||||||
@scroll="handleScroll"
|
@scroll="handleScroll"
|
||||||
@visible-change="handleVisibleChange"
|
@visible-change="handleVisibleChange"
|
||||||
|
|
@ -103,7 +104,7 @@ const isInitializing = ref(true)
|
||||||
const { canvasVisible, canvasImage, canvasReferenceImages, canvasSource } = storeToRefs(useDisplay)
|
const { canvasVisible, canvasImage, canvasReferenceImages, canvasSource } = storeToRefs(useDisplay)
|
||||||
|
|
||||||
const chargeType = computed(() => getChargeType(props.type))
|
const chargeType = computed(() => getChargeType(props.type))
|
||||||
console.log(chargeType.value)
|
// console.log(chargeType.value)
|
||||||
|
|
||||||
const timeOptions = [
|
const timeOptions = [
|
||||||
{ label: '全部', value: 'all' },
|
{ label: '全部', value: 'all' },
|
||||||
|
|
@ -135,42 +136,25 @@ const toggleDisplay = (newValue, oldValue) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversion = (newlist) => {
|
const conversion = (newlist) => {
|
||||||
const temp = newlist.list.map((item) => {
|
const temp = newlist.map((item) => {
|
||||||
const files = item.fileUrl ? item.fileUrl.split(',').filter(url => url.trim()) : []
|
const files = item.fileUrl ? item.fileUrl.split(',').filter(url => url.trim()) : []
|
||||||
|
const generateData = JSON.parse(item.result || '{}')
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
taskId: item.taskId,
|
taskId: item.taskId,
|
||||||
|
type: props.type,
|
||||||
collection: item.collection,
|
collection: item.collection,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
prompt: item.prompt,
|
generateData: generateData,
|
||||||
params: item.params,
|
|
||||||
result: item.result,
|
|
||||||
time: item.createTime,
|
time: item.createTime,
|
||||||
files: files,
|
files: files,
|
||||||
collectStatus: item.collectStatus || {},
|
collectStatus: item.collectStatus || {}
|
||||||
model: item.model || '',
|
|
||||||
proportion: item.proportion || '',
|
|
||||||
resolution: item.resolution || '',
|
|
||||||
quantity: item.quantity || 1
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
console.log(temp)
|
||||||
return temp
|
return temp
|
||||||
}
|
}
|
||||||
|
|
||||||
const adaptDataList = (dataList) => {
|
|
||||||
const convertedList = conversion({ list: dataList })
|
|
||||||
return convertedList.map((item, index) => {
|
|
||||||
const originalItem = dataList[index]
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
text: originalItem?.title || item.prompt || '生成图片',
|
|
||||||
name: originalItem?.title || item.prompt || '生成图片',
|
|
||||||
type: 'image',
|
|
||||||
title: originalItem?.title || '生成图片'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const fetchHistory = async (isLoadMore = false) => {
|
const fetchHistory = async (isLoadMore = false) => {
|
||||||
if (isLoading.value || (!isLoadMore && !hasMoreData.value)) return
|
if (isLoading.value || (!isLoadMore && !hasMoreData.value)) return
|
||||||
|
|
||||||
|
|
@ -200,7 +184,7 @@ const fetchHistory = async (isLoadMore = false) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const adaptedList = adaptDataList(dataList)
|
const adaptedList = conversion(dataList)
|
||||||
|
|
||||||
if (isLoadMore) {
|
if (isLoadMore) {
|
||||||
useDisplay.appendHistoryList(adaptedList)
|
useDisplay.appendHistoryList(adaptedList)
|
||||||
|
|
@ -244,13 +228,13 @@ const fetchHistory = async (isLoadMore = false) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScroll = (scrollTop, scrollInfo) => {
|
const handleScroll = (scrollInfo) => {
|
||||||
if (isInitializing.value) return
|
if (isInitializing.value) return
|
||||||
|
|
||||||
if (!scrollInfo) return
|
if (!scrollInfo) return
|
||||||
const { isAtTop, isAtBottom, distanceToTop, distanceToBottom } = scrollInfo
|
const { isAtPageTop, isAtPageBottom, distanceToPageTop, distanceToPageBottom } = scrollInfo
|
||||||
|
|
||||||
if (isAtTop && !isLoading.value && !isLoadingMoreLocked.value && hasMoreData.value) {
|
if (isAtPageTop && !isLoading.value && !isLoadingMoreLocked.value && hasMoreData.value) {
|
||||||
isLoadingMoreLocked.value = true
|
isLoadingMoreLocked.value = true
|
||||||
fetchHistory(true)
|
fetchHistory(true)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
@ -258,9 +242,9 @@ const handleScroll = (scrollTop, scrollInfo) => {
|
||||||
}, 3000)
|
}, 3000)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAtBottom) {
|
if (isAtPageBottom) {
|
||||||
useDisplay.Sender_variant = 'updown'
|
useDisplay.Sender_variant = 'updown'
|
||||||
} else if (distanceToTop >= 350) {
|
} else if (distanceToPageTop >= 350) {
|
||||||
useDisplay.Sender_variant = 'default'
|
useDisplay.Sender_variant = 'default'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import { computed, ref, onMounted, watch } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import display from './display/index.vue'
|
import display from './display/index.vue'
|
||||||
import { useDisplayStore } from '@/stores'
|
import { useDisplayStore } from '@/stores'
|
||||||
|
|
@ -14,8 +14,16 @@ const Generate = computed(() => route.query.Generate || false)
|
||||||
const type = computed(() => route.query.type || 'painting')
|
const type = computed(() => route.query.type || 'painting')
|
||||||
console.log(type.value)
|
console.log(type.value)
|
||||||
|
|
||||||
|
watch(dialogBoxRef, (newRef) => {
|
||||||
|
if (newRef) {
|
||||||
|
useDisplay.setDialogBoxRef(newRef)
|
||||||
|
}
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
useDisplay.setDialogBoxRef(dialogBoxRef.value)
|
if (dialogBoxRef.value) {
|
||||||
|
useDisplay.setDialogBoxRef(dialogBoxRef.value)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue