refactor(ads/compliance/tencent): 重构腾讯素材校验页面布局与逻辑
1. 移除统计卡片组件,调整页面整体结构为卡片式布局 2. 移除校验日志相关页面、逻辑与接口调用 3. 优化表格列配置与工具栏样式 4. 简化筛选表单结构,统一组件样式 5. 更新页面类名与全局样式适配新布局
This commit is contained in:
@@ -1,71 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="material-verify-container">
|
<div class="ads-compliance-tencent">
|
||||||
<!-- 头部 -->
|
<el-card shadow="hover" class="main-card">
|
||||||
<div class="header">
|
<template #header
|
||||||
<h1>素材送检状态管理</h1>
|
><div class="card-header"><span>素材送检状态管理</span></div></template
|
||||||
<p>腾讯图片/视频素材自动校验系统 - 基于易盾内容安全检测</p>
|
>
|
||||||
</div>
|
<p class="card-description">腾讯图片/视频素材自动校验系统 - 基于易盾内容安全检测</p>
|
||||||
|
|
||||||
<!-- 统计卡片 -->
|
|
||||||
<div class="stats-card">
|
|
||||||
<div class="stat-item pending">
|
|
||||||
<div class="stat-value">{{ imageStats.pending + videoStats.pending || 0 }}</div>
|
|
||||||
<div class="stat-label">待校验</div>
|
|
||||||
<div class="stat-breakdown">
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag image">图</span>
|
|
||||||
<span>图片: </span>
|
|
||||||
<span class="num">{{ imageStats.pending || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag video">视</span>
|
|
||||||
<span>视频: </span>
|
|
||||||
<span class="num">{{ videoStats.pending || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item verified">
|
|
||||||
<div class="stat-value">{{ imageStats.verified + videoStats.verified || 0 }}</div>
|
|
||||||
<div class="stat-label">校验通过</div>
|
|
||||||
<div class="stat-breakdown">
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag image">图</span>
|
|
||||||
<span>图片: </span>
|
|
||||||
<span class="num">{{ imageStats.verified || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag video">视</span>
|
|
||||||
<span>视频: </span>
|
|
||||||
<span class="num">{{ videoStats.verified || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="stat-item rejected">
|
|
||||||
<div class="stat-value">{{ imageStats.rejected + videoStats.rejected || 0 }}</div>
|
|
||||||
<div class="stat-label">校验不通过</div>
|
|
||||||
<div class="stat-breakdown">
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag image">图</span>
|
|
||||||
<span>图片: </span>
|
|
||||||
<span class="num">{{ imageStats.rejected || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="stat-breakdown-item">
|
|
||||||
<span class="type-tag video">视</span>
|
|
||||||
<span>视频: </span>
|
|
||||||
<span class="num">{{ videoStats.rejected || 0 }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Tabs -->
|
<!-- Tabs -->
|
||||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
<el-tabs v-model="activeTab" @tab-click="handleTabClick" class="main-tabs">
|
||||||
<el-tab-pane label="图片素材" name="image">
|
<el-tab-pane label="图片素材" name="image">
|
||||||
<div class="card">
|
<!-- 图片素材内容区域 -->
|
||||||
<!-- 筛选 -->
|
<div class="tab-content">
|
||||||
<div class="filter-bar">
|
<!-- 工具栏区域 -->
|
||||||
<div class="filter-item">
|
<div class="toolbar">
|
||||||
<label>状态:</label>
|
<!-- 左侧:筛选表单 -->
|
||||||
|
<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-select v-model="imageFilters.status" placeholder="全部" clearable style="width: 120px">
|
||||||
<el-option label="全部" value=""></el-option>
|
<el-option label="全部" value=""></el-option>
|
||||||
<el-option label="待校验" value="PENDING"></el-option>
|
<el-option label="待校验" value="PENDING"></el-option>
|
||||||
@@ -73,17 +23,24 @@
|
|||||||
<el-option label="校验通过" value="VERIFIED"></el-option>
|
<el-option label="校验通过" value="VERIFIED"></el-option>
|
||||||
<el-option label="校验不通过" value="REJECTED"></el-option>
|
<el-option label="校验不通过" value="REJECTED"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</el-form-item>
|
||||||
<div class="filter-item">
|
<el-form-item label="账户ID">
|
||||||
<label>账户ID:</label>
|
<el-input
|
||||||
<el-input v-model="imageFilters.accountId" placeholder="账户ID" style="width: 120px" clearable @keyup.enter="searchImage"></el-input>
|
v-model="imageFilters.accountId"
|
||||||
</div>
|
placeholder="账户ID"
|
||||||
|
style="width: 150px"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="searchImage"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
<el-button type="primary" @click="searchImage">搜索</el-button>
|
<el-button type="primary" @click="searchImage">搜索</el-button>
|
||||||
<el-button @click="resetImageFilter">重置</el-button>
|
<el-button @click="resetImageFilter">重置</el-button>
|
||||||
</div>
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 右侧:操作按钮 -->
|
||||||
<div class="action-bar">
|
<div class="action-buttons">
|
||||||
<el-button type="success" @click="batchVerifyImage" :loading="batchLoading"> 批量校验图片 </el-button>
|
<el-button type="success" @click="batchVerifyImage" :loading="batchLoading"> 批量校验图片 </el-button>
|
||||||
<el-button type="primary" plain @click="pollImageResults" :loading="pollLoading">
|
<el-button type="primary" plain @click="pollImageResults" :loading="pollLoading">
|
||||||
<el-icon><Refresh /></el-icon> 刷新检测结果
|
<el-icon><Refresh /></el-icon> 刷新检测结果
|
||||||
@@ -92,13 +49,13 @@
|
|||||||
<el-icon><Download /></el-icon> 导出失败素材
|
<el-icon><Download /></el-icon> 导出失败素材
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-wrapper">
|
||||||
<el-table :data="imageList" border style="width: 100%" v-loading="imageLoading">
|
<el-table :data="imageList" border style="width: 100%" v-loading="imageLoading">
|
||||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
<el-table-column prop="accountId" label="账户ID" width="120"></el-table-column>
|
||||||
<el-table-column prop="imageId" label="图片ID" width="180"></el-table-column>
|
<el-table-column prop="imageUsage" label="用途" width="120"></el-table-column>
|
||||||
<el-table-column prop="accountId" label="账户ID" width="100"></el-table-column>
|
|
||||||
<el-table-column prop="imageUsage" label="用途" width="100"></el-table-column>
|
|
||||||
<el-table-column label="预览" width="120">
|
<el-table-column label="预览" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<img
|
<img
|
||||||
@@ -109,28 +66,24 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="verifyStatus" label="校验状态" width="110">
|
<el-table-column prop="verifyStatus" label="校验状态" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
|
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
|
||||||
{{ getStatusText(scope.row.verifyStatus) }}
|
{{ getStatusText(scope.row.verifyStatus) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="description" label="描述" min-width="150">
|
<el-table-column prop="description" label="描述" min-width="200"></el-table-column>
|
||||||
|
<el-table-column label="操作" width="80" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span class="description-text" :title="scope.row.description">{{ scope.row.description || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button size="small" type="text" @click="showLogs('IMAGE', scope.row.imageId)">查看日志</el-button>
|
|
||||||
<el-button size="small" type="text" @click="verifyImage(scope.row.imageId)">送检</el-button>
|
<el-button size="small" type="text" @click="verifyImage(scope.row.imageId)">送检</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页区域 -->
|
||||||
<div class="pagination">
|
<div class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@current-change="handleImagePageChange"
|
@current-change="handleImagePageChange"
|
||||||
v-model:current-page="imagePage"
|
v-model:current-page="imagePage"
|
||||||
@@ -142,13 +95,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="视频素材" name="video">
|
<el-tab-pane label="视频素材" name="video">
|
||||||
<div class="card">
|
<!-- 视频素材内容区域 -->
|
||||||
<!-- 筛选 -->
|
<div class="tab-content">
|
||||||
<div class="filter-bar">
|
<!-- 工具栏区域 -->
|
||||||
<div class="filter-item">
|
<div class="toolbar">
|
||||||
<label>状态:</label>
|
<!-- 左侧:筛选表单 -->
|
||||||
|
<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-select v-model="videoFilters.status" placeholder="全部" clearable style="width: 120px">
|
||||||
<el-option label="全部" value=""></el-option>
|
<el-option label="全部" value=""></el-option>
|
||||||
<el-option label="待校验" value="PENDING"></el-option>
|
<el-option label="待校验" value="PENDING"></el-option>
|
||||||
@@ -156,17 +110,24 @@
|
|||||||
<el-option label="校验通过" value="VERIFIED"></el-option>
|
<el-option label="校验通过" value="VERIFIED"></el-option>
|
||||||
<el-option label="校验不通过" value="REJECTED"></el-option>
|
<el-option label="校验不通过" value="REJECTED"></el-option>
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</el-form-item>
|
||||||
<div class="filter-item">
|
<el-form-item label="账户ID">
|
||||||
<label>账户ID:</label>
|
<el-input
|
||||||
<el-input v-model="videoFilters.accountId" placeholder="账户ID" style="width: 120px" clearable @keyup.enter="searchVideo"></el-input>
|
v-model="videoFilters.accountId"
|
||||||
</div>
|
placeholder="账户ID"
|
||||||
|
style="width: 150px"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="searchVideo"
|
||||||
|
></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
<el-button type="primary" @click="searchVideo">搜索</el-button>
|
<el-button type="primary" @click="searchVideo">搜索</el-button>
|
||||||
<el-button @click="resetVideoFilter">重置</el-button>
|
<el-button @click="resetVideoFilter">重置</el-button>
|
||||||
</div>
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
<!-- 操作按钮 -->
|
<!-- 右侧:操作按钮 -->
|
||||||
<div class="action-bar">
|
<div class="action-buttons">
|
||||||
<el-button type="success" @click="batchVerifyVideo" :loading="batchLoading"> 批量校验视频 </el-button>
|
<el-button type="success" @click="batchVerifyVideo" :loading="batchLoading"> 批量校验视频 </el-button>
|
||||||
<el-button type="primary" plain @click="pollVideoResults" :loading="pollLoading">
|
<el-button type="primary" plain @click="pollVideoResults" :loading="pollLoading">
|
||||||
<el-icon><Refresh /></el-icon> 刷新检测结果
|
<el-icon><Refresh /></el-icon> 刷新检测结果
|
||||||
@@ -175,12 +136,12 @@
|
|||||||
<el-icon><Download /></el-icon> 导出失败素材
|
<el-icon><Download /></el-icon> 导出失败素材
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- 表格区域 -->
|
||||||
|
<div class="table-wrapper">
|
||||||
<el-table :data="videoList" border style="width: 100%" v-loading="videoLoading">
|
<el-table :data="videoList" border style="width: 100%" v-loading="videoLoading">
|
||||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
<el-table-column prop="accountId" label="账户ID" width="120"></el-table-column>
|
||||||
<el-table-column prop="videoId" label="视频ID" width="180"></el-table-column>
|
|
||||||
<el-table-column prop="accountId" label="账户ID" width="100"></el-table-column>
|
|
||||||
<el-table-column label="预览" width="120">
|
<el-table-column label="预览" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div class="video-preview" @click="previewMedia(scope.row.previewUrl, 'video')">
|
<div class="video-preview" @click="previewMedia(scope.row.previewUrl, 'video')">
|
||||||
@@ -188,28 +149,24 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="verifyStatus" label="校验状态" width="110">
|
<el-table-column prop="verifyStatus" label="校验状态" width="120">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
|
<span :class="'table-status status-' + (scope.row.verifyStatus || 'pending').toLowerCase()">
|
||||||
{{ getStatusText(scope.row.verifyStatus) }}
|
{{ getStatusText(scope.row.verifyStatus) }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="description" label="描述" min-width="150">
|
<el-table-column prop="description" label="描述" min-width="200"></el-table-column>
|
||||||
|
<el-table-column label="操作" width="80" fixed="right">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span class="description-text" :title="scope.row.description">{{ scope.row.description || '-' }}</span>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="200" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button size="small" type="text" @click="showLogs('VIDEO', scope.row.videoId)">查看日志</el-button>
|
|
||||||
<el-button size="small" type="text" @click="verifyVideo(scope.row.videoId)">送检</el-button>
|
<el-button size="small" type="text" @click="verifyVideo(scope.row.videoId)">送检</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 分页 -->
|
<!-- 分页区域 -->
|
||||||
<div class="pagination">
|
<div class="pagination-container">
|
||||||
<el-pagination
|
<el-pagination
|
||||||
@current-change="handleVideoPageChange"
|
@current-change="handleVideoPageChange"
|
||||||
v-model:current-page="videoPage"
|
v-model:current-page="videoPage"
|
||||||
@@ -221,123 +178,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
|
||||||
<el-tab-pane label="校验日志" name="log">
|
|
||||||
<div class="card">
|
|
||||||
<!-- 筛选 -->
|
|
||||||
<div class="filter-bar">
|
|
||||||
<div class="filter-item">
|
|
||||||
<label>素材类型:</label>
|
|
||||||
<el-select v-model="logFilters.materialType" placeholder="全部" clearable style="width: 120px">
|
|
||||||
<el-option label="全部" value=""></el-option>
|
|
||||||
<el-option label="图片" value="IMAGE"></el-option>
|
|
||||||
<el-option label="视频" value="VIDEO"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="filter-item">
|
|
||||||
<label>校验状态:</label>
|
|
||||||
<el-select v-model="logFilters.verifyStatus" placeholder="全部" clearable style="width: 120px">
|
|
||||||
<el-option label="全部" value=""></el-option>
|
|
||||||
<el-option label="待校验" value="PENDING"></el-option>
|
|
||||||
<el-option label="校验通过" value="VERIFIED"></el-option>
|
|
||||||
<el-option label="校验不通过" value="REJECTED"></el-option>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
|
||||||
<div class="filter-item">
|
|
||||||
<label>素材ID:</label>
|
|
||||||
<el-input v-model="logFilters.materialId" placeholder="素材ID" style="width: 150px" clearable></el-input>
|
|
||||||
</div>
|
|
||||||
<el-button type="primary" @click="searchLog">搜索</el-button>
|
|
||||||
<el-button @click="resetLogFilter">重置</el-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 表格 -->
|
|
||||||
<el-table :data="logList" border style="width: 100%" v-loading="logLoading">
|
|
||||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
|
||||||
<el-table-column prop="materialType" label="类型" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ scope.row.materialType === 'IMAGE' ? '图片' : '视频' }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="materialId" label="素材ID" width="180"></el-table-column>
|
|
||||||
<el-table-column prop="accountId" label="账户ID" width="100"></el-table-column>
|
|
||||||
<el-table-column prop="taskId" label="任务ID" width="180"></el-table-column>
|
|
||||||
<el-table-column prop="verifyStatus" label="校验状态" width="110">
|
|
||||||
<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="suggestion" label="建议" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ getSuggestionText(scope.row.suggestion) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="createdAt" label="创建时间" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatTime(scope.row.createdAt) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="操作" width="120" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button size="small" type="text" @click="showLogDetail(scope.row)">详情</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="pagination">
|
|
||||||
<el-pagination
|
|
||||||
@current-change="handleLogPageChange"
|
|
||||||
v-model:current-page="logPage"
|
|
||||||
:page-size="logPageSize"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
:total="logTotal"
|
|
||||||
>
|
|
||||||
</el-pagination>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
|
</el-card>
|
||||||
<!-- 日志详情对话框 -->
|
|
||||||
<el-dialog title="校验日志详情" v-model="logDialogVisible" width="800px">
|
|
||||||
<div v-if="currentLog">
|
|
||||||
<el-descriptions :column="2" border>
|
|
||||||
<el-descriptions-item label="日志ID">{{ currentLog.id }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="素材类型">{{ currentLog.materialType === 'IMAGE' ? '图片' : '视频' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="素材ID">{{ currentLog.materialId }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="账户ID">{{ currentLog.accountId }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="任务ID">{{ currentLog.taskId || '-' }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="校验状态">
|
|
||||||
<span :class="'table-status status-' + (currentLog.verifyStatus || 'pending').toLowerCase()">
|
|
||||||
{{ getStatusText(currentLog.verifyStatus) }}
|
|
||||||
</span>
|
|
||||||
</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="处置建议">{{ getSuggestionText(currentLog.suggestion) }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="创建时间">{{ formatTime(currentLog.createdAt) }}</el-descriptions-item>
|
|
||||||
</el-descriptions>
|
|
||||||
|
|
||||||
<h4 style="margin-top: 20px">请求参数:</h4>
|
|
||||||
<div class="log-detail">
|
|
||||||
<pre>{{ currentLog.requestParams || '无' }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>响应结果:</h4>
|
|
||||||
<div class="log-detail">
|
|
||||||
<pre>{{ currentLog.responseResult || '无' }}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h4>错误信息:</h4>
|
|
||||||
<div class="log-detail" v-if="currentLog.errorMsg">
|
|
||||||
<pre style="color: #f56c6c">{{ currentLog.errorMsg }}</pre>
|
|
||||||
</div>
|
|
||||||
<div class="log-detail" v-else>
|
|
||||||
<pre>无</pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<!-- 预览对话框 -->
|
<!-- 预览对话框 -->
|
||||||
<el-dialog title="媒体预览" v-model="previewVisible" width="60%">
|
<el-dialog title="媒体预览" v-model="previewVisible" width="60%">
|
||||||
@@ -346,32 +188,6 @@
|
|||||||
<video v-if="previewType === 'video'" :src="previewUrl" controls style="max-width: 100%"></video>
|
<video v-if="previewType === 'video'" :src="previewUrl" controls style="max-width: 100%"></video>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 日志列表对话框 -->
|
|
||||||
<el-dialog title="校验日志" v-model="logsDialogVisible" width="900px">
|
|
||||||
<el-table :data="materialLogs" border style="width: 100%" size="small">
|
|
||||||
<el-table-column prop="id" label="ID" width="80"></el-table-column>
|
|
||||||
<el-table-column prop="taskId" label="任务ID" width="180"></el-table-column>
|
|
||||||
<el-table-column prop="verifyStatus" label="状态" width="100">
|
|
||||||
<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="suggestion" label="建议" width="80">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ getSuggestionText(scope.row.suggestion) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column prop="errorMsg" label="错误信息" show-overflow-tooltip></el-table-column>
|
|
||||||
<el-table-column prop="createdAt" label="时间" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
{{ formatTime(scope.row.createdAt) }}
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -384,7 +200,6 @@ import {
|
|||||||
getVideoStats,
|
getVideoStats,
|
||||||
getImageList,
|
getImageList,
|
||||||
getVideoList,
|
getVideoList,
|
||||||
getVerifyLogList,
|
|
||||||
manualVerifyImage,
|
manualVerifyImage,
|
||||||
manualVerifyVideo,
|
manualVerifyVideo,
|
||||||
pollImageResults as pollImageResultsApi,
|
pollImageResults as pollImageResultsApi,
|
||||||
@@ -419,22 +234,10 @@ const videoPageSize = ref(20);
|
|||||||
const videoTotal = ref(0);
|
const videoTotal = ref(0);
|
||||||
const videoFilters = reactive({ status: '', accountId: '' });
|
const videoFilters = reactive({ status: '', accountId: '' });
|
||||||
|
|
||||||
// 日志列表
|
|
||||||
const logList = ref<any[]>([]);
|
|
||||||
const logLoading = ref(false);
|
|
||||||
const logPage = ref(1);
|
|
||||||
const logPageSize = ref(20);
|
|
||||||
const logTotal = ref(0);
|
|
||||||
const logFilters = reactive({ materialType: '', verifyStatus: '', materialId: '' });
|
|
||||||
|
|
||||||
// 对话框
|
// 对话框
|
||||||
const logDialogVisible = ref(false);
|
|
||||||
const previewVisible = ref(false);
|
const previewVisible = ref(false);
|
||||||
const logsDialogVisible = ref(false);
|
|
||||||
const currentLog = ref<any>(null);
|
|
||||||
const previewUrl = ref('');
|
const previewUrl = ref('');
|
||||||
const previewType = ref('image');
|
const previewType = ref('image');
|
||||||
const materialLogs = ref<any[]>([]);
|
|
||||||
|
|
||||||
// 加载统计
|
// 加载统计
|
||||||
const loadStats = async () => {
|
const loadStats = async () => {
|
||||||
@@ -453,7 +256,7 @@ const loadStats = async () => {
|
|||||||
rejected: vidStats.rejected || 0,
|
rejected: vidStats.rejected || 0,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('加载统计失败', err);
|
ElMessage.error('加载统计失败');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -499,36 +302,12 @@ const loadVideoList = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载日志列表
|
|
||||||
const loadLogList = () => {
|
|
||||||
logLoading.value = true;
|
|
||||||
getVerifyLogList({
|
|
||||||
page: logPage.value,
|
|
||||||
pageSize: logPageSize.value,
|
|
||||||
materialType: logFilters.materialType,
|
|
||||||
verifyStatus: logFilters.verifyStatus,
|
|
||||||
materialId: logFilters.materialId,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
logList.value = res.data?.list || [];
|
|
||||||
logTotal.value = res.data?.total || 0;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
ElMessage.error('加载日志列表失败');
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
logLoading.value = false;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tab 切换
|
// Tab 切换
|
||||||
const handleTabClick = (tab: any) => {
|
const handleTabClick = (tab: any) => {
|
||||||
if (tab.name === 'image') {
|
if (tab.name === 'image') {
|
||||||
loadImageList();
|
loadImageList();
|
||||||
} else if (tab.name === 'video') {
|
} else if (tab.name === 'video') {
|
||||||
loadVideoList();
|
loadVideoList();
|
||||||
} else if (tab.name === 'log') {
|
|
||||||
loadLogList();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -543,11 +322,6 @@ const searchVideo = () => {
|
|||||||
loadVideoList();
|
loadVideoList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const searchLog = () => {
|
|
||||||
logPage.value = 1;
|
|
||||||
loadLogList();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 重置筛选
|
// 重置筛选
|
||||||
const resetImageFilter = () => {
|
const resetImageFilter = () => {
|
||||||
Object.assign(imageFilters, { status: '', accountId: '' });
|
Object.assign(imageFilters, { status: '', accountId: '' });
|
||||||
@@ -559,11 +333,6 @@ const resetVideoFilter = () => {
|
|||||||
searchVideo();
|
searchVideo();
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetLogFilter = () => {
|
|
||||||
Object.assign(logFilters, { materialType: '', verifyStatus: '', materialId: '' });
|
|
||||||
searchLog();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
const handleImagePageChange = (page: number) => {
|
const handleImagePageChange = (page: number) => {
|
||||||
imagePage.value = page;
|
imagePage.value = page;
|
||||||
@@ -575,11 +344,6 @@ const handleVideoPageChange = (page: number) => {
|
|||||||
loadVideoList();
|
loadVideoList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogPageChange = (page: number) => {
|
|
||||||
logPage.value = page;
|
|
||||||
loadLogList();
|
|
||||||
};
|
|
||||||
|
|
||||||
// 手动送检
|
// 手动送检
|
||||||
const verifyImage = (imageId: string) => {
|
const verifyImage = (imageId: string) => {
|
||||||
ElMessageBox.confirm('确认提交图片 ' + imageId + ' 进行校验?', '提示', {
|
ElMessageBox.confirm('确认提交图片 ' + imageId + ' 进行校验?', '提示', {
|
||||||
@@ -738,29 +502,6 @@ const batchVerifyVideo = () => {
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看日志
|
|
||||||
const showLogs = (materialType: string, materialId: string) => {
|
|
||||||
getVerifyLogList({
|
|
||||||
materialType,
|
|
||||||
materialId,
|
|
||||||
pageSize: 100,
|
|
||||||
page: 1,
|
|
||||||
})
|
|
||||||
.then((res) => {
|
|
||||||
materialLogs.value = res.data?.list || [];
|
|
||||||
logsDialogVisible.value = true;
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
ElMessage.error('加载日志失败');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 日志详情
|
|
||||||
const showLogDetail = (log: any) => {
|
|
||||||
currentLog.value = log;
|
|
||||||
logDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 预览
|
// 预览
|
||||||
const previewMedia = (url: string, type: string) => {
|
const previewMedia = (url: string, type: string) => {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@@ -783,29 +524,6 @@ const getStatusText = (status: string) => {
|
|||||||
return map[status] || status || '待校验';
|
return map[status] || status || '待校验';
|
||||||
};
|
};
|
||||||
|
|
||||||
const getSuggestionText = (suggestion: number) => {
|
|
||||||
const map: Record<number, string> = {
|
|
||||||
0: '通过',
|
|
||||||
1: '嫌疑',
|
|
||||||
2: '不通过',
|
|
||||||
};
|
|
||||||
return map[suggestion] || '-';
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatTime = (timeStr: string) => {
|
|
||||||
if (!timeStr) return '-';
|
|
||||||
const date = new Date(timeStr);
|
|
||||||
if (isNaN(date.getTime())) return '-';
|
|
||||||
return date.toLocaleString('zh-CN', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: '2-digit',
|
|
||||||
day: '2-digit',
|
|
||||||
hour: '2-digit',
|
|
||||||
minute: '2-digit',
|
|
||||||
second: '2-digit',
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 组件挂载时加载数据
|
// 组件挂载时加载数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadStats();
|
loadStats();
|
||||||
@@ -814,141 +532,101 @@ onMounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.material-verify-container {
|
.ads-compliance-tencent {
|
||||||
max-width: 1400px;
|
padding: 24px;
|
||||||
margin: 0 auto;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f5f7fa;
|
background: #f5f7fa;
|
||||||
min-height: calc(100vh - 60px);
|
min-height: calc(100vh - 60px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.main-card {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-bottom: 20px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #409eff;
|
|
||||||
font-size: 24px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: #909399;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card {
|
.card-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
align-items: center;
|
||||||
margin-bottom: 20px;
|
justify-content: space-between;
|
||||||
}
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
.stat-item {
|
|
||||||
background: #fff;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
flex: 1;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
&.pending {
|
|
||||||
border-left: 4px solid #e6a23c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.verified {
|
|
||||||
border-left: 4px solid #67c23a;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.rejected {
|
|
||||||
border-left: 4px solid #f56c6c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-value {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #303133;
|
color: #303133;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.card-description {
|
||||||
font-size: 14px;
|
|
||||||
color: #909399;
|
color: #909399;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-breakdown {
|
|
||||||
margin-top: 12px;
|
|
||||||
padding-top: 10px;
|
|
||||||
border-top: 1px dashed #ebeef5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stat-breakdown-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 24px;
|
|
||||||
|
|
||||||
.type-tag {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 1px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: normal;
|
|
||||||
min-width: 28px;
|
|
||||||
|
|
||||||
&.image {
|
|
||||||
background: #ecf5ff;
|
|
||||||
color: #409eff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.video {
|
|
||||||
background: #fdf6ec;
|
|
||||||
color: #e6a23c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.num {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
margin: 12px 0 20px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding-bottom: 16px;
|
||||||
|
border-bottom: 1px solid #ebeeef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card {
|
.main-tabs {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
padding: 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
background: #fff;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 20px;
|
border: 1px solid #ebeef5;
|
||||||
margin-bottom: 20px;
|
overflow: hidden;
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-bar {
|
.table-wrapper :deep(.el-table) {
|
||||||
display: flex;
|
border: none;
|
||||||
gap: 15px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-item {
|
.table-wrapper :deep(.el-table__header th) {
|
||||||
display: flex;
|
background: #fafafa;
|
||||||
align-items: center;
|
font-weight: 600;
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: #606266;
|
color: #606266;
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-bar {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-status {
|
.table-status {
|
||||||
@@ -978,23 +656,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
.pagination-container {
|
||||||
margin-top: 20px;
|
margin-top: 16px;
|
||||||
text-align: right;
|
display: flex;
|
||||||
}
|
justify-content: flex-end;
|
||||||
|
|
||||||
.log-detail {
|
|
||||||
background: #f5f7fa;
|
|
||||||
padding: 15px;
|
|
||||||
border-radius: 4px;
|
|
||||||
margin: 10px 0;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-family: Monaco, Consolas, monospace;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-preview {
|
.media-preview {
|
||||||
@@ -1021,5 +686,10 @@ onMounted(() => {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
word-break: break-all;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #606266;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user