This commit is contained in:
2025-11-22 16:32:07 +08:00
parent 161d9568a9
commit 809f257bc7
2 changed files with 224 additions and 32 deletions

View File

@@ -66,20 +66,21 @@
:title="groupModalTitle"
@ok="handleSaveGroup"
@cancel="handleCancelGroup"
@close="handleCancelGroup"
>
<a-form :model="groupForm" :label-col-props="{ span: 6 }" :wrapper-col-props="{ span: 18 }">
<a-form-item label="分组名称" field="name" :rules="[{ required: true, message: '请输入分组名称' }]">
<a-input v-model="groupForm.name" placeholder="请输入分组名称" />
<a-form ref="groupFormRef" :model="groupForm" layout="vertical">
<a-form-item label="分组名称" name="name" :rules="[{ required: true, message: '请输入分组名称' }]">
<a-input v-model:value="groupForm.name" placeholder="请输入分组名称" />
</a-form-item>
<a-form-item label="分组描述" field="description">
<a-form-item label="分组描述" name="description">
<a-textarea
v-model="groupForm.description"
v-model:value="groupForm.description"
placeholder="请输入分组描述"
:auto-size="{ minRows: 3, maxRows: 5 }"
/>
</a-form-item>
<a-form-item label="排序" field="sort">
<a-input-number v-model="groupForm.sort" :min="0" placeholder="排序值" />
<a-form-item label="排序" name="sort">
<a-input-number v-model:value="groupForm.sort" :min="0" placeholder="排序值" />
</a-form-item>
</a-form>
</a-modal>
@@ -102,6 +103,7 @@ const groupList = ref([])
const groupModalVisible = ref(false)
const groupModalTitle = ref('新建分组')
const isEdit = ref(false)
const groupFormRef = ref(null)
// 表单
const groupForm = reactive({
@@ -141,6 +143,10 @@ const handleCreateGroup = () => {
groupForm.description = ''
groupForm.sort = 0
groupModalVisible.value = true
// 清除表单验证状态
setTimeout(() => {
groupFormRef.value?.clearValidate()
}, 0)
}
// 编辑分组
@@ -152,16 +158,18 @@ const handleEditGroup = (group) => {
groupForm.description = group.description || ''
groupForm.sort = group.sort || 0
groupModalVisible.value = true
// 清除表单验证状态
setTimeout(() => {
groupFormRef.value?.clearValidate()
}, 0)
}
// 保存分组
const handleSaveGroup = async () => {
if (!groupForm.name.trim()) {
message.warning('请输入分组名称')
return
}
try {
// 验证表单
const values = await groupFormRef.value.validateFields()
if (isEdit.value) {
await MaterialGroupService.updateGroup(groupForm)
message.success('更新成功')
@@ -172,6 +180,10 @@ const handleSaveGroup = async () => {
groupModalVisible.value = false
loadGroupList()
} catch (error) {
if (error?.errorFields) {
// 表单验证失败不显示错误提示Ant Design会自动显示
return
}
console.error('保存分组失败:', error)
message.error(error.message || '保存失败,请重试')
}
@@ -184,6 +196,8 @@ const handleCancelGroup = () => {
groupForm.name = ''
groupForm.description = ''
groupForm.sort = 0
// 清除表单验证状态
groupFormRef.value?.resetFields()
}
// 删除分组

View File

@@ -9,10 +9,17 @@
</template>
上传素材
</a-button>
<a-button
v-if="selectedFileIds.length > 0"
type="primary"
ghost
@click="handleOpenGroupModal"
>
批量分组 ({{ selectedFileIds.length }})
</a-button>
<a-button
type="primary"
ghost
:disabled="selectedFileIds.length === 0"
@click="handleOpenMixModal"
>
素材混剪
@@ -120,6 +127,10 @@
<div class="material-item__delete" @click.stop="handleDeleteFile(file)">
<DeleteOutlined />
</div>
<!-- 分组图标 -->
<div class="material-item__group" @click.stop="handleSingleGroup(file)">
<TagsOutlined />
</div>
</div>
<!-- 文件信息 -->
<div class="material-item__info">
@@ -160,6 +171,35 @@
@confirm="handleConfirmUpload"
@cancel="handleUploadCancel"
/>
<!-- 批量分组模态框 -->
<a-modal
v-model:open="groupModalVisible"
title="批量分组"
centered
@ok="handleBatchGroup"
@cancel="handleGroupCancel"
>
<a-form layout="vertical">
<a-form-item label="选择分组" required>
<a-select
v-model:value="selectedGroupId"
placeholder="请选择要添加到的分组"
style="width: 100%"
>
<a-select-option v-for="group in groupList" :key="group.id" :value="group.id">
{{ group.name }}
</a-select-option>
</a-select>
</a-form-item>
<div style="margin-top: 16px; padding: 12px; background: var(--color-bg-2); border-radius: var(--radius-card);">
<p style="margin: 0; font-size: 13px; color: var(--color-text-2);">
{{ groupingInfo }}
</p>
</div>
</a-form>
</a-modal>
<a-modal
v-model:open="mixModalVisible"
title="素材混剪"
@@ -171,9 +211,11 @@
@cancel="handleMixCancel"
>
<div class="mix-modal__summary">
<p>选中素材:{{ selectedFiles.length }} 个</p>
<p>视频素材:{{ selectedVideoUrls.length }} 个</p>
<p>背景音乐:{{ selectedAudioUrls.length }} 个</p>
<p>视频素材:{{ allVideoUrls.length }} 个</p>
<p>背景音乐:{{ allAudioUrls.length }} 个</p>
<p style="margin-top: 8px; font-size: 12px; color: var(--color-text-3);">
系统将智能随机选择素材进行混剪
</p>
</div>
<a-form layout="vertical">
<a-form-item label="视频标题" required>
@@ -206,9 +248,10 @@ import {
UploadOutlined,
SearchOutlined,
FileOutlined,
DeleteOutlined
DeleteOutlined,
TagsOutlined
} from '@ant-design/icons-vue'
import { MaterialService } from '@/api/material'
import { MaterialService, MaterialGroupService } from '@/api/material'
import { MixService } from '@/api/mix'
import MaterialUploadModal from '@/components/material/MaterialUploadModal.vue'
import { formatFileSize, formatDate } from '@/utils/file'
@@ -222,6 +265,12 @@ const uploading = ref(false)
const mixModalVisible = ref(false)
const mixing = ref(false)
// 分组相关
const groupModalVisible = ref(false)
const groupList = ref([])
const selectedGroupId = ref(null)
const groupingFileId = ref(null) // 当前正在分组的单个文件ID
// 筛选条件
const filters = reactive({
fileCategory: 'video', // 默认分类为视频
@@ -485,6 +534,28 @@ const selectedAudioUrls = computed(() =>
selectedFiles.value.map((file) => (isAudioFile(file) ? file?.fileUrl || file?.previewUrl : null)).filter(Boolean)
)
// 获取所有视频素材
const allVideoUrls = computed(() =>
fileList.value.map((file) => (isVideoFile(file) ? file?.fileUrl || file?.previewUrl : null)).filter(Boolean)
)
// 获取所有音频素材
const allAudioUrls = computed(() =>
fileList.value.map((file) => (isAudioFile(file) ? file?.fileUrl || file?.previewUrl : null)).filter(Boolean)
)
// 分组提示信息
const groupingInfo = computed(() => {
if (groupingFileId.value) {
// 单个文件分组
const file = fileList.value.find(f => f.id === groupingFileId.value)
return file ? `将为文件 "${file.fileName}" 添加到选中的分组中` : ''
} else {
// 批量分组
return `将为 ${selectedFileIds.value.length} 个文件添加到选中的分组中`
}
})
const mixForm = reactive({
title: '',
text: '',
@@ -498,16 +569,13 @@ const resetMixForm = () => {
}
const handleOpenMixModal = () => {
if (selectedFileIds.value.length === 0) {
message.warning('请先选择至少一个素材')
// 检查是否有可用的视频和音频素材
if (allVideoUrls.value.length === 0) {
message.warning('暂无可用的视频素材')
return
}
if (selectedVideoUrls.value.length === 0) {
message.warning('请至少选择一个视频素材')
return
}
if (selectedAudioUrls.value.length === 0) {
message.warning('请至少选择一个背景音乐素材')
if (allAudioUrls.value.length === 0) {
message.warning('暂无可用的音频素材')
return
}
mixModalVisible.value = true
@@ -529,21 +597,24 @@ const handleMixConfirm = async () => {
return
}
const produceCount = Math.max(1, Math.min(10, Number(mixForm.produceCount) || 1))
if (selectedVideoUrls.value.length === 0) {
message.warning('请至少选择一个视频素材')
// 再次检查素材可用性
if (allVideoUrls.value.length === 0) {
message.warning('暂无可用的视频素材')
return
}
if (selectedAudioUrls.value.length === 0) {
message.warning('请至少选择一个背景音乐素材')
if (allAudioUrls.value.length === 0) {
message.warning('暂无可用的音频素材')
return
}
mixing.value = true
try {
const { data } = await MixService.batchProduceAlignment({
title,
text,
videoUrls: selectedVideoUrls.value,
bgMusicUrls: selectedAudioUrls.value,
videoUrls: allVideoUrls.value,
bgMusicUrls: allAudioUrls.value,
produceCount
})
const jobIds = Array.isArray(data) ? data : []
@@ -565,7 +636,87 @@ const handleMixConfirm = async () => {
// 初始化
onMounted(() => {
loadFileList()
loadGroupList()
})
// 加载分组列表
const loadGroupList = async () => {
try {
const res = await MaterialGroupService.getGroupList()
if (res.code === 0) {
groupList.value = res.data || []
}
} catch (error) {
console.error('加载分组列表失败:', error)
}
}
// 打开批量分组模态框
const handleOpenGroupModal = () => {
if (selectedFileIds.value.length === 0) {
message.warning('请先选择要分组的文件')
return
}
groupingFileId.value = null // 清空单个文件分组标记
groupModalVisible.value = true
}
// 单个文件分组
const handleSingleGroup = (file) => {
groupingFileId.value = file.id // 标记是单个文件分组
groupModalVisible.value = true
}
// 执行批量分组
const handleBatchGroup = async () => {
if (!selectedGroupId.value) {
message.warning('请选择分组')
return
}
try {
let fileIds
let successMessage
if (groupingFileId.value) {
// 单个文件分组
fileIds = [groupingFileId.value]
successMessage = '文件分组成功'
} else {
// 批量分组
fileIds = selectedFileIds.value
successMessage = '批量分组成功'
}
await MaterialGroupService.addFilesToGroups({
fileIds: fileIds,
groupIds: [selectedGroupId.value]
})
message.success(successMessage)
// 重置状态
groupModalVisible.value = false
selectedGroupId.value = null
groupingFileId.value = null
// 如果是批量分组,清除选中状态
if (!groupingFileId.value) {
selectedFileIds.value = []
}
loadGroupList() // 刷新分组列表以更新文件计数
} catch (error) {
console.error('分组失败:', error)
message.error(error.message || '分组失败,请重试')
}
}
// 取消分组操作
const handleGroupCancel = () => {
groupModalVisible.value = false
selectedGroupId.value = null
groupingFileId.value = null
}
</script>
<style scoped>
@@ -699,15 +850,42 @@ onMounted(() => {
font-size: 16px;
}
.material-item__group {
position: absolute;
bottom: 8px;
right: 42px; /* 在删除按钮左边 */
width: 28px;
height: 28px;
background: rgba(24, 144, 255, 0.9);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
opacity: 0;
transition: all 0.3s;
font-size: 16px;
}
.material-item:hover .material-item__delete {
opacity: 1;
}
.material-item:hover .material-item__group {
opacity: 1;
}
.material-item__delete:hover {
background: rgb(255, 77, 79);
transform: scale(1.1);
}
.material-item__group:hover {
background: rgb(24, 144, 255);
transform: scale(1.1);
}
.material-item__info {
padding: 12px;
}