diff --git a/portal-ui/src/components/VideoComposeCard.vue b/portal-ui/src/components/VideoComposeCard.vue
index 47bde21..85425c4 100644
--- a/portal-ui/src/components/VideoComposeCard.vue
+++ b/portal-ui/src/components/VideoComposeCard.vue
@@ -235,6 +235,42 @@
+
+
+
+
类型选择:
+
+ 图片
+ 视频
+ 音频
+
+
+
添加素材,拖拽或者点击上传
+
+
+
+
@@ -348,6 +384,11 @@ const assetLoading = ref(false)
const assetPickerVisible = ref(false)
const assetQueryResults = ref([])
const assetPickerSelectedKeys = ref([])
+const createAssetDialogVisible = ref(false)
+const createAssetMediaType = ref('image')
+const createAssetUploaderKey = ref(0)
+const createAssetPendingUrls = ref([])
+const createAssetSubmitting = ref(false)
/** 参考图区拖拽高亮(嵌套 dragenter/leave 计数) */
const referenceDragDepth = ref(0)
/** 仅「上传资产」按钮为 true;左侧直接上传/拖拽/粘贴为 false */
@@ -429,9 +470,9 @@ const openReferenceDirectUpload = () => {
/** 仅「上传资产」:/api/cos/upload(pathPrefix=asset) + /api/tos/asset,与三方素材管理一致 */
const openFilePickerForAssetUpload = () => {
- createAssetIntent.value = true
- currentUploadIndex.value = -1
- fileInputRef.value?.click()
+ createAssetMediaType.value = 'image'
+ clearCreateAssetUploads()
+ createAssetDialogVisible.value = true
}
const openFilePicker = () => {
@@ -449,6 +490,100 @@ const openFilePickerFor = (index) => {
fileInputRef.value?.click()
}
+const uploadAcceptByType = (mediaType) => {
+ if (mediaType === 'audio') return 'audio/*'
+ if (mediaType === 'video') return 'video/*'
+ return 'image/*'
+}
+
+const mediaDataKeyByType = (mediaType) => {
+ if (mediaType === 'audio') return 'audios'
+ if (mediaType === 'video') return 'videos'
+ return 'images'
+}
+
+const clearCreateAssetUploads = () => {
+ createAssetPendingUrls.value = []
+ createAssetUploaderKey.value += 1
+}
+
+const onCreateAssetTypeChange = () => {
+ clearCreateAssetUploads()
+}
+
+const cancelCreateAssetDialog = () => {
+ clearCreateAssetUploads()
+ createAssetDialogVisible.value = false
+}
+
+const customCreateAssetRequest = async (option) => {
+ const { onProgress, onError, onSuccess, fileItem } = option || {}
+ try {
+ const form = new FormData()
+ form.append('file', fileItem.file)
+ form.append('pathPrefix', 'asset')
+ const res = await request({
+ url: 'api/cos/upload',
+ method: 'post',
+ data: form,
+ headers: {
+ 'Content-Type': 'multipart/form-data;boundary=' + new Date().getTime()
+ },
+ onUploadProgress: (evt) => {
+ if (evt.total && onProgress) onProgress(evt.loaded / evt.total, evt)
+ }
+ })
+ if (res && res.code === 200 && res.url) {
+ createAssetPendingUrls.value.push(res.url)
+ if (onSuccess) onSuccess(res)
+ } else {
+ const msg = (res && res.msg) || '上传失败'
+ throw new Error(msg)
+ }
+ } catch (err) {
+ const msg = err?.message || '上传失败'
+ Message.error(msg)
+ if (onError) onError(err)
+ }
+}
+
+const onCreateAssetUploadChange = (_list, fileItem) => {
+ const st = fileItem && fileItem.status
+ if (st === 'removed' && fileItem.response && fileItem.response.url) {
+ const u = fileItem.response.url
+ const idx = createAssetPendingUrls.value.findIndex((x) => x === u)
+ if (idx >= 0) createAssetPendingUrls.value.splice(idx, 1)
+ }
+}
+
+const submitCreateAssetModeration = async () => {
+ if (!createAssetPendingUrls.value.length) {
+ Message.warning('请先上传素材')
+ return
+ }
+ const payload = {
+ [mediaDataKeyByType(createAssetMediaType.value)]: [...createAssetPendingUrls.value]
+ }
+ createAssetSubmitting.value = true
+ try {
+ const res = await request({
+ url: 'api/tos/asset',
+ method: 'post',
+ data: payload
+ })
+ if (res.code !== 200) {
+ throw new Error(res.msg || '提交失败')
+ }
+ Message.success('已提交审核')
+ clearCreateAssetUploads()
+ createAssetDialogVisible.value = false
+ } catch (err) {
+ Message.error(err?.message || '提交失败')
+ } finally {
+ createAssetSubmitting.value = false
+ }
+}
+
const acceptAttr = computed(() => {
const types = new Set(props.allowedMediaTypes || [])
const hasI = types.has('image')
@@ -1972,6 +2107,35 @@ defineExpose({
padding: 8px 0;
}
+.vg-create-asset-block {
+ padding: 2px 0;
+}
+
+.vg-create-asset-type {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.vg-create-asset-type-label {
+ color: rgba(255, 255, 255, 0.85);
+ font-size: 13px;
+}
+
+.vg-create-asset-label {
+ margin-bottom: 10px;
+ color: rgba(255, 255, 255, 0.75);
+ font-size: 14px;
+}
+
+.vg-create-asset-actions {
+ margin-top: 14px;
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
.hidden-input {
display: none;
}
diff --git a/portal-ui/src/views/ThirdPartyAsset.vue b/portal-ui/src/views/ThirdPartyAsset.vue
index bf92e63..429b4b1 100644
--- a/portal-ui/src/views/ThirdPartyAsset.vue
+++ b/portal-ui/src/views/ThirdPartyAsset.vue
@@ -302,6 +302,7 @@ export default {