Files
admin-ui/src/views/ads/compliance/tencent/index.vue

970 lines
25 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="ads-compliance-tencent">
<el-card shadow="hover" class="main-card">
<!-- Tabs -->
<el-tabs v-model="activeTab" class="main-tabs">
<el-tab-pane label="图片素材" name="image">
<!-- 图片素材内容区域 -->
<div class="tab-content">
<!-- 工具栏区域 -->
<div class="toolbar">
<!-- 左侧筛选表单 -->
<el-form :model="imageFilters" :inline="true" class="search-form">
<el-form-item label="状态">
<el-select v-model="imageFilters.status" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value=""></el-option>
<el-option label="待校验" value="PENDING"></el-option>
<el-option label="送检中" value="SUBMITTING"></el-option>
<el-option label="校验通过" value="VERIFIED"></el-option>
<el-option label="校验不通过" value="REJECTED"></el-option>
</el-select>
</el-form-item>
<el-form-item label="账户">
<el-select v-model="imageFilters.accountId" placeholder="请选择账户" style="width: 220px" clearable>
<el-option
v-for="account in accountList"
:key="account.accountId"
:label="`${account.accountId} - ${account.corporationName}`"
:value="String(account.accountId)"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchImage">搜索</el-button>
<el-button @click="resetImageFilter">重置</el-button>
</el-form-item>
</el-form>
<!-- 右侧操作按钮 -->
<div class="action-buttons">
<!-- <el-button type="success" @click="batchVerifyImage" :loading="batchLoading"> 批量校验图片 </el-button> -->
<!-- <el-button type="primary" plain @click="pollImageResults" :loading="pollLoading">
<el-icon><Refresh /></el-icon> 刷新检测结果
</el-button> -->
<el-button type="warning" plain @click="exportImageUrls">
<el-icon><Download /></el-icon> 导出失败素材
</el-button>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table :data="imageList" border style="width: 100%" v-loading="imageLoading">
<el-table-column prop="accountId" label="账户ID" width="120"></el-table-column>
<el-table-column prop="imageUsage" label="用途" width="120"></el-table-column>
<el-table-column label="预览" width="180">
<template #default="scope">
<img
v-if="scope.row.previewUrl"
:src="scope.row.previewUrl"
class="media-preview"
@click="previewMedia(scope.row.previewUrl, 'image')"
/>
</template>
</el-table-column>
<el-table-column prop="verifyStatus" label="校验状态" width="120">
<template #default="scope">
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
{{ getStatusText(scope.row.verifyStatus) }}
</span>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200"></el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="scope">
<el-button size="small" type="text" @click="viewLog(scope.row)">查看日志</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="pagination-container">
<el-pagination
@current-change="handleImagePageChange"
v-model:current-page="imagePage"
v-model:page-size="imagePageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="imageTotal"
background
>
</el-pagination>
</div>
</div>
</el-tab-pane>
<el-tab-pane label="视频素材" name="video">
<!-- 视频素材内容区域 -->
<div class="tab-content">
<!-- 工具栏区域 -->
<div class="toolbar">
<!-- 左侧筛选表单 -->
<el-form :model="videoFilters" :inline="true" class="search-form">
<el-form-item label="状态">
<el-select v-model="videoFilters.status" placeholder="全部" clearable style="width: 120px">
<el-option label="全部" value=""></el-option>
<el-option label="待校验" value="PENDING"></el-option>
<el-option label="送检中" value="SUBMITTING"></el-option>
<el-option label="校验通过" value="VERIFIED"></el-option>
<el-option label="校验不通过" value="REJECTED"></el-option>
</el-select>
</el-form-item>
<el-form-item label="账户">
<el-select v-model="videoFilters.accountId" placeholder="请选择账户" style="width: 220px" clearable>
<el-option
v-for="account in accountList"
:key="account.accountId"
:label="`${account.accountId} - ${account.corporationName}`"
:value="String(account.accountId)"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="searchVideo">搜索</el-button>
<el-button @click="resetVideoFilter">重置</el-button>
</el-form-item>
</el-form>
<!-- 右侧操作按钮 -->
<div class="action-buttons">
<!-- <el-button type="success" @click="batchVerifyVideo" :loading="batchLoading"> 批量校验视频 </el-button>
<el-button type="primary" plain @click="pollVideoResults" :loading="pollLoading">
<el-icon><Refresh /></el-icon> 刷新检测结果
</el-button> -->
<el-button type="warning" plain @click="exportVideoUrls">
<el-icon><Download /></el-icon> 导出失败素材
</el-button>
</div>
</div>
<!-- 表格区域 -->
<div class="table-wrapper">
<el-table :data="videoList" border style="width: 100%" v-loading="videoLoading">
<el-table-column prop="accountId" label="账户ID" width="120"></el-table-column>
<el-table-column label="预览" width="220">
<template #default="scope">
<div class="video-preview" @click="previewMedia(scope.row.previewUrl, 'video')">
<video
v-if="scope.row.previewUrl"
:src="scope.row.previewUrl"
class="video-thumbnail"
poster="data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw=="
preload="metadata"
@loadedmetadata="(e) => ((e.target as HTMLVideoElement).currentTime = 0)"
></video>
<el-icon v-else><VideoPlay style="font-size: 32px" /></el-icon>
<div class="play-overlay">
<el-icon><VideoPlay style="font-size: 24px" /></el-icon>
</div>
</div>
</template>
</el-table-column>
<el-table-column prop="verifyStatus" label="校验状态" width="120">
<template #default="scope">
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
{{ getStatusText(scope.row.verifyStatus) }}
</span>
</template>
</el-table-column>
<el-table-column prop="description" label="描述" min-width="200"></el-table-column>
<el-table-column label="操作" width="160" fixed="right">
<template #default="scope">
<el-button size="small" type="text" @click="viewLog(scope.row)">查看日志</el-button>
</template>
</el-table-column>
</el-table>
</div>
<!-- 分页区域 -->
<div class="pagination-container">
<el-pagination
@current-change="handleVideoPageChange"
v-model:current-page="videoPage"
v-model:page-size="videoPageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="videoTotal"
background
>
</el-pagination>
</div>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
<!-- 预览对话框 -->
<el-dialog title="媒体预览" v-model="previewVisible" width="60%" class="preview-dialog">
<div class="preview-content">
<img v-if="previewType === 'image'" :src="previewUrl" />
<video v-if="previewType === 'video'" :src="previewUrl" controls></video>
</div>
</el-dialog>
<!-- 日志对话框 -->
<el-dialog title="校验日志" v-model="logVisible" width="70%">
<el-table :data="currentLogList" border style="width: 100%">
<el-table-column prop="time" label="时间" width="180"></el-table-column>
<el-table-column prop="type" label="类型" width="120">
<template #default="scope">
<span :class="'table-status status-' + (scope.row.type || 'info').toLowerCase()">
{{ getLogTypeText(scope.row.type) }}
</span>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="120">
<template #default="scope">
<span :class="'table-status status-' + (scope.row.status || 'info').toLowerCase()">
{{ getStatusText(scope.row.status) }}
</span>
</template>
</el-table-column>
<el-table-column prop="suggestion" label="建议" width="120">
<template #default="scope">
<span :class="'table-status status-' + getSuggestionClass(scope.row.suggestion)">
{{ getSuggestionText(scope.row.suggestion) }}
</span>
</template>
</el-table-column>
<el-table-column prop="errorMsg" label="错误信息" min-width="200"></el-table-column>
<el-table-column prop="message" label="日志内容" min-width="200"></el-table-column>
</el-table>
<div v-if="!currentLogList.length" style="text-align: center; color: #909399; padding: 40px">暂无日志记录</div>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { Download, VideoPlay } from '@element-plus/icons-vue';
import {
getImageStats,
getVideoStats,
getImageList,
getVideoList,
getVerifyLogList,
exportRejectedMaterials,
getAccountList,
// manualVerifyImage,
// manualVerifyVideo,
// pollImageResults as pollImageResultsApi,
// pollVideoResults as pollVideoResultsApi,
// batchVerifyImage as batchVerifyImageApi,
// batchVerifyVideo as batchVerifyVideoApi,
} from '/@/api/ads/compliance/tencent/materialVerify';
// 响应式状态
const activeTab = ref('image');
// const pollLoading = ref(false);
// const batchLoading = ref(false);
// 账户列表
const accountList = ref<any[]>([]);
// 图片统计
const imageStats = reactive({ pending: 0, verified: 0, rejected: 0 });
// 视频统计
const videoStats = reactive({ pending: 0, verified: 0, rejected: 0 });
// 图片列表
const imageList = ref<any[]>([]);
const imageLoading = ref(false);
const imagePage = ref(1);
const imagePageSize = ref(20);
const imageTotal = ref(0);
const imageFilters = reactive({ status: '', accountId: '' });
// 视频列表
const videoList = ref<any[]>([]);
const videoLoading = ref(false);
const videoPage = ref(1);
const videoPageSize = ref(20);
const videoTotal = ref(0);
const videoFilters = reactive({ status: '', accountId: '' });
// 对话框
const previewVisible = ref(false);
const previewUrl = ref('');
const previewType = ref('image');
// 日志对话框
const logVisible = ref(false);
const currentLogList = ref<any[]>([]);
// 加载统计
const loadStats = async () => {
try {
const [imgRes, vidRes] = await Promise.all([getImageStats(), getVideoStats()]);
const imgStats = imgRes.data || {};
const vidStats = vidRes.data || {};
Object.assign(imageStats, {
pending: imgStats.pending || 0,
verified: imgStats.verified || 0,
rejected: imgStats.rejected || 0,
});
Object.assign(videoStats, {
pending: vidStats.pending || 0,
verified: vidStats.verified || 0,
rejected: vidStats.rejected || 0,
});
} catch (err) {
ElMessage.error('加载统计失败');
}
};
// 加载图片列表
const loadImageList = () => {
imageLoading.value = true;
getImageList({
page: imagePage.value,
pageSize: imagePageSize.value,
status: imageFilters.status,
accountId: imageFilters.accountId,
})
.then((res) => {
imageList.value = res.data?.list || [];
imageTotal.value = res.data?.total || 0;
})
.catch(() => {
ElMessage.error('加载图片列表失败');
})
.finally(() => {
imageLoading.value = false;
});
};
// 加载视频列表
const loadVideoList = () => {
videoLoading.value = true;
getVideoList({
page: videoPage.value,
pageSize: videoPageSize.value,
status: videoFilters.status,
accountId: videoFilters.accountId,
})
.then((res) => {
videoList.value = res.data?.list || [];
videoTotal.value = res.data?.total || 0;
})
.catch(() => {
ElMessage.error('加载视频列表失败');
})
.finally(() => {
videoLoading.value = false;
});
};
// Tab 切换监听
watch(activeTab, (newTab) => {
if (newTab === 'image') {
loadImageList();
} else if (newTab === 'video') {
loadVideoList();
}
});
// 搜索
const searchImage = () => {
imagePage.value = 1;
loadImageList();
};
const searchVideo = () => {
videoPage.value = 1;
loadVideoList();
};
// 重置筛选
const resetImageFilter = () => {
Object.assign(imageFilters, { status: '', accountId: '' });
searchImage();
};
const resetVideoFilter = () => {
Object.assign(videoFilters, { status: '', accountId: '' });
searchVideo();
};
// 分页
const handleImagePageChange = (page: number) => {
imagePage.value = page;
loadImageList();
};
const handleVideoPageChange = (page: number) => {
videoPage.value = page;
loadVideoList();
};
// 手动送检
// const verifyImage = (imageId: string) => {
// ElMessageBox.confirm('确认提交图片 ' + imageId + ' 进行校验?', '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'warning',
// })
// .then(() => {
// manualVerifyImage({ materialId: imageId })
// .then(() => {
// ElMessage.success('提交成功');
// loadImageList();
// loadStats();
// })
// .catch(() => {});
// })
// .catch(() => {});
// };
// const verifyVideo = (videoId: string) => {
// ElMessageBox.confirm('确认提交视频 ' + videoId + ' 进行校验?', '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'warning',
// })
// .then(() => {
// manualVerifyVideo({ materialId: videoId })
// .then(() => {
// ElMessage.success('提交成功');
// loadVideoList();
// loadStats();
// })
// .catch(() => {});
// })
// .catch(() => {});
// };
// // 刷新检测结果
// const pollImageResults = () => {
// pollLoading.value = true;
// pollImageResultsApi()
// .then((res: any) => {
// ElMessage.success(res?.msg || '刷新完成');
// loadImageList();
// loadStats();
// })
// .catch(() => {})
// .finally(() => {
// pollLoading.value = false;
// });
// };
// const pollVideoResults = () => {
// pollLoading.value = true;
// pollVideoResultsApi()
// .then((res: any) => {
// ElMessage.success(res?.msg || '刷新完成');
// loadVideoList();
// loadStats();
// })
// .catch(() => {})
// .finally(() => {
// pollLoading.value = false;
// });
// };
// 导出失败素材
const exportImageUrls = () => {
ElMessage.info('正在导出图片失败素材...');
exportRejectedMaterials({ materialType: 'IMAGE' })
.then((res: any) => {
if (res.data && res.data.items && res.data.items.length > 0) {
downloadJsonAsCsv('图片失败素材.csv', res.data.items);
ElMessage.success('导出成功');
} else {
ElMessage.warning('没有失败的图片素材');
}
})
.catch(() => {
ElMessage.error('导出失败');
});
};
const exportVideoUrls = () => {
ElMessage.info('正在导出视频失败素材...');
exportRejectedMaterials({ materialType: 'VIDEO' })
.then((res: any) => {
if (res.data && res.data.items && res.data.items.length > 0) {
downloadJsonAsCsv('视频失败素材.csv', res.data.items);
ElMessage.success('导出成功');
} else {
ElMessage.warning('没有失败的视频素材');
}
})
.catch(() => {
ElMessage.error('导出失败');
});
};
const downloadJsonAsCsv = (filename: string, items: any[]) => {
const headers = ['序号', '账户(名称)', '预览URL', '描述', '失败原因', '检测时间'];
const rows = [headers];
items.forEach((item, index) => {
// 将日期转换为 Excel 可识别的格式,添加等号前缀强制作为文本处理
const formattedDate = item.createdAt ? `="${item.createdAt}"` : '-';
rows.push([
index + 1,
`${item.accountId} - ${item.corporationName || ''}`,
(item.previewUrl || '').trim(),
item.description || '-',
item.errorMsg || '-',
formattedDate,
]);
});
const csv = rows
.map((r) =>
r
.map((v) => {
// 如果值以 "=" 开头,需要特殊处理避免被 Excel 当作公式
if (String(v).startsWith('=')) {
return v;
}
return `"${String(v).replace(/"/g, '""')}"`;
})
.join(',')
)
.join('\n');
const blob = new Blob(['\uFEFF' + csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(link.href);
};
// 批量送检
// const batchVerifyImage = () => {
// ElMessageBox.confirm('确认批量校验待处理的图片?', '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'warning',
// })
// .then(() => {
// batchLoading.value = true;
// batchVerifyImageApi()
// .then((res: any) => {
// ElMessage.success(res?.msg || '批量校验完成');
// loadImageList();
// loadStats();
// })
// .catch(() => {})
// .finally(() => {
// batchLoading.value = false;
// });
// })
// .catch(() => {});
// };
// const batchVerifyVideo = () => {
// ElMessageBox.confirm('确认批量校验待处理的视频?', '提示', {
// confirmButtonText: '确定',
// cancelButtonText: '取消',
// type: 'warning',
// })
// .then(() => {
// batchLoading.value = true;
// batchVerifyVideoApi()
// .then((res: any) => {
// ElMessage.success(res?.msg || '批量校验完成');
// loadVideoList();
// loadStats();
// })
// .catch(() => {})
// .finally(() => {
// batchLoading.value = false;
// });
// })
// .catch(() => {});
// };
// 预览
const previewMedia = (url: string, type: string) => {
if (!url) {
ElMessage.warning('无预览地址');
return;
}
previewUrl.value = url;
previewType.value = type;
previewVisible.value = true;
};
// 工具方法
const getStatusText = (status: string) => {
const map: Record<string, string> = {
PENDING: '待校验',
SUBMITTING: '送检中',
VERIFIED: '校验通过',
REJECTED: '校验不通过',
};
return map[status] || status || '待校验';
};
// 解析响应结果
const parseResponseResult = (responseResult: string) => {
try {
const result = JSON.parse(responseResult);
if (result.antispam && result.antispam.riskDescription) {
return result.antispam.riskDescription;
}
if (result.riskDescription) {
return result.riskDescription;
}
} catch (e) {
// 解析失败,返回原始字符串
}
return '';
};
// 查看日志
const viewLog = (row: any) => {
const materialId = row.imageId || row.videoId;
const materialType = row.imageId ? 'IMAGE' : 'VIDEO';
getVerifyLogList({
page: 1,
pageSize: 100,
materialId,
materialType,
})
.then((res: any) => {
if (res.data && res.data.list && res.data.list.length > 0) {
currentLogList.value = res.data.list.map((item: any) => {
const riskDesc = parseResponseResult(item.responseResult);
return {
time: item.createdAt || '-',
type: item.verifyStatus === 'REJECTED' ? 'ERROR' : 'SUCCESS',
status: item.verifyStatus,
suggestion: item.suggestion,
errorMsg: item.errorMsg || '-',
message: riskDesc || `校验状态:${getStatusText(item.verifyStatus)}`,
};
});
} else {
currentLogList.value = [
{
time: row.createdAt || new Date().toLocaleString('zh-CN'),
type: row.verifyStatus === 'REJECTED' ? 'ERROR' : 'SUCCESS',
status: row.verifyStatus,
suggestion: row.suggestion,
errorMsg: row.errorMsg || '-',
message: `校验完成,结果:${getStatusText(row.verifyStatus)}`,
},
];
}
})
.catch(() => {
currentLogList.value = [
{
time: row.createdAt || new Date().toLocaleString('zh-CN'),
type: row.verifyStatus === 'REJECTED' ? 'ERROR' : 'SUCCESS',
status: row.verifyStatus,
suggestion: row.suggestion,
errorMsg: row.errorMsg || '-',
message: `校验完成,结果:${getStatusText(row.verifyStatus)}`,
},
];
});
logVisible.value = true;
};
// 获取建议文本
const getSuggestionText = (suggestion: number) => {
const map: Record<number, string> = {
0: '通过',
1: '建议通过',
2: '建议删除',
3: '建议删除',
};
return map[suggestion] ?? '-';
};
// 获取建议样式类
const getSuggestionClass = (suggestion: number) => {
if (suggestion === 0) return 'success';
if (suggestion === 1) return 'info';
if (suggestion === 2 || suggestion === 3) return 'rejected';
return 'info';
};
// 获取日志类型文本
const getLogTypeText = (type: string) => {
const map: Record<string, string> = {
INFO: '信息',
SUCCESS: '成功',
WARN: '警告',
ERROR: '错误',
};
return map[type] || type || '信息';
};
// 加载账户列表
const loadAccountList = () => {
getAccountList()
.then((res: any) => {
if (res.data && res.data.list) {
accountList.value = res.data.list;
}
})
.catch(() => {
ElMessage.error('获取账户列表失败');
});
};
// 组件挂载时加载数据
onMounted(() => {
loadAccountList();
loadStats();
loadImageList();
});
</script>
<style scoped lang="scss">
.ads-compliance-tencent {
background: #f5f7fa;
height: 100vh;
}
.main-card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 18px;
font-weight: 600;
color: #303133;
}
.card-description {
color: #909399;
font-size: 14px;
margin: 12px 0 20px 0;
line-height: 1.6;
padding-bottom: 16px;
border-bottom: 1px solid #ebeeef;
}
.main-tabs {
margin-top: 16px;
}
.tab-content {
padding: 20px;
display: flex;
flex-direction: column;
max-height: calc(100vh - 280px);
}
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: #fafafa;
border-radius: 8px;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 16px;
}
.search-form {
display: flex;
align-items: center;
}
.search-form :deep(.el-form-item) {
margin-right: 16px;
margin-bottom: 0;
}
.search-form :deep(.el-form-item__label) {
font-size: 14px;
color: #606266;
padding-right: 8px;
}
.search-form :deep(.el-button) {
margin-left: 8px;
}
.action-buttons {
display: flex;
gap: 12px;
}
.action-buttons :deep(.el-button) {
padding: 8px 20px;
font-size: 14px;
}
.table-wrapper {
background: #fff;
border-radius: 8px;
border: 1px solid #ebeef5;
overflow: hidden;
flex: 1;
min-height: 0;
overflow-y: auto;
}
.table-wrapper :deep(.el-table) {
border: none;
}
.table-wrapper :deep(.el-table__header th) {
background: #fafafa;
font-weight: 600;
color: #606266;
}
.table-status {
display: inline-block;
padding: 4px 12px;
border-radius: 4px;
font-size: 12px;
&.status-pending {
background: #fdf6ec;
color: #e6a23c;
}
&.status-submitting {
background: #e8f4fd;
color: #409eff;
}
&.status-verified {
background: #f0f9eb;
color: #67c23a;
}
&.status-rejected {
background: #fef0f0;
color: #f56c6c;
}
&.status-info {
background: #e8f4fd;
color: #409eff;
}
&.status-success {
background: #f0f9eb;
color: #67c23a;
}
&.status-warn {
background: #fdf6ec;
color: #e6a23c;
}
&.status-error {
background: #fef0f0;
color: #f56c6c;
}
}
.pagination-container {
margin-top: 16px;
text-align: right;
flex-shrink: 0;
padding-top: 16px;
border-top: 1px solid #ebeef5;
background: #fff;
}
.media-preview {
max-width: 200px;
max-height: 150px;
border-radius: 4px;
cursor: pointer;
}
.video-preview {
width: 200px;
height: 120px;
background: #000;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
color: #fff;
cursor: pointer;
position: relative;
overflow: hidden;
}
.video-thumbnail {
width: 100%;
height: 100%;
object-fit: cover;
}
.play-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 44px;
height: 44px;
background: rgba(0, 0, 0, 0.6);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
transition: opacity 0.3s;
}
.video-preview:hover .play-overlay {
opacity: 1;
}
.video-preview:not(:hover) .play-overlay {
opacity: 0.7;
}
.description-text {
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
word-break: break-all;
font-size: 13px;
line-height: 1.6;
color: #606266;
margin: 0;
}
.preview-dialog {
:deep(.el-dialog__body) {
padding: 16px;
overflow: hidden;
}
.preview-content {
display: flex;
align-items: center;
justify-content: center;
max-height: calc(80vh - 120px);
overflow: hidden;
img,
video {
max-width: 100%;
max-height: calc(80vh - 120px);
object-fit: contain;
}
}
}
</style>