1209 lines
40 KiB
HTML
1209 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: 300px;
|
|
}
|
|
.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;
|
|
}
|
|
.category-card {
|
|
background-color: #f5f7fa;
|
|
border-radius: 8px;
|
|
padding: 20px;
|
|
text-align: center;
|
|
transition: transform 0.3s, box-shadow 0.3s;
|
|
cursor: pointer;
|
|
position: relative;
|
|
}
|
|
.category-card:hover {
|
|
transform: translateY(-5px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.category-icon {
|
|
width: 64px;
|
|
height: 64px;
|
|
border-radius: 50%;
|
|
background-color: #ecf5ff;
|
|
color: #1989fa;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 32px;
|
|
margin: 0 auto 15px;
|
|
}
|
|
.category-name {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #303133;
|
|
margin-bottom: 10px;
|
|
}
|
|
.category-count {
|
|
font-size: 24px;
|
|
font-weight: 600;
|
|
color: #1989fa;
|
|
margin-bottom: 5px;
|
|
}
|
|
.category-desc {
|
|
font-size: 12px;
|
|
color: #909399;
|
|
}
|
|
.category-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
gap: 20px;
|
|
}
|
|
.chart-container {
|
|
height: 300px;
|
|
width: 100%;
|
|
}
|
|
.logout-btn {
|
|
cursor: pointer;
|
|
color: #606266;
|
|
transition: color 0.3s;
|
|
}
|
|
.logout-btn:hover {
|
|
color: #f56c6c;
|
|
}
|
|
.color-preview {
|
|
display: inline-block;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 4px;
|
|
margin-right: 10px;
|
|
border: 1px solid #dcdfe6;
|
|
}
|
|
.icon-preview {
|
|
font-size: 24px;
|
|
margin-right: 10px;
|
|
}
|
|
.category-settings {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
opacity: 0;
|
|
transition: opacity 0.3s;
|
|
}
|
|
.category-card:hover .category-settings {
|
|
opacity: 1;
|
|
}
|
|
.settings-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
color: #606266;
|
|
margin-left: 5px;
|
|
cursor: pointer;
|
|
}
|
|
.settings-btn:hover {
|
|
background-color: white;
|
|
color: #1989fa;
|
|
}
|
|
.settings-dropdown {
|
|
position: absolute;
|
|
top: 40px;
|
|
right: 0;
|
|
background-color: white;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
z-index: 1000;
|
|
width: 120px;
|
|
padding: 5px 0;
|
|
}
|
|
.dropdown-item {
|
|
padding: 10px 15px;
|
|
font-size: 14px;
|
|
color: #606266;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
}
|
|
.dropdown-item:hover {
|
|
background-color: #f5f7fa;
|
|
color: #1989fa;
|
|
}
|
|
.dropdown-item.danger:hover {
|
|
color: #f56c6c;
|
|
}
|
|
.image-preview-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
}
|
|
.image-thumb {
|
|
width: 60px;
|
|
height: 60px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
border: 1px solid #dcdfe6;
|
|
}
|
|
.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" @click="navigateTo('image')">
|
|
<i class="fa fa-picture-o menu-icon"></i>
|
|
<span>图片管理</span>
|
|
</div>
|
|
<div class="menu-item active" @click="navigateTo('category')">
|
|
<i class="fa fa-th-large 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="card">
|
|
<div class="search-section">
|
|
<el-tabs v-model="activeTab">
|
|
<el-tab-pane label="列表管理" name="list">
|
|
<div class="search-section" style="margin-bottom: 0;">
|
|
<el-input
|
|
v-model="searchQuery"
|
|
placeholder="搜索分类名称或描述"
|
|
prefix-icon="el-icon-search"
|
|
class="search-input"
|
|
></el-input>
|
|
<el-button
|
|
type="primary"
|
|
class="btn-primary"
|
|
@click="showAddCategoryDialog"
|
|
>
|
|
<i class="fa fa-plus"></i> 新增分类
|
|
</el-button>
|
|
</div>
|
|
</el-tab-pane>
|
|
<el-tab-pane label="分类概览" name="overview">
|
|
<div class="search-section" style="margin-bottom: 0;">
|
|
<el-input
|
|
v-model="searchQuery"
|
|
placeholder="搜索分类名称"
|
|
prefix-icon="el-icon-search"
|
|
class="search-input"
|
|
></el-input>
|
|
</div>
|
|
</el-tab-pane>
|
|
</el-tabs>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分类统计图表 -->
|
|
<div v-if="activeTab === 'overview'" class="card">
|
|
<h3 style="margin-bottom: 20px; color: #303133;">分类统计图表</h3>
|
|
<div id="categoryChart" class="chart-container"></div>
|
|
</div>
|
|
|
|
<!-- 分类列表 -->
|
|
<div v-if="activeTab === 'list'" class="card">
|
|
<div class="tab-content">
|
|
<div class="table-wrapper">
|
|
<el-table
|
|
:data="filteredCategories"
|
|
stripe
|
|
style="width: 100%"
|
|
>
|
|
<el-table-column prop="id" label="分类ID" width="80"></el-table-column>
|
|
<el-table-column label="图标" width="80">
|
|
<template slot-scope="scope">
|
|
<div class="icon-preview" :style="{ color: scope.row.color }">
|
|
<i :class="['fa', scope.row.icon]"></i>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="name" label="分类名称" width="180"></el-table-column>
|
|
<el-table-column prop="description" label="分类描述" min-width="200"></el-table-column>
|
|
<el-table-column prop="imageCount" label="图片数量" width="100">
|
|
<template slot-scope="scope">
|
|
<span style="color: #1989fa; font-weight: 600;">{{ scope.row.imageCount }}</span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="sortOrder" label="排序权重" width="100">
|
|
<template slot-scope="scope">
|
|
<el-input-number
|
|
v-model="scope.row.sortOrder"
|
|
:min="1"
|
|
:max="100"
|
|
size="small"
|
|
@change="updateSortOrder(scope.row.id, scope.row.sortOrder)"
|
|
></el-input-number>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="isActive" label="状态" width="80">
|
|
<template slot-scope="scope">
|
|
<el-switch
|
|
v-model="scope.row.isActive"
|
|
active-text="启用"
|
|
inactive-text="禁用"
|
|
@change="updateStatus(scope.row.id, scope.row.isActive)"
|
|
></el-switch>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column prop="createTime" label="创建时间" min-width="150">
|
|
<template slot-scope="scope">
|
|
{{ formatDateTime(scope.row.createTime) }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="180" fixed="right">
|
|
<template slot-scope="scope">
|
|
<div class="operation-buttons">
|
|
<el-button
|
|
type="primary"
|
|
size="small"
|
|
@click="viewCategoryDetail(scope.row)"
|
|
>
|
|
详情
|
|
</el-button>
|
|
<el-button
|
|
type="warning"
|
|
size="small"
|
|
@click="editCategory(scope.row)"
|
|
>
|
|
编辑
|
|
</el-button>
|
|
<el-button
|
|
type="danger"
|
|
size="small"
|
|
@click="deleteCategory(scope.row.id)"
|
|
:disabled="scope.row.imageCount > 0"
|
|
>
|
|
删除
|
|
</el-button>
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分类概览网格 -->
|
|
<div v-else-if="activeTab === 'overview'" class="card">
|
|
<div class="tab-content">
|
|
<div class="category-grid">
|
|
<div
|
|
v-for="category in filteredCategories"
|
|
:key="category.id"
|
|
class="category-card"
|
|
>
|
|
<div class="category-icon" :style="{ backgroundColor: getCategoryBgColor(category.color), color: category.color }">
|
|
<i :class="['fa', category.icon]"></i>
|
|
</div>
|
|
<div class="category-name">{{ category.name }}</div>
|
|
<div class="category-count">{{ category.imageCount }}</div>
|
|
<div class="category-desc">
|
|
{{ category.description }}<br>
|
|
<span v-if="!category.isActive" style="color: #f56c6c;">(已禁用)</span>
|
|
</div>
|
|
|
|
<!-- 分类设置按钮 -->
|
|
<div class="category-settings">
|
|
<div class="settings-btn" @click.stop="showSettingsMenu(category, $event)">
|
|
<i class="fa fa-ellipsis-v"></i>
|
|
</div>
|
|
|
|
<!-- 设置下拉菜单 -->
|
|
<div
|
|
v-if="settingsMenuVisible && settingsMenuCategory.id === category.id"
|
|
class="settings-dropdown"
|
|
@click.stop
|
|
>
|
|
<div class="dropdown-item" @click="viewCategoryDetail(category)">
|
|
<i class="fa fa-eye"></i> 查看详情
|
|
</div>
|
|
<div class="dropdown-item" @click="editCategory(category)">
|
|
<i class="fa fa-pencil"></i> 编辑分类
|
|
</div>
|
|
<div class="dropdown-item danger" @click="deleteCategory(category.id)" :class="{ disabled: category.imageCount > 0 }" :style="{ opacity: category.imageCount > 0 ? 0.5 : 1, cursor: category.imageCount > 0 ? 'not-allowed' : 'pointer' }">
|
|
<i class="fa fa-trash"></i> 删除分类
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 添加/编辑分类对话框 -->
|
|
<el-dialog
|
|
:title="dialogTitle"
|
|
:visible.sync="showDialog"
|
|
width="600px"
|
|
>
|
|
<el-form :model="categoryForm" :rules="categoryRules" ref="categoryForm" label-width="100px">
|
|
<el-form-item label="分类名称" prop="name">
|
|
<el-input v-model="categoryForm.name" placeholder="请输入分类名称"></el-input>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="分类描述" prop="description">
|
|
<el-input
|
|
v-model="categoryForm.description"
|
|
type="textarea"
|
|
placeholder="请输入分类描述"
|
|
:rows="3"
|
|
></el-input>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="分类图标" prop="icon">
|
|
<el-select v-model="categoryForm.icon" placeholder="请选择分类图标" style="width: 200px;">
|
|
<el-option
|
|
v-for="icon in availableIcons"
|
|
:key="icon.value"
|
|
:label="`${icon.label} <i class=\"fa ${icon.value}\"></i>`"
|
|
:value="icon.value"
|
|
></el-option>
|
|
</el-select>
|
|
<div v-if="categoryForm.icon" class="icon-preview" :style="{ color: categoryForm.color || '#1989fa' }">
|
|
<i :class="['fa', categoryForm.icon]"></i>
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="分类颜色" prop="color">
|
|
<el-color-picker v-model="categoryForm.color" show-alpha></el-color-picker>
|
|
<div v-if="categoryForm.color" class="color-preview" :style="{ backgroundColor: categoryForm.color }"></div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="排序权重" prop="sortOrder">
|
|
<el-input-number
|
|
v-model="categoryForm.sortOrder"
|
|
:min="1"
|
|
:max="100"
|
|
placeholder="请输入排序权重"
|
|
></el-input-number>
|
|
<div style="color: #909399; font-size: 12px; margin-top: 5px;">
|
|
数值越小,排序越靠前
|
|
</div>
|
|
</el-form-item>
|
|
|
|
<el-form-item label="状态" prop="isActive">
|
|
<el-switch v-model="categoryForm.isActive" active-text="启用" inactive-text="禁用"></el-switch>
|
|
</el-form-item>
|
|
</el-form>
|
|
|
|
<span slot="footer" class="dialog-footer">
|
|
<el-button @click="cancelDialog">取消</el-button>
|
|
<el-button type="primary" @click="submitForm">确定</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
|
|
<!-- 分类详情对话框 -->
|
|
<el-dialog
|
|
:title="'分类详情 - ' + categoryDetail.name"
|
|
:visible.sync="showCategoryDetail"
|
|
width="800px"
|
|
>
|
|
<div v-if="categoryDetail.id">
|
|
<div style="display: flex; align-items: center; margin-bottom: 30px;">
|
|
<div class="category-icon" :style="{ backgroundColor: getCategoryBgColor(categoryDetail.color), color: categoryDetail.color, margin: '0 30px 0 0' }">
|
|
<i :class="['fa', categoryDetail.icon]"></i>
|
|
</div>
|
|
<div>
|
|
<h2 style="margin: 0 0 10px 0; color: #303133;">{{ categoryDetail.name }}</h2>
|
|
<p style="margin: 0 0 10px 0; color: #606266; line-height: 1.6;">{{ categoryDetail.description }}</p>
|
|
<div style="display: flex; gap: 30px;">
|
|
<div>
|
|
<span style="color: #909399;">图片数量:</span>
|
|
<span style="color: #1989fa; font-weight: 600; font-size: 18px;">{{ categoryDetail.imageCount }}</span>
|
|
</div>
|
|
<div>
|
|
<span style="color: #909399;">创建时间:</span>
|
|
<span>{{ formatDateTime(categoryDetail.createTime) }}</span>
|
|
</div>
|
|
<div>
|
|
<span style="color: #909399;">状态:</span>
|
|
<span :style="{ color: categoryDetail.isActive ? '#67c23a' : '#f56c6c' }">
|
|
{{ categoryDetail.isActive ? '已启用' : '已禁用' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="categoryDetail.representativeImages && categoryDetail.representativeImages.length > 0">
|
|
<h3 style="margin: 0 0 15px 0; color: #303133;">代表性图片</h3>
|
|
<div class="image-preview-list">
|
|
<img
|
|
v-for="(img, index) in categoryDetail.representativeImages"
|
|
:key="index"
|
|
:src="img.url"
|
|
:alt="img.title"
|
|
class="image-thumb"
|
|
@click="viewImage(img)"
|
|
>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<span slot="footer" class="dialog-footer">
|
|
<el-button @click="editCurrentCategory">编辑分类</el-button>
|
|
<el-button @click="showCategoryDetail = false">关闭</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
|
|
<!-- 查看图片对话框 -->
|
|
<el-dialog
|
|
:title="selectedImage.title"
|
|
:visible.sync="showImageDialog"
|
|
width="70%"
|
|
fullscreen
|
|
>
|
|
<div style="text-align: center; padding: 20px;">
|
|
<img
|
|
:src="selectedImage.url"
|
|
:alt="selectedImage.title"
|
|
style="max-width: 100%; max-height: 80vh; object-fit: contain;"
|
|
>
|
|
<p style="margin-top: 20px; color: #606266;">{{ selectedImage.description || '暂无描述' }}</p>
|
|
</div>
|
|
|
|
<span slot="footer" class="dialog-footer">
|
|
<el-button @click="showImageDialog = false">关闭</el-button>
|
|
</span>
|
|
</el-dialog>
|
|
</div>
|
|
|
|
<script>
|
|
new Vue({
|
|
el: '#app',
|
|
data() {
|
|
return {
|
|
searchQuery: '',
|
|
activeTab: 'list',
|
|
showDialog: false,
|
|
dialogTitle: '',
|
|
isEditMode: false,
|
|
showCategoryDetail: false,
|
|
showImageDialog: false,
|
|
settingsMenuVisible: false,
|
|
settingsMenuCategory: {},
|
|
|
|
// 可用图标列表
|
|
availableIcons: [
|
|
{ value: 'fa-image', label: '图片' },
|
|
{ value: 'fa-file-image-o', label: '文件' },
|
|
{ value: 'fa-camera', label: '相机' },
|
|
{ value: 'fa-cube', label: '产品' },
|
|
{ value: 'fa-building', label: '建筑' },
|
|
{ value: 'fa-users', label: '用户' },
|
|
{ value: 'fa-calendar', label: '日历' },
|
|
{ value: 'fa-credit-card', label: '信用卡' },
|
|
{ value: 'fa-desktop', label: '电脑' },
|
|
{ value: 'fa-mobile', label: '手机' },
|
|
{ value: 'fa-briefcase', label: '公文包' },
|
|
{ value: 'fa-home', label: '家' },
|
|
{ value: 'fa-map-marker', label: '位置' },
|
|
{ value: 'fa-coffee', label: '咖啡' },
|
|
{ value: 'fa-cutlery', label: '餐具' }
|
|
],
|
|
|
|
// 分类表单
|
|
categoryForm: {
|
|
name: '',
|
|
description: '',
|
|
icon: 'fa-image',
|
|
color: '#1989fa',
|
|
sortOrder: 1,
|
|
isActive: true
|
|
},
|
|
|
|
// 表单验证规则
|
|
categoryRules: {
|
|
name: [
|
|
{ required: true, message: '请输入分类名称', trigger: 'blur' },
|
|
{ min: 1, max: 50, message: '分类名称长度在 1 到 50 个字符之间', trigger: 'blur' }
|
|
],
|
|
description: [
|
|
{ max: 200, message: '分类描述长度不能超过 200 个字符', trigger: 'blur' }
|
|
],
|
|
icon: [
|
|
{ required: true, message: '请选择分类图标', trigger: 'change' }
|
|
],
|
|
color: [
|
|
{ required: true, message: '请选择分类颜色', trigger: 'change' }
|
|
],
|
|
sortOrder: [
|
|
{ required: true, message: '请输入排序权重', trigger: 'blur', type: 'number' }
|
|
]
|
|
},
|
|
|
|
// 分类数据
|
|
categories: [
|
|
{
|
|
id: 1,
|
|
name: '产品包装',
|
|
description: '各类产品的包装设计和实物照片',
|
|
icon: 'fa-cube',
|
|
color: '#1989fa',
|
|
sortOrder: 1,
|
|
isActive: true,
|
|
imageCount: 15,
|
|
createTime: '2023-11-20 09:30:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/26/400/300', title: '产品包装正面' },
|
|
{ url: 'https://picsum.photos/id/44/400/300', title: '产品包装侧面' },
|
|
{ url: 'https://picsum.photos/id/48/400/300', title: '产品包装展示' }
|
|
]
|
|
},
|
|
{
|
|
id: 2,
|
|
name: '办公环境',
|
|
description: '公司办公区域、会议室等环境照片',
|
|
icon: 'fa-building',
|
|
color: '#67c23a',
|
|
sortOrder: 2,
|
|
isActive: true,
|
|
imageCount: 23,
|
|
createTime: '2023-11-21 14:20:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/28/400/300', title: '办公区域全景' },
|
|
{ url: 'https://picsum.photos/id/45/400/300', title: '会议室' },
|
|
{ url: 'https://picsum.photos/id/42/400/300', title: '接待区' }
|
|
]
|
|
},
|
|
{
|
|
id: 3,
|
|
name: '活动现场',
|
|
description: '公司各类活动、展会、团建等现场照片',
|
|
icon: 'fa-calendar',
|
|
color: '#e6a23c',
|
|
sortOrder: 3,
|
|
isActive: true,
|
|
imageCount: 47,
|
|
createTime: '2023-11-22 10:15:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/29/400/300', title: '活动现场' },
|
|
{ url: 'https://picsum.photos/id/36/400/300', title: '展会布置' },
|
|
{ url: 'https://picsum.photos/id/42/400/300', title: '团队合影' }
|
|
]
|
|
},
|
|
{
|
|
id: 4,
|
|
name: '员工证件',
|
|
description: '员工证件照、工作证等照片',
|
|
icon: 'fa-users',
|
|
color: '#f56c6c',
|
|
sortOrder: 4,
|
|
isActive: true,
|
|
imageCount: 89,
|
|
createTime: '2023-11-23 16:45:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/33/400/300', title: '员工证件照模板' }
|
|
]
|
|
},
|
|
{
|
|
id: 5,
|
|
name: '财务凭证',
|
|
description: '各类财务发票、收据、报销凭证照片',
|
|
icon: 'fa-credit-card',
|
|
color: '#909399',
|
|
sortOrder: 5,
|
|
isActive: true,
|
|
imageCount: 64,
|
|
createTime: '2023-11-24 11:30:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/36/400/300', title: '发票示例' },
|
|
{ url: 'https://picsum.photos/id/42/400/300', title: '财务凭证' }
|
|
]
|
|
},
|
|
{
|
|
id: 6,
|
|
name: 'IT设备',
|
|
description: '公司IT设备、服务器、办公电脑等照片',
|
|
icon: 'fa-desktop',
|
|
color: '#409eff',
|
|
sortOrder: 6,
|
|
isActive: true,
|
|
imageCount: 12,
|
|
createTime: '2023-11-25 09:00:00',
|
|
representativeImages: [
|
|
{ url: 'https://picsum.photos/id/42/400/300', title: '服务器设备' },
|
|
{ url: 'https://picsum.photos/id/48/400/300', title: '办公电脑' }
|
|
]
|
|
},
|
|
{
|
|
id: 7,
|
|
name: '营销物料',
|
|
description: '宣传单页、海报、广告牌等营销材料',
|
|
icon: 'fa-bullhorn',
|
|
color: '#f7ba1e',
|
|
sortOrder: 7,
|
|
isActive: false,
|
|
imageCount: 0,
|
|
createTime: '2023-11-26 15:40:00',
|
|
representativeImages: []
|
|
}
|
|
],
|
|
|
|
categoryDetail: {},
|
|
selectedImage: {}
|
|
}
|
|
},
|
|
computed: {
|
|
filteredCategories() {
|
|
return this.categories
|
|
.filter(category => {
|
|
return !this.searchQuery ||
|
|
category.name.toLowerCase().includes(this.searchQuery.toLowerCase()) ||
|
|
category.description.toLowerCase().includes(this.searchQuery.toLowerCase());
|
|
})
|
|
.sort((a, b) => a.sortOrder - b.sortOrder);
|
|
}
|
|
},
|
|
mounted() {
|
|
this.initCharts();
|
|
|
|
// 点击文档其他地方关闭设置菜单
|
|
document.addEventListener('click', this.closeSettingsMenu);
|
|
},
|
|
beforeDestroy() {
|
|
document.removeEventListener('click', this.closeSettingsMenu);
|
|
},
|
|
methods: {
|
|
// 初始化图表
|
|
initCharts() {
|
|
// 确保 DOM 元素存在再初始化
|
|
setTimeout(() => {
|
|
const chartDom = document.getElementById('categoryChart');
|
|
if (chartDom) {
|
|
const categoryChart = echarts.init(chartDom);
|
|
|
|
const chartData = this.categories
|
|
.filter(cat => cat.isActive)
|
|
.map(cat => ({
|
|
name: cat.name,
|
|
value: cat.imageCount,
|
|
itemStyle: { color: cat.color }
|
|
}))
|
|
.sort((a, b) => b.value - a.value);
|
|
|
|
const option = {
|
|
tooltip: {
|
|
trigger: 'item'
|
|
},
|
|
legend: {
|
|
orient: 'vertical',
|
|
left: 'left'
|
|
},
|
|
series: [
|
|
{
|
|
name: '分类图片数量',
|
|
type: 'pie',
|
|
radius: ['40%', '70%'],
|
|
avoidLabelOverlap: false,
|
|
itemStyle: {
|
|
borderRadius: 10,
|
|
borderColor: '#fff',
|
|
borderWidth: 2
|
|
},
|
|
label: {
|
|
show: false,
|
|
position: 'center'
|
|
},
|
|
emphasis: {
|
|
label: {
|
|
show: true,
|
|
fontSize: '18',
|
|
fontWeight: 'bold'
|
|
}
|
|
},
|
|
labelLine: {
|
|
show: false
|
|
},
|
|
data: chartData
|
|
}
|
|
]
|
|
};
|
|
|
|
categoryChart.setOption(option);
|
|
|
|
// 监听窗口大小变化
|
|
window.addEventListener('resize', () => {
|
|
categoryChart.resize();
|
|
});
|
|
}
|
|
}, 100);
|
|
},
|
|
|
|
// 导航到其他页面
|
|
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;
|
|
case 'category':
|
|
url = 'category-management.html';
|
|
break;
|
|
}
|
|
if (url && url !== window.location.pathname) {
|
|
window.location.href = url;
|
|
}
|
|
},
|
|
|
|
// 格式化日期时间
|
|
formatDateTime(dateStr) {
|
|
return dateStr || '';
|
|
},
|
|
|
|
// 获取分类背景颜色(用于图标背景)
|
|
getCategoryBgColor(color) {
|
|
// 计算颜色的亮度
|
|
const rgb = this.hexToRgb(color);
|
|
const brightness = (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000;
|
|
|
|
// 如果颜色较亮,返回浅色调;如果颜色较暗,返回稍亮的色调
|
|
if (brightness > 128) {
|
|
return this.adjustBrightness(color, -40);
|
|
} else {
|
|
return this.adjustBrightness(color, 60);
|
|
}
|
|
},
|
|
|
|
// 十六进制转RGB
|
|
hexToRgb(hex) {
|
|
// 移除 # 号
|
|
hex = hex.replace('#', '');
|
|
|
|
// 解析 RGB 值
|
|
const r = parseInt(hex.substring(0, 2), 16);
|
|
const g = parseInt(hex.substring(2, 4), 16);
|
|
const b = parseInt(hex.substring(4, 6), 16);
|
|
|
|
return { r, g, b };
|
|
},
|
|
|
|
// 调整颜色亮度
|
|
adjustBrightness(hex, percent) {
|
|
let r = parseInt(hex.substring(1, 3), 16);
|
|
let g = parseInt(hex.substring(3, 5), 16);
|
|
let b = parseInt(hex.substring(5, 7), 16);
|
|
|
|
// 增加或减少亮度
|
|
r = Math.floor(r * (100 + percent) / 100);
|
|
g = Math.floor(g * (100 + percent) / 100);
|
|
b = Math.floor(b * (100 + percent) / 100);
|
|
|
|
// 确保值在 0-255 范围内
|
|
r = (r < 255) ? r : 255;
|
|
r = (r > 0) ? r : 0;
|
|
g = (g < 255) ? g : 255;
|
|
g = (g > 0) ? g : 0;
|
|
b = (b < 255) ? b : 255;
|
|
b = (b > 0) ? b : 0;
|
|
|
|
// 转换回十六进制
|
|
const rr = ((r.toString(16).length === 1) ? '0' + r.toString(16) : r.toString(16));
|
|
const gg = ((g.toString(16).length === 1) ? '0' + g.toString(16) : g.toString(16));
|
|
const bb = ((b.toString(16).length === 1) ? '0' + b.toString(16) : b.toString(16));
|
|
|
|
return '#' + rr + gg + bb;
|
|
},
|
|
|
|
// 显示添加分类对话框
|
|
showAddCategoryDialog() {
|
|
this.isEditMode = false;
|
|
this.dialogTitle = '新增分类';
|
|
this.resetForm();
|
|
this.showDialog = true;
|
|
},
|
|
|
|
// 编辑分类
|
|
editCategory(category) {
|
|
this.isEditMode = true;
|
|
this.dialogTitle = '编辑分类';
|
|
this.categoryForm = { ...category };
|
|
this.showDialog = true;
|
|
},
|
|
|
|
// 重置表单
|
|
resetForm() {
|
|
this.categoryForm = {
|
|
name: '',
|
|
description: '',
|
|
icon: 'fa-image',
|
|
color: '#1989fa',
|
|
sortOrder: 1,
|
|
isActive: true
|
|
};
|
|
this.$refs.categoryForm && this.$refs.categoryForm.resetFields();
|
|
},
|
|
|
|
// 取消对话框
|
|
cancelDialog() {
|
|
this.showDialog = false;
|
|
this.resetForm();
|
|
},
|
|
|
|
// 提交表单
|
|
submitForm() {
|
|
this.$refs.categoryForm.validate((valid) => {
|
|
if (valid) {
|
|
if (this.isEditMode) {
|
|
// 编辑模式
|
|
const index = this.categories.findIndex(cat => cat.id === this.categoryForm.id);
|
|
if (index > -1) {
|
|
// 保留图片数量和创建时间
|
|
const imageCount = this.categories[index].imageCount;
|
|
const createTime = this.categories[index].createTime;
|
|
const representativeImages = this.categories[index].representativeImages;
|
|
|
|
this.categories[index] = {
|
|
...this.categoryForm,
|
|
imageCount,
|
|
createTime,
|
|
representativeImages
|
|
};
|
|
this.$message.success('分类编辑成功');
|
|
}
|
|
} else {
|
|
// 新增模式
|
|
const newCategory = {
|
|
...this.categoryForm,
|
|
id: Date.now(), // 模拟生成ID
|
|
imageCount: 0,
|
|
createTime: this.formatDateTime(new Date().toLocaleString('zh-CN')),
|
|
representativeImages: []
|
|
};
|
|
this.categories.push(newCategory);
|
|
this.$message.success('分类创建成功');
|
|
}
|
|
|
|
this.showDialog = false;
|
|
this.resetForm();
|
|
|
|
// 如果在概览页,重新初始化图表
|
|
if (this.activeTab === 'overview') {
|
|
this.initCharts();
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 删除分类
|
|
deleteCategory(categoryId) {
|
|
const category = this.categories.find(cat => cat.id === categoryId);
|
|
if (!category) return;
|
|
|
|
// 检查分类下是否有图片
|
|
if (category.imageCount > 0) {
|
|
this.$message.warning('该分类下还有图片,无法删除');
|
|
return;
|
|
}
|
|
|
|
this.$confirm(`确定要删除「${category.name}」分类吗?`, '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
const index = this.categories.findIndex(cat => cat.id === categoryId);
|
|
if (index > -1) {
|
|
this.categories.splice(index, 1);
|
|
this.$message.success('分类删除成功');
|
|
|
|
// 关闭设置菜单
|
|
this.settingsMenuVisible = false;
|
|
|
|
// 如果在概览页,重新初始化图表
|
|
if (this.activeTab === 'overview') {
|
|
this.initCharts();
|
|
}
|
|
}
|
|
}).catch(() => {
|
|
// 取消删除
|
|
});
|
|
},
|
|
|
|
// 更新分类状态
|
|
updateStatus(categoryId, isActive) {
|
|
const index = this.categories.findIndex(cat => cat.id === categoryId);
|
|
if (index > -1) {
|
|
this.categories[index].isActive = isActive;
|
|
this.$message.success(`分类${isActive ? '已启用' : '已禁用'}`);
|
|
|
|
// 如果在概览页,重新初始化图表
|
|
if (this.activeTab === 'overview') {
|
|
this.initCharts();
|
|
}
|
|
}
|
|
},
|
|
|
|
// 更新排序权重
|
|
updateSortOrder(categoryId, sortOrder) {
|
|
const index = this.categories.findIndex(cat => cat.id === categoryId);
|
|
if (index > -1) {
|
|
this.categories[index].sortOrder = sortOrder;
|
|
// 这里可以添加保存到服务器的逻辑
|
|
}
|
|
},
|
|
|
|
// 查看分类详情
|
|
viewCategoryDetail(category) {
|
|
this.categoryDetail = { ...category };
|
|
this.showCategoryDetail = true;
|
|
|
|
// 关闭设置菜单
|
|
this.settingsMenuVisible = false;
|
|
},
|
|
|
|
// 编辑当前查看的分类
|
|
editCurrentCategory() {
|
|
this.showCategoryDetail = false;
|
|
this.editCategory(this.categoryDetail);
|
|
},
|
|
|
|
// 查看图片
|
|
viewImage(image) {
|
|
this.selectedImage = { ...image };
|
|
this.showImageDialog = true;
|
|
},
|
|
|
|
// 显示设置菜单
|
|
showSettingsMenu(category, event) {
|
|
event.stopPropagation();
|
|
this.settingsMenuCategory = { ...category };
|
|
this.settingsMenuVisible = true;
|
|
},
|
|
|
|
// 关闭设置菜单
|
|
closeSettingsMenu() {
|
|
this.settingsMenuVisible = false;
|
|
},
|
|
|
|
// 处理退出登录
|
|
handleLogout() {
|
|
this.$confirm('确定要退出登录吗?', '提示', {
|
|
confirmButtonText: '确定',
|
|
cancelButtonText: '取消',
|
|
type: 'warning'
|
|
}).then(() => {
|
|
this.$message.success('退出成功');
|
|
setTimeout(() => {
|
|
window.location.href = 'admin-login.html';
|
|
}, 1000);
|
|
}).catch(() => {
|
|
// 取消退出
|
|
});
|
|
}
|
|
}
|
|
})
|
|
</script>
|
|
</body>
|
|
</html> |