|
|
@@ -6,19 +6,13 @@
|
|
|
<i class="el-icon-collection" /> 稿件管理
|
|
|
</h3>
|
|
|
<div class="filter-controls">
|
|
|
- <el-select
|
|
|
- v-model="queryInfo.scope"
|
|
|
- clearable
|
|
|
- placeholder="可见范围筛选"
|
|
|
+ <el-button
|
|
|
+ type="success"
|
|
|
+ icon="el-icon-refresh"
|
|
|
size="small"
|
|
|
- class="filter-select"
|
|
|
- @change="handleFilterChange"
|
|
|
- >
|
|
|
- <el-option label="全部范围" :value="null" />
|
|
|
- <el-option label="本人可见" :value="1" />
|
|
|
- <el-option label="所有人可见" :value="2" />
|
|
|
- <el-option label="VIP 可见" :value="3" />
|
|
|
- </el-select>
|
|
|
+ class="publish-btn"
|
|
|
+ @click="handleFilterChange"
|
|
|
+ >刷新</el-button>
|
|
|
<el-button
|
|
|
type="primary"
|
|
|
icon="el-icon-plus"
|
|
|
@@ -40,18 +34,35 @@
|
|
|
>
|
|
|
<el-table-column label="No" type="index" width="60" align="center" />
|
|
|
|
|
|
- <el-table-column label="封面" width="120" align="center">
|
|
|
+ <el-table-column label="封面" width="140" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
- <el-image
|
|
|
- class="video-cover"
|
|
|
- :src="scope.row.coverUrl"
|
|
|
- fit="cover"
|
|
|
- :preview-src-list="[scope.row.coverUrl]"
|
|
|
+ <el-popover
|
|
|
+ placement="right"
|
|
|
+ trigger="hover"
|
|
|
+ popper-class="cover-popover-panel"
|
|
|
+ :open-delay="200"
|
|
|
>
|
|
|
- <div slot="error" class="image-slot">
|
|
|
- <i class="el-icon-picture-outline" />
|
|
|
+ <div class="popover-big-cover">
|
|
|
+ <img :src="scope.row.coverUrl" class="big-target-img">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div slot="reference" class="table-cover-wrapper" @click="handleVideoCover(scope.row)">
|
|
|
+ <el-image
|
|
|
+ class="video-cover"
|
|
|
+ :src="scope.row.coverUrl"
|
|
|
+ fit="cover"
|
|
|
+ >
|
|
|
+ <div slot="error" class="image-slot">
|
|
|
+ <i class="el-icon-picture-outline" />
|
|
|
+ </div>
|
|
|
+ </el-image>
|
|
|
+
|
|
|
+ <div class="cover-action-mask">
|
|
|
+ <i class="el-icon-edit" />
|
|
|
+ <span>修改封面</span>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
- </el-image>
|
|
|
+ </el-popover>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
@@ -85,16 +96,29 @@
|
|
|
|
|
|
<el-table-column label="稿件状态" width="120" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
- <span :class="['status-dot', getStatusClass(scope.row.status)]">
|
|
|
+ <el-tag
|
|
|
+ :type="getStatusClass(scope.row.status)"
|
|
|
+ size="medium"
|
|
|
+ effect="light"
|
|
|
+ >
|
|
|
{{ getStatusText(scope.row.status) }}
|
|
|
- </span>
|
|
|
+ </el-tag>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
|
|
|
- <el-table-column label="操作" width="220" fixed="right" align="center">
|
|
|
+ <el-table-column label="操作" fixed="right" align="center">
|
|
|
<template slot-scope="scope">
|
|
|
- <el-button type="text" size="small" icon="el-icon-view" @click="handlePreview(scope.$index, scope.row)">预览</el-button>
|
|
|
- <el-button type="text" size="small" icon="el-icon-edit" @click="handleEdit(scope.row)">编辑</el-button>
|
|
|
+ <el-button type="text" size="small" icon="el-icon-video-play" @click="handlePreview(scope.$index, scope.row)">预览</el-button>
|
|
|
+ <el-button type="text" size="small" icon="el-icon-upload" @click="handleVideoFile(scope.row)">重传</el-button>
|
|
|
+ <el-button
|
|
|
+ type="text"
|
|
|
+ size="small"
|
|
|
+ :icon="scope.row.status === 1 ? 'el-icon-circle-check' : 'el-icon-edit'"
|
|
|
+ :class="scope.row.status === 1 ? 'success-text' : ''"
|
|
|
+ @click="handleEdit(scope.row)"
|
|
|
+ >
|
|
|
+ {{ scope.row.status === 1 ? '发布' : '编辑' }}
|
|
|
+ </el-button>
|
|
|
<el-button type="text" size="small" icon="el-icon-delete" class="danger-text" @click="handleDelete(scope.$index, scope.row)">删除</el-button>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
@@ -123,16 +147,82 @@
|
|
|
@close="editingVideo = null"
|
|
|
>
|
|
|
<div slot="title" class="dialog-header-custom">
|
|
|
- <i :class="editingVideo ? 'el-icon-edit' : 'el-icon-circle-plus-outline'" />
|
|
|
- <span>{{ editingVideo ? '编辑视频稿件' : '发布新视频稿件' }}</span>
|
|
|
+ <i class="el-icon-circle-plus-outline" />
|
|
|
+ <span>发布新视频稿件</span>
|
|
|
</div>
|
|
|
-
|
|
|
<video-post-publish
|
|
|
- :video-info="editingVideo"
|
|
|
+ :video-info="null"
|
|
|
@video-publish="onVideoPublish"
|
|
|
/>
|
|
|
</el-dialog>
|
|
|
|
|
|
+ <el-dialog
|
|
|
+ :visible.sync="coverDialogVisible"
|
|
|
+ width="600px"
|
|
|
+ custom-class="custom-glass-dialog"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ @close="currentSelectVideo = null"
|
|
|
+ >
|
|
|
+ <div slot="title" class="dialog-header-custom">
|
|
|
+ <i class="el-icon-picture" /><span>更新视频封面</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="currentSelectVideo" class="dialog-body-padding">
|
|
|
+ <step-upload-cover
|
|
|
+ v-model="editForm"
|
|
|
+ :cover-url.sync="currentSelectVideo.coverUrl"
|
|
|
+ :is-edit="true"
|
|
|
+ :is-single="true"
|
|
|
+ @update:coverUrl="onSingleUpdateSuccess('封面修改成功')"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog
|
|
|
+ :visible.sync="videoFileDialogVisible"
|
|
|
+ width="600px"
|
|
|
+ custom-class="custom-glass-dialog"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ @close="currentSelectVideo = null"
|
|
|
+ >
|
|
|
+ <div slot="title" class="dialog-header-custom">
|
|
|
+ <i class="el-icon-upload" /><span>重新上传视频文件</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="currentSelectVideo" class="dialog-body-padding">
|
|
|
+ <step-upload-video
|
|
|
+ v-model="editForm"
|
|
|
+ :is-edit="true"
|
|
|
+ :is-single="true"
|
|
|
+ @success="onSingleUpdateSuccess('视频重传成功,正在重新转码')"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
+ <el-dialog
|
|
|
+ :visible.sync="infoEditDialogVisible"
|
|
|
+ width="700px"
|
|
|
+ custom-class="custom-glass-dialog"
|
|
|
+ append-to-body
|
|
|
+ destroy-on-close
|
|
|
+ @close="currentSelectVideo = null"
|
|
|
+ >
|
|
|
+ <div slot="title" class="dialog-header-custom">
|
|
|
+ <i class="el-icon-edit" />
|
|
|
+ <span>{{ currentSelectVideo && currentSelectVideo.status === 1 ? '完善并发布稿件信息' : '修改稿件基本信息' }}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="currentSelectVideo" class="dialog-body-padding">
|
|
|
+ <step-form-info
|
|
|
+ v-model="editForm"
|
|
|
+ :is-edit="currentSelectVideo.status !== 1"
|
|
|
+ :is-single="true"
|
|
|
+ :submitting="submitting"
|
|
|
+ @prev="infoEditDialogVisible = false"
|
|
|
+ @submit="onSingleInfoSubmit"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-dialog>
|
|
|
+
|
|
|
<el-dialog :visible.sync="showEditScopeDialog" width="400px" custom-class="custom-glass-dialog compact-dialog" append-to-body center>
|
|
|
<div slot="title" class="dialog-header-custom">
|
|
|
<i class="el-icon-lock" /><span>权限设置</span>
|
|
|
@@ -165,18 +255,25 @@
|
|
|
<script>
|
|
|
import VideoPreviewPlayer from 'components/VideoPreviewPlayer'
|
|
|
import VideoPostPublish from '@/views/post/VideoPostPublish'
|
|
|
+import StepUploadVideo from '@/components/StepUploadVideo.vue'
|
|
|
+import StepUploadCover from '@/components/StepUploadCover.vue'
|
|
|
+import StepFormInfo from '@/components/StepFormInfo.vue'
|
|
|
+
|
|
|
import { updateVideoScope, deleteVideoPost, getVideoPosts, addVideoPost, updateVideoInfo, getVideoPost } from '@/api/vod'
|
|
|
|
|
|
export default {
|
|
|
name: 'VideoPost',
|
|
|
- components: { VideoPreviewPlayer, VideoPostPublish },
|
|
|
+ components: {
|
|
|
+ VideoPreviewPlayer,
|
|
|
+ VideoPostPublish,
|
|
|
+ StepUploadVideo,
|
|
|
+ StepUploadCover,
|
|
|
+ StepFormInfo
|
|
|
+ },
|
|
|
data() {
|
|
|
return {
|
|
|
loading: false,
|
|
|
- queryInfo: {
|
|
|
- scope: null, // 从 URL 获取
|
|
|
- pn: 1 // 从 URL 获取
|
|
|
- },
|
|
|
+ queryInfo: { scope: null, pn: 1 },
|
|
|
screenWidth: document.body.clientWidth,
|
|
|
pageSize: 12,
|
|
|
totalSize: 0,
|
|
|
@@ -185,80 +282,181 @@ export default {
|
|
|
showEditScopeDialog: false,
|
|
|
showPreviewDialog: false,
|
|
|
form: { videoId: null, scope: 1 },
|
|
|
- publishVideoDiaglog: false,
|
|
|
- editingVideo: null
|
|
|
+
|
|
|
+ // 弹窗状态控制定义
|
|
|
+ publishVideoDiaglog: false, // 纯净发布弹窗
|
|
|
+ coverDialogVisible: false, // 独立修改封面弹窗
|
|
|
+ videoFileDialogVisible: false, // 独立重传视频弹窗
|
|
|
+ infoEditDialogVisible: false, // 独立修改信息弹窗
|
|
|
+
|
|
|
+ editingVideo: null,
|
|
|
+ currentSelectVideo: null, // 当前操作的表格行原始行数据
|
|
|
+ submitting: false,
|
|
|
+
|
|
|
+ // 核心封装:提供给独立的编辑子组件双向绑定的临时表单
|
|
|
+ editForm: {
|
|
|
+ videoId: null,
|
|
|
+ coverFileId: null,
|
|
|
+ title: '',
|
|
|
+ description: '',
|
|
|
+ categoryPid: null,
|
|
|
+ categoryId: null,
|
|
|
+ tags: [],
|
|
|
+ scope: '2'
|
|
|
+ }
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
- // 映射当前页码给 el-pagination 使用
|
|
|
- currentPage() {
|
|
|
- return parseInt(this.queryInfo.pn) || 1
|
|
|
- }
|
|
|
+ currentPage() { return parseInt(this.queryInfo.pn) || 1 }
|
|
|
},
|
|
|
watch: {
|
|
|
- // 核心优化:监听路由变化,一旦 URL 参数变了,就重新抓取数据
|
|
|
- '$route': {
|
|
|
- handler: 'syncParamsAndLoad',
|
|
|
- immediate: true
|
|
|
- }
|
|
|
+ '$route': { handler: 'syncParamsAndLoad', immediate: true }
|
|
|
},
|
|
|
methods: {
|
|
|
- // 1. 同步 URL 参数到 data,并执行请求
|
|
|
syncParamsAndLoad() {
|
|
|
const { pn, scope } = this.$route.query
|
|
|
this.queryInfo.pn = pn ? parseInt(pn) : 1
|
|
|
this.queryInfo.scope = scope ? (isNaN(scope) ? scope : parseInt(scope)) : null
|
|
|
this.getData()
|
|
|
},
|
|
|
-
|
|
|
- // 2. 更新 URL 的通用方法
|
|
|
updateRouter() {
|
|
|
this.$router.push({
|
|
|
path: this.$route.path,
|
|
|
- query: {
|
|
|
- pn: this.queryInfo.pn,
|
|
|
- scope: this.queryInfo.scope || undefined // 为 null 时不显示在 URL 中
|
|
|
- }
|
|
|
- }).catch(err => {
|
|
|
- // 捕获冗余导航错误(点击同一页时)
|
|
|
- if (err.name !== 'NavigationDuplicated') throw err
|
|
|
- })
|
|
|
+ query: { pn: this.queryInfo.pn, scope: this.queryInfo.scope || undefined }
|
|
|
+ }).catch(err => { if (err.name !== 'NavigationDuplicated') throw err })
|
|
|
},
|
|
|
-
|
|
|
- // 3. 交互触发:切换分页
|
|
|
handlePageChange(pageNumber) {
|
|
|
this.queryInfo.pn = pageNumber
|
|
|
this.updateRouter()
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
|
},
|
|
|
-
|
|
|
- // 4. 交互触发:切换筛选
|
|
|
handleFilterChange() {
|
|
|
- this.queryInfo.pn = 1 // 筛选条件变了,重置回第一页
|
|
|
- this.updateRouter()
|
|
|
+ this.queryInfo.pn = 1
|
|
|
+ this.getData()
|
|
|
},
|
|
|
-
|
|
|
getData() {
|
|
|
this.loading = true
|
|
|
- // 注意:这里建议给后端接口也加上 scope 参数,此处假设你原有接口支持
|
|
|
getVideoPosts(this.queryInfo).then(resp => {
|
|
|
if (resp.code === 0) {
|
|
|
this.dataList = resp.data.list
|
|
|
this.totalSize = resp.data.totalSize
|
|
|
}
|
|
|
- }).finally(() => {
|
|
|
- this.loading = false
|
|
|
- })
|
|
|
+ }).finally(() => { this.loading = false })
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化编辑表单数据映射器(深拷贝隔离引用)
|
|
|
+ prepareEditForm(row) {
|
|
|
+ this.currentSelectVideo = row
|
|
|
+ this.editForm = {
|
|
|
+ videoId: row.videoId,
|
|
|
+ coverFileId: row.coverFileId || null,
|
|
|
+ title: row.title || '',
|
|
|
+ description: row.description || '',
|
|
|
+ categoryPid: row.categoryPid || null,
|
|
|
+ categoryId: row.categoryId || null,
|
|
|
+ tags: Array.isArray(row.tags) ? row.tags : (row.tags ? row.tags.split(',') : []),
|
|
|
+ scope: String(row.scope || '2')
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 交互一:点击表格封面 -> 弹出修改封面
|
|
|
+ handleVideoCover(row) {
|
|
|
+ this.prepareEditForm(row)
|
|
|
+ this.coverDialogVisible = true
|
|
|
+ },
|
|
|
+
|
|
|
+ // 交互二:点击操作列中的“重传” -> 弹出视频上传
|
|
|
+ handleVideoFile(row) {
|
|
|
+ this.prepareEditForm(row)
|
|
|
+ this.videoFileDialogVisible = true
|
|
|
+ },
|
|
|
+
|
|
|
+ // 交互三:点击操作列中的“编辑” -> 弹出信息表单组件
|
|
|
+ handleEdit(row) {
|
|
|
+ this.prepareEditForm(row)
|
|
|
+ this.infoEditDialogVisible = true
|
|
|
},
|
|
|
|
|
|
- /* --- 其余 UI 逻辑保持不变 --- */
|
|
|
+ // 封面和视频更新成功后的公共回调(因为底层组件内部已调用过 updateVideoCover/updateVideoFile 接口)
|
|
|
+ onSingleUpdateSuccess(msg) {
|
|
|
+ this.$message.success(msg)
|
|
|
+ this.coverDialogVisible = false
|
|
|
+ this.videoFileDialogVisible = false
|
|
|
+ this.getData() // 刷新列表
|
|
|
+ },
|
|
|
+
|
|
|
+ // 信息修改的独立提交保存逻辑
|
|
|
+ onSingleInfoSubmit() {
|
|
|
+ this.submitting = true
|
|
|
+ // 核心判断:根据当前操作的视频状态来决定行为
|
|
|
+ const isDraftMode = this.currentSelectVideo.status === 1
|
|
|
+ if (isDraftMode) {
|
|
|
+ // 场景 A:文件和封面都有了,属于第一次正式“提交发布”
|
|
|
+ addVideoPost(this.editForm).then(res => {
|
|
|
+ if (res.code === 0) {
|
|
|
+ this.$message.success('稿件信息提交成功,已成功发布!')
|
|
|
+ this.infoEditDialogVisible = false
|
|
|
+ this.getData() // 刷新列表
|
|
|
+ } else {
|
|
|
+ this.$message.warning(res.msg)
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error(error.message)
|
|
|
+ }).finally(() => { this.submitting = false })
|
|
|
+ } else {
|
|
|
+ // 场景 B:本身就是线上已有的稿件,属于正常的“修改更新”
|
|
|
+ updateVideoInfo(this.editForm).then(res => {
|
|
|
+ if (res.code === 0) {
|
|
|
+ this.$message.success('稿件基本信息更新成功!')
|
|
|
+ this.infoEditDialogVisible = false
|
|
|
+ this.getData()
|
|
|
+ } else {
|
|
|
+ this.$message.warning(res.msg)
|
|
|
+ }
|
|
|
+ }).catch(error => {
|
|
|
+ this.$message.error(error.message)
|
|
|
+ }).finally(() => { this.submitting = false })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 原发布的控制保持不动
|
|
|
+ handlePost() {
|
|
|
+ this.editingVideo = null
|
|
|
+ this.publishVideoDiaglog = true
|
|
|
+ },
|
|
|
+ onVideoPublish({ data, mode }) {
|
|
|
+ if (mode === 'publish') {
|
|
|
+ addVideoPost(data).then(res => {
|
|
|
+ if (res.code === 0) {
|
|
|
+ this.publishVideoDiaglog = false
|
|
|
+ this.$message.info('投稿成功,请等待审核')
|
|
|
+ this.getData()
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /* --- 其余基础状态逻辑保持不变 --- */
|
|
|
getStatusClass(status) {
|
|
|
- const map = { 1: 'info', 2: 'warning', 3: 'success', 4: 'danger', 5: 'info' }
|
|
|
+ const map = {
|
|
|
+ 1: 'info', // 待发布:灰色(草稿或未开始状态,保持低调)
|
|
|
+ 2: 'warning', // 待审核:黄色(进行中,提示用户需要等待或平台正在处理)
|
|
|
+ 3: 'success', // 已发布:绿色(终态,最健康的成功颜色)
|
|
|
+ 4: 'danger', // 审核未通过:红色(高警示色,提醒创作者需要修改稿件)
|
|
|
+ 5: 'info' // 下架:灰色(已失效状态,冷色调处理)
|
|
|
+ }
|
|
|
return map[status] || 'info'
|
|
|
},
|
|
|
+
|
|
|
getStatusText(status) {
|
|
|
- const map = { 1: '待发布', 2: '待审核', 3: '已发布', 4: '审核未通过', 5: '下架' }
|
|
|
- return map[status] || `未知(${status})`
|
|
|
+ const map = {
|
|
|
+ 1: '待发布',
|
|
|
+ 2: '审核中',
|
|
|
+ 3: '已发布',
|
|
|
+ 4: '审核未通过',
|
|
|
+ 5: '已下架'
|
|
|
+ }
|
|
|
+ return map[status] || `未知状态(${status})`
|
|
|
},
|
|
|
handleScope(index, row) {
|
|
|
this.form.videoId = row.videoId
|
|
|
@@ -277,22 +475,12 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
- handleEdit(row) {
|
|
|
- this.editingVideo = row
|
|
|
- this.publishVideoDiaglog = true
|
|
|
- },
|
|
|
- handlePost() {
|
|
|
- this.editingVideo = null
|
|
|
- this.publishVideoDiaglog = true
|
|
|
- },
|
|
|
handleDelete(index, row) {
|
|
|
this.$confirm(`确定要删除《${row.title}》?`, '警告', {
|
|
|
type: 'warning',
|
|
|
confirmButtonClass: 'el-button--danger'
|
|
|
}).then(() => {
|
|
|
- const payload = {}
|
|
|
- payload.videoId = row.videoId
|
|
|
- deleteVideoPost(payload).then(res => {
|
|
|
+ deleteVideoPost({ videoId: row.videoId }).then(res => {
|
|
|
if (res.code === 0) {
|
|
|
this.$message.info('稿件已删除')
|
|
|
this.getData()
|
|
|
@@ -310,37 +498,13 @@ export default {
|
|
|
this.$message.info('可见范围已更新')
|
|
|
}
|
|
|
})
|
|
|
- },
|
|
|
- onVideoPublish({ data, mode }) {
|
|
|
- const videoForm = data
|
|
|
- if (mode === 'edit') {
|
|
|
- updateVideoInfo(videoForm).then(res => {
|
|
|
- this.$message.info(res.msg)
|
|
|
- }).catch(error => {
|
|
|
- this.$message.error(error.message)
|
|
|
- })
|
|
|
- } else {
|
|
|
- addVideoPost(videoForm).then(res => {
|
|
|
- if (res.code === 0) {
|
|
|
- this.publishVideoDiaglog = false
|
|
|
- this.$message.info('投稿成功,请等待审核')
|
|
|
- this.getData()
|
|
|
- }
|
|
|
- })
|
|
|
- }
|
|
|
- this.finishPublish()
|
|
|
- },
|
|
|
- finishPublish() {
|
|
|
- this.publishVideoDiaglog = false
|
|
|
- this.editingVideo = null
|
|
|
- this.getData()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
-<style scoped>
|
|
|
-/* 样式部分同上一版,保持“三层”视觉感 */
|
|
|
+<style scoped lang="scss">
|
|
|
+/* 保留你原有全部美化过的高级视觉 SCSS 样式 */
|
|
|
.video-post-container { padding: 24px; background-color: #f5f7fa; min-height: 100%; }
|
|
|
.post-header { background: white; padding: 20px 24px; border-radius: 12px 12px 0 0; box-shadow: 0 4px 12px 0 rgba(0,0,0,0.03); }
|
|
|
.header-wrapper { display: flex; justify-content: space-between; align-items: center; }
|
|
|
@@ -349,26 +513,37 @@ export default {
|
|
|
.publish-btn { box-shadow: 0 4px 10px rgba(64, 158, 255, 0.3); }
|
|
|
.post-main { padding: 0; }
|
|
|
.table-card { background: white; border-radius: 0 0 12px 12px; padding: 10px 24px 24px; box-shadow: 0 8px 24px 0 rgba(0,0,0,0.04); min-height: 400px; }
|
|
|
-.video-cover { width: 90px; height: 50px; border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.12); transition: transform 0.3s; }
|
|
|
-.video-cover:hover { transform: scale(1.08); }
|
|
|
-.video-title-link { color: #303133; text-decoration: none; font-weight: 600; font-size: 14px; }
|
|
|
-.video-title-link:hover { color: #409EFF; }
|
|
|
+.video-title-link { color: #303133; text-decoration: none; font-weight: 600; font-size: 14px; &:hover { color: #409EFF; } }
|
|
|
.video-meta { margin-top: 6px; font-size: 12px; color: #909399; }
|
|
|
.vid-tag { background: #f0f2f5; padding: 2px 6px; border-radius: 4px; margin-right: 10px; }
|
|
|
-.status-dot { position: relative; padding-left: 16px; font-size: 13px; color: #606266; }
|
|
|
-.status-dot::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); width: 7px; height: 7px; border-radius: 50%; }
|
|
|
-.status-dot.success::before { background-color: #67C23A; box-shadow: 0 0 6px #67C23A; }
|
|
|
-.status-dot.warning::before { background-color: #E6A23C; box-shadow: 0 0 6px #E6A23C; }
|
|
|
-.status-dot.danger::before { background-color: #F56C6C; box-shadow: 0 0 6px #F56C6C; }
|
|
|
.danger-text { color: #F56C6C; }
|
|
|
.pagination-container { margin-top: 30px; display: flex; justify-content: center; }
|
|
|
|
|
|
-::v-deep .custom-glass-dialog { border-radius: 16px !important; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.15) !important; }
|
|
|
-::v-deep .custom-glass-dialog .el-dialog__header { padding: 20px 24px; background: #fcfcfd; border-bottom: 1px solid #f0f0f0; }
|
|
|
-.dialog-header-custom { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: #303133; }
|
|
|
-.dialog-header-custom i { color: #409EFF; font-size: 20px; }
|
|
|
-::v-deep .custom-glass-dialog .el-dialog__body { padding: 24px; }
|
|
|
+/* 弹窗样式 */
|
|
|
+::v-deep .custom-glass-dialog { border-radius: 16px !important; overflow: hidden; box-shadow: 0 20px 50px rgba(0,0,0,0.15) !important; .el-dialog__header { padding: 20px 24px; background: #fcfcfd; border-bottom: 1px solid #f0f0f0; } .el-dialog__body { padding: 24px; } }
|
|
|
+.dialog-header-custom { display: flex; align-items: center; gap: 10px; font-size: 18px; font-weight: 600; color: #303133; i { color: #409EFF; font-size: 20px; } }
|
|
|
+.dialog-body-padding { padding: 8px 4px; }
|
|
|
.input-label-tip { font-size: 13px; color: #909399; margin-bottom: 12px; }
|
|
|
.preview-wrapper { background: #1a1a1a; border-radius: 12px; overflow: hidden; line-height: 0; }
|
|
|
::v-deep .table-header-cell { background-color: #f8f9fb !important; color: #303133; font-weight: 600; padding: 12px 0; }
|
|
|
+
|
|
|
+/* 封面组件特效 */
|
|
|
+.table-cover-wrapper {
|
|
|
+ position: relative; width: 100px; height: 62px; border-radius: 6px; overflow: hidden; cursor: pointer; border: 1px solid #f0f2f5; display: inline-block; vertical-align: middle;
|
|
|
+ .video-cover { width: 100%; height: 100%; transition: transform 0.3s ease; }
|
|
|
+ .cover-action-mask { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(29, 33, 41, 0.65); color: #ffffff; opacity: 0; display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 4px; font-size: 11px; transition: opacity 0.25s ease-in-out; i { font-size: 14px; } }
|
|
|
+ &:hover { .video-cover { transform: scale(1.06); } .cover-action-mask { opacity: 1; } }
|
|
|
+ .image-slot { display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background: #f5f7fa; color: #909399; font-size: 18px; }
|
|
|
+}
|
|
|
+.popover-big-cover { width: 320px; height: 180px; overflow: hidden; border-radius: 8px; background: #f5f7fa; .big-target-img { width: 100%; height: 100%; object-fit: cover; } }
|
|
|
+
|
|
|
+.success-text {
|
|
|
+ color: #67C23A;
|
|
|
+}
|
|
|
+.success-text:hover, .success-text:focus {
|
|
|
+ color: #85ce61;
|
|
|
+}
|
|
|
+.danger-text {
|
|
|
+ color: #F56C6C;
|
|
|
+}
|
|
|
</style>
|