You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1323 lines
38 KiB
1323 lines
38 KiB
<template>
|
|
<div class="app-container">
|
|
<!-- <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">-->
|
|
<!-- <el-form-item>-->
|
|
<!-- <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>-->
|
|
<!-- <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>-->
|
|
<!-- </el-form-item>-->
|
|
<!-- </el-form>-->
|
|
|
|
<el-row :gutter="10" class="mb8">
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="primary"
|
|
plain
|
|
icon="el-icon-plus"
|
|
size="mini"
|
|
@click="handleAdd"
|
|
v-hasPermi="['vet:training:add']"
|
|
>新增</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="info"
|
|
plain
|
|
icon="el-icon-s-promotion"
|
|
size="mini"
|
|
:disabled="single"
|
|
@click="handleSubmitAudit"
|
|
v-hasPermi="['vet:training:submit']"
|
|
>提交审核</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="primary"
|
|
plain
|
|
icon="el-icon-finished"
|
|
size="mini"
|
|
:disabled="single"
|
|
@click="handleAuditDialog"
|
|
v-hasPermi="['vet:training:audit']"
|
|
>审核</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="success"
|
|
plain
|
|
icon="el-icon-check"
|
|
size="mini"
|
|
:disabled="single"
|
|
@click="handlePublish"
|
|
v-hasPermi="['vet:training:publish']"
|
|
>上架视频</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="danger"
|
|
plain
|
|
icon="el-icon-delete"
|
|
size="mini"
|
|
:disabled="multiple"
|
|
@click="handleDelete"
|
|
v-hasPermi="['vet:training:remove']"
|
|
>删除</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="warning"
|
|
plain
|
|
icon="el-icon-download"
|
|
size="mini"
|
|
:disabled="single"
|
|
@click="handleOffline"
|
|
v-hasPermi="['vet:training:offline']"
|
|
>下架视频</el-button>
|
|
</el-col>
|
|
<el-col :span="1.5">
|
|
<el-button
|
|
type="warning"
|
|
plain
|
|
icon="el-icon-download"
|
|
size="mini"
|
|
@click="handleExport"
|
|
v-hasPermi="['vet:training:export']"
|
|
>导出</el-button>
|
|
</el-col>
|
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
|
</el-row>
|
|
|
|
<el-table v-loading="loading" :data="trainingList" @selection-change="handleSelectionChange">
|
|
<el-table-column type="selection" width="55" align="center" />
|
|
<el-table-column label="发布者" align="center" prop="publisherName" width="120">
|
|
<template slot-scope="scope">
|
|
<span>{{ scope.row.publisherName || '-' }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="视频标题" align="center" prop="title" show-overflow-tooltip/>
|
|
<el-table-column label="视频描述" align="center" prop="description" show-overflow-tooltip/>
|
|
<el-table-column label="视频地址" align="center" prop="videoUrl" width="180" />
|
|
<el-table-column label="封面图片" align="center" prop="coverImage" width="100">
|
|
<template slot-scope="scope">
|
|
<image-preview :src="scope.row.coverImage" :width="50" :height="50"/>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="分类" align="center" prop="category">
|
|
<template slot-scope="scope">
|
|
<dict-tag :options="dict.type.video_category" :value="scope.row.category" />
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="观看次数" align="center" prop="viewCount" />
|
|
<el-table-column label="发布时间" align="center" prop="publishTime" min-width="200" show-overflow-tooltip />
|
|
|
|
<!-- 状态列 -->
|
|
<el-table-column label="上架状态" align="center" prop="status" width="100">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getStatusType(scope.row.status)" size="small">
|
|
{{ getStatusText(scope.row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<!-- 审核状态列 -->
|
|
<el-table-column label="审核状态" align="center" prop="auditStatus" width="100">
|
|
<template slot-scope="scope">
|
|
<el-tag :type="getAuditStatusType(scope.row.auditStatus)" size="small">
|
|
{{ getAuditStatusText(scope.row.auditStatus) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<el-table-column label="审核意见" align="center" prop="auditOpinion" width="150" show-overflow-tooltip>
|
|
<template slot-scope="scope">
|
|
<span v-if="scope.row.auditOpinion">{{ truncateText(scope.row.auditOpinion, 15) }}</span>
|
|
<span v-else>-</span>
|
|
</template>
|
|
</el-table-column>
|
|
|
|
<!-- 操作列 -->
|
|
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="300">
|
|
<template slot-scope="scope">
|
|
<!-- 修改按钮 -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-edit"
|
|
style="color: #42B983"
|
|
@click="handleUpdate(scope.row)"
|
|
v-hasPermi="['vet:training:edit']"
|
|
v-if="scope.row.status === '0' && (scope.row.auditStatus === '3' || scope.row.auditStatus === '0')"
|
|
class="info-btn alter-btn"
|
|
>修改</el-button>
|
|
|
|
<!-- 提交审核按钮:审核拒绝或无需审核状态 -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-s-promotion"
|
|
style="color: #dab708"
|
|
@click="handleSubmitAudit(scope.row.id)"
|
|
v-hasPermi="['vet:training:submit']"
|
|
v-if="scope.row.status === '0' && scope.row.auditStatus === '0'"
|
|
class="info-btn submit-btn"
|
|
>提交审核</el-button>
|
|
|
|
<!-- 审核按钮:待审核状态(管理员) -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-finished"
|
|
@click="handleAuditDialog(scope.row.id)"
|
|
style="color: #072eed"
|
|
v-hasPermi="['vet:training:audit']"
|
|
v-if="scope.row.auditStatus === '0' && isAdmin"
|
|
class="info-btn audit-btn"
|
|
>审核</el-button>
|
|
|
|
<!-- 上架按钮:审核通过且未上架 -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-top"
|
|
@click="handlePublish(scope.row.id)"
|
|
style="color: #f46a0c"
|
|
v-hasPermi="['vet:training:publish']"
|
|
v-if="scope.row.auditStatus === '2' && scope.row.status === '0'"
|
|
class="info-btn publish-btn"
|
|
>上架</el-button>
|
|
|
|
<!-- 下架按钮:已上架状态 -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-bottom"
|
|
style="color: #636361"
|
|
@click="handleOffline(scope.row.id)"
|
|
v-hasPermi="['vet:training:offline']"
|
|
v-if="scope.row.status === '1'"
|
|
class="info-btn offline-btn"
|
|
>下架</el-button>
|
|
|
|
<!-- 取消审核按钮 -->
|
|
<!-- <el-button-->
|
|
<!-- size="mini"-->
|
|
<!-- type="text"-->
|
|
<!-- icon="el-icon-close"-->
|
|
<!-- style="color: #5607b3"-->
|
|
<!-- @click="handleCancelAudit(scope.row.id)"-->
|
|
<!-- v-hasPermi="['vet:training:edit']"-->
|
|
<!-- v-if="scope.row.auditStatus === '1' && !isAdmin"-->
|
|
<!-- class="info-btn cancel-btn"-->
|
|
<!-- >取消审核</el-button>-->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-close"
|
|
style="color: #5607b3"
|
|
@click="handleCancelAudit(scope.row.id)"
|
|
v-hasPermi="['vet:training:edit']"
|
|
v-if="scope.row.auditStatus === '1'"
|
|
class="info-btn cancel-btn"
|
|
>取消审核</el-button>
|
|
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-s-promotion"
|
|
style="color: #c108af"
|
|
@click="handleResubmitAudit(scope.row)"
|
|
class="info-btn resubmit-btn"
|
|
v-hasPermi="['vet:training:submit']"
|
|
v-if="scope.row.status === '0' && scope.row.auditStatus === '3'"
|
|
>
|
|
重新提交
|
|
</el-button>
|
|
|
|
<!-- 删除按钮 -->
|
|
<el-button
|
|
size="mini"
|
|
type="text"
|
|
icon="el-icon-delete"
|
|
style="color: #f56c6c"
|
|
@click="handleDelete(scope.row)"
|
|
v-hasPermi="['vet:training:remove']"
|
|
class="info-btn delete-btn"
|
|
>删除</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<div class="pagestyle">
|
|
<pagination
|
|
v-show="total>0"
|
|
:total="total"
|
|
:page.sync="queryParams.pageNum"
|
|
:limit.sync="queryParams.pageSize"
|
|
@pagination="getList"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 添加或修改兽医培训视频对话框 -->
|
|
<el-dialog :title="title" :visible.sync="open" width="80%" append-to-body>
|
|
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
|
<el-form-item label="视频标题" prop="title">
|
|
<el-input v-model="form.title" placeholder="请输入视频标题" />
|
|
</el-form-item>
|
|
<el-form-item label="视频描述" prop="description">
|
|
<el-input v-model="form.description" type="textarea" placeholder="请输入内容" />
|
|
</el-form-item>
|
|
<el-form-item label="发布时间" prop="publishTime">
|
|
<el-date-picker
|
|
v-model="form.publishTime"
|
|
type="date"
|
|
value-format="yyyy-MM-dd"
|
|
placeholder="请选择时间"
|
|
style="width: 100%;"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item label="视频" required>
|
|
<div style="display: flex; flex-direction: column; gap: 12px;">
|
|
<!-- 简单的文件选择方式 -->
|
|
<div v-if="!form.videoUrl">
|
|
<input
|
|
type="file"
|
|
ref="videoInput"
|
|
accept=".mp4,.avi,.mov,.wmv,.flv,.mkv"
|
|
@change="handleFileSelect"
|
|
style="display: none"
|
|
/>
|
|
<el-button
|
|
type="primary"
|
|
@click="$refs.videoInput.click()"
|
|
:loading="uploading"
|
|
>
|
|
<el-icon><Upload /></el-icon>
|
|
{{ uploading ? '上传中...' : '点击上传视频' }}
|
|
</el-button>
|
|
<div class="el-upload__tip" style="margin-top: 7px; color: #909399; font-size: 12px;">
|
|
支持 MP4、AVI、MOV、WMV、FLV、MKV 格式,大小不超过200MB
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 预览区域(上传后显示) -->
|
|
<div v-if="form.videoUrl" class="video-preview">
|
|
<div style="display: flex; align-items: flex-start; gap: 10px;">
|
|
<video
|
|
:src="getVideoUrl(form.videoUrl)"
|
|
controls
|
|
style="max-width: 300px; max-height: 150px; border-radius: 4px; background: #000;"
|
|
></video>
|
|
<div style="display: flex; flex-direction: column; gap: 5px;">
|
|
<el-button
|
|
type="danger"
|
|
size="small"
|
|
@click="removeVideo"
|
|
circle
|
|
>
|
|
<el-icon><Delete /></el-icon>
|
|
</el-button>
|
|
<el-button
|
|
type="primary"
|
|
size="small"
|
|
@click="previewVideo"
|
|
>
|
|
预览
|
|
</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 地址输入框(可编辑) -->
|
|
<el-form-item prop="videoUrl" style="margin-bottom: 0;">
|
|
<el-input
|
|
v-model="form.videoUrl"
|
|
type="textarea"
|
|
:rows="2"
|
|
placeholder="请上传视频或直接输入视频地址"
|
|
clearable
|
|
/>
|
|
</el-form-item>
|
|
</div>
|
|
</el-form-item>
|
|
<el-form-item label="封面图片" prop="coverImage">
|
|
<image-upload v-model="form.coverImage"/>
|
|
</el-form-item>
|
|
<el-form-item label="分类" prop="category">
|
|
<el-select v-model="form.category" placeholder="请选择分类" clearable>
|
|
<el-option v-for="dict in dict.type.video_category" :key="dict.value" :label="dict.label" :value="dict.value" />
|
|
</el-select>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button type="primary" @click="submitForm">确 定</el-button>
|
|
<el-button @click="cancel">取 消</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
|
|
<!-- ================= 审核对话框 ================= -->
|
|
<el-dialog
|
|
title="视频审核"
|
|
:visible.sync="auditOpen"
|
|
width="80%"
|
|
append-to-body
|
|
>
|
|
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="80px">
|
|
<el-form-item label="审核结果" prop="auditStatus" required>
|
|
<el-radio-group v-model="auditForm.auditStatus">
|
|
<el-radio
|
|
v-for="item in auditStatusOptions.filter(item => ['2', '3'].includes(item.dictValue))"
|
|
:key="item.dictValue"
|
|
:label="item.dictValue"
|
|
>
|
|
{{ item.dictLabel }}
|
|
</el-radio>
|
|
</el-radio-group>
|
|
</el-form-item>
|
|
<el-form-item label="审核意见" prop="auditOpinion">
|
|
<el-input
|
|
v-model="auditForm.auditOpinion"
|
|
type="textarea"
|
|
:rows="4"
|
|
placeholder="请输入审核意见(审核拒绝时必须填写)"
|
|
maxlength="500"
|
|
show-word-limit
|
|
/>
|
|
</el-form-item>
|
|
</el-form>
|
|
<div slot="footer" class="dialog-footer">
|
|
<el-button type="primary" @click="submitAudit">确 定</el-button>
|
|
<el-button @click="cancelAudit">取 消</el-button>
|
|
</div>
|
|
</el-dialog>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import { listTraining, getTraining, delTraining, addTraining, updateTraining, uploadVideo, resubmitAudit} from "@/api/vet/training"
|
|
import {
|
|
submitForAudit,
|
|
cancelAudit,
|
|
auditVideo,
|
|
publishVideo,
|
|
offlineVideo,
|
|
batchSubmitAudit,
|
|
batchAuditVideo,
|
|
batchPublishVideo,
|
|
batchOfflineVideo
|
|
} from "@/api/vet/training"
|
|
import { getToken } from "@/utils/auth"
|
|
|
|
export default {
|
|
name: "Training",
|
|
dicts: ['video_category'],
|
|
data() {
|
|
return {
|
|
// 遮罩层
|
|
loading: true,
|
|
// 选中数组
|
|
ids: [],
|
|
// 非单个禁用
|
|
single: true,
|
|
// 非多个禁用
|
|
multiple: true,
|
|
// 显示搜索条件
|
|
showSearch: true,
|
|
// 总条数
|
|
total: 0,
|
|
// 兽医培训视频表格数据
|
|
trainingList: [],
|
|
// 弹出层标题
|
|
title: "",
|
|
// 是否显示弹出层
|
|
open: false,
|
|
// 上传相关变量
|
|
uploading: false,
|
|
|
|
// 是否为管理员
|
|
isAdmin: false,
|
|
|
|
// 字典数据
|
|
statusOptions: [
|
|
{ dictValue: '0', dictLabel: '未上架', listClass: 'info' },
|
|
{ dictValue: '1', dictLabel: '已上架', listClass: 'success' }
|
|
],
|
|
auditStatusOptions: [
|
|
{ dictValue: '0', dictLabel: '待审核', listClass: 'info' },
|
|
{ dictValue: '1', dictLabel: '审核中', listClass: 'warning' },
|
|
{ dictValue: '2', dictLabel: '审核通过', listClass: 'success' },
|
|
{ dictValue: '3', dictLabel: '审核驳回', listClass: 'danger' }
|
|
],
|
|
|
|
// 查询参数
|
|
queryParams: {
|
|
pageNum: 1,
|
|
pageSize: 10,
|
|
title: null,
|
|
description: null,
|
|
videoUrl: null,
|
|
coverImage: null,
|
|
category: null,
|
|
tags: null,
|
|
duration: null,
|
|
fileSize: null,
|
|
viewCount: null,
|
|
status: null,
|
|
auditStatus: null,
|
|
auditOpinion: null,
|
|
auditUserId: null,
|
|
auditTime: null
|
|
},
|
|
|
|
// 表单参数
|
|
form: {
|
|
id: undefined,
|
|
userId: undefined,
|
|
title: "",
|
|
description: "",
|
|
videoUrl: "",
|
|
coverImage: "",
|
|
category: "",
|
|
tags: "",
|
|
duration: undefined,
|
|
fileSize: undefined,
|
|
viewCount: undefined,
|
|
status: "0",
|
|
createTime: undefined,
|
|
updateTime: undefined,
|
|
delFlag: undefined,
|
|
auditStatus: "0",
|
|
auditOpinion: "",
|
|
auditUserId: undefined,
|
|
auditTime: undefined
|
|
},
|
|
|
|
// 表单校验
|
|
rules: {
|
|
title: [
|
|
{ required: true, message: "视频标题不能为空", trigger: "blur" }
|
|
],
|
|
videoUrl: [
|
|
{ required: true, message: "视频地址不能为空", trigger: "blur" }
|
|
],
|
|
category: [
|
|
{ required: true, message: "分类不能为空", trigger: "blur" }
|
|
]
|
|
},
|
|
|
|
// ========== 审核相关 ==========
|
|
auditOpen: false,
|
|
auditForm: {
|
|
id: null,
|
|
auditStatus: '1',
|
|
auditOpinion: ''
|
|
},
|
|
auditRules: {
|
|
auditOpinion: [
|
|
{
|
|
validator: (rule, value, callback) => {
|
|
if (this.auditForm.auditStatus === '3' && (!value || value.trim() === '')) {
|
|
callback(new Error('审核拒绝时必须填写审核意见'))
|
|
} else {
|
|
callback()
|
|
}
|
|
},
|
|
trigger: 'blur'
|
|
}
|
|
]
|
|
}
|
|
}
|
|
},
|
|
created() {
|
|
this.getList()
|
|
this.checkAdminRole()
|
|
},
|
|
methods: {
|
|
/** 检查是否是管理员 */
|
|
checkAdminRole() {
|
|
const userInfo = this.$store.getters.userInfo || {}
|
|
this.isAdmin = userInfo.roles && userInfo.roles.includes('admin')
|
|
},
|
|
|
|
/** 查询兽医培训视频列表 */
|
|
getList() {
|
|
this.loading = true
|
|
listTraining(this.queryParams).then(response => {
|
|
this.trainingList = response.rows
|
|
this.total = response.total
|
|
this.loading = false
|
|
}).catch(() => {
|
|
this.loading = false
|
|
})
|
|
},
|
|
|
|
/** ============ 字典工具方法 ============ */
|
|
/** 通过字典值获取标签 */
|
|
getDictLabel(options, value) {
|
|
if (!options || !value) return ''
|
|
const item = options.find(item => item.dictValue === String(value))
|
|
return item ? item.dictLabel : String(value)
|
|
},
|
|
|
|
/** 通过字典值获取样式类型 */
|
|
getDictType(options, value) {
|
|
if (!options || !value) return 'info'
|
|
const item = options.find(item => item.dictValue === String(value))
|
|
const type = item ? (item.listClass || 'info') : 'info'
|
|
|
|
const tagTypeMap = {
|
|
'info': 'info',
|
|
'warning': 'warning',
|
|
'success': 'success',
|
|
'danger': 'danger',
|
|
// 'primary': 'primary',
|
|
}
|
|
return tagTypeMap[type] || 'info'
|
|
},
|
|
|
|
/** 获取上架状态文本 */
|
|
getStatusText(status) {
|
|
return this.getDictLabel(this.statusOptions, status)
|
|
},
|
|
|
|
/** 获取上架状态类型 */
|
|
getStatusType(status) {
|
|
return this.getDictType(this.statusOptions, status)
|
|
},
|
|
|
|
/** 获取审核状态文本 */
|
|
getAuditStatusText(auditStatus) {
|
|
return this.getDictLabel(this.auditStatusOptions, auditStatus)
|
|
},
|
|
|
|
/** 获取审核状态类型 */
|
|
getAuditStatusType(auditStatus) {
|
|
return this.getDictType(this.auditStatusOptions, auditStatus)
|
|
},
|
|
|
|
/** 截断文本 */
|
|
truncateText(text, maxLength) {
|
|
if (!text) return ''
|
|
if (text.length <= maxLength) return text
|
|
return text.substring(0, maxLength) + '...'
|
|
},
|
|
|
|
/** ============ 视频URL处理相关方法 ============ */
|
|
// 截短URL显示
|
|
truncateUrl(url) {
|
|
if (!url) return ''
|
|
if (url.includes('/')) {
|
|
const parts = url.split('/')
|
|
const filename = parts[parts.length - 1]
|
|
if (filename.length > 15) {
|
|
return filename.substring(0, 12) + '...'
|
|
}
|
|
return filename
|
|
}
|
|
if (url.length > 15) {
|
|
return url.substring(0, 12) + '...'
|
|
}
|
|
return url
|
|
},
|
|
|
|
// 表格中的视频预览
|
|
previewTableVideo(url) {
|
|
if (!url) {
|
|
this.$modal.msgWarning("视频地址为空")
|
|
return
|
|
}
|
|
|
|
const fullUrl = this.getVideoUrl(url)
|
|
const htmlContent = `
|
|
<div style="text-align: center; padding: 20px;">
|
|
<iframe
|
|
src="${fullUrl}"
|
|
style="width: 100%; height: 400px; border: none; border-radius: 4px; background: #000;"
|
|
frameborder="0"
|
|
allowfullscreen
|
|
allow="autoplay; encrypted-media"
|
|
>
|
|
</iframe>
|
|
<div style="margin-top: 10px; color: #666; font-size: 12px; word-break: break-all;">
|
|
视频地址: ${url}
|
|
</div>
|
|
</div>
|
|
`
|
|
|
|
this.$modal.alert({
|
|
title: '视频预览',
|
|
message: htmlContent,
|
|
dangerouslyUseHTMLString: true,
|
|
customClass: 'video-preview-modal',
|
|
width: '600px',
|
|
showConfirmButton: false,
|
|
showCancelButton: true,
|
|
cancelButtonText: '关闭'
|
|
})
|
|
},
|
|
|
|
// 取消按钮
|
|
cancel() {
|
|
this.open = false
|
|
this.reset()
|
|
},
|
|
|
|
// 表单重置
|
|
reset() {
|
|
this.form = {
|
|
id: undefined,
|
|
userId: undefined,
|
|
title: "",
|
|
description: "",
|
|
videoUrl: "",
|
|
coverImage: "",
|
|
category: "",
|
|
tags: "",
|
|
duration: undefined,
|
|
fileSize: undefined,
|
|
viewCount: undefined,
|
|
status: "0",
|
|
createTime: undefined,
|
|
updateTime: undefined,
|
|
delFlag: undefined,
|
|
auditStatus: "0",
|
|
auditOpinion: "",
|
|
auditUserId: undefined,
|
|
auditTime: undefined
|
|
}
|
|
|
|
if (this.$refs.form) {
|
|
this.$nextTick(() => {
|
|
this.$refs.form.clearValidate()
|
|
})
|
|
}
|
|
|
|
this.uploading = false
|
|
},
|
|
|
|
/** ============ 视频上传相关方法 ============ */
|
|
// 处理文件选择
|
|
handleFileSelect(event) {
|
|
const file = event.target.files[0]
|
|
if (file) {
|
|
this.uploadFile(file)
|
|
}
|
|
event.target.value = ''
|
|
},
|
|
|
|
// 上传文件
|
|
async uploadFile(file) {
|
|
const fileName = file.name.toLowerCase()
|
|
const validExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
|
|
const hasValidExtension = validExtensions.some(ext => fileName.endsWith(ext))
|
|
|
|
if (!hasValidExtension) {
|
|
this.$modal.msgError('请上传 MP4、AVI、MOV、WMV、FLV、MKV 格式的视频文件')
|
|
return false
|
|
}
|
|
|
|
const maxSize = 200 * 1024 * 1024
|
|
if (file.size > maxSize) {
|
|
this.$modal.msgError('视频大小不能超过 200MB')
|
|
return false
|
|
}
|
|
|
|
this.uploading = true
|
|
|
|
try {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
|
|
const response = await uploadVideo(formData)
|
|
|
|
if (response.code === 200) {
|
|
this.form.videoUrl = response.data
|
|
this.$modal.msgSuccess("视频上传成功")
|
|
return true
|
|
} else {
|
|
this.$modal.msgError(response.msg || '上传失败')
|
|
return false
|
|
}
|
|
} catch (error) {
|
|
console.error('上传失败:', error)
|
|
this.$modal.msgError('上传失败,请重试')
|
|
return false
|
|
} finally {
|
|
this.uploading = false
|
|
}
|
|
},
|
|
|
|
// 移除视频
|
|
removeVideo() {
|
|
this.form.videoUrl = ''
|
|
this.$modal.msgSuccess("已移除视频")
|
|
},
|
|
|
|
// 预览视频
|
|
previewVideo() {
|
|
if (this.form.videoUrl) {
|
|
let url = this.form.videoUrl
|
|
if (url && url.startsWith('/')) {
|
|
url = process.env.VUE_APP_BASE_API + url
|
|
}
|
|
if (url) {
|
|
window.open(url, '_blank')
|
|
}
|
|
} else {
|
|
this.$modal.msgWarning("请先上传或输入视频地址")
|
|
}
|
|
},
|
|
|
|
// 获取完整的视频URL
|
|
getVideoUrl(url) {
|
|
if (!url) return ''
|
|
if (url.startsWith('/')) {
|
|
return process.env.VUE_APP_BASE_API + url
|
|
}
|
|
return url
|
|
},
|
|
|
|
/** ============ 审核相关方法 ============ */
|
|
/** 提交审核(批量或单个) */
|
|
async handleSubmitAudit(videoId) {
|
|
let id
|
|
|
|
if (videoId && typeof videoId !== 'object' && videoId !== '') {
|
|
id = videoId
|
|
} else if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else if (typeof videoId === 'object') {
|
|
if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else {
|
|
this.$message.warning('请先选择一个视频')
|
|
return
|
|
}
|
|
} else {
|
|
this.$message.warning('请选择需要提交审核的视频')
|
|
return
|
|
}
|
|
|
|
if (!id) {
|
|
this.$message.warning('请选择需要提交审核的视频')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await this.$confirm('是否确认提交审核?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
await submitForAudit(id)
|
|
this.$message.success('提交审核成功')
|
|
this.getList()
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
console.error('提交审核失败:', error)
|
|
this.$message.error(error.message || '提交审核失败')
|
|
}
|
|
}
|
|
},
|
|
|
|
/** 取消审核 */
|
|
async handleCancelAudit(videoId) {
|
|
const id = videoId || this.ids[0]
|
|
if (!id) {
|
|
this.$message.warning('请选择需要取消审核的视频')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await this.$confirm('确定要取消审核吗?取消后可以重新提交审核', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
await cancelAudit(id)
|
|
this.$message.success('取消审核成功')
|
|
this.getList()
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
console.error('取消审核失败:', error)
|
|
this.$message.error(error.message || '取消审核失败')
|
|
}
|
|
}
|
|
},
|
|
|
|
/** 审核(批量或单个) */
|
|
handleAuditDialog(videoId) {
|
|
let id
|
|
|
|
if (videoId && typeof videoId !== 'object' && videoId !== '') {
|
|
id = videoId
|
|
} else if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else if (typeof videoId === 'object') {
|
|
if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else {
|
|
this.$message.warning('请先选择一个视频')
|
|
return
|
|
}
|
|
} else {
|
|
this.$message.warning('请选择需要审核的视频')
|
|
return
|
|
}
|
|
|
|
if (!id) {
|
|
this.$message.warning('请选择需要审核的视频')
|
|
return
|
|
}
|
|
|
|
this.auditForm.id = id
|
|
this.auditForm.auditStatus = '1'
|
|
this.auditForm.auditOpinion = ''
|
|
this.auditOpen = true
|
|
},
|
|
|
|
/** 取消审核对话框 */
|
|
cancelAudit() {
|
|
this.auditOpen = false
|
|
this.resetAuditForm()
|
|
},
|
|
|
|
/** 重置审核表单 */
|
|
resetAuditForm() {
|
|
this.auditForm = {
|
|
id: null,
|
|
auditStatus: '1',
|
|
auditOpinion: ''
|
|
}
|
|
if (this.$refs.auditFormRef) {
|
|
this.$refs.auditFormRef.resetFields()
|
|
}
|
|
},
|
|
|
|
/** 提交审核(管理员审核) */
|
|
async submitAudit() {
|
|
try {
|
|
const valid = await this.$refs.auditFormRef.validate()
|
|
if (!valid) return
|
|
|
|
const message = this.auditForm.auditStatus === '2'
|
|
? '是否确认审核通过?'
|
|
: '是否确认审核拒绝?'
|
|
|
|
await this.$confirm(message, '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
// 方式1:传递对象参数
|
|
const response = await auditVideo({
|
|
id: this.auditForm.id,
|
|
auditStatus: this.auditForm.auditStatus,
|
|
auditOpinion: this.auditForm.auditOpinion || ''
|
|
})
|
|
|
|
// 或者方式2:如果不想改API函数,可以直接调用request
|
|
// const response = await request({
|
|
// url: `/vet/training/audit/${this.auditForm.id}`,
|
|
// method: 'post',
|
|
// data: {
|
|
// auditStatus: this.auditForm.auditStatus,
|
|
// auditOpinion: this.auditForm.auditOpinion || ''
|
|
// }
|
|
// })
|
|
|
|
if (response.code === 200) {
|
|
this.$message.success('审核成功')
|
|
this.auditOpen = false
|
|
this.getList()
|
|
this.resetAuditForm()
|
|
} else {
|
|
this.$message.error(response.msg || '审核失败')
|
|
}
|
|
|
|
} catch (error) {
|
|
if (error === 'cancel') return
|
|
console.error('审核错误:', error)
|
|
this.$message.error('审核失败,请检查参数')
|
|
}
|
|
},
|
|
|
|
/** ============ 上架/下架相关方法 ============ */
|
|
/** 上架视频(批量或单个) */
|
|
async handlePublish(videoId) {
|
|
let id
|
|
|
|
if (videoId && typeof videoId !== 'object' && videoId !== '') {
|
|
id = videoId
|
|
} else if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else if (typeof videoId === 'object') {
|
|
if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else {
|
|
this.$message.warning('请先选择一个视频')
|
|
return
|
|
}
|
|
} else {
|
|
this.$message.warning('请选择需要上架的视频')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await this.$confirm('是否确认上架视频?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
await publishVideo(id)
|
|
this.$message.success('上架成功')
|
|
this.getList()
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
console.error('上架失败:', error)
|
|
this.$message.error(error.message || '上架失败')
|
|
}
|
|
}
|
|
},
|
|
|
|
/** 下架视频(批量或单个) */
|
|
async handleOffline(videoId) {
|
|
let id
|
|
|
|
if (videoId && typeof videoId !== 'object' && videoId !== '') {
|
|
id = videoId
|
|
} else if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else if (typeof videoId === 'object') {
|
|
if (this.ids.length === 1) {
|
|
id = this.ids[0]
|
|
} else {
|
|
this.$message.warning('请先选择一个视频')
|
|
return
|
|
}
|
|
} else {
|
|
this.$message.warning('请选择需要下架的视频')
|
|
return
|
|
}
|
|
|
|
try {
|
|
await this.$confirm('是否确认下架视频?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
})
|
|
|
|
await offlineVideo(id)
|
|
this.$message.success('下架成功')
|
|
this.getList()
|
|
} catch (error) {
|
|
if (error !== 'cancel') {
|
|
console.error('下架失败:', error)
|
|
this.$message.error(error.message || '下架失败')
|
|
}
|
|
}
|
|
},
|
|
|
|
// handleResubmitAudit重新提交审核
|
|
handleResubmitAudit(row) {
|
|
const id = row.id || this.ids[0];
|
|
if (!id) {
|
|
this.$modal.msgWarning('请选择需要重新提交审核的视频');
|
|
return;
|
|
}
|
|
this.$confirm('是否确认重新提交审核?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(async () => {
|
|
try {
|
|
const response = await resubmitAudit(id);
|
|
if (response.code === 200) {
|
|
this.$modal.msgSuccess("重新提交审核成功");
|
|
this.getList();
|
|
} else {
|
|
this.$modal.msgError(response.msg || "重新提交审核失败");
|
|
}
|
|
} catch (error) {
|
|
console.error('重新提交审核失败:', error);
|
|
this.$modal.msgError('重新提交审核失败:' + (error.message || '网络异常'));
|
|
}
|
|
}).catch(() => {
|
|
});
|
|
},
|
|
|
|
/** ============ 其他方法 ============ */
|
|
/** 搜索按钮操作 */
|
|
handleQuery() {
|
|
this.queryParams.pageNum = 1
|
|
this.getList()
|
|
},
|
|
|
|
/** 重置按钮操作 */
|
|
resetQuery() {
|
|
this.resetForm("queryForm")
|
|
this.handleQuery()
|
|
},
|
|
|
|
// 多选框选中数据
|
|
handleSelectionChange(selection) {
|
|
this.ids = selection.map(item => item.id)
|
|
this.single = selection.length !== 1
|
|
this.multiple = !selection.length
|
|
},
|
|
|
|
/** 新增按钮操作 */
|
|
handleAdd() {
|
|
this.reset()
|
|
this.open = true
|
|
this.title = "添加兽医培训视频"
|
|
},
|
|
|
|
/** 修改按钮操作 */
|
|
handleUpdate(row) {
|
|
this.reset()
|
|
const id = row.id || this.ids
|
|
getTraining(id).then(response => {
|
|
const data = response.data || {}
|
|
this.form = {
|
|
id: data.id || undefined,
|
|
userId: data.userId || undefined,
|
|
title: data.title || "",
|
|
description: data.description || "",
|
|
videoUrl: data.videoUrl || "",
|
|
coverImage: data.coverImage || "",
|
|
category: data.category || "",
|
|
tags: data.tags || "",
|
|
duration: data.duration || undefined,
|
|
fileSize: data.fileSize || undefined,
|
|
viewCount: data.viewCount || undefined,
|
|
status: data.status || "0",
|
|
createTime: data.createTime || undefined,
|
|
updateTime: data.updateTime || undefined,
|
|
delFlag: data.delFlag || undefined,
|
|
auditStatus: data.auditStatus || "0",
|
|
auditOpinion: data.auditOpinion || "",
|
|
auditUserId: data.auditUserId || undefined,
|
|
auditTime: data.auditTime || undefined
|
|
}
|
|
this.open = true
|
|
this.title = "修改兽医培训视频"
|
|
}).catch(() => {
|
|
this.$modal.msgError("获取数据失败")
|
|
})
|
|
},
|
|
|
|
/** 提交按钮 */
|
|
submitForm() {
|
|
this.$refs["form"].validate(valid => {
|
|
if (valid) {
|
|
const submitData = {
|
|
...this.form,
|
|
title: (this.form.title || "").trim(),
|
|
category: (this.form.category || "").trim(),
|
|
videoUrl: this.form.videoUrl || "",
|
|
description: this.form.description || "",
|
|
status: this.form.status || "0",
|
|
auditStatus: this.form.auditStatus || "0"
|
|
}
|
|
|
|
if (submitData.id) {
|
|
updateTraining(submitData).then(response => {
|
|
this.$modal.msgSuccess("修改成功")
|
|
this.open = false
|
|
this.getList()
|
|
}).catch((error) => {
|
|
console.error('修改失败:', error)
|
|
this.$modal.msgError("修改失败: " + (error.message || "未知错误"))
|
|
})
|
|
} else {
|
|
addTraining(submitData).then(response => {
|
|
this.$modal.msgSuccess("新增成功")
|
|
this.open = false
|
|
this.getList()
|
|
}).catch((error) => {
|
|
console.error('新增失败:', error)
|
|
this.$modal.msgError("新增失败: " + (error.message || "未知错误"))
|
|
})
|
|
}
|
|
} else {
|
|
return false
|
|
}
|
|
})
|
|
},
|
|
|
|
/** 删除按钮操作 */
|
|
handleDelete(row) {
|
|
const ids = row.id || this.ids
|
|
this.$modal.confirm('是否确认删除兽医培训视频编号为"' + ids + '"的数据项?').then(() => {
|
|
return delTraining(ids)
|
|
}).then(() => {
|
|
this.getList()
|
|
this.$modal.msgSuccess("删除成功")
|
|
}).catch(() => {})
|
|
},
|
|
|
|
/** 导出按钮操作 */
|
|
handleExport() {
|
|
this.download('vet/training/export', {
|
|
...this.queryParams
|
|
}, `training_${new Date().getTime()}.xlsx`)
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
::v-deep .pagestyle .el-input{
|
|
width: auto !important;
|
|
}
|
|
</style>
|
|
|
|
<style lang="scss" scoped>
|
|
.video-url-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.url-text {
|
|
max-width: 100px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
// 操作按钮样式
|
|
.info-btn {
|
|
padding: 6px 10px;
|
|
border-radius: 4px;
|
|
margin: 0 10px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.view-btn:hover {
|
|
background-color: rgb(216, 238, 248);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.alter-btn:hover{
|
|
background-color: rgb(230, 255, 238);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.delete-btn:hover {
|
|
background-color: rgba(245, 108, 108, 0.1);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.submit-btn:hover {
|
|
background-color: rgb(253, 250, 232);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.publish-btn:hover {
|
|
background-color: rgb(253, 238, 228);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.offline-btn:hover {
|
|
background-color: rgb(237, 237, 235);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.cancel-btn:hover {
|
|
background-color: rgb(244, 237, 251);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.audit-btn:hover {
|
|
background-color: rgb(215, 223, 246);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.resubmit-btn:hover {
|
|
background-color: rgb(248, 232, 250);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
// 新增/修改的弹窗
|
|
::v-deep .el-dialog {
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.12);
|
|
animation: dialogFadeIn 0.3s ease;
|
|
}
|
|
|
|
::v-deep .el-dialog__header {
|
|
background: linear-gradient(135deg, #42b983 0%, #83df92 100%);
|
|
padding: 18px 24px;
|
|
border-bottom: none;
|
|
position: relative;
|
|
}
|
|
|
|
::v-deep .el-dialog__title {
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
color: white;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
::v-deep .el-dialog__headerbtn:hover .el-dialog__close {
|
|
color: #ffd04b;
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
::v-deep .el-dialog__body {
|
|
padding: 28px 24px 20px;
|
|
background-color: #f8fafc;
|
|
max-height: 70vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
::v-deep .el-form-item {
|
|
margin-bottom: 20px;
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
::v-deep .el-form-item__label {
|
|
font-weight: 500;
|
|
color: #2d3748;
|
|
font-size: 14px;
|
|
transition: color 0.3s;
|
|
}
|
|
|
|
::v-deep .el-input,
|
|
::v-deep .el-textarea,
|
|
::v-deep .el-select {
|
|
width: 100%;
|
|
}
|
|
|
|
::v-deep .el-input__inner,
|
|
::v-deep .el-textarea__inner {
|
|
border-radius: 8px;
|
|
border: 1px solid #dcdfe6;
|
|
font-size: 14px;
|
|
transition: all 0.3s;
|
|
background-color: #fcfdfe;
|
|
}
|
|
|
|
::v-deep .el-input__inner:focus,
|
|
::v-deep .el-textarea__inner:focus {
|
|
border-color: #42B983;
|
|
box-shadow: 0 0 0 3px rgb(230, 255, 238);
|
|
background-color: white;
|
|
}
|
|
|
|
::v-deep .el-select .el-input__inner {
|
|
padding-right: 35px;
|
|
}
|
|
|
|
::v-deep .el-dialog__footer {
|
|
padding: 20px 24px;
|
|
background-color: #f8fafc;
|
|
border-top: 1px solid #eef2f7;
|
|
border-radius: 0 0 12px 12px;
|
|
}
|
|
</style>
|