修改视频案例管理

This commit is contained in:
2025-10-31 16:46:33 +08:00
parent 7dfe56dd84
commit adb9ffa5eb

View File

@@ -1,5 +1,537 @@
<template>
<div>
<el-config-provider :locale="zhCn">
<div class="p-6 bg-white rounded-lg shadow-md min-h-screen pb-24" v-loading="loading">
<!-- 标题与新增按钮 -->
<div class="flex justify-between items-center mb-6">
<span class="text-2xl font-bold text-gray-700">视频案例管理列表</span>
<el-button type="primary" :icon="Plus" @click="handleAdd">新增视频案例</el-button>
</div>
<!-- 搜索栏 -->
<div class="mb-6 flex gap-4">
<el-input
v-model="searchKeyword"
placeholder="搜索标题/设计人员/指导老师"
clearable
style="width: 300px;"
/>
<el-button type="primary" @click="fetchData">搜索</el-button>
</div>
<!-- 视频案例表格 -->
<el-table :data="tableData" style="width: 100%" row-key="id" max-height="82vh">
<el-table-column prop="id" label="ID" width="80" sortable fixed></el-table-column>
<el-table-column prop="title" label="视频标题" min-width="200" show-overflow-tooltip>
<template #default="scope">
<span class="font-semibold">{{ scope.row.title }}</span>
</template>
</el-table-column>
<el-table-column label="视频预览" width="200">
<template #default="scope">
<el-button
type="text"
class="text-blue-600"
@click="handlePreviewVideo(scope.row.video_url)"
>
点击播放
</el-button>
</template>
</el-table-column>
<el-table-column label="视频简介" min-width="250">
<template #default="scope">
<div class="intro-preview" :title="scope.row.intro || '无简介'">
{{ (scope.row.intro || '无简介').length > 80 ? `${(scope.row.intro || '').slice(0, 80)}...` : scope.row.intro || '无简介' }}
</div>
</template>
</el-table-column>
<el-table-column prop="designer_names" label="设计人员" min-width="180" show-overflow-tooltip>
<template #default="scope">
<span :title="scope.row.designer_names">{{ formatNames(scope.row.designer_names) }}</span>
</template>
</el-table-column>
<el-table-column prop="tutor_names" label="指导老师" min-width="180" show-overflow-tooltip>
<template #default="scope">
<span :title="scope.row.tutor_names">{{ formatNames(scope.row.tutor_names) }}</span>
</template>
</el-table-column>
<el-table-column prop="sort" label="排序" width="100" sortable></el-table-column>
<el-table-column prop="create_time" label="创建时间" width="180" sortable></el-table-column>
<el-table-column prop="update_time" label="更新时间" width="180" sortable></el-table-column>
<el-table-column label="操作" width="220" fixed="right">
<template #default="scope">
<div class="action-buttons">
<el-button size="small" type="primary" :icon="Edit" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" :icon="Delete" @click="handleDelete(scope.row)">删除</el-button>
</div>
</template>
</el-table-column>
</el-table>
<!-- 视频预览弹窗 -->
<el-dialog v-model="videoDialogVisible" title="视频预览" :width="`800px`" :before-close="handleCloseVideo">
<div class="video-container" v-if="currentVideoUrl">
<video
:src="currentVideoUrl"
controls
style="width: 100%; max-height: 500px;"
:poster="videoPoster"
>
您的浏览器不支持视频播放
</video>
<div class="mt-4 text-gray-600">
<p><span class="font-semibold">视频地址</span>{{ currentVideoUrl }}</p>
</div>
</div>
</el-dialog>
<!-- 详情弹窗可选如需完整详情 -->
<el-dialog v-model="detailDialogVisible" title="视频案例详情" :width="`800px`">
<div class="detail-container">
<h3 class="text-xl font-bold mb-4">{{ currentVideoCase.title }}</h3>
<div class="flex flex-wrap gap-4 mb-6 text-gray-600">
<div><span class="font-semibold">设计人员</span>{{ currentVideoCase.designer_names }}</div>
<div><span class="font-semibold">指导老师</span>{{ currentVideoCase.tutor_names }}</div>
<div><span class="font-semibold">排序</span>{{ currentVideoCase.sort }}</div>
<div><span class="font-semibold">创建时间</span>{{ currentVideoCase.create_time }}</div>
</div>
<div class="mb-6">
<h4 class="font-semibold mb-2">视频简介</h4>
<div class="bg-gray-50 p-4 rounded-lg text-gray-700">
{{ currentVideoCase.intro || '无简介' }}
</div>
</div>
<div>
<h4 class="font-semibold mb-2">视频播放</h4>
<video
:src="currentVideoCase.video_url"
controls
style="width: 100%; max-height: 400px;"
:poster="videoPoster"
>
您的浏览器不支持视频播放
</video>
</div>
</div>
</el-dialog>
</div>
<!-- 分页组件 -->
<div class="fixed bottom-0 right-0 w-full p-4 bg-white shadow-[0_-2px_5px_rgba(0,0,0,0.05)] flex justify-end z-10 border-t border-gray-200">
<el-pagination
class="custom-pagination"
background
layout="total, sizes, prev, pager, next, jumper"
:total="total"
v-model:current-page="currentPage"
v-model:page-size="pageSize"
:page-sizes="[10, 20, 50, 100]"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
></el-pagination>
</div>
<!-- 新增/编辑抽屉 -->
<el-drawer
v-model="drawerVisible"
:title="form.id ? '编辑视频案例' : '新增视频案例'"
direction="rtl"
size="60%"
:before-close="handleDrawerClose"
destroy-on-close
>
<div class="publish-form-container">
<div class="form-group title-group">
<label class="form-label required">视频标题</label>
<el-input
v-model="form.title"
placeholder="请输入视频案例标题"
clearable
:disabled="isSubmitting"
maxlength="255"
/>
</div>
<div class="form-group video-url-group">
<label class="form-label required">视频播放地址</label>
<el-input
v-model="form.video_url"
placeholder="请输入视频直接播放地址如MP4 URL"
clearable
:disabled="isSubmitting"
maxlength="512"
/>
<p class="text-gray-500 text-sm mt-2">支持MP4等主流视频格式的直接播放地址</p>
</div>
<div class="form-group intro-group">
<label class="form-label">视频简介</label>
<el-input
v-model="form.intro"
type="textarea"
:rows="4"
placeholder="请描述视频案例的背景、内容、亮点等(可选)"
clearable
:disabled="isSubmitting"
/>
</div>
<div class="form-group designer-group">
<label class="form-label required">设计人员名单</label>
<el-input
v-model="form.designer_names"
placeholder="多人用逗号分隔(如:张三,李四,王五)"
clearable
:disabled="isSubmitting"
maxlength="1000"
/>
</div>
<div class="form-group tutor-group">
<label class="form-label required">指导老师名单</label>
<el-input
v-model="form.tutor_names"
placeholder="多人用逗号分隔(如:赵六,钱七)"
clearable
:disabled="isSubmitting"
maxlength="1000"
/>
</div>
<div class="form-group sort-group">
<label class="form-label">排序</label>
<el-input
v-model.number="form.sort"
placeholder="数字越小越靠前默认0"
clearable
:disabled="isSubmitting"
type="number"
/>
</div>
</div>
<template #footer>
<div style="flex: auto">
<el-button @click="handleDrawerClose">取消</el-button>
<el-button type="primary" @click="submitVideoCase" :loading="isSubmitting">
{{ form.id ? '更新案例' : '新增案例' }}
</el-button>
</div>
</template>
</el-drawer>
</el-config-provider>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch, nextTick } from 'vue';
import { ElMessage, ElMessageBox, ElConfigProvider, ElDrawer, ElInput, ElUpload, ElPagination, ElTable, ElTableColumn, ElDialog, ElButton } from 'element-plus';
import { Edit, Delete, Plus } from '@element-plus/icons-vue';
import type { UploadProps } from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
// --- 类型定义 ---
interface VideoCase {
id: number;
title: string;
intro: string;
video_url: string;
designer_names: string;
tutor_names: string;
sort: number;
create_time: string;
update_time: string;
is_delete: number;
}
interface ListVideoCaseReq {
page: number;
size: number;
keyword: string;
sort: number;
}
interface CreateVideoCaseReq {
title: string;
intro: string;
video_url: string;
designer_names: string;
tutor_names: string;
sort: number;
}
interface UpdateVideoCaseReq extends CreateVideoCaseReq {
id: number;
}
// --- 常量定义 ---
const API_BASE_URL = 'http://localhost:8080/api';
const videoPoster = 'https://via.placeholder.com/800x450?text=视频封面'; // 默认视频封面
// =================================================================
// 列表页相关状态与逻辑
// =================================================================
const loading = ref(true);
const tableData = ref<VideoCase[]>([]);
const currentPage = ref(1);
const pageSize = ref(10);
const total = ref(0);
const searchKeyword = ref('');
// 弹窗状态
const videoDialogVisible = ref(false);
const currentVideoUrl = ref('');
const detailDialogVisible = ref(false);
const currentVideoCase = ref<VideoCase>({
id: 0,
title: '',
intro: '',
video_url: '',
designer_names: '',
tutor_names: '',
sort: 0,
create_time: '',
update_time: '',
is_delete: 0
});
// --- 格式化姓名显示(超长截断) ---
const formatNames = (names: string) => {
if (!names) return '';
return names.length > 15 ? `${names.slice(0, 15)}...` : names;
};
// --- 获取视频案例列表 ---
const fetchData = async () => {
loading.value = true;
try {
const reqData: ListVideoCaseReq = {
page: currentPage.value,
size: pageSize.value,
keyword: searchKeyword.value.trim(),
sort: 0
};
const response = await fetch(`${API_BASE_URL}/video-cases/list`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(reqData)
});
if (!response.ok) throw new Error(`请求失败!状态码:${response.status}`);
const data = await response.json();
console.log("获取到视频案例数据:", data);
// 适配后端返回的小写字段list和total
tableData.value = data.list || [];
total.value = data.total || 0;
} catch (error) {
console.error('[ERROR] 获取视频案例列表失败:', error);
ElMessage.error('获取视频案例列表失败,请检查接口或网络!');
} finally {
loading.value = false;
}
};
onMounted(fetchData);
// --- 视频预览 ---
const handlePreviewVideo = (url: string) => {
currentVideoUrl.value = url;
videoDialogVisible.value = true;
};
const handleCloseVideo = () => {
videoDialogVisible.value = false;
currentVideoUrl.value = '';
};
// --- 查看详情 ---
const handleViewDetail = (row: VideoCase) => {
currentVideoCase.value = { ...row };
detailDialogVisible.value = true;
};
// --- 删除案例 ---
const handleDelete = (row: VideoCase) => {
ElMessageBox.confirm(`确定要删除视频案例《${row.title}》吗?此操作无法撤销!`, '警告', {
confirmButtonText: '确定删除',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
try {
const response = await fetch(`${API_BASE_URL}/video-cases/${row.id}`, {
method: 'DELETE',
});
if (!response.ok) {
const errData = await response.json().catch(() => null);
throw new Error(errData?.msg || '删除失败');
}
ElMessage.success('删除成功!');
fetchData();
} catch (error) {
console.error('[ERROR] 删除视频案例失败:', error);
ElMessage.error(`删除视频案例失败: ${(error as Error).message}`);
}
})
.catch(() => {
ElMessage.info('已取消删除');
});
};
// --- 分页处理 ---
const handleSizeChange = (val: number) => {
pageSize.value = val;
currentPage.value = 1;
fetchData();
};
const handleCurrentChange = (val: number) => {
currentPage.value = val;
fetchData();
};
// =================================================================
// 抽屉编辑/新增相关状态与逻辑
// =================================================================
const drawerVisible = ref(false);
const isSubmitting = ref(false);
// --- 表单默认值 ---
const defaultFormState = () => ({
id: null as number | null,
title: '',
intro: '',
video_url: '',
designer_names: '',
tutor_names: '',
sort: 0,
});
const form = ref(defaultFormState());
// --- 操作处理 ---
const handleAdd = () => {
form.value = defaultFormState();
drawerVisible.value = true;
};
const handleEdit = (row: VideoCase) => {
form.value = {
id: row.id,
title: row.title,
intro: row.intro,
video_url: row.video_url,
designer_names: row.designer_names,
tutor_names: row.tutor_names,
sort: row.sort,
};
drawerVisible.value = true;
};
const handleDrawerClose = () => {
drawerVisible.value = false;
form.value = defaultFormState();
};
// --- 提交视频案例(新增/更新) ---
const submitVideoCase = async () => {
// 表单校验
if (!form.value.title.trim()) {
ElMessage.warning('请输入视频标题');
return;
}
if (!form.value.video_url.trim()) {
ElMessage.warning('请输入视频播放地址');
return;
}
if (!form.value.designer_names.trim()) {
ElMessage.warning('请输入设计人员名单');
return;
}
if (!form.value.tutor_names.trim()) {
ElMessage.warning('请输入指导老师名单');
return;
}
isSubmitting.value = true;
// 构造提交数据
const submitData: CreateVideoCaseReq | UpdateVideoCaseReq = {
title: form.value.title.trim(),
intro: form.value.intro.trim(),
video_url: form.value.video_url.trim(),
designer_names: form.value.designer_names.trim(),
tutor_names: form.value.tutor_names.trim(),
sort: form.value.sort || 0,
};
// 编辑模式补充id
if (form.value.id) {
(submitData as UpdateVideoCaseReq).id = form.value.id;
}
try {
let url = `${API_BASE_URL}/video-cases`;
let method = 'POST';
// 编辑模式
if (form.value.id) {
url = `${API_BASE_URL}/video-cases/${form.value.id}`;
method = 'PUT';
}
const response = await fetch(url, {
method: method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(submitData),
});
if (!response.ok) {
const errData = await response.json().catch(() => null);
throw new Error(errData?.msg || '提交失败');
}
ElMessage.success(form.value.id ? '视频案例更新成功!' : '视频案例新增成功!');
drawerVisible.value = false;
form.value = defaultFormState();
fetchData();
} catch (error) {
const err = error as Error;
ElMessage.error(`${form.value.id ? '更新' : '新增'}失败: ${err.message}`);
} finally {
isSubmitting.value = false;
}
};
</script>
<style scoped>
/* 基础样式 */
::v-deep(.el-drawer__body) { padding: 20px 0 !important; }
.required::after { content: '*'; color: #f56c6c; margin-left: 4px; }
/* 表格样式 */
.el-table .el-table__cell { vertical-align: middle; }
.el-table__header-wrapper th { background-color: #fafafa !important; font-weight: 600; color: #333; }
.action-buttons .el-button { margin-right: 8px; }
.intro-preview { color: #666; line-height: 1.5; word-break: break-all; }
/* 弹窗样式 */
.video-container { padding: 10px 0; }
.detail-container { line-height: 1.8; }
/* 分页器样式 */
.custom-pagination { justify-content: flex-end !important; }
.custom-pagination .el-pagination__total,
.custom-pagination .el-pagination__sizes,
.custom-pagination .el-pagination__jump { margin-right: 16px !important; }
.custom-pagination .el-pagination__jump .el-input { width: 60px !important; }
/* 抽屉内表单样式 */
.publish-form-container { padding: 0 20px; }
.form-group { margin-bottom: 24px; }
.form-label { display: block; margin-bottom: 8px; color: #333; font-size: 14px; font-weight: 600; }
.el-textarea__inner { min-height: 100px !important; }
/* 响应式调整 */
@media (max-width: 1440px) {
.el-table-column { min-width: 120px; }
}
</style>