| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840 |
- <template>
- <div class="video-container">
- <h1 class="video-title">视频管理</h1>
-
- <!-- 搜索和操作区域 -->
- <div class="video-operations">
- <el-input
- v-model="searchQuery"
- placeholder="搜索视频名称"
- class="search-input"
- clearable
- @clear="handleSearch"
- @keyup.enter.native="handleSearch"
- >
- <el-button slot="append" icon="el-icon-search" @click="handleSearch" />
- </el-input>
-
- <el-button
- type="primary"
- icon="el-icon-upload"
- @click="showUploadDialog"
- >
- 上传视频
- </el-button>
- </div>
-
- <!-- 视频列表表格 -->
- <el-table
- :data="filteredVideos"
- border
- style="width: 100%"
- v-loading="loading"
- >
- <el-table-column prop="id" label="ID" width="80" />
- <el-table-column prop="upload_user" label="上传员工" />
- <el-table-column prop="name" label="视频名称" />
- <el-table-column label="封面">
- <template slot-scope="{ row }">
- <img v-if="row.video_img" :src="row.video_img" class="video-cover" />
- </template>
- </el-table-column>
- <el-table-column label="视频地址">
- <template slot-scope="{ row }">
- <el-link :href="row.video_address" target="_blank">查看视频</el-link>
- </template>
- </el-table-column>
- <el-table-column prop="create_time" label="创建时间" width="180" />
- <el-table-column prop="update_time" label="更新时间" width="180" />
- <el-table-column label="操作" width="180">
- <template slot-scope="{ row }">
- <el-button size="small" @click="handleEdit(row)">编辑</el-button>
- <el-button size="small" type="danger" @click="handleDelete(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
-
- <!-- 分页 -->
- <div class="pagination-container">
- <el-pagination
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page="currentPage"
- :page-sizes="[10, 20, 50, 100]"
- :page-size="pageSize"
- layout="total, sizes, prev, pager, next, jumper"
- :total="total"
- />
- </div>
-
- <!-- 上传视频对话框 -->
- <el-dialog
- title="上传视频"
- :visible.sync="uploadDialogVisible"
- width="50%"
- :close-on-click-modal="false"
- >
- <el-form
- ref="uploadForm"
- :model="uploadForm"
- :rules="uploadRules"
- label-width="100px"
- >
- <el-form-item label="视频名称" prop="name">
- <el-input v-model="uploadForm.name" />
- </el-form-item>
-
- <el-form-item label="视频封面" prop="coverUrl">
- <el-upload
- class="cover-uploader"
- action=""
- :show-file-list="false"
- :auto-upload="false"
- :on-change="handleCoverChange"
- :before-upload="beforeCoverUpload"
- >
- <img v-if="uploadForm.coverUrl" :src="uploadForm.coverUrl" class="cover-image" />
- <i v-else class="el-icon-plus cover-uploader-icon"></i>
- <div class="el-upload__tip">建议尺寸16:9,不超过2MB</div>
- <!-- 增加上传状态提示 -->
- <div v-if="coverUploading" class="el-upload__tip">封面上传中...</div>
- <div v-if="coverUploadError" class="el-upload__tip" style="color: red;">{{ coverUploadError }}</div>
- </el-upload>
- </el-form-item>
-
- <el-form-item label="视频文件" prop="video">
- <el-upload
- class="video-uploader"
- drag
- action=""
- :auto-upload="false"
- :on-change="handleVideoChange"
- :before-upload="beforeVideoUpload"
- :file-list="videoFileList"
- :limit="1"
- >
- <i class="el-icon-upload"></i>
- <div class="el-upload__text">将视频拖到此处,或<em>点击上传</em></div>
- <div class="el-upload__tip">
- 支持MP4/AVI/MOV/WMV格式,不超过100MB
- <el-progress
- v-if="uploadProgress > 0"
- :percentage="uploadProgress"
- :stroke-width="2"
- style="margin-top: 10px;"
- />
- </div>
- </el-upload>
- </el-form-item>
- </el-form>
-
- <span slot="footer" class="dialog-footer">
- <el-button @click="closeUploadDialog">取 消</el-button>
- <el-button
- type="primary"
- @click="handleUpload"
-
- >
- 确 定
- </el-button>
- </span>
- </el-dialog>
-
- <!-- 编辑对话框 -->
- <el-dialog
- title="编辑视频信息"
- :visible.sync="editDialogVisible"
- width="30%"
- >
- <!-- <el-form :model="editForm" ref="editForm" label-width="100px">
- <el-form-item label="视频名称" prop="name">
- <el-input v-model="editForm.name" />
- </el-form-item>
- </el-form> -->
-
- <el-form
- ref="uploadForm"
- :model="uploadForm"
- :rules="uploadRules"
- label-width="100px"
- >
- <el-form-item label="视频名称" prop="name">
- <el-input v-model="editForm.name" />
- </el-form-item>
-
- <el-form-item label="视频封面" prop="coverUrl">
- <el-upload
- class="cover-uploader"
- action=""
- :show-file-list="false"
- :auto-upload="false"
- :on-change="edithandleCoverChange"
- :before-upload="beforeCoverUpload"
- >
- <img v-if="editForm.video_img" :src="editForm.video_img" class="cover-image" />
- <i v-else class="el-icon-plus cover-uploader-icon"></i>
- <div class="el-upload__tip">建议尺寸16:9,不超过2MB</div>
- <!-- 增加上传状态提示 -->
- <div v-if="coverUploading" class="el-upload__tip">封面上传中...</div>
- <div v-if="coverUploadError" class="el-upload__tip" style="color: red;">{{ coverUploadError }}</div>
- </el-upload>
- </el-form-item>
-
- <el-form-item label="视频文件" prop="video">
- <el-upload
- class="video-uploader"
- drag
- action=""
- :auto-upload="false"
- :on-change="edithandleVideoChange"
- :before-upload="beforeVideoUpload"
- :file-list="videoFileList"
- :limit="1"
- >
- <i class="el-icon-upload"></i>
- <div class="el-upload__text">将视频拖到此处,或<em>点击上传</em></div>
- <div class="el-upload__tip">
- 支持MP4/AVI/MOV/WMV格式,不超过100MB
- <el-progress
- v-if="uploadProgress > 0"
- :percentage="uploadProgress"
- :stroke-width="2"
- style="margin-top: 10px;"
- />
- </div>
- </el-upload>
- </el-form-item>
- </el-form>
- <span slot="footer" class="dialog-footer">
- <el-button @click="editDialogVisible = false">取 消</el-button>
- <el-button type="primary" @click="submitEdit">确 定</el-button>
- </span>
- </el-dialog>
- </div>
- </template>
- <script>
- import { userApi } from '@/api/index.js';
- export default {
- name: "VideoManager",
- data() {
- return {
- videoList: [],
- loading: false,
- searchQuery: '',
- currentPage: 1,
- pageSize: 10,
- total: 0,
-
- // 上传相关
- uploadDialogVisible: false,
- uploadLoading: false,
- uploadProgress: 0,
- coverUploading: false, // 新增:封面上传状态
- coverUploadError: '', // 新增:封面上传错误信息
- uploadForm: {
- name: '',
- coverUrl: '',
- coverPath: '', // 新增:存储服务器返回的封面路径
- video: null,
- videoUrl: ''
- },
- videoFileList: [],
-
- // 编辑相关
- editDialogVisible: false,
- editForm: {
- id: '',
- name: ''
- },
- is_image:'',
-
- // 验证规则
- uploadRules: {
- name: [{ required: true, message: '请输入视频名称', trigger: 'blur' }],
- coverUrl: [{ required: true, message: '请上传视频封面', trigger: 'change' }],
- video: [{ required: true, message: '请上传视频文件', trigger: 'change' }]
- }
- };
- },
- computed: {
- filteredVideos() {
- return this.videoList.filter(item =>
- item.name.toLowerCase().includes(this.searchQuery.toLowerCase())
- );
- },
- // 新增计算属性:检查表单是否有效
- isUploadFormValid() {
- return (
- this.uploadForm.name &&
- this.uploadForm.coverPath &&
- this.uploadForm.videoUrl &&
- !this.coverUploading
- );
- }
- },
- created() {
- this.fetchVideoList();
- },
- methods: {
- // 获取视频列表
- async fetchVideoList() {
- this.loading = true;
- try {
- const res = await userApi.getVideoList({
- page: this.currentPage,
- size: this.pageSize
- });
- this.videoList = res.data.data.list || [];
- this.total = res.data.data.total || 0;
- } catch (error) {
- this.$message.error('获取视频列表失败');
- console.error(error);
- } finally {
- this.loading = false;
- }
- },
-
- // 搜索
- handleSearch() {
- this.currentPage = 1;
- this.fetchVideoList();
- },
-
- // 分页
- handleSizeChange(val) {
- this.pageSize = val;
- this.fetchVideoList();
- },
- handleCurrentChange(val) {
- this.currentPage = val;
- this.fetchVideoList();
- },
-
- // 上传相关方法
- showUploadDialog() {
- this.uploadDialogVisible = true;
- this.resetUploadForm();
- },
-
- closeUploadDialog() {
- this.uploadDialogVisible = false;
- this.resetUploadForm();
- },
-
- resetUploadForm() {
- this.uploadForm = {
- name: '',
- coverUrl: '',
- coverPath: '',
- video: null,
- videoUrl: ''
- };
- this.videoFileList = [];
- this.uploadProgress = 0;
- this.coverUploading = false;
- this.coverUploadError = '';
- this.$refs.uploadForm?.resetFields();
- },
-
- beforeCoverUpload(file) {
- const isImage = ['image/jpeg', 'image/png'].includes(file.type);
- const isLt2M = file.size / 1024 / 1024 < 2;
-
- if (!isImage) {
- this.$message.error('封面必须是JPG/PNG图片!');
- return false;
- }
- if (!isLt2M) {
- this.$message.error('封面大小不能超过2MB!');
- return false;
- }
- return true;
- },
-
- // 修改handleCoverChange方法
- async handleCoverChange(file) {
- if (!this.beforeCoverUpload(file.raw)) {
- return;
- }
-
- this.coverUploading = true;
- this.coverUploadError = '';
-
-
- // 显示本地预览
- this.uploadForm.coverUrl = URL.createObjectURL(file.raw);
-
- // 创建FormData并上传
- const formData = new FormData();
- formData.append('file', file.raw);
-
- // 添加请求配置
- // const config = {
- // headers: {
- // 'Content-Type': 'multipart/form-data'
- // },
- // timeout: 30000 // 30秒超时
- // };
-
- const res = await userApi.uploadImage(formData);
- console.log('封面上传响应:', res);
- console.log('封面上传响应:', res.data);
- // 适配不同的返回格式
- if (res.data.code === 200) {
- this.uploadForm.coverPath = res.data.filePath;
- this.$message.success('封面上传成功');
- } else {
- throw new Error(res.message || '封面上传失败');
- }
-
- },
- async edithandleCoverChange(file) {
- if (!this.beforeCoverUpload(file.raw)) {
- return;
- }
-
- this.coverUploading = true;
- this.coverUploadError = '';
-
-
- // 显示本地预览
- this.uploadForm.coverUrl = URL.createObjectURL(file.raw);
-
- // 创建FormData并上传
- const formData = new FormData();
- formData.append('file', file.raw);
-
- // 添加请求配置
-
-
- const res = await userApi.uploadImage(formData);
- console.log('封面上传响应:', res);
- console.log('封面上传响应:', res.data);
- console.log('封面上传响应:', res.data.filePath);
- // 适配不同的返回格式
- if (res.data.code === 200) {
- this.editForm.video_img = res.data.filePath;
-
- this.is_image = res.data.filePath;
- this.$message.success('封面上传成功');
- } else {
- throw new Error(res.message || '封面上传失败');
- }
-
- },
-
- beforeVideoUpload(file) {
- const validExts = ['mp4', 'avi', 'mov', 'wmv'];
- const validMimes = [
- 'video/mp4',
- 'video/quicktime',
- 'video/x-msvideo',
- 'video/x-ms-wmv'
- ];
-
- const fileExt = file.name.split('.').pop().toLowerCase();
- const isValidExt = validExts.includes(fileExt);
- const isValidMime = validMimes.includes(file.type);
- const isLt100M = file.size / 1024 / 1024 < 100;
- if (!isValidExt) {
- this.$message.error(`仅支持 ${validExts.join(', ')} 格式!`);
- return false;
- }
-
- if (!isValidMime) {
- this.$message.error('文件类型不匹配!');
- return false;
- }
-
- if (!isLt100M) {
- this.$message.error('视频不能超过100MB!');
- return false;
- }
-
- return true;
- },
-
- // 修改handleVideoChange方法
- async handleVideoChange(file) {
- if (!this.beforeVideoUpload(file.raw)) {
- this.videoFileList = [];
- return;
- }
- this.videoFileList = [file];
- this.uploadProgress = 0;
-
- try {
- const formData = new FormData();
- formData.append('file', file.raw, file.name);
-
-
- // 添加错误处理
- try {
- const res = await userApi.uploadVideo(formData);
-
- console.log('视频上传响应:', res.data);
- console.log('视频上传响应:', res.data.data);
- if (res.data.code === 200) {
- this.uploadForm.videoUrl = res.data.data.url;
- this.$message.success('视频上传成功');
- } else {
- throw new Error(res.message || '上传失败');
- }
- } catch (error) {
- // 捕获并显示详细的错误信息
- let errorMsg = error.message;
- if (error.response) {
- errorMsg = error.response.data?.message || errorMsg;
- }
- throw new Error(`视频上传失败: ${errorMsg}`);
- }
- } catch (error) {
- this.$message.error(error.message);
- // this.resetVideoUpload();
- console.error('视频上传错误详情:', error);
- }
- },
-
- // 修改handleVideoChange方法
- async edithandleVideoChange(file) {
- console.log('编辑视频上传文件111:', file);
- if (!this.beforeVideoUpload(file.raw)) {
- this.videoFileList = [];
- return;
- }
- this.videoFileList = [file];
- this.uploadProgress = 0;
-
- try {
- const formData = new FormData();
- formData.append('file', file.raw, file.name);
-
-
- // 添加错误处理
- try {
- const res = await userApi.uploadVideo(formData);
-
- console.log('视频上传响应:', res.data);
- console.log('视频上传响应:', res.data.data);
- if (res.data.code === 200) {
- this.editForm.video_address = res.data.data.url;
- this.$message.success('视频上传成功');
- } else {
- throw new Error(res.message || '上传失败');
- }
- } catch (error) {
- // 捕获并显示详细的错误信息
- let errorMsg = error.message;
- if (error.response) {
- errorMsg = error.response.data?.message || errorMsg;
- }
- throw new Error(`视频上传失败: ${errorMsg}`);
- }
- } catch (error) {
- this.$message.error(error.message);
- // this.resetVideoUpload();
- console.error('视频上传错误详情:', error);
- }
- },
-
- // 提交上传
- // 提交上传
- async handleUpload() {
- console.log('提交上传表单:', this.uploadForm);
- try {
- // 验证表单
- // await this.$refs.uploadForm.validate();
-
- // 检查视频是否已上传
- if (!this.uploadForm.videoUrl) {
- this.$message.error('请先上传视频文件');
- return;
- }
-
- this.uploadLoading = true;
-
- // 如果用户没有上传封面,则从视频中截取一帧作为封面
- if (!this.uploadForm.coverPath) {
- try {
- // 创建视频元素来截取帧
- const video = document.createElement('video');
- video.crossOrigin = 'anonymous';
- video.src = this.uploadForm.videoUrl;
-
- // 等待视频加载完成
- await new Promise((resolve, reject) => {
- video.addEventListener('loadeddata', resolve);
- video.addEventListener('error', reject);
- video.load();
- });
-
- // 等待视频可以播放
- await new Promise(resolve => {
- video.addEventListener('canplay', resolve);
- });
-
- // 跳转到视频的10%位置(通常这里会有有意义的画面)
- video.currentTime = video.duration * 0.1;
-
- // 等待seek完成
- await new Promise(resolve => {
- video.addEventListener('seeked', resolve);
- });
-
- // 创建canvas来截取帧
- const canvas = document.createElement('canvas');
- canvas.width = video.videoWidth;
- canvas.height = video.videoHeight;
- const ctx = canvas.getContext('2d');
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-
- // 将canvas转换为blob
- const blob = await new Promise(resolve => {
- canvas.toBlob(resolve, 'image/jpeg', 0.8);
- });
-
- // 创建FormData并上传截取的封面
- const formData = new FormData();
- formData.append('file', blob, 'auto_capture.jpg');
-
- const config = {
- headers: {
- 'Content-Type': 'multipart/form-data'
- },
- timeout: 30000
- };
-
- const res = await userApi.uploadImage(formData, config);
-
- if (res.data.code === 200) {
- this.uploadForm.coverPath = res.data.filePath;
- this.uploadForm.coverUrl = URL.createObjectURL(blob);
- this.$message.success('已自动截取视频帧作为封面');
- } else {
- throw new Error(res.message || '封面截取失败');
- }
- } catch (error) {
- console.error('截取视频封面失败:', error);
- this.$message.error('自动截取封面失败,请手动上传封面');
- return;
- }
- }
-
- const userInfo = JSON.parse(localStorage.getItem('userInfo'));
-
- const user = userInfo.employeeName + '(' + userInfo.employeeUsername + ')';
- // 提交视频信息
- const res = await userApi.addVideoList({
- upload_user: user,
- name: this.uploadForm.name,
- video_img: this.uploadForm.coverPath,
- video_address: this.uploadForm.videoUrl
- });
-
- if (res.data.code === 1 || res.data.code === 200) {
- this.$message.success('上传成功');
- this.closeUploadDialog();
- this.fetchVideoList();
- } else {
- throw new Error(res.message || '提交失败');
- }
-
- } catch (error) {
- console.error('上传失败:', error);
- this.$message.error(error.message || '上传失败');
- } finally {
- this.uploadLoading = false;
- }
- },
-
- // 编辑相关
- handleEdit(row) {
- this.editForm = { ...row };
- this.editDialogVisible = true;
- },
-
- async submitEdit() {
- if(!this.is_image){
- console.log('is_image:', this.is_image);
- try {
- // 创建视频元素来截取帧
- const video = document.createElement('video');
- video.crossOrigin = 'anonymous';
- video.src = this.editForm.video_address;
-
- // 等待视频加载完成
- await new Promise((resolve, reject) => {
- video.addEventListener('loadeddata', resolve);
- video.addEventListener('error', reject);
- video.load();
- });
-
- // 等待视频可以播放
- await new Promise(resolve => {
- video.addEventListener('canplay', resolve);
- });
-
- // 跳转到视频的10%位置(通常这里会有有意义的画面)
- video.currentTime = video.duration * 0.1;
-
- // 等待seek完成
- await new Promise(resolve => {
- video.addEventListener('seeked', resolve);
- });
-
- // 创建canvas来截取帧
- const canvas = document.createElement('canvas');
- canvas.width = video.videoWidth;
- canvas.height = video.videoHeight;
- const ctx = canvas.getContext('2d');
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
-
- // 将canvas转换为blob
- const blob = await new Promise(resolve => {
- canvas.toBlob(resolve, 'image/jpeg', 0.8);
- });
-
- // 创建FormData并上传截取的封面
- const formData = new FormData();
- formData.append('file', blob, 'auto_capture.jpg');
-
- // const config = {
- // headers: {
- // 'Content-Type': 'multipart/form-data'
- // },
- // timeout: 30000
- // };
-
- const res = await userApi.uploadImage(formData);
-
- if (res.data.code === 200) {
- this.editForm.video_img = res.data.filePath;
-
- this.$message.success('已自动截取视频帧作为封面');
- } else {
- throw new Error(res.message || '封面截取失败');
- }
- } catch (error) {
- console.error('截取视频封面失败:', error);
- this.$message.error('自动截取封面失败,请手动上传封面');
- return;
- }
- }
- try {
- const res = await userApi.editVideoList(this.editForm);
- if (res.data.code === 200) {
- this.$message.success('修改成功');
- this.editDialogVisible = false;
- this.fetchVideoList();
- this.is_image = '';
- }
- } catch (error) {
- this.$message.error('修改失败');
- }
- },
-
- // 删除
- async handleDelete(row) {
- try {
- await this.$confirm('确认删除该视频?', '提示', { type: 'warning' });
-
- const res = await userApi.deleteVideoList({ id: row.id });
- if (res.code === 200) {
- this.$message.success('删除成功');
- this.fetchVideoList();
- }
- } catch (error) {
- if (error !== 'cancel') {
- this.$message.error('删除失败');
- }
- }
- }
- }
- };
- </script>
- <style scoped>
- .video-container {
- padding: 20px;
- }
- .video-title {
- margin-bottom: 20px;
- font-size: 24px;
- font-weight: bold;
- }
- .video-operations {
- display: flex;
- justify-content: space-between;
- margin-bottom: 20px;
- }
- .search-input {
- width: 300px;
- }
- .pagination-container {
- margin-top: 20px;
- text-align: right;
- }
- .video-cover {
- max-width: 120px;
- max-height: 80px;
- }
- .cover-uploader {
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- width: 178px;
- height: 100px;
- }
- .cover-uploader:hover {
- border-color: #409EFF;
- }
- .cover-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 178px;
- height: 100px;
- line-height: 100px;
- text-align: center;
- }
- .cover-image {
- width: 178px;
- height: 100px;
- display: block;
- }
- .video-uploader {
- width: 100%;
- }
- .el-upload__tip {
- font-size: 12px;
- color: #606266;
- margin-top: 7px;
- }
- </style>
|