fix: 移除 dialogBox 中残留的平台分支,统一为 descriptor 接口方法
This commit is contained in:
parent
ec81dce28a
commit
3507eddfb3
@ -104,16 +104,7 @@ const showUploader = computed(() => {
|
||||
return platform.value.showImageUploader()
|
||||
})
|
||||
|
||||
const uploaderBindings = computed(() => {
|
||||
const p = platform.value
|
||||
if (p.id === 'painting') {
|
||||
return { limit: p.imageUploadLimit() }
|
||||
}
|
||||
if (p.id === 'video') {
|
||||
return { modelType: p.modelType.value, imagesCount: p.imageUploadLimit() }
|
||||
}
|
||||
return {}
|
||||
})
|
||||
const uploaderBindings = computed(() => platform.value.getUploaderBindings())
|
||||
|
||||
const autoSizeConfig = computed(() => {
|
||||
if (useDisplay.Sender_variant !== 'default') {
|
||||
@ -125,8 +116,9 @@ const autoSizeConfig = computed(() => {
|
||||
const handleStart = async () => {
|
||||
const p = platform.value
|
||||
|
||||
if (props.type === 'Video' && p.model.value === 'Seedance 2.0') {
|
||||
ElMessage.primary('敬请期待 Seedance 2.0')
|
||||
const validationError = p.validateBeforeSubmit()
|
||||
if (validationError) {
|
||||
ElMessage.primary(validationError)
|
||||
return
|
||||
}
|
||||
|
||||
@ -195,11 +187,7 @@ watch(
|
||||
[() => platform.value.model.value, () => platform.value.modelType.value],
|
||||
async ([newModel, newModelType]) => {
|
||||
if (!newModel) return
|
||||
if (platform.value.id === 'video') {
|
||||
await platform.value.loadConfig(newModel, newModelType)
|
||||
} else {
|
||||
platform.value.loadConfig(newModel)
|
||||
}
|
||||
await platform.value.loadConfig(newModel, newModelType)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@ -26,7 +26,6 @@
|
||||
<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"
|
||||
@ -119,15 +118,11 @@ const emit = defineEmits(['scroll', 'scroll-start', 'scroll-end', 'visible-chang
|
||||
const containerRef = ref(null)
|
||||
const wrapperRef = ref(null)
|
||||
const renderContainerRef = ref(null)
|
||||
const itemRefs = new Map()
|
||||
const itemHeights = ref(new Map())
|
||||
const heightVersion = ref(0)
|
||||
const resizeObserver = ref(null)
|
||||
const scrollTop = ref(0)
|
||||
const isScrolling = ref(false)
|
||||
const scrollTimeout = ref(null)
|
||||
const isInitialized = ref(false)
|
||||
const pendingScrollToBottom = ref(false)
|
||||
const previousDataLength = ref(0)
|
||||
|
||||
const containerStyle = computed(() => {
|
||||
return {
|
||||
@ -138,6 +133,7 @@ const containerStyle = computed(() => {
|
||||
})
|
||||
|
||||
const totalHeight = computed(() => {
|
||||
heightVersion.value // 依赖追踪:measureItem 直接 mutate Map 时通过此版本号触发重算
|
||||
let height = 0
|
||||
const len = computedData.value.length
|
||||
|
||||
@ -173,51 +169,50 @@ const visibleRange = computed(() => {
|
||||
if (!renderContainerRef.value || computedData.value.length === 0) {
|
||||
return { start: 0, end: 0, offset: 0 }
|
||||
}
|
||||
|
||||
|
||||
heightVersion.value // 依赖追踪:measureItem 直接 mutate Map 时通过此版本号触发重算
|
||||
const viewportHeight = containerHeight.value
|
||||
const currentScrollTop = scrollTop.value
|
||||
const bufferCount = computedBuffer.value
|
||||
|
||||
let startIndex = 0
|
||||
let endIndex = computedData.value.length - 1
|
||||
let startOffset = 0
|
||||
|
||||
const len = computedData.value.length
|
||||
|
||||
// 第一趟:定位首个可见项索引
|
||||
let offset = 0
|
||||
for (let i = 0; i < computedData.value.length; i++) {
|
||||
const cachedHeight = itemHeights.value.get(i)
|
||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||
|
||||
let firstVisibleIdx = 0
|
||||
for (let i = 0; i < len; i++) {
|
||||
const height = itemHeights.value.get(i) ?? props.estimatedHeight
|
||||
if (offset + height > currentScrollTop) {
|
||||
startIndex = Math.max(0, i - bufferCount)
|
||||
firstVisibleIdx = i
|
||||
break
|
||||
}
|
||||
|
||||
offset += height
|
||||
}
|
||||
|
||||
startOffset = 0
|
||||
for (let i = 0; i < startIndex; i++) {
|
||||
const cachedHeight = itemHeights.value.get(i)
|
||||
startOffset += cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||
}
|
||||
|
||||
offset = startOffset
|
||||
endIndex = startIndex
|
||||
for (let i = startIndex; i < computedData.value.length; i++) {
|
||||
const cachedHeight = itemHeights.value.get(i)
|
||||
const height = cachedHeight !== undefined ? cachedHeight : props.estimatedHeight
|
||||
|
||||
offset += height
|
||||
|
||||
if (offset > currentScrollTop + viewportHeight) {
|
||||
endIndex = Math.min(computedData.value.length - 1, i + bufferCount)
|
||||
break
|
||||
|
||||
const startIndex = Math.max(0, firstVisibleIdx - bufferCount)
|
||||
|
||||
// 第二趟:计算 startOffset 并同时定位 endIndex
|
||||
let startOffset = 0
|
||||
let endIndex = len - 1
|
||||
offset = 0
|
||||
for (let i = 0; i < len; i++) {
|
||||
const height = itemHeights.value.get(i) ?? props.estimatedHeight
|
||||
|
||||
if (i < startIndex) {
|
||||
startOffset += height
|
||||
}
|
||||
|
||||
endIndex = i
|
||||
|
||||
if (i >= startIndex) {
|
||||
if (offset + height > currentScrollTop + viewportHeight) {
|
||||
endIndex = Math.min(len - 1, i + bufferCount)
|
||||
break
|
||||
}
|
||||
endIndex = i
|
||||
}
|
||||
|
||||
offset += height
|
||||
}
|
||||
|
||||
return { start: Math.min(startIndex, endIndex), end: Math.max(startIndex, endIndex), offset: startOffset }
|
||||
|
||||
return { start: startIndex, end: endIndex, offset: startOffset }
|
||||
})
|
||||
|
||||
const visibleItems = computed(() => {
|
||||
@ -247,7 +242,6 @@ const wrapperStyle = computed(() => ({
|
||||
direction: 'rtl',
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
scrollbarWidth: 'auto',
|
||||
overflow: 'hidden',
|
||||
transform: 'rotate(180deg)',
|
||||
width: '100%'
|
||||
@ -302,28 +296,19 @@ const getItemStyle = (renderItem) => {
|
||||
}
|
||||
}
|
||||
|
||||
const setItemRef = (el, index) => {
|
||||
if (el) {
|
||||
itemRefs.set(index, el)
|
||||
} else {
|
||||
itemRefs.delete(index)
|
||||
}
|
||||
}
|
||||
|
||||
const measureItem = (index, element) => {
|
||||
if (!element) return
|
||||
|
||||
|
||||
const firstChild = element.firstElementChild
|
||||
const targetElement = firstChild || element
|
||||
|
||||
|
||||
const height = Math.ceil(targetElement.offsetHeight)
|
||||
|
||||
|
||||
if (height > 0) {
|
||||
const cachedHeight = itemHeights.value.get(index)
|
||||
if (cachedHeight !== height) {
|
||||
const newHeights = new Map(itemHeights.value)
|
||||
newHeights.set(index, height)
|
||||
itemHeights.value = newHeights
|
||||
itemHeights.value.set(index, height)
|
||||
heightVersion.value++
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -357,29 +342,10 @@ const handleWheel = (event) => {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
const scrollCleanupTimeout = ref(null)
|
||||
|
||||
const handleScroll = (event) => {
|
||||
const target = event.target
|
||||
scrollTop.value = target.scrollTop
|
||||
isScrolling.value = true
|
||||
|
||||
if (scrollTimeout.value) {
|
||||
clearTimeout(scrollTimeout.value)
|
||||
}
|
||||
|
||||
scrollTimeout.value = setTimeout(() => {
|
||||
isScrolling.value = false
|
||||
}, 150)
|
||||
|
||||
// 滚动时添加防抖清理,每100ms最多执行一次
|
||||
if (scrollCleanupTimeout.value) {
|
||||
clearTimeout(scrollCleanupTimeout.value)
|
||||
}
|
||||
scrollCleanupTimeout.value = setTimeout(() => {
|
||||
cleanupExtraItems(visibleItems.value)
|
||||
}, 300)
|
||||
|
||||
|
||||
const st = target.scrollTop
|
||||
const scrollHeight = target.scrollHeight
|
||||
const clientHeight = target.clientHeight
|
||||
@ -462,7 +428,6 @@ const getVisibleIndices = () => {
|
||||
|
||||
const resetMeasurements = () => {
|
||||
itemHeights.value = new Map()
|
||||
itemRefs.clear()
|
||||
}
|
||||
|
||||
const isAtPageBottom = () => {
|
||||
@ -478,13 +443,15 @@ const isAtPageTop = () => {
|
||||
}
|
||||
|
||||
const observeVisibleItems = () => {
|
||||
if (!resizeObserver.value) return
|
||||
|
||||
if (!resizeObserver.value || !renderContainerRef.value) return
|
||||
|
||||
resizeObserver.value.disconnect()
|
||||
|
||||
for (const [index, element] of itemRefs) {
|
||||
if (element) {
|
||||
resizeObserver.value.observe(element)
|
||||
|
||||
// 基于 visibleItems 的 index 通过 DOM 查询定位元素,避免 itemRefs 残留旧 DOM 引用
|
||||
for (const item of visibleItems.value) {
|
||||
const el = renderContainerRef.value.querySelector(`[data-index="${item.index}"]`)
|
||||
if (el) {
|
||||
resizeObserver.value.observe(el)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,31 +459,28 @@ const observeVisibleItems = () => {
|
||||
watch(() => computedData.value, (newData, oldData) => {
|
||||
const oldLength = oldData?.length || 0
|
||||
const newLength = newData.length
|
||||
|
||||
|
||||
if (newLength !== oldLength) {
|
||||
const newHeights = new Map()
|
||||
|
||||
|
||||
const minLen = Math.min(oldLength, newLength)
|
||||
for (let i = 0; i < minLen; i++) {
|
||||
if (itemHeights.value.has(i)) {
|
||||
newHeights.set(i, itemHeights.value.get(i))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
itemHeights.value = newHeights
|
||||
|
||||
|
||||
nextTick(() => {
|
||||
observeVisibleItems()
|
||||
})
|
||||
}
|
||||
|
||||
previousDataLength.value = newLength
|
||||
}, { deep: false })
|
||||
|
||||
watch(visibleItems, (newItems) => {
|
||||
nextTick(() => {
|
||||
observeVisibleItems()
|
||||
cleanupExtraItems(newItems)
|
||||
})
|
||||
if (newItems.length > 0) {
|
||||
const firstItem = newItems[0]
|
||||
@ -525,56 +489,16 @@ watch(visibleItems, (newItems) => {
|
||||
}
|
||||
}, { deep: true })
|
||||
|
||||
const cleanupExtraItems = (currentVisibleItems) => {
|
||||
if (!renderContainerRef.value || !currentVisibleItems.length) return
|
||||
|
||||
// 构建当前应该可见的索引集合
|
||||
const visibleIndices = new Set(currentVisibleItems.map(item => item.index))
|
||||
|
||||
// 直接获取 render-container 内所有实际渲染的 .virtual-scroller-item 元素
|
||||
const renderedItems = renderContainerRef.value.querySelectorAll('.virtual-scroller-item')
|
||||
|
||||
const toRemove = []
|
||||
|
||||
for (const el of renderedItems) {
|
||||
const dataIndex = parseInt(el.getAttribute('data-index'), 10)
|
||||
|
||||
// 如果元素的 data-index 不在可见范围内,标记为删除
|
||||
if (!isNaN(dataIndex) && !visibleIndices.has(dataIndex)) {
|
||||
toRemove.push(el)
|
||||
}
|
||||
}
|
||||
|
||||
// 从 DOM 中删除多余元素
|
||||
for (const el of toRemove) {
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el)
|
||||
}
|
||||
// 同步清理 itemRefs Map
|
||||
const index = parseInt(el.getAttribute('data-index'), 10)
|
||||
if (!isNaN(index)) {
|
||||
itemRefs.delete(index)
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove.length > 0) {
|
||||
console.log(`[VirtualScroller] 清理了 ${toRemove.length} 个多余DOM元素`)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupResizeObserver()
|
||||
isInitialized.value = true
|
||||
previousDataLength.value = computedData.value.length
|
||||
|
||||
|
||||
nextTick(() => {
|
||||
if (pendingScrollToBottom.value) {
|
||||
pendingScrollToBottom.value = false
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
|
||||
observeVisibleItems()
|
||||
cleanupExtraItems(visibleItems.value)
|
||||
})
|
||||
})
|
||||
|
||||
@ -582,16 +506,6 @@ onBeforeUnmount(() => {
|
||||
if (resizeObserver.value) {
|
||||
resizeObserver.value.disconnect()
|
||||
}
|
||||
|
||||
if (scrollTimeout.value) {
|
||||
clearTimeout(scrollTimeout.value)
|
||||
}
|
||||
|
||||
if (scrollCleanupTimeout.value) {
|
||||
clearTimeout(scrollCleanupTimeout.value)
|
||||
}
|
||||
|
||||
itemRefs.clear()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
|
||||
@ -191,7 +191,7 @@ export function definePaintingPlatform() {
|
||||
return fetchPlatformModels(code)
|
||||
},
|
||||
|
||||
async loadConfig(modelName) {
|
||||
async loadConfig(modelName, _modelType) {
|
||||
const config = getModelConfig(modelName)
|
||||
syncDefaults(config)
|
||||
return config
|
||||
@ -201,6 +201,14 @@ export function definePaintingPlatform() {
|
||||
return 'Flux 2'
|
||||
},
|
||||
|
||||
validateBeforeSubmit() {
|
||||
return null // 无阻塞,返回 null 表示通过
|
||||
},
|
||||
|
||||
getUploaderBindings() {
|
||||
return { limit: imageUploadLimit() }
|
||||
},
|
||||
|
||||
showImageUploader() {
|
||||
if (modelType.value !== 'text') return true
|
||||
return modelConfig.value?.inputType === 'image' || modelConfig.value?.inputType === 'both'
|
||||
|
||||
@ -127,6 +127,17 @@ export function defineVideoPlatform() {
|
||||
return 'LTX2.0'
|
||||
},
|
||||
|
||||
validateBeforeSubmit() {
|
||||
if (model.value === 'Seedance 2.0') {
|
||||
return '敬请期待 Seedance 2.0'
|
||||
}
|
||||
return null // 通过
|
||||
},
|
||||
|
||||
getUploaderBindings() {
|
||||
return { modelType: modelType.value, imagesCount: imageUploadLimit() }
|
||||
},
|
||||
|
||||
showImageUploader() {
|
||||
return modelType.value !== 'text'
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user