|
@@ -0,0 +1,585 @@
|
|
|
|
|
+<template>
|
|
|
|
|
+ <div class="image-management">
|
|
|
|
|
+ <el-container>
|
|
|
|
|
+ <!-- 左侧分类树 -->
|
|
|
|
|
+ <el-aside width="280px" class="category-aside">
|
|
|
|
|
+ <div class="aside-header-buttons">
|
|
|
|
|
+ <el-button type="primary" :icon="Plus" @click="handleAddCategory">新增</el-button>
|
|
|
|
|
+ <el-button :icon="Edit" @click="handleEditSelectedCategory" :disabled="!currentCategory || currentCategory.id === '0'">修改</el-button>
|
|
|
|
|
+ <el-button type="danger" :icon="Delete" @click="handleDeleteSelectedCategory" :disabled="!currentCategory || currentCategory.id === '0'">删除</el-button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="aside-content">
|
|
|
|
|
+ <el-tree
|
|
|
|
|
+ ref="categoryTree"
|
|
|
|
|
+ :data="categories"
|
|
|
|
|
+ :props="defaultProps"
|
|
|
|
|
+ node-key="id"
|
|
|
|
|
+ default-expand-all
|
|
|
|
|
+ highlight-current
|
|
|
|
|
+ @node-click="handleCategoryClick"
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #default="{ node, data }">
|
|
|
|
|
+ <span class="custom-tree-node">
|
|
|
|
|
+ <span class="label">
|
|
|
|
|
+ <el-icon v-if="data.id === '0'"><FolderOpened /></el-icon>
|
|
|
|
|
+ <el-icon v-else><Folder /></el-icon>
|
|
|
|
|
+ {{ node.label }}
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-tree>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-aside>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 右侧图片列表 -->
|
|
|
|
|
+ <el-main class="image-main">
|
|
|
|
|
+ <div class="main-header">
|
|
|
|
|
+ <div class="header-left">
|
|
|
|
|
+ <span class="current-category">{{ currentCategoryName }}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="header-right">
|
|
|
|
|
+ <el-upload
|
|
|
|
|
+ ref="uploadRef"
|
|
|
|
|
+ class="upload-btn"
|
|
|
|
|
+ action="#"
|
|
|
|
|
+ :auto-upload="false"
|
|
|
|
|
+ :show-file-list="false"
|
|
|
|
|
+ multiple
|
|
|
|
|
+ accept="image/*"
|
|
|
|
|
+ :on-change="handleFileChange"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-button type="primary" :icon="Upload">新增图片</el-button>
|
|
|
|
|
+ </el-upload>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="image-list" v-loading="loading">
|
|
|
|
|
+ <el-table :data="images" style="width: 100%" height="calc(100vh - 240px)">
|
|
|
|
|
+ <el-table-column label="图片预览" width="120">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-image
|
|
|
|
|
+ class="table-image"
|
|
|
|
|
+ :src="scope.row.image_url"
|
|
|
|
|
+ :preview-src-list="[scope.row.image_url]"
|
|
|
|
|
+ fit="cover"
|
|
|
|
|
+ preview-teleported
|
|
|
|
|
+ >
|
|
|
|
|
+ <template #error>
|
|
|
|
|
+ <div class="image-error">
|
|
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-image>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="image_name" label="图片名称" min-width="150" show-overflow-tooltip />
|
|
|
|
|
+ <el-table-column prop="category_name" label="图片分类" width="120" />
|
|
|
|
|
+ <el-table-column prop="creator_name" label="创建人" width="120" />
|
|
|
|
|
+ <el-table-column prop="created_time" label="创建时间" width="170">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateTime(scope.row.created_time) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column prop="updated_time" label="修改时间" width="170">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ {{ formatDateTime(scope.row.updated_time) }}
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <el-table-column label="操作" width="120" fixed="right">
|
|
|
|
|
+ <template #default="scope">
|
|
|
|
|
+ <el-button type="primary" link @click="handlePreview(scope.row)">预览</el-button>
|
|
|
|
|
+ <el-button type="danger" link @click="handleDeleteImage(scope.row)">删除</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table-column>
|
|
|
|
|
+ <template #empty>
|
|
|
|
|
+ <el-empty description="暂无图片" />
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-table>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="pagination-container">
|
|
|
|
|
+ <el-pagination
|
|
|
|
|
+ v-model:current-page="queryParams.page"
|
|
|
|
|
+ v-model:page-size="queryParams.page_size"
|
|
|
|
|
+ :page-sizes="[10, 20, 50, 100]"
|
|
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
|
|
+ :total="total"
|
|
|
|
|
+ @size-change="handleSizeChange"
|
|
|
|
|
+ @current-change="handleCurrentChange"
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </el-main>
|
|
|
|
|
+ </el-container>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 分类弹窗 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="categoryDialogVisible"
|
|
|
|
|
+ :title="categoryForm.id ? '修改分类' : '新增分类'"
|
|
|
|
|
+ width="400px"
|
|
|
|
|
+ >
|
|
|
|
|
+ <el-form :model="categoryForm" label-width="80px">
|
|
|
|
|
+ <el-form-item label="分类名称" required>
|
|
|
|
|
+ <el-input v-model="categoryForm.type_name" placeholder="请输入分类名称" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="备注">
|
|
|
|
|
+ <el-input v-model="categoryForm.remark" type="textarea" placeholder="请输入备注" />
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="categoryDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" @click="submitCategory">确定</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 图片上传/编辑弹窗 -->
|
|
|
|
|
+ <el-dialog
|
|
|
|
|
+ v-model="uploadDialogVisible"
|
|
|
|
|
+ title="上传图片"
|
|
|
|
|
+ width="500px"
|
|
|
|
|
+ :close-on-click-modal="false"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="upload-list-container">
|
|
|
|
|
+ <div v-for="(item, index) in uploadFileList" :key="index" class="upload-item">
|
|
|
|
|
+ <div class="upload-item-preview">
|
|
|
|
|
+ <el-image :src="item.url" fit="cover" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="upload-item-info">
|
|
|
|
|
+ <el-input v-model="item.name" placeholder="请输入图片名称">
|
|
|
|
|
+ <template #append>{{ item.ext }}</template>
|
|
|
|
|
+ </el-input>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="upload-item-ops">
|
|
|
|
|
+ <el-button type="danger" :icon="Delete" circle size="small" @click="removeUploadFile(index)" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <template #footer>
|
|
|
|
|
+ <el-button @click="uploadDialogVisible = false">取消</el-button>
|
|
|
|
|
+ <el-button type="primary" :loading="uploading" @click="startBatchUpload">开始上传</el-button>
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-dialog>
|
|
|
|
|
+ </div>
|
|
|
|
|
+</template>
|
|
|
|
|
+
|
|
|
|
|
+<script setup lang="ts">
|
|
|
|
|
+import { ref, onMounted, reactive, computed } from 'vue'
|
|
|
|
|
+import { Plus, Edit, Delete, Upload, Picture, Folder, FolderOpened } from '@element-plus/icons-vue'
|
|
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
|
+import { imageApi, type ImageCategory, type ImageItem } from '@/api/image'
|
|
|
|
|
+import axios from 'axios'
|
|
|
|
|
+
|
|
|
|
|
+// --- 数据定义 ---
|
|
|
|
|
+
|
|
|
|
|
+const loading = ref(false)
|
|
|
|
|
+const categories = ref<ImageCategory[]>([])
|
|
|
|
|
+const images = ref<ImageItem[]>([])
|
|
|
|
|
+const total = ref(0)
|
|
|
|
|
+const currentCategory = ref<ImageCategory | null>(null)
|
|
|
|
|
+
|
|
|
|
|
+const defaultProps = {
|
|
|
|
|
+ children: 'children',
|
|
|
|
|
+ label: 'type_name'
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const queryParams = reactive({
|
|
|
|
|
+ category_id: '',
|
|
|
|
|
+ page: 1,
|
|
|
|
|
+ page_size: 10
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const categoryDialogVisible = ref(false)
|
|
|
|
|
+const uploadDialogVisible = ref(false)
|
|
|
|
|
+const uploading = ref(false)
|
|
|
|
|
+const uploadFileList = ref<any[]>([])
|
|
|
|
|
+const uploadRef = ref()
|
|
|
|
|
+
|
|
|
|
|
+const categoryForm = reactive({
|
|
|
|
|
+ id: '',
|
|
|
|
|
+ type_name: '',
|
|
|
|
|
+ parent_id: '0',
|
|
|
|
|
+ remark: ''
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const currentCategoryName = computed(() => {
|
|
|
|
|
+ return currentCategory.value ? currentCategory.value.type_name : '全部分类'
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+// --- 方法定义 ---
|
|
|
|
|
+
|
|
|
|
|
+const fetchCategories = async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await imageApi.getCategories()
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ categories.value = [
|
|
|
|
|
+ { id: '0', type_name: '全部分类', parent_id: '-1', children: res.data, created_time: '', updated_time: '' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取分类失败:', error)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const fetchImages = async () => {
|
|
|
|
|
+ loading.value = true
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await imageApi.getList(queryParams)
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ images.value = res.data.list
|
|
|
|
|
+ total.value = res.data.total
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('获取图片失败:', error)
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ loading.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleCategoryClick = (data: ImageCategory) => {
|
|
|
|
|
+ currentCategory.value = data
|
|
|
|
|
+ queryParams.category_id = data.id === '0' ? '' : data.id
|
|
|
|
|
+ queryParams.page = 1
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleEditSelectedCategory = () => {
|
|
|
|
|
+ if (currentCategory.value && currentCategory.value.id !== '0') {
|
|
|
|
|
+ handleEditCategory(currentCategory.value)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDeleteSelectedCategory = () => {
|
|
|
|
|
+ if (currentCategory.value && currentCategory.value.id !== '0') {
|
|
|
|
|
+ handleDeleteCategory(currentCategory.value)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleAddCategory = () => {
|
|
|
|
|
+ categoryForm.id = ''
|
|
|
|
|
+ categoryForm.type_name = ''
|
|
|
|
|
+ categoryForm.remark = ''
|
|
|
|
|
+ categoryForm.parent_id = currentCategory.value?.id === '0' ? '0' : (currentCategory.value?.id || '0')
|
|
|
|
|
+ categoryDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleEditCategory = (data: ImageCategory) => {
|
|
|
|
|
+ categoryForm.id = data.id
|
|
|
|
|
+ categoryForm.type_name = data.type_name
|
|
|
|
|
+ categoryForm.remark = data.remark || ''
|
|
|
|
|
+ categoryForm.parent_id = data.parent_id
|
|
|
|
|
+ categoryDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDeleteCategory = (data: ImageCategory) => {
|
|
|
|
|
+ ElMessageBox.confirm(`确定要删除分类 "${data.type_name}" 吗?`, '提示', {
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await imageApi.deleteCategory(data.id)
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ ElMessage.success('删除成功')
|
|
|
|
|
+ fetchCategories()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ ElMessage.error(error.message || '删除失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const submitCategory = async () => {
|
|
|
|
|
+ if (!categoryForm.type_name) {
|
|
|
|
|
+ return ElMessage.warning('请输入分类名称')
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ let res
|
|
|
|
|
+ if (categoryForm.id) {
|
|
|
|
|
+ res = await imageApi.updateCategory(categoryForm.id, categoryForm)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ res = await imageApi.addCategory(categoryForm)
|
|
|
|
|
+ }
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ ElMessage.success(categoryForm.id ? '更新成功' : '新增成功')
|
|
|
|
|
+ categoryDialogVisible.value = false
|
|
|
|
|
+ fetchCategories()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ ElMessage.error(error.message || '操作失败')
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleFileChange = (file: any) => {
|
|
|
|
|
+ const categoryId = queryParams.category_id || '0'
|
|
|
|
|
+ if (categoryId === '0') {
|
|
|
|
|
+ ElMessage.warning('请先在左侧选择一个具体的分类再上传图片')
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const name = file.name.replace(/\.[^/.]+$/, "")
|
|
|
|
|
+ const ext = file.name.split('.').pop()
|
|
|
|
|
+
|
|
|
|
|
+ uploadFileList.value.push({
|
|
|
|
|
+ file: file.raw,
|
|
|
|
|
+ name: name,
|
|
|
|
|
+ ext: `.${ext}`,
|
|
|
|
|
+ url: URL.createObjectURL(file.raw)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ uploadDialogVisible.value = true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const removeUploadFile = (index: number) => {
|
|
|
|
|
+ uploadFileList.value.splice(index, 1)
|
|
|
|
|
+ if (uploadFileList.value.length === 0) {
|
|
|
|
|
+ uploadDialogVisible.value = false
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const startBatchUpload = async () => {
|
|
|
|
|
+ if (uploadFileList.value.length === 0) return
|
|
|
|
|
+
|
|
|
|
|
+ uploading.value = true
|
|
|
|
|
+ let successCount = 0
|
|
|
|
|
+ let failCount = 0
|
|
|
|
|
+
|
|
|
|
|
+ const categoryId = queryParams.category_id
|
|
|
|
|
+
|
|
|
|
|
+ for (const item of uploadFileList.value) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 1. 获取预签名 URL
|
|
|
|
|
+ const res = await imageApi.getUploadUrl(item.file.name, item.file.type || 'application/octet-stream')
|
|
|
|
|
+ if (res.code !== 0) throw new Error(res.message)
|
|
|
|
|
+
|
|
|
|
|
+ const { upload_url, file_url } = res.data
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 直接上传到 MinIO
|
|
|
|
|
+ await axios.put(upload_url, item.file, {
|
|
|
|
|
+ headers: { 'Content-Type': item.file.type || 'application/octet-stream' }
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 保存到数据库 (使用修改后的名字)
|
|
|
|
|
+ await imageApi.add({
|
|
|
|
|
+ image_name: item.name,
|
|
|
|
|
+ image_url: file_url,
|
|
|
|
|
+ image_type: categoryId,
|
|
|
|
|
+ description: ''
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ successCount++
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error(`图片 ${item.name} 上传失败:`, error)
|
|
|
|
|
+ failCount++
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ uploading.value = false
|
|
|
|
|
+ uploadDialogVisible.value = false
|
|
|
|
|
+ uploadFileList.value = []
|
|
|
|
|
+
|
|
|
|
|
+ if (failCount === 0) {
|
|
|
|
|
+ ElMessage.success(`成功上传 ${successCount} 张图片`)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.warning(`上传完成:成功 ${successCount},失败 ${failCount}`)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleDeleteImage = (row: ImageItem) => {
|
|
|
|
|
+ ElMessageBox.confirm(`确定要删除图片 "${row.image_name}" 吗?`, '提示', {
|
|
|
|
|
+ type: 'warning'
|
|
|
|
|
+ }).then(async () => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await imageApi.delete(row.id)
|
|
|
|
|
+ if (res.code === 0) {
|
|
|
|
|
+ ElMessage.success('删除成功')
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ElMessage.error(res.message)
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ ElMessage.error(error.message || '删除失败')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handlePreview = (row: ImageItem) => {
|
|
|
|
|
+ // el-image 的预览功能已经集成在组件中了
|
|
|
|
|
+ // 这里可以手动触发大图预览,如果需要的话
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleSizeChange = (val: number) => {
|
|
|
|
|
+ queryParams.page_size = val
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const handleCurrentChange = (val: number) => {
|
|
|
|
|
+ queryParams.page = val
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const formatDateTime = (dateStr: string) => {
|
|
|
|
|
+ if (!dateStr) return '-'
|
|
|
|
|
+ const date = new Date(dateStr)
|
|
|
|
|
+ return date.toLocaleString()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+onMounted(() => {
|
|
|
|
|
+ fetchCategories()
|
|
|
|
|
+ fetchImages()
|
|
|
|
|
+})
|
|
|
|
|
+</script>
|
|
|
|
|
+
|
|
|
|
|
+<style scoped>
|
|
|
|
|
+.image-management {
|
|
|
|
|
+ height: calc(100vh - 120px);
|
|
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.category-aside {
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ margin-right: 20px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.aside-header-buttons {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f2f5;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ flex-wrap: wrap;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.aside-header-buttons .el-button {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.aside-content {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-tree-node {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ padding-right: 8px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-tree-node .label {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.custom-tree-node .ops {
|
|
|
|
|
+ display: none;
|
|
|
|
|
+ gap: 8px;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.el-tree-node__content:hover .ops {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.ops .el-icon:hover {
|
|
|
|
|
+ color: #409eff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.image-main {
|
|
|
|
|
+ background-color: #fff;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.05);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.main-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.current-category {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #303133;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.image-list {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.table-image {
|
|
|
|
|
+ width: 80px;
|
|
|
|
|
+ height: 60px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.image-error {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: #f5f7fa;
|
|
|
|
|
+ color: #909399;
|
|
|
|
|
+ font-size: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-list-container {
|
|
|
|
|
+ max-height: 400px;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-item {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ border-bottom: 1px solid #f0f2f5;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-item:last-child {
|
|
|
|
|
+ border-bottom: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-item-preview {
|
|
|
|
|
+ width: 60px;
|
|
|
|
|
+ height: 60px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-item-info {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-item-ops {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.pagination-container {
|
|
|
|
|
+ margin-top: 20px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: flex-end;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.upload-btn {
|
|
|
|
|
+ display: inline-block;
|
|
|
|
|
+}
|
|
|
|
|
+</style>
|