1232 lines
40 KiB
HTML
1232 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>图片管理 - 图片收集系统</title>
|
||
<!-- 引入Vue.js -->
|
||
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
|
||
<!-- 引入Element UI CSS -->
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.8/lib/theme-chalk/index.css">
|
||
<!-- 引入Element UI JavaScript -->
|
||
<script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.8/lib/index.js"></script>
|
||
<!-- 引入Font Awesome -->
|
||
<link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||
<!-- 引入ECharts -->
|
||
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
|
||
<style>
|
||
body {
|
||
margin: 0;
|
||
padding: 0;
|
||
height: 100vh;
|
||
background-color: #f0f2f5;
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||
}
|
||
.container {
|
||
height: 100vh;
|
||
display: flex;
|
||
}
|
||
.sidebar {
|
||
width: 220px;
|
||
background-color: #001529;
|
||
color: white;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.sidebar-header {
|
||
padding: 20px;
|
||
text-align: center;
|
||
border-bottom: 1px solid #1f2937;
|
||
}
|
||
.logo {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.logo-icon {
|
||
margin-right: 8px;
|
||
font-size: 22px;
|
||
}
|
||
.menu-item {
|
||
padding: 15px 20px;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.menu-item:hover {
|
||
background-color: #1f2937;
|
||
}
|
||
.menu-item.active {
|
||
background-color: #1989fa;
|
||
}
|
||
.menu-icon {
|
||
margin-right: 10px;
|
||
font-size: 16px;
|
||
}
|
||
.main-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
}
|
||
.header {
|
||
background-color: white;
|
||
padding: 15px 20px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
.header-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
.header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 15px;
|
||
}
|
||
.user-info {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.user-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
background-color: #1989fa;
|
||
color: white;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
margin-right: 8px;
|
||
font-size: 14px;
|
||
}
|
||
.user-name {
|
||
color: #606266;
|
||
}
|
||
.content-wrapper {
|
||
flex: 1;
|
||
padding: 20px;
|
||
overflow-y: auto;
|
||
}
|
||
.card {
|
||
background-color: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||
margin-bottom: 20px;
|
||
}
|
||
.search-section {
|
||
display: flex;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.search-input {
|
||
width: 250px;
|
||
}
|
||
.select-wrapper {
|
||
width: 200px;
|
||
}
|
||
.table-wrapper {
|
||
overflow-x: auto;
|
||
}
|
||
.table-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
.table-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
.btn-primary {
|
||
background-color: #1989fa;
|
||
border-color: #1989fa;
|
||
}
|
||
.btn-primary:hover {
|
||
background-color: #409eff;
|
||
border-color: #409eff;
|
||
}
|
||
.operation-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.el-button {
|
||
padding: 6px 12px;
|
||
font-size: 14px;
|
||
}
|
||
.dialog-form-item {
|
||
margin-bottom: 20px;
|
||
}
|
||
.dialog-form-label {
|
||
font-weight: 500;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
font-size: 14px;
|
||
}
|
||
.status-badge {
|
||
padding: 4px 8px;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
}
|
||
.status-pending {
|
||
background-color: #fdf6ec;
|
||
color: #e6a23c;
|
||
}
|
||
.status-approved {
|
||
background-color: #f0f9eb;
|
||
color: #67c23a;
|
||
}
|
||
.status-rejected {
|
||
background-color: #fef0f0;
|
||
color: #f56c6c;
|
||
}
|
||
.statistics-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.stat-card {
|
||
background-color: white;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.stat-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 50%;
|
||
background-color: #ecf5ff;
|
||
color: #1989fa;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24px;
|
||
margin-right: 15px;
|
||
}
|
||
.stat-content {
|
||
flex: 1;
|
||
}
|
||
.stat-value {
|
||
font-size: 24px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 5px;
|
||
}
|
||
.stat-label {
|
||
font-size: 14px;
|
||
color: #909399;
|
||
}
|
||
.logout-btn {
|
||
cursor: pointer;
|
||
color: #606266;
|
||
transition: color 0.3s;
|
||
}
|
||
.logout-btn:hover {
|
||
color: #f56c6c;
|
||
}
|
||
.chart-container {
|
||
height: 300px;
|
||
width: 100%;
|
||
}
|
||
.chart-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 20px;
|
||
margin-bottom: 20px;
|
||
}
|
||
.image-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
.image-item {
|
||
background-color: #f5f7fa;
|
||
border-radius: 8px;
|
||
overflow: hidden;
|
||
transition: transform 0.3s, box-shadow 0.3s;
|
||
cursor: pointer;
|
||
position: relative;
|
||
}
|
||
.image-item:hover {
|
||
transform: translateY(-5px);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||
}
|
||
.image-preview {
|
||
width: 100%;
|
||
height: 120px;
|
||
object-fit: cover;
|
||
}
|
||
.image-info {
|
||
padding: 10px;
|
||
}
|
||
.image-title {
|
||
font-size: 13px;
|
||
color: #303133;
|
||
margin-bottom: 5px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
.image-meta {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
.image-status-badge {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 8px;
|
||
padding: 2px 8px;
|
||
border-radius: 10px;
|
||
font-size: 11px;
|
||
}
|
||
.category-tag {
|
||
margin-right: 5px;
|
||
margin-bottom: 5px;
|
||
}
|
||
.image-detail-dialog .el-dialog__body {
|
||
max-height: 70vh;
|
||
overflow-y: auto;
|
||
}
|
||
.image-detail {}
|
||
.image-detail-preview {
|
||
width: 100%;
|
||
max-height: 400px;
|
||
object-fit: contain;
|
||
margin-bottom: 20px;
|
||
}
|
||
.detail-section {
|
||
margin-bottom: 20px;
|
||
}
|
||
.detail-label {
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 8px;
|
||
}
|
||
.detail-value {
|
||
color: #606266;
|
||
line-height: 1.6;
|
||
}
|
||
.detail-row {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
}
|
||
.detail-col {
|
||
flex: 1;
|
||
}
|
||
.tab-content {
|
||
padding: 20px 0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<div class="container">
|
||
<!-- 侧边栏 -->
|
||
<div class="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="logo">
|
||
<i class="fa fa-tachometer logo-icon"></i>
|
||
<span>管理平台</span>
|
||
</div>
|
||
</div>
|
||
<div class="menu-item" @click="navigateTo('department')">
|
||
<i class="fa fa-building-o menu-icon"></i>
|
||
<span>部门管理</span>
|
||
</div>
|
||
<div class="menu-item" @click="navigateTo('user')">
|
||
<i class="fa fa-users menu-icon"></i>
|
||
<span>用户管理</span>
|
||
</div>
|
||
<div class="menu-item" @click="navigateTo('task')">
|
||
<i class="fa fa-tasks menu-icon"></i>
|
||
<span>任务管理</span>
|
||
</div>
|
||
<div class="menu-item active" @click="navigateTo('image')">
|
||
<i class="fa fa-picture-o menu-icon"></i>
|
||
<span>图片管理</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区 -->
|
||
<div class="main-content">
|
||
<!-- 顶部导航栏 -->
|
||
<div class="header">
|
||
<div class="header-title">图片管理</div>
|
||
<div class="header-actions">
|
||
<div class="user-info">
|
||
<div class="user-avatar">管</div>
|
||
<span class="user-name">系统管理员</span>
|
||
</div>
|
||
<div class="logout-btn" @click="handleLogout">
|
||
<i class="fa fa-sign-out"></i> 退出
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 内容区域 -->
|
||
<div class="content-wrapper">
|
||
<!-- 统计卡片 -->
|
||
<div class="statistics-cards">
|
||
<div class="stat-card">
|
||
<div class="stat-icon">
|
||
<i class="fa fa-image"></i>
|
||
</div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">{{ imageStats.total }}</div>
|
||
<div class="stat-label">总图片数</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background-color: #f0f9eb; color: #67c23a;">
|
||
<i class="fa fa-check-circle"></i>
|
||
</div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">{{ imageStats.approved }}</div>
|
||
<div class="stat-label">已审核通过</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background-color: #fdf6ec; color: #e6a23c;">
|
||
<i class="fa fa-clock-o"></i>
|
||
</div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">{{ imageStats.pending }}</div>
|
||
<div class="stat-label">待审核</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-card">
|
||
<div class="stat-icon" style="background-color: #fef0f0; color: #f56c6c;">
|
||
<i class="fa fa-times-circle"></i>
|
||
</div>
|
||
<div class="stat-content">
|
||
<div class="stat-value">{{ imageStats.rejected }}</div>
|
||
<div class="stat-label">已拒绝</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图表区域 -->
|
||
<div class="chart-row">
|
||
<div class="card">
|
||
<h3 style="margin-bottom: 20px; color: #303133;">图片状态分布</h3>
|
||
<div id="imageStatusChart" class="chart-container"></div>
|
||
</div>
|
||
<div class="card">
|
||
<h3 style="margin-bottom: 20px; color: #303133;">图片分类统计</h3>
|
||
<div id="imageCategoryChart" class="chart-container"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 搜索和操作区域 -->
|
||
<div class="card">
|
||
<el-tabs v-model="activeTab" @tab-click="handleTabChange">
|
||
<el-tab-pane label="列表视图" name="list">
|
||
<div class="search-section">
|
||
<el-input
|
||
v-model="searchQuery"
|
||
placeholder="搜索图片标题或描述"
|
||
prefix-icon="el-icon-search"
|
||
class="search-input"
|
||
></el-input>
|
||
<el-select
|
||
v-model="statusFilter"
|
||
placeholder="选择状态"
|
||
class="select-wrapper"
|
||
clearable
|
||
>
|
||
<el-option label="待审核" value="pending"></el-option>
|
||
<el-option label="已通过" value="approved"></el-option>
|
||
<el-option label="已拒绝" value="rejected"></el-option>
|
||
</el-select>
|
||
<el-select
|
||
v-model="categoryFilter"
|
||
placeholder="选择分类"
|
||
class="select-wrapper"
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="category in categories"
|
||
:key="category.id"
|
||
:label="category.name"
|
||
:value="category.id"
|
||
></el-option>
|
||
</el-select>
|
||
<el-select
|
||
v-model="departmentFilter"
|
||
placeholder="选择部门"
|
||
class="select-wrapper"
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="dept in departments"
|
||
:key="dept.id"
|
||
:label="dept.name"
|
||
:value="dept.id"
|
||
></el-option>
|
||
</el-select>
|
||
<el-button
|
||
type="primary"
|
||
class="btn-primary"
|
||
@click="batchApprove"
|
||
:disabled="selectedImages.length === 0"
|
||
>
|
||
<i class="fa fa-check"></i> 批量审核通过
|
||
</el-button>
|
||
</div>
|
||
</el-tab-pane>
|
||
<el-tab-pane label="网格视图" name="grid">
|
||
<div class="search-section">
|
||
<el-input
|
||
v-model="searchQuery"
|
||
placeholder="搜索图片标题或描述"
|
||
prefix-icon="el-icon-search"
|
||
class="search-input"
|
||
></el-input>
|
||
<el-select
|
||
v-model="statusFilter"
|
||
placeholder="选择状态"
|
||
class="select-wrapper"
|
||
clearable
|
||
>
|
||
<el-option label="待审核" value="pending"></el-option>
|
||
<el-option label="已通过" value="approved"></el-option>
|
||
<el-option label="已拒绝" value="rejected"></el-option>
|
||
</el-select>
|
||
<el-select
|
||
v-model="categoryFilter"
|
||
placeholder="选择分类"
|
||
class="select-wrapper"
|
||
clearable
|
||
>
|
||
<el-option
|
||
v-for="category in categories"
|
||
:key="category.id"
|
||
:label="category.name"
|
||
:value="category.id"
|
||
></el-option>
|
||
</el-select>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<!-- 图片列表 -->
|
||
<div class="card">
|
||
<div class="tab-content" v-if="activeTab === 'list'">
|
||
<div class="table-wrapper">
|
||
<el-table
|
||
:data="filteredImages"
|
||
stripe
|
||
style="width: 100%"
|
||
@selection-change="handleSelectionChange"
|
||
>
|
||
<el-table-column type="selection" width="55"></el-table-column>
|
||
<el-table-column prop="id" label="图片ID" width="80"></el-table-column>
|
||
<el-table-column label="预览" width="100">
|
||
<template slot-scope="scope">
|
||
<img
|
||
:src="scope.row.url"
|
||
:alt="scope.row.title"
|
||
style="width: 80px; height: 60px; object-fit: cover; border-radius: 4px; cursor: pointer;"
|
||
@click="viewImageDetail(scope.row)"
|
||
>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="title" label="标题" min-width="180">
|
||
<template slot-scope="scope">
|
||
<div style="font-weight: 500; cursor: pointer;" @click="viewImageDetail(scope.row)">{{ scope.row.title }}</div>
|
||
<div style="color: #909399; font-size: 12px; margin-top: 2px; line-height: 1.4;">{{ scope.row.description }}</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="categoryName" label="分类" width="100"></el-table-column>
|
||
<el-table-column prop="departmentName" label="所属部门" width="120"></el-table-column>
|
||
<el-table-column prop="uploaderName" label="上传人" width="100"></el-table-column>
|
||
<el-table-column prop="taskName" label="关联任务" min-width="150"></el-table-column>
|
||
<el-table-column prop="status" label="状态" width="100">
|
||
<template slot-scope="scope">
|
||
<span
|
||
class="status-badge"
|
||
:class="{
|
||
'status-pending': scope.row.status === 'pending',
|
||
'status-approved': scope.row.status === 'approved',
|
||
'status-rejected': scope.row.status === 'rejected'
|
||
}"
|
||
>
|
||
{{ getStatusText(scope.row.status) }}
|
||
</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="uploadTime" label="上传时间" min-width="150">
|
||
<template slot-scope="scope">
|
||
{{ formatDateTime(scope.row.uploadTime) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="size" label="大小" width="80">
|
||
<template slot-scope="scope">
|
||
{{ formatFileSize(scope.row.size) }}
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="160" fixed="right">
|
||
<template slot-scope="scope">
|
||
<div class="operation-buttons">
|
||
<el-button
|
||
type="primary"
|
||
size="small"
|
||
@click="viewImageDetail(scope.row)"
|
||
>
|
||
查看
|
||
</el-button>
|
||
<template v-if="scope.row.status === 'pending'">
|
||
<el-button
|
||
type="success"
|
||
size="small"
|
||
@click="approveImage(scope.row.id)"
|
||
>
|
||
通过
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
size="small"
|
||
@click="rejectImage(scope.row.id)"
|
||
>
|
||
拒绝
|
||
</el-button>
|
||
</template>
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图片网格 -->
|
||
<div class="tab-content" v-else-if="activeTab === 'grid'">
|
||
<div class="image-grid">
|
||
<div
|
||
v-for="image in filteredImages"
|
||
:key="image.id"
|
||
class="image-item"
|
||
@click="viewImageDetail(image)"
|
||
>
|
||
<img
|
||
:src="image.url"
|
||
:alt="image.title"
|
||
class="image-preview"
|
||
>
|
||
<span
|
||
class="image-status-badge"
|
||
:class="{
|
||
'status-pending': image.status === 'pending',
|
||
'status-approved': image.status === 'approved',
|
||
'status-rejected': image.status === 'rejected'
|
||
}"
|
||
>
|
||
{{ getStatusText(image.status) }}
|
||
</span>
|
||
<div class="image-info">
|
||
<div class="image-title">{{ image.title }}</div>
|
||
<div class="image-meta">
|
||
<span>{{ image.categoryName }}</span>
|
||
<span>{{ formatDate(image.uploadTime) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 图片详情对话框 -->
|
||
<el-dialog
|
||
:title="'图片详情 - ' + imageDetail.title"
|
||
:visible.sync="showImageDetail"
|
||
width="800px"
|
||
class="image-detail-dialog"
|
||
>
|
||
<div class="image-detail" v-if="imageDetail.id">
|
||
<img
|
||
:src="imageDetail.url"
|
||
:alt="imageDetail.title"
|
||
class="image-detail-preview"
|
||
>
|
||
|
||
<div class="detail-section">
|
||
<div class="detail-label">基本信息</div>
|
||
<div class="detail-value">
|
||
<div class="detail-row">
|
||
<div class="detail-col">
|
||
<strong>标题:</strong>{{ imageDetail.title || '-' }}
|
||
</div>
|
||
<div class="detail-col">
|
||
<strong>ID:</strong>{{ imageDetail.id || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-col">
|
||
<strong>描述:</strong>{{ imageDetail.description || '-' }}
|
||
</div>
|
||
<div class="detail-col">
|
||
<strong>分类:</strong>{{ imageDetail.categoryName || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-col">
|
||
<strong>上传人:</strong>{{ imageDetail.uploaderName || '-' }}
|
||
</div>
|
||
<div class="detail-col">
|
||
<strong>所属部门:</strong>{{ imageDetail.departmentName || '-' }}
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-col">
|
||
<strong>关联任务:</strong>{{ imageDetail.taskName || '-' }}
|
||
</div>
|
||
<div class="detail-col">
|
||
<strong>状态:</strong>
|
||
<span
|
||
class="status-badge"
|
||
:class="{
|
||
'status-pending': imageDetail.status === 'pending',
|
||
'status-approved': imageDetail.status === 'approved',
|
||
'status-rejected': imageDetail.status === 'rejected'
|
||
}"
|
||
>
|
||
{{ getStatusText(imageDetail.status) }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="detail-row">
|
||
<div class="detail-col">
|
||
<strong>上传时间:</strong>{{ formatDateTime(imageDetail.uploadTime) || '-' }}
|
||
</div>
|
||
<div class="detail-col">
|
||
<strong>文件大小:</strong>{{ formatFileSize(imageDetail.size) || '-' }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-section" v-if="imageDetail.tags && imageDetail.tags.length > 0">
|
||
<div class="detail-label">标签</div>
|
||
<div class="tag-list">
|
||
<el-tag
|
||
v-for="tag in imageDetail.tags"
|
||
:key="tag"
|
||
class="category-tag"
|
||
size="small"
|
||
>
|
||
{{ tag }}
|
||
</el-tag>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-section" v-if="imageDetail.status === 'pending'">
|
||
<div class="detail-label">审核操作</div>
|
||
<div class="operation-buttons">
|
||
<el-button
|
||
type="success"
|
||
size="medium"
|
||
@click="approveImage(imageDetail.id, true)"
|
||
>
|
||
<i class="fa fa-check"></i> 审核通过
|
||
</el-button>
|
||
<el-button
|
||
type="danger"
|
||
size="medium"
|
||
@click="rejectImage(imageDetail.id, true)"
|
||
>
|
||
<i class="fa fa-times"></i> 审核拒绝
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
|
||
<script>
|
||
new Vue({
|
||
el: '#app',
|
||
data() {
|
||
return {
|
||
searchQuery: '',
|
||
statusFilter: '',
|
||
categoryFilter: '',
|
||
departmentFilter: '',
|
||
activeTab: 'list',
|
||
showImageDetail: false,
|
||
selectedImages: [],
|
||
departments: [
|
||
{ id: 1, name: '技术部' },
|
||
{ id: 2, name: '市场部' },
|
||
{ id: 3, name: '人力资源部' },
|
||
{ id: 4, name: '财务部' },
|
||
{ id: 5, name: '行政部' }
|
||
],
|
||
categories: [
|
||
{ id: 1, name: '产品包装' },
|
||
{ id: 2, name: '办公环境' },
|
||
{ id: 3, name: '活动现场' },
|
||
{ id: 4, name: '员工证件' },
|
||
{ id: 5, name: '财务凭证' },
|
||
{ id: 6, name: 'IT设备' }
|
||
],
|
||
images: [
|
||
{
|
||
id: 1,
|
||
title: '产品包装正面图',
|
||
description: '新款产品包装的正面展示图',
|
||
url: 'https://picsum.photos/id/26/800/600',
|
||
categoryId: 1,
|
||
categoryName: '产品包装',
|
||
departmentId: 2,
|
||
departmentName: '市场部',
|
||
uploaderId: 3,
|
||
uploaderName: '李明',
|
||
taskId: 1,
|
||
taskName: '产品包装图片采集',
|
||
status: 'approved',
|
||
uploadTime: '2023-12-01 14:30:00',
|
||
size: 2048000,
|
||
tags: ['产品', '包装', '正面']
|
||
},
|
||
{
|
||
id: 2,
|
||
title: '办公区域全景',
|
||
description: '新办公区的全景照片',
|
||
url: 'https://picsum.photos/id/28/800/600',
|
||
categoryId: 2,
|
||
categoryName: '办公环境',
|
||
departmentId: 5,
|
||
departmentName: '行政部',
|
||
uploaderId: 5,
|
||
uploaderName: '赵明',
|
||
taskId: 2,
|
||
taskName: '办公环境照片拍摄',
|
||
status: 'approved',
|
||
uploadTime: '2023-12-02 10:15:00',
|
||
size: 3500000,
|
||
tags: ['办公环境', '全景']
|
||
},
|
||
{
|
||
id: 3,
|
||
title: '市场活动现场',
|
||
description: '新产品发布会现场照片',
|
||
url: 'https://picsum.photos/id/29/800/600',
|
||
categoryId: 3,
|
||
categoryName: '活动现场',
|
||
departmentId: 2,
|
||
departmentName: '市场部',
|
||
uploaderId: 3,
|
||
uploaderName: '李明',
|
||
taskId: 3,
|
||
taskName: '活动现场照片收集',
|
||
status: 'approved',
|
||
uploadTime: '2023-12-03 16:45:00',
|
||
size: 4200000,
|
||
tags: ['活动', '现场', '发布会']
|
||
},
|
||
{
|
||
id: 4,
|
||
title: '员工证件照',
|
||
description: '新入职员工的证件照片',
|
||
url: 'https://picsum.photos/id/33/800/600',
|
||
categoryId: 4,
|
||
categoryName: '员工证件',
|
||
departmentId: 3,
|
||
departmentName: '人力资源部',
|
||
uploaderId: 4,
|
||
uploaderName: '王红',
|
||
taskId: 4,
|
||
taskName: '员工证件照更新',
|
||
status: 'pending',
|
||
uploadTime: '2023-12-04 09:20:00',
|
||
size: 1500000,
|
||
tags: ['员工', '证件照']
|
||
},
|
||
{
|
||
id: 5,
|
||
title: '财务发票扫描件',
|
||
description: '11月份的办公费用发票',
|
||
url: 'https://picsum.photos/id/36/800/600',
|
||
categoryId: 5,
|
||
categoryName: '财务凭证',
|
||
departmentId: 4,
|
||
departmentName: '财务部',
|
||
uploaderId: 5,
|
||
uploaderName: '赵明',
|
||
taskId: 5,
|
||
taskName: '财务凭证扫描',
|
||
status: 'pending',
|
||
uploadTime: '2023-12-05 13:10:00',
|
||
size: 800000,
|
||
tags: ['财务', '发票', '凭证']
|
||
},
|
||
{
|
||
id: 6,
|
||
title: '服务器设备照片',
|
||
description: '机房服务器设备的照片',
|
||
url: 'https://picsum.photos/id/42/800/600',
|
||
categoryId: 6,
|
||
categoryName: 'IT设备',
|
||
departmentId: 1,
|
||
departmentName: '技术部',
|
||
uploaderId: 2,
|
||
uploaderName: '张经理',
|
||
taskId: 6,
|
||
taskName: '设备库存盘点',
|
||
status: 'rejected',
|
||
uploadTime: '2023-12-06 11:30:00',
|
||
size: 3200000,
|
||
tags: ['IT设备', '服务器', '机房']
|
||
},
|
||
{
|
||
id: 7,
|
||
title: '产品包装侧面图',
|
||
description: '新款产品包装的侧面展示图',
|
||
url: 'https://picsum.photos/id/44/800/600',
|
||
categoryId: 1,
|
||
categoryName: '产品包装',
|
||
departmentId: 2,
|
||
departmentName: '市场部',
|
||
uploaderId: 3,
|
||
uploaderName: '李明',
|
||
taskId: 1,
|
||
taskName: '产品包装图片采集',
|
||
status: 'approved',
|
||
uploadTime: '2023-12-07 15:20:00',
|
||
size: 2800000,
|
||
tags: ['产品', '包装', '侧面']
|
||
},
|
||
{
|
||
id: 8,
|
||
title: '会议室照片',
|
||
description: '新装修的会议室照片',
|
||
url: 'https://picsum.photos/id/45/800/600',
|
||
categoryId: 2,
|
||
categoryName: '办公环境',
|
||
departmentId: 5,
|
||
departmentName: '行政部',
|
||
uploaderId: 5,
|
||
uploaderName: '赵明',
|
||
taskId: 2,
|
||
taskName: '办公环境照片拍摄',
|
||
status: 'pending',
|
||
uploadTime: '2023-12-08 14:45:00',
|
||
size: 3800000,
|
||
tags: ['办公环境', '会议室']
|
||
}
|
||
],
|
||
imageDetail: {},
|
||
imageStats: {
|
||
total: 8,
|
||
approved: 4,
|
||
pending: 3,
|
||
rejected: 1
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
filteredImages() {
|
||
return this.images.filter(image => {
|
||
// 搜索图片标题或描述
|
||
const matchesSearch = !this.searchQuery ||
|
||
image.title.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
||
image.description.toLowerCase().includes(this.searchQuery.toLowerCase());
|
||
|
||
// 状态筛选
|
||
const matchesStatus = !this.statusFilter ||
|
||
image.status === this.statusFilter;
|
||
|
||
// 分类筛选
|
||
const matchesCategory = !this.categoryFilter ||
|
||
image.categoryId === this.categoryFilter;
|
||
|
||
// 部门筛选
|
||
const matchesDepartment = !this.departmentFilter ||
|
||
image.departmentId === this.departmentFilter;
|
||
|
||
return matchesSearch && matchesStatus && matchesCategory && matchesDepartment;
|
||
});
|
||
}
|
||
},
|
||
mounted() {
|
||
this.initCharts();
|
||
},
|
||
methods: {
|
||
// 初始化图表
|
||
initCharts() {
|
||
// 图片状态分布图表
|
||
const statusChart = echarts.init(document.getElementById('imageStatusChart'));
|
||
const statusOption = {
|
||
tooltip: {
|
||
trigger: 'item'
|
||
},
|
||
legend: {
|
||
orient: 'vertical',
|
||
left: 'left'
|
||
},
|
||
series: [
|
||
{
|
||
name: '图片状态',
|
||
type: 'pie',
|
||
radius: '50%',
|
||
data: [
|
||
{ value: this.imageStats.approved, name: '已通过', itemStyle: { color: '#67c23a' } },
|
||
{ value: this.imageStats.pending, name: '待审核', itemStyle: { color: '#e6a23c' } },
|
||
{ value: this.imageStats.rejected, name: '已拒绝', itemStyle: { color: '#f56c6c' } }
|
||
],
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
};
|
||
statusChart.setOption(statusOption);
|
||
|
||
// 图片分类统计图表
|
||
const categoryChart = echarts.init(document.getElementById('imageCategoryChart'));
|
||
const categoryData = [];
|
||
const categoryColors = ['#1989fa', '#67c23a', '#e6a23c', '#f56c6c', '#909399', '#409eff'];
|
||
|
||
this.categories.forEach((category, index) => {
|
||
const count = this.images.filter(img => img.categoryId === category.id).length;
|
||
if (count > 0) {
|
||
categoryData.push({
|
||
name: category.name,
|
||
value: count,
|
||
itemStyle: { color: categoryColors[index % categoryColors.length] }
|
||
});
|
||
}
|
||
});
|
||
|
||
const categoryOption = {
|
||
tooltip: {
|
||
trigger: 'item'
|
||
},
|
||
legend: {
|
||
orient: 'vertical',
|
||
left: 'left'
|
||
},
|
||
series: [
|
||
{
|
||
name: '图片分类',
|
||
type: 'pie',
|
||
radius: '50%',
|
||
data: categoryData,
|
||
emphasis: {
|
||
itemStyle: {
|
||
shadowBlur: 10,
|
||
shadowOffsetX: 0,
|
||
shadowColor: 'rgba(0, 0, 0, 0.5)'
|
||
}
|
||
}
|
||
}
|
||
]
|
||
};
|
||
categoryChart.setOption(categoryOption);
|
||
|
||
// 监听窗口大小变化
|
||
window.addEventListener('resize', () => {
|
||
statusChart.resize();
|
||
categoryChart.resize();
|
||
});
|
||
},
|
||
|
||
// 处理标签切换
|
||
handleTabChange() {
|
||
// 标签切换时清空选中项
|
||
this.selectedImages = [];
|
||
},
|
||
|
||
// 处理表格选择
|
||
handleSelectionChange(selection) {
|
||
this.selectedImages = selection;
|
||
},
|
||
|
||
// 导航到其他页面
|
||
navigateTo(page) {
|
||
let url = '';
|
||
switch (page) {
|
||
case 'department':
|
||
url = 'department-management.html';
|
||
break;
|
||
case 'user':
|
||
url = 'user-management.html';
|
||
break;
|
||
case 'task':
|
||
url = 'task-management.html';
|
||
break;
|
||
case 'image':
|
||
url = 'image-management.html';
|
||
break;
|
||
}
|
||
if (url && url !== window.location.pathname) {
|
||
window.location.href = url;
|
||
}
|
||
},
|
||
|
||
// 格式化日期
|
||
formatDate(dateStr) {
|
||
if (!dateStr) return '';
|
||
return dateStr.split(' ')[0];
|
||
},
|
||
|
||
// 格式化日期时间
|
||
formatDateTime(dateStr) {
|
||
return dateStr || '';
|
||
},
|
||
|
||
// 格式化文件大小
|
||
formatFileSize(bytes) {
|
||
if (!bytes) return '';
|
||
if (bytes < 1024) return bytes + ' B';
|
||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||
},
|
||
|
||
// 获取状态文本
|
||
getStatusText(status) {
|
||
const statusMap = {
|
||
'pending': '待审核',
|
||
'approved': '已通过',
|
||
'rejected': '已拒绝'
|
||
};
|
||
return statusMap[status] || '待审核';
|
||
},
|
||
|
||
// 查看图片详情
|
||
viewImageDetail(image) {
|
||
this.imageDetail = { ...image };
|
||
this.showImageDetail = true;
|
||
},
|
||
|
||
// 审核通过图片
|
||
approveImage(imageId, fromDetail = false) {
|
||
this.$confirm(`确定要审核通过该图片吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'success'
|
||
}).then(() => {
|
||
const index = this.images.findIndex(img => img.id === imageId);
|
||
if (index > -1) {
|
||
if (this.images[index].status === 'pending') {
|
||
this.images[index].status = 'approved';
|
||
this.imageStats.pending--;
|
||
this.imageStats.approved++;
|
||
}
|
||
this.$message.success('图片已审核通过');
|
||
|
||
// 如果从详情页操作,关闭详情页
|
||
if (fromDetail) {
|
||
this.showImageDetail = false;
|
||
}
|
||
|
||
this.initCharts(); // 重新初始化图表
|
||
}
|
||
}).catch(() => {
|
||
// 取消操作
|
||
});
|
||
},
|
||
|
||
// 审核拒绝图片
|
||
rejectImage(imageId, fromDetail = false) {
|
||
this.$confirm(`确定要拒绝该图片吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
const index = this.images.findIndex(img => img.id === imageId);
|
||
if (index > -1) {
|
||
if (this.images[index].status === 'pending') {
|
||
this.images[index].status = 'rejected';
|
||
this.imageStats.pending--;
|
||
this.imageStats.rejected++;
|
||
}
|
||
this.$message.success('图片已拒绝');
|
||
|
||
// 如果从详情页操作,关闭详情页
|
||
if (fromDetail) {
|
||
this.showImageDetail = false;
|
||
}
|
||
|
||
this.initCharts(); // 重新初始化图表
|
||
}
|
||
}).catch(() => {
|
||
// 取消操作
|
||
});
|
||
},
|
||
|
||
// 批量审核通过
|
||
batchApprove() {
|
||
if (this.selectedImages.length === 0) {
|
||
this.$message.warning('请先选择要审核的图片');
|
||
return;
|
||
}
|
||
|
||
const pendingImages = this.selectedImages.filter(img => img.status === 'pending');
|
||
if (pendingImages.length === 0) {
|
||
this.$message.warning('选中的图片中没有待审核的图片');
|
||
return;
|
||
}
|
||
|
||
this.$confirm(`确定要批量审核通过这 ${pendingImages.length} 张图片吗?`, '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'success'
|
||
}).then(() => {
|
||
pendingImages.forEach(img => {
|
||
const index = this.images.findIndex(i => i.id === img.id);
|
||
if (index > -1) {
|
||
this.images[index].status = 'approved';
|
||
this.imageStats.pending--;
|
||
this.imageStats.approved++;
|
||
}
|
||
});
|
||
this.$message.success(`已成功审核通过 ${pendingImages.length} 张图片`);
|
||
this.selectedImages = []; // 清空选中项
|
||
this.initCharts(); // 重新初始化图表
|
||
}).catch(() => {
|
||
// 取消操作
|
||
});
|
||
},
|
||
|
||
// 处理退出登录
|
||
handleLogout() {
|
||
this.$confirm('确定要退出登录吗?', '提示', {
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning'
|
||
}).then(() => {
|
||
this.$message.success('退出成功');
|
||
setTimeout(() => {
|
||
window.location.href = 'admin-login.html';
|
||
}, 1000);
|
||
}).catch(() => {
|
||
// 取消退出
|
||
});
|
||
}
|
||
}
|
||
})
|
||
</script>
|
||
</body>
|
||
</html> |