From 0d4b1d18bea8c3c70cc57373f9bb2ac37d62baed Mon Sep 17 00:00:00 2001 From: old burden Date: Thu, 2 Apr 2026 15:56:46 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=94=9F=E6=88=90=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- portal-ui/src/components/Forbidden.vue | 4 +- portal-ui/src/components/VideoComposeCard.vue | 653 +++++++++++++----- portal-ui/src/store/modules/main.js | 4 +- portal-ui/src/views/AssetGroupManage.vue | 44 +- portal-ui/src/views/AssetManage.vue | 70 +- portal-ui/src/views/VideoGen.vue | 338 +++++++-- 6 files changed, 845 insertions(+), 268 deletions(-) diff --git a/portal-ui/src/components/Forbidden.vue b/portal-ui/src/components/Forbidden.vue index 6c1e74c..884e5ce 100644 --- a/portal-ui/src/components/Forbidden.vue +++ b/portal-ui/src/components/Forbidden.vue @@ -51,12 +51,12 @@ export default { this.$router.replace('/403') }, ok() { - // 满18+:关闭弹窗并直接进入视频生成 + // 满18+:关闭弹窗并进入首页 Promise.resolve(this.$store.dispatch('main/setForbidden', false)).finally(() => { // 默认语言:繁体中文(zh_HK) this.$store.dispatch('main/setLanguage', 'zh_HK') i18n.global.locale = 'zh_HK' - this.$router.push({ name: 'video-gen' }) + this.$router.push({ name: 'index' }) }) } } diff --git a/portal-ui/src/components/VideoComposeCard.vue b/portal-ui/src/components/VideoComposeCard.vue index a1a9ba1..86c0437 100644 --- a/portal-ui/src/components/VideoComposeCard.vue +++ b/portal-ui/src/components/VideoComposeCard.vue @@ -4,19 +4,27 @@ class="vg-compose-card" :class="{ 'vg-compose-card--reference': isReference, - 'vg-compose-card--split': !isReference && !isTextToVideo, - 'vg-compose-card--solo': isTextToVideo + 'vg-compose-card--split': !isReference }"> - -
+ +
+
+
+ +
+
+
+ +
首帧图
@@ -141,17 +157,19 @@
+ + +
+
+ +
+
-
-
-
描述画面与动态
- - 清空 - -
- -
+
+
-
+
-
@@ -300,6 +315,10 @@ const assetGroups = ref([]) const assetPickerVisible = ref(false) const assetQueryResults = ref([]) const assetPickerSelectedKeys = ref([]) +/** 参考图区拖拽高亮(嵌套 dragenter/leave 计数) */ +const referenceDragDepth = ref(0) +/** 仅「上传资产」按钮为 true;左侧直接上传/拖拽/粘贴为 false */ +const createAssetIntent = ref(false) watch( () => props.modelValue, @@ -382,18 +401,34 @@ const buildReferenceListAssetsPayload = () => { } const onReferenceEmptyAreaClick = () => { + openReferenceDirectUpload() +} + +/** 参考图区直接上传(仅 COS,不调 createAsset) */ +const openReferenceDirectUpload = () => { + if (!isReference.value) return + createAssetIntent.value = false + currentUploadIndex.value = -1 + fileInputRef.value?.click() +} + +/** 仅「上传资产」:COS + createAsset 并写入素材组 */ +const openFilePickerForAssetUpload = () => { if (!hasAssetGroupId.value) { Message.warning('请先选择素材组') return } - openFilePicker() + createAssetIntent.value = true + currentUploadIndex.value = -1 + fileInputRef.value?.click() } const openFilePicker = () => { - if (isReference.value && !hasAssetGroupId.value) { - Message.warning('请先选择素材组') + if (isReference.value) { + openReferenceDirectUpload() return } + createAssetIntent.value = false currentUploadIndex.value = -1 fileInputRef.value?.click() } @@ -601,135 +636,182 @@ const confirmPickAssets = () => { assetPickerSelectedKeys.value = [] } - const handleSelectFiles = async (event) => { - const input = event.target - const files = Array.from(input.files || []) - if (!files.length) return + const processFilesList = async (files, options = {}) => { + if (!files || !files.length) return - const isReferenceMode = isReference.value - if (isReferenceMode && !String(assetGroupId.value || '').trim()) { - Message.warning('请先选择素材组') - input.value = '' - return - } - let targetList = [...mediaList.value] - - // 首尾帧模式特殊处理索引 - if (isFirstLastFrame.value && currentUploadIndex.value >= 0) { - const idx = currentUploadIndex.value - if (files.length > 0) { - const file = files[0] - const mediaType = detectMediaType(file) - if (mediaType !== 'image') { - Message.warning('首尾帧仅支持图片') - input.value = '' - return - } - const id = `frame_${Date.now()}` - const localPreview = URL.createObjectURL(file) - const entry = { id, url: localPreview, mediaType: 'image', name: file.name, _fileRef: file, isUploading: true, label: idx === 0 ? '[首帧]' : '[尾帧]' } - targetList[idx] = entry - } - } else { - // 其他模式正常处理 - const remain = props.maxMediaCount - mediaList.value.length - if (remain <= 0 && !isReferenceMode) { - Message.warning(`最多添加 ${props.maxMediaCount} 个参考素材`) - input.value = '' + const isReferenceMode = isReference.value + const wantCreateAsset = isReferenceMode && options.createAsset === true + if (wantCreateAsset && !String(assetGroupId.value || '').trim()) { + Message.warning('请先选择素材组') return } + let targetList = [...mediaList.value] - const selected = files.slice(0, remain || files.length) - const uploadingEntries = [] - - for (const file of selected) { - const mediaType = detectMediaType(file) - if (!props.allowedMediaTypes.includes(mediaType)) { - Message.warning(`当前模式不支持该类型:${mediaType}`) - continue - } - - const id = `tmp_${Date.now()}_${Math.random().toString(16).slice(2)}` - const localPreview = URL.createObjectURL(file) - - const entry = { - id, - url: localPreview, - mediaType, - name: file.name, - _fileRef: file, - isUploading: true - } - uploadingEntries.push(entry) - } - - if (uploadingEntries.length) { - targetList = [...targetList, ...uploadingEntries] - } - } - - setMediaList(targetList) - await nextTick() - - // 上传处理 - const toUpload = targetList.filter(item => item.isUploading) - for (const entry of toUpload) { - try { - const res = await uploadFile({ - url: PORTAL_TENCENT_COS_UPLOAD_URL, - file: entry._fileRef, - name: 'file' - }) - const url = extractUploadUrlFromResponse(res) - if (!url) throw new Error(res?.msg || '未返回文件地址') - let assetId = entry.assetId || '' - if (isReferenceMode) { - const gid = String(assetGroupId.value || '').trim() - if (!gid) throw new Error('请先选择素材组') - const mt = entry.mediaType || 'image' - const assetType = - mt === 'video' ? 'Video' : mt === 'audio' ? 'Audio' : 'Image' - const fd = new FormData() - fd.append('file', entry._fileRef) - fd.append('groupId', gid) - fd.append('assetType', assetType) - fd.append('name', entry?.name || '') - const createRes = await request({ - url: 'api/byteAsset/createAsset', - method: 'POST', - data: fd - }) - if (createRes.code !== 200) { - throw new Error(createRes?.msg || '创建素材失败') + if (isFirstLastFrame.value && currentUploadIndex.value >= 0) { + const idx = currentUploadIndex.value + if (files.length > 0) { + const file = files[0] + const mediaType = detectMediaType(file) + if (mediaType !== 'image') { + Message.warning('首尾帧仅支持图片') + return } - assetId = createRes?.data?.Id || createRes?.data?.id || '' - if (!assetId) throw new Error(createRes?.msg || '创建素材失败:未返回资产ID') + const id = `frame_${Date.now()}` + const localPreview = URL.createObjectURL(file) + const entry = { + id, + url: localPreview, + mediaType: 'image', + name: file.name, + _fileRef: file, + isUploading: true, + label: idx === 0 ? '[首帧]' : '[尾帧]' + } + targetList[idx] = entry + } + } else { + const remain = props.maxMediaCount - mediaList.value.length + if (remain <= 0 && !isReferenceMode) { + Message.warning(`最多添加 ${props.maxMediaCount} 个参考素材`) + return } - const localPreview = entry.url - setMediaList( - mediaList.value.map((x) => - normalizeItemKey(x) === normalizeItemKey(entry) - ? { ...x, url, assetId, isUploading: false } - : x - ) - ) - if (isReferenceMode) { - Message.success('已上传完成') + const selected = files.slice(0, remain || files.length) + const uploadingEntries = [] + + for (const file of selected) { + const mediaType = detectMediaType(file) + if (!props.allowedMediaTypes.includes(mediaType)) { + Message.warning(`当前模式不支持该类型:${mediaType}`) + continue + } + + const id = `tmp_${Date.now()}_${Math.random().toString(16).slice(2)}` + const localPreview = URL.createObjectURL(file) + + const entry = { + id, + url: localPreview, + mediaType, + name: file.name, + _fileRef: file, + isUploading: true + } + uploadingEntries.push(entry) } + if (uploadingEntries.length) { + targetList = [...targetList, ...uploadingEntries] + } + } + + setMediaList(targetList) + await nextTick() + + const toUpload = targetList.filter((item) => item.isUploading) + for (const entry of toUpload) { try { - URL.revokeObjectURL(localPreview) - } catch (_) {} - } catch (err) { - setMediaList(mediaList.value.filter((x) => normalizeItemKey(x) !== normalizeItemKey(entry))) - Message.error('上传失败,请重试') + const res = await uploadFile({ + url: PORTAL_TENCENT_COS_UPLOAD_URL, + file: entry._fileRef, + name: 'file' + }) + const url = extractUploadUrlFromResponse(res) + if (!url) throw new Error(res?.msg || '未返回文件地址') + let assetId = entry.assetId || '' + if (wantCreateAsset) { + const gid = String(assetGroupId.value || '').trim() + if (!gid) throw new Error('请先选择素材组') + const mt = entry.mediaType || 'image' + const assetType = + mt === 'video' ? 'Video' : mt === 'audio' ? 'Audio' : 'Image' + const fd = new FormData() + fd.append('file', entry._fileRef) + fd.append('groupId', gid) + fd.append('assetType', assetType) + fd.append('name', entry?.name || '') + const createRes = await request({ + url: 'api/byteAsset/createAsset', + method: 'POST', + data: fd + }) + if (createRes.code !== 200) { + throw new Error(createRes?.msg || '创建素材失败') + } + assetId = createRes?.data?.Id || createRes?.data?.id || '' + if (!assetId) throw new Error(createRes?.msg || '创建素材失败:未返回资产ID') + } + + const localPreview = entry.url + setMediaList( + mediaList.value.map((x) => + normalizeItemKey(x) === normalizeItemKey(entry) + ? { ...x, url, assetId, isUploading: false } + : x + ) + ) + if (isReferenceMode) { + Message.success(wantCreateAsset ? '已上传并写入素材库' : '已上传完成') + } + + try { + URL.revokeObjectURL(localPreview) + } catch (_) {} + } catch (err) { + setMediaList(mediaList.value.filter((x) => normalizeItemKey(x) !== normalizeItemKey(entry))) + Message.error('上传失败,请重试') + } } } - input.value = '' - currentUploadIndex.value = -1 -} + const handleSelectFiles = async (event) => { + const input = event.target + const files = Array.from(input.files || []) + const createAsset = + isReference.value && createAssetIntent.value === true + try { + await processFilesList(files, { createAsset }) + } finally { + createAssetIntent.value = false + } + if (input) input.value = '' + currentUploadIndex.value = -1 + } + + const onReferenceDragEnter = () => { + if (!isReference.value) return + referenceDragDepth.value++ + } + const onReferenceDragLeave = () => { + if (!isReference.value) return + referenceDragDepth.value = Math.max(0, referenceDragDepth.value - 1) + } + const onReferenceDragOver = () => { + if (!isReference.value) return + referenceDragDepth.value = Math.max(1, referenceDragDepth.value) + } + const onReferenceDrop = async (e) => { + if (!isReference.value) return + referenceDragDepth.value = 0 + const files = Array.from(e.dataTransfer?.files || []) + if (!files.length) return + await processFilesList(files, { createAsset: false }) + } + + const onReferencePaste = async (e) => { + if (!isReference.value) return + const items = e.clipboardData?.items + if (!items || !items.length) return + const files = [] + for (const item of items) { + if (item.kind !== 'file') continue + const f = item.getAsFile() + if (f && f.type.startsWith('image/')) files.push(f) + } + if (!files.length) return + e.preventDefault() + await processFilesList(files, { createAsset: false }) + } const onPromptInput = (e) => { setPrompt(e.target.value) @@ -1159,33 +1241,13 @@ const onEditorKeyup = (e) => { } } -const selectMentionItem = (item) => { - if (!item?.url || !editorRef.value) return +const buildReferenceHolderElement = (item) => { const kind = item.mediaType || 'image' - const key = `${kind}:${(item.assetId || '').trim() || String(item.url || '').trim()}` - const keys = getUniqueRefKeysInDocForKind(kind) - if (!keys.has(key) && keys.size >= MAX_REFERENCE_UNIQUE_KIND) { - const label = kind === 'video' ? '视频' : kind === 'audio' ? '音频' : '图片' - Message.warning(`同类参考${label}最多 ${MAX_REFERENCE_UNIQUE_KIND} 种,无法再插入新素材`) - mentionVisible.value = false - return - } - editorRef.value.focus() - restoreSelection() - removeMentionTrigger() - saveSelection() - - const selection = window.getSelection() - if (!selection || selection.rangeCount === 0) return - const range = selection.getRangeAt(0) - if (!editorRef.value.contains(range.commonAncestorContainer)) return - - const token = '[?]' const holder = document.createElement('span') holder.className = 'vg-inline-ref' holder.setAttribute('data-mention-reference', '1') - holder.setAttribute('data-token', token) - holder.setAttribute('data-reference-url', item.url) + holder.setAttribute('data-token', '[?]') + holder.setAttribute('data-reference-url', String(item.url || '').trim()) holder.setAttribute('data-reference-asset-id', item.assetId || '') holder.setAttribute('data-reference-kind', kind) holder.setAttribute('contenteditable', 'false') @@ -1214,6 +1276,157 @@ const selectMentionItem = (item) => { img.className = 'vg-inline-ref-image' holder.appendChild(img) } + return holder +} + +const extractRefsFromContent = (content) => { + const images = [] + const videos = [] + const audios = [] + if (!Array.isArray(content)) return { images, videos, audios } + for (const item of content) { + if (item?.type === 'image_url' && (!item.role || item.role === 'reference_image')) { + const url = item?.image_url?.url + if (url) images.push(url) + } + if (item?.type === 'video_url' && item?.role === 'reference_video') { + const url = item?.video_url?.url + if (url) videos.push(url) + } + if (item?.type === 'audio_url' && item?.role === 'reference_audio') { + const url = item?.audio_url?.url + if (url) audios.push(url) + } + } + return { images, videos, audios } +} + +const parseContentItemToMedia = (it, idx) => { + const id = `hist_${idx}_${Date.now().toString(36)}` + if (it?.type === 'image_url' && (!it.role || it.role === 'reference_image')) { + const raw = String(it.image_url?.url || '').trim() + if (!raw) return null + const assetId = raw.startsWith('asset://') ? raw.slice(9) : '' + return { id, url: raw, assetId, mediaType: 'image', name: '' } + } + if (it?.type === 'video_url' && it.role === 'reference_video') { + const raw = String(it.video_url?.url || '').trim() + if (!raw) return null + const assetId = raw.startsWith('asset://') ? raw.slice(9) : '' + return { id, url: raw, assetId, mediaType: 'video', name: '' } + } + if (it?.type === 'audio_url' && it.role === 'reference_audio') { + const raw = String(it.audio_url?.url || '').trim() + if (!raw) return null + const assetId = raw.startsWith('asset://') ? raw.slice(9) : '' + return { id, url: raw, assetId, mediaType: 'audio', name: '' } + } + return null +} + +const normRefKey = (u) => String(u || '').trim() + +const findMediaItemForRefUrl = (items, url, kind) => { + const u = normRefKey(url) + if (!u) return null + return ( + items.find((x) => { + if ((x.mediaType || 'image') !== kind) return false + const xu = normRefKey(x.url) + const xa = normRefKey(x.assetId) + if (u === xu) return true + if (u.startsWith('asset://') && u.slice(9) === xa) return true + if (xa && `asset://${xa}` === u) return true + return false + }) || null + ) +} + +const loadReferenceFromTaskRow = async (row) => { + if (!isReference.value || !editorRef.value) return + let vp = row?.videoParams + try { + vp = typeof vp === 'string' ? JSON.parse(vp) : vp + } catch { + return + } + const content = vp?.content + if (!Array.isArray(content) || !content.length) return + const head = content[0] + if (head?.type !== 'text') return + + const mediaItems = [] + for (let i = 1; i < content.length; i++) { + const parsed = parseContentItemToMedia(content[i], i) + if (parsed) mediaItems.push(parsed) + } + setMediaList(mediaItems.slice(0, props.maxMediaCount)) + await nextTick() + + const refs = extractRefsFromContent(content) + const text = head.text || '' + editorRef.value.innerHTML = '' + const tokenReg = /(\[图片(\d+)\]|\[视频(\d+)\]|\[音频(\d+)\]|\[图(\d+)\])/g + let last = 0 + let m + while ((m = tokenReg.exec(text)) !== null) { + if (m.index > last) { + editorRef.value.appendChild(document.createTextNode(text.slice(last, m.index))) + } + let url = '' + let kind = 'image' + if (m[2] != null) { + url = refs.images[Number(m[2]) - 1] || '' + kind = 'image' + } else if (m[3] != null) { + url = refs.videos[Number(m[3]) - 1] || '' + kind = 'video' + } else if (m[4] != null) { + url = refs.audios[Number(m[4]) - 1] || '' + kind = 'audio' + } else if (m[5] != null) { + url = refs.images[Number(m[5]) - 1] || '' + kind = 'image' + } + const mediaItem = findMediaItemForRefUrl(mediaItems, url, kind) + if (mediaItem) { + const holder = buildReferenceHolderElement(mediaItem) + editorRef.value.appendChild(holder) + } else { + editorRef.value.appendChild(document.createTextNode(m[0])) + } + last = m.index + m[0].length + } + if (last < text.length) { + editorRef.value.appendChild(document.createTextNode(text.slice(last))) + } + renumberAllReferenceMentions() + setPrompt(getEditorPlainText()) + saveReferenceToStorage(internalMediaList.value) +} + +const selectMentionItem = (item) => { + if (!item?.url || !editorRef.value) return + const kind = item.mediaType || 'image' + const key = `${kind}:${(item.assetId || '').trim() || String(item.url || '').trim()}` + const keys = getUniqueRefKeysInDocForKind(kind) + if (!keys.has(key) && keys.size >= MAX_REFERENCE_UNIQUE_KIND) { + const label = kind === 'video' ? '视频' : kind === 'audio' ? '音频' : '图片' + Message.warning(`同类参考${label}最多 ${MAX_REFERENCE_UNIQUE_KIND} 种,无法再插入新素材`) + mentionVisible.value = false + return + } + editorRef.value.focus() + restoreSelection() + removeMentionTrigger() + saveSelection() + + const selection = window.getSelection() + if (!selection || selection.rangeCount === 0) return + const range = selection.getRangeAt(0) + if (!editorRef.value.contains(range.commonAncestorContainer)) return + + const holder = buildReferenceHolderElement({ ...item, mediaType: kind }) range.insertNode(holder) range.setStartAfter(holder) @@ -1232,6 +1445,7 @@ defineExpose({ getEditorPlainText, getImageReferenceContentItems, clearAll, + loadReferenceFromTaskRow, clearPromptOnly: () => { setPrompt('') if (editorRef.value) editorRef.value.innerHTML = '' @@ -1267,6 +1481,8 @@ defineExpose({ /* 参考图:参考图 | 资产(组+操作) | 选择区域(生成参数) | 富文本 */ .vg-compose-card--reference { display: grid; + height: 100%; + min-height: 0; grid-template-columns: minmax(72px, 0.85fr) minmax(72px, 0.75fr) minmax(168px, 1.35fr) minmax(120px, 2.95fr); grid-template-rows: minmax(0, 1fr); column-gap: 10px; @@ -1346,10 +1562,25 @@ defineExpose({ color: rgba(255, 255, 255, 0.38); } +/* 参考图列:限制在网格单元高度内,多图时在列表内滚动 */ +.vg-compose-mod--ref { + overflow: hidden; + min-height: 0; +} + .vg-compose-mod--ref .vg-mod-body { - min-height: 72px; - overflow-x: hidden; - overflow-y: auto; + flex: 1 1 0; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.vg-compose-mod--ref.vg-compose-mod--dragover { + outline: 2px dashed rgba(0, 202, 224, 0.65); + outline-offset: 2px; + border-radius: 12px; + background: rgba(0, 202, 224, 0.06); } .vg-compose-empty--compact { @@ -1377,14 +1608,19 @@ defineExpose({ } .vg-compose-media-scroll--vertical { + display: flex; flex-direction: column; + flex: 1 1 0; + min-height: 0; + max-height: 100%; overflow-x: hidden; overflow-y: auto; - flex: 1; gap: 8px; - padding: 2px; + padding: 2px 4px 2px 2px; scroll-snap-type: y proximity; touch-action: pan-y; + overscroll-behavior: contain; + -webkit-overflow-scrolling: touch; } .vg-compose-media-item--tile { @@ -1400,6 +1636,28 @@ defineExpose({ display: flex; flex-direction: column; gap: 10px; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; +} + +/* 首帧/首尾帧:左侧(两列布局)增加参数后,让右侧富文本框宽度略收窄 */ +.vg-compose-card--split .vg-compose-left--two-col { + width: 40%; + overflow-y: hidden; /* 让滚动优先发生在参数模块内部 */ +} + +.vg-compose-left--two-col { + display: grid; + grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); + gap: 6px; + overflow-y: hidden; + overflow-x: hidden; + align-items: stretch; +} + +.vg-compose-left--two-col > * { + min-height: 0; } .vg-compose-left.hidden { @@ -1419,6 +1677,60 @@ defineExpose({ display: flex; flex-direction: column; gap: 16px; + min-height: 0; +} + +.vg-first-frame-panel, +.vg-first-last-panel { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 12px; + background: rgba(0, 0, 0, 0.22); + padding: 10px 10px; +} + +/* 首帧/首尾帧:上传区不参与 flex 收缩,避免参数模块挤压导致上传区裁剪 */ +.vg-compose-card--split .vg-first-frame-panel, +.vg-compose-card--split .vg-first-last-panel { + flex-shrink: 0; +} + +/* 首帧/首尾帧:参数模块允许在内部滚动 */ +.vg-compose-card--split .vg-compose-mod--pick { + flex: 1; + min-height: 0; +} + +/* 首帧/首尾帧:缩小左侧上传区,避免高度不足导致滚动 */ +.vg-first-frame-panel, +.vg-first-last-panel { + gap: 8px; +} + +.vg-first-frame-panel .vg-upload-placeholder, +.vg-first-last-panel .vg-upload-placeholder { + padding: 16px 10px; + border-radius: 10px; +} + +.vg-first-frame-panel .vg-upload-icon, +.vg-first-last-panel .vg-upload-icon { + font-size: 24px; + margin-bottom: 4px; +} + +.vg-first-frame-panel .vg-upload-text, +.vg-first-last-panel .vg-upload-text { + font-size: 12px; + line-height: 1.2; +} + +.vg-first-frame-panel .vg-compose-media-item, +.vg-first-last-panel .vg-compose-media-item { + width: 60px; + height: 60px; + border-radius: 14px; } .vg-upload-single { @@ -1711,6 +2023,7 @@ defineExpose({ flex-direction: column; min-height: 0; min-width: 0; + overflow: hidden; } .vg-reference-editor-row { @@ -1991,6 +2304,12 @@ defineExpose({ .vg-compose-left { width: 100%; } + + .vg-compose-left--two-col { + display: flex; + flex-direction: column; + overflow-y: auto; + } } /* 中等宽度:四列改为「参考条 + 右侧两行」,富文本独占一行(平板横屏等) */ diff --git a/portal-ui/src/store/modules/main.js b/portal-ui/src/store/modules/main.js index 829bc80..9978547 100644 --- a/portal-ui/src/store/modules/main.js +++ b/portal-ui/src/store/modules/main.js @@ -41,8 +41,8 @@ const state = { showMessage: false, messageData: {}, messageCount: 0, - // 是否显示18禁弹窗 - showForbidden: true, + // 是否显示18禁弹窗:默认不拦截,直接进入首页等主页面 + showForbidden: false, // 阻止页面切换 showPrevent: false, showLogin: false diff --git a/portal-ui/src/views/AssetGroupManage.vue b/portal-ui/src/views/AssetGroupManage.vue index 80b9aec..821deee 100644 --- a/portal-ui/src/views/AssetGroupManage.vue +++ b/portal-ui/src/views/AssetGroupManage.vue @@ -11,27 +11,27 @@
- - + +
- + - AIGC + AIGC(生成类)
- + - CreateTime - UpdateTime + 创建时间 + 更新时间
- + - Desc - Asc + 从新到旧 + 从旧到新
@@ -56,13 +56,13 @@ - ID - Name - Description - GroupType - ProjectName - CreateTime - UpdateTime + 编号 + 名称 + 描述 + 类型 + 项目名称 + 创建时间 + 更新时间 操作 @@ -71,7 +71,7 @@ {{ item.Id || item.id }} {{ item.Name || item.name }} {{ item.Description || item.description || '-' }} - {{ item.GroupType || item.groupType || '-' }} + {{ formatGroupTypeLabel(item) }} {{ item.ProjectName || item.projectName || '-' }} {{ item.CreateTime || item.createTime || '-' }} {{ item.UpdateTime || item.updateTime || '-' }} @@ -181,6 +181,12 @@ export default { this.search(1) }, methods: { + formatGroupTypeLabel(item) { + const t = String(item?.GroupType || item?.groupType || '').trim() + if (!t) return '-' + if (t === 'AIGC') return 'AIGC(生成类)' + return t + }, clampGroupPage(n) { const page = Number(n) || 1 const size = Number(this.filters.pageSize) || 10 @@ -294,7 +300,7 @@ export default { const res = await this.$axios({ url: 'api/byteAssetGroup/getAssetGroup', method: 'POST', - data: { Id: id } + data: { id: id } }) if (res.code === 200) { this.detailData = res.data || {} diff --git a/portal-ui/src/views/AssetManage.vue b/portal-ui/src/views/AssetManage.vue index 2c66eb1..6b1d7cd 100644 --- a/portal-ui/src/views/AssetManage.vue +++ b/portal-ui/src/views/AssetManage.vue @@ -44,31 +44,31 @@
- - + +
- - + + 全部 - Active - Processing - Failed + 可用 + 处理中 + 失败
- + - CreateTime - UpdateTime - GroupId + 创建时间 + 更新时间 + 素材组
- + - Desc - Asc + 从新到旧 + 从旧到新
@@ -83,13 +83,13 @@ - - - - - - - + + + + + + + @@ -135,8 +135,8 @@ - - + +
IdNameURLGroupIdAssetTypeStatusCreateTime素材编号名称访问地址所属素材组类型状态创建时间 操作
{{ it.GroupId || it.groupId || '-' }}{{ it.AssetType || it.assetType || '-' }}{{ it.Status || it.status || '-' }}{{ formatAssetTypeLabel(it) }}{{ formatAssetStatusLabel(it) }} {{ it.CreateTime || it.createTime || '-' }} 详情 @@ -265,6 +265,26 @@ export default { this.loadGroups() }, methods: { + formatAssetTypeLabel(it) { + const t = String(it?.AssetType || it?.assetType || '').trim() + if (!t) return '-' + const map = { + Image: '图片', + Video: '视频', + Audio: '音频' + } + return map[t] || t + }, + formatAssetStatusLabel(it) { + const s = String(it?.Status || it?.status || '').trim() + if (!s) return '-' + const map = { + Active: '可用', + Processing: '处理中', + Failed: '失败' + } + return map[s] || s + }, assetUrl(it) { return String(it?.URL || it?.url || '').trim() }, @@ -356,7 +376,7 @@ export default { }, async createAsset() { const groupId = String(this.createForm.groupId || '').trim() - if (!groupId) return this.$message.error('请填写 GroupId') + if (!groupId) return this.$message.error('请选择素材组') if (!this.createForm.file) return this.$message.error('请选择上传文件') this.createLoading = true try { @@ -476,7 +496,7 @@ export default { const res = await this.$axios({ url: ASSET_DELETE_API, method: 'POST', - data: { Id: id } + data: { id: id } }) if (res.code === 200) { this.$message.success('删除成功') diff --git a/portal-ui/src/views/VideoGen.vue b/portal-ui/src/views/VideoGen.vue index 08a85b8..7d2bca1 100644 --- a/portal-ui/src/views/VideoGen.vue +++ b/portal-ui/src/views/VideoGen.vue @@ -46,6 +46,29 @@ preload="metadata" /> +
+ {{ rowChatParams(row).modeLabel }} + 模型 {{ rowChatParams(row).modelLabel }} + 比例 {{ rowChatParams(row).ratio }} + 时长 {{ rowChatParams(row).duration }} + 分辨率 {{ rowChatParams(row).resolution }} +
+
+ + +
{{ formatCreateTime(row.createTime) }}
@@ -73,6 +96,29 @@ preload="metadata" />
+
+ {{ rowChatParams(row).modeLabel }} + 模型 {{ rowChatParams(row).modelLabel }} + 比例 {{ rowChatParams(row).ratio }} + 时长 {{ rowChatParams(row).duration }} + 分辨率 {{ rowChatParams(row).resolution }} +
+
+ + +
{{ formatCreateTime(row.createTime) }}
@@ -98,6 +144,13 @@
{{ taskStatusText(row) }}
+
{{ formatCreateTime(row.updateTime || row.createTime) }}
@@ -186,62 +239,11 @@