|
|
@@ -0,0 +1,685 @@
|
|
|
+<template>
|
|
|
+ <div class="material-library">
|
|
|
+ <div class="gva-table-box">
|
|
|
+ <div class="gva-btn-list">
|
|
|
+ <el-button type="primary" icon="Plus" @click="openAdd">新增素材</el-button>
|
|
|
+ <el-button icon="Refresh" @click="getList">刷新</el-button>
|
|
|
+ <el-input
|
|
|
+ v-model="searchKeyword"
|
|
|
+ placeholder="搜索素材名称、分类"
|
|
|
+ style="width: 280px; margin-left: auto;"
|
|
|
+ @keyup.enter="handleSearch"
|
|
|
+ >
|
|
|
+ <template #prefix>
|
|
|
+ <el-icon><Search /></el-icon>
|
|
|
+ </template>
|
|
|
+ </el-input>
|
|
|
+ <el-button type="primary" @click="handleSearch">查询</el-button>
|
|
|
+ </div>
|
|
|
+ <div class="category-bar">
|
|
|
+ <div class="category-row">
|
|
|
+ <span class="category-label">分类:</span>
|
|
|
+ <span
|
|
|
+ class="category-chip"
|
|
|
+ :class="{ active: !selectedCategoryId }"
|
|
|
+ @click="selectCategory(null)"
|
|
|
+ >全部</span>
|
|
|
+ <span
|
|
|
+ v-for="c1 in categoryList"
|
|
|
+ :key="c1.id"
|
|
|
+ class="category-chip"
|
|
|
+ :class="{ active: selectedCategoryId === c1.id }"
|
|
|
+ @click="selectCategory(c1.id)"
|
|
|
+ >{{ c1.category_name }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-loading="loading" class="material-waterfall">
|
|
|
+ <div v-for="item in tableData" :key="item.id" class="material-card">
|
|
|
+ <div class="material-preview" @click="previewImage(item)">
|
|
|
+ <span class="material-id-badge">素材 {{ item.id }}</span>
|
|
|
+ <el-image
|
|
|
+ :src="formatImageUrl(item.material_url)"
|
|
|
+ fit="cover"
|
|
|
+ class="material-img"
|
|
|
+ >
|
|
|
+ <template #error>
|
|
|
+ <div class="material-img-error">
|
|
|
+ <el-icon><Picture /></el-icon>
|
|
|
+ <span>加载失败</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-image>
|
|
|
+ </div>
|
|
|
+ <div class="material-info">
|
|
|
+ <div class="material-info-text">
|
|
|
+ <div class="material-name" :title="item.material_name">{{ item.material_name || '-' }}</div>
|
|
|
+ <div class="material-meta">{{ item.category_name || '未分类' }}</div>
|
|
|
+ </div>
|
|
|
+ <div class="material-card-actions" @click.stop>
|
|
|
+ <el-button type="primary" link icon="Edit" title="修改" @click="openEdit(item)" />
|
|
|
+ <el-button type="danger" link icon="Delete" title="删除" @click="handleDelete(item)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="!loading && tableData.length === 0" class="material-empty">暂无素材</div>
|
|
|
+ <div v-if="total > 0" class="gva-pagination">
|
|
|
+ <el-pagination
|
|
|
+ v-model:current-page="page"
|
|
|
+ v-model:page-size="pageSize"
|
|
|
+ :page-sizes="[50, 100, 200, 300]"
|
|
|
+ :total="total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ @size-change="getList"
|
|
|
+ @current-change="getList"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-image-viewer
|
|
|
+ v-if="previewVisible"
|
|
|
+ :url-list="previewList"
|
|
|
+ :initial-index="previewIndex"
|
|
|
+ @close="previewVisible = false"
|
|
|
+ />
|
|
|
+ <el-dialog v-model="addVisible" title="新增素材" width="760px" class="add-material-dialog" @closed="resetAddForm">
|
|
|
+ <el-form ref="addFormRef" :model="addForm" :rules="addRules" label-width="80px">
|
|
|
+ <el-form-item label="分类" prop="Category_id">
|
|
|
+ <el-select v-model="addForm.Category_id" placeholder="选择分类" style="width: 100%">
|
|
|
+ <el-option v-for="opt in categoryOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="图片上传">
|
|
|
+ <div class="add-upload-area add-upload-fixed">
|
|
|
+ <el-upload
|
|
|
+ v-if="addFileList.length < 10"
|
|
|
+ ref="addUploadRef"
|
|
|
+ class="add-upload-drag"
|
|
|
+ drag
|
|
|
+ :auto-upload="false"
|
|
|
+ :show-file-list="false"
|
|
|
+ accept="image/jpeg,image/png,image/webp,image/gif"
|
|
|
+ :on-change="onAddFileChange"
|
|
|
+ multiple
|
|
|
+ >
|
|
|
+ <el-icon class="add-upload-icon" :size="48"><UploadFilled /></el-icon>
|
|
|
+ <div class="add-upload-text">将图片拖到此处,或<em>点击上传</em></div>
|
|
|
+ <div class="add-upload-hint">最多10张,每张≤1MB</div>
|
|
|
+ </el-upload>
|
|
|
+ <div class="add-file-list">
|
|
|
+ <div v-for="(item, idx) in addFileList" :key="item.id" class="add-file-item">
|
|
|
+ <div class="add-file-preview">
|
|
|
+ <el-image :src="item.url" fit="cover" />
|
|
|
+ <span class="add-file-remove" @click.stop="removeAddFile(idx)">×</span>
|
|
|
+ </div>
|
|
|
+ <el-input
|
|
|
+ v-model="item.material_name"
|
|
|
+ size="small"
|
|
|
+ class="add-file-name-input"
|
|
|
+ placeholder="素材名称"
|
|
|
+ maxlength="100"
|
|
|
+ />
|
|
|
+ <div class="add-file-size">{{ item.sizeStr }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="addVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="addLoading" :disabled="addFileList.length === 0" @click="submitAdd">确定上传</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ <el-dialog v-model="editVisible" title="修改素材" width="420px" @closed="editFormRef?.resetFields()">
|
|
|
+ <el-form ref="editFormRef" :model="editForm" :rules="editRules" label-width="80px">
|
|
|
+ <el-form-item label="素材名称" prop="material_name">
|
|
|
+ <el-input v-model="editForm.material_name" placeholder="素材名称" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="分类" prop="Category_id">
|
|
|
+ <el-select v-model="editForm.Category_id" placeholder="选择分类" style="width: 100%">
|
|
|
+ <el-option
|
|
|
+ v-for="opt in categoryOptions"
|
|
|
+ :key="opt.value"
|
|
|
+ :label="opt.label"
|
|
|
+ :value="opt.value"
|
|
|
+ />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="editVisible = false">取消</el-button>
|
|
|
+ <el-button type="primary" :loading="editLoading" @click="submitEdit">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { Material_List, Material_Category_List, materialDelete, Material_Update, Material_Add } from '@/api/mes/job'
|
|
|
+import { resolveMaterialUrl } from '@/view/TemplateManagement/utils'
|
|
|
+import { ref, computed, onMounted, toRaw } from 'vue'
|
|
|
+import { Search, Picture, Delete, Edit, Plus, UploadFilled } from '@element-plus/icons-vue'
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
+import { useUserStore } from '@/pinia/modules/user'
|
|
|
+
|
|
|
+defineOptions({ name: 'MaterialLibrary' })
|
|
|
+
|
|
|
+const userStore = useUserStore()
|
|
|
+
|
|
|
+const loading = ref(false)
|
|
|
+const searchKeyword = ref('')
|
|
|
+const categoryList = ref([])
|
|
|
+const selectedCategoryId = ref(null)
|
|
|
+const tableData = ref([])
|
|
|
+const page = ref(1)
|
|
|
+const pageSize = ref(30)
|
|
|
+const total = ref(0)
|
|
|
+const previewVisible = ref(false)
|
|
|
+const previewList = ref([])
|
|
|
+const previewIndex = ref(0)
|
|
|
+const editVisible = ref(false)
|
|
|
+const editLoading = ref(false)
|
|
|
+const editFormRef = ref(null)
|
|
|
+const editForm = ref({ id: null, material_name: '', Category_id: null })
|
|
|
+const editRules = { material_name: [{ required: true, message: '请输入素材名称', trigger: 'blur' }] }
|
|
|
+
|
|
|
+const addVisible = ref(false)
|
|
|
+const addLoading = ref(false)
|
|
|
+const addFormRef = ref(null)
|
|
|
+const addUploadRef = ref(null)
|
|
|
+/** 与 addFileList 同步:每张待上传图的名称,提交时组成 material_name 数组 */
|
|
|
+const addForm = ref({ Category_id: null, material_name: [] })
|
|
|
+const addRules = { Category_id: [{ required: true, message: '请选择分类', trigger: 'change' }] }
|
|
|
+const addFileList = ref([])
|
|
|
+const addFileId = ref(0)
|
|
|
+const MAX_FILES = 10
|
|
|
+const MAX_SIZE = 1024 * 1024
|
|
|
+
|
|
|
+const formatFileSize = (bytes) => {
|
|
|
+ if (!bytes) return '0 B'
|
|
|
+ if (bytes < 1024) return `${bytes} B`
|
|
|
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
|
+ return `${(bytes / 1024 / 1024).toFixed(2)} MB`
|
|
|
+}
|
|
|
+
|
|
|
+const categoryOptions = computed(() => {
|
|
|
+ const opts = []
|
|
|
+ for (const c1 of categoryList.value) {
|
|
|
+ opts.push({ label: c1.category_name, value: c1.id })
|
|
|
+ for (const c2 of c1.children || []) {
|
|
|
+ opts.push({ label: `${c1.category_name} / ${c2.category_name}`, value: c2.id })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return opts
|
|
|
+})
|
|
|
+
|
|
|
+const formatImageUrl = (path) => {
|
|
|
+ if (!path || typeof path !== 'string') return ''
|
|
|
+ return resolveMaterialUrl(path)
|
|
|
+}
|
|
|
+
|
|
|
+const selectCategory = (id) => {
|
|
|
+ selectedCategoryId.value = id
|
|
|
+ page.value = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const handleSearch = () => {
|
|
|
+ page.value = 1
|
|
|
+ getList()
|
|
|
+}
|
|
|
+
|
|
|
+const defaultMaterialNameFromFile = (file) => {
|
|
|
+ const n = file?.name || ''
|
|
|
+ return n.replace(/\.[^.]+$/, '') || n || '未命名'
|
|
|
+}
|
|
|
+
|
|
|
+const openAdd = () => {
|
|
|
+ addForm.value = { Category_id: null, material_name: [] }
|
|
|
+ addFileList.value = []
|
|
|
+ addVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const resetAddForm = () => {
|
|
|
+ addFileList.value.forEach((item) => URL.revokeObjectURL(item.url))
|
|
|
+ addFileList.value = []
|
|
|
+ addForm.value = { Category_id: null, material_name: [] }
|
|
|
+ addFormRef.value?.resetFields()
|
|
|
+}
|
|
|
+
|
|
|
+const onAddFileChange = (uploadFile) => {
|
|
|
+ const file = uploadFile?.raw
|
|
|
+ if (!file) return
|
|
|
+ if (addFileList.value.length >= MAX_FILES) {
|
|
|
+ ElMessage.warning(`最多上传 ${MAX_FILES} 张图片`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (file.size > MAX_SIZE) {
|
|
|
+ ElMessage.warning(`「${file.name}」超过 1MB 限制`)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const url = URL.createObjectURL(file)
|
|
|
+ addFileList.value.push({
|
|
|
+ id: ++addFileId.value,
|
|
|
+ file,
|
|
|
+ url,
|
|
|
+ sizeStr: formatFileSize(file.size),
|
|
|
+ material_name: defaultMaterialNameFromFile(file)
|
|
|
+ })
|
|
|
+ addUploadRef.value?.clearFiles()
|
|
|
+}
|
|
|
+
|
|
|
+const removeAddFile = (idx) => {
|
|
|
+ const item = addFileList.value[idx]
|
|
|
+ URL.revokeObjectURL(item.url)
|
|
|
+ addFileList.value.splice(idx, 1)
|
|
|
+}
|
|
|
+
|
|
|
+const submitAdd = async () => {
|
|
|
+ if (!addFormRef.value) return
|
|
|
+ try {
|
|
|
+ await addFormRef.value.validate()
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (addFileList.value.length === 0) {
|
|
|
+ ElMessage.warning('请至少上传一张图片')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ /** 与文件顺序一致的名称列表(用于表单状态) */
|
|
|
+ const material_name = addFileList.value.map((item) => {
|
|
|
+ const n = (item.material_name || '').trim()
|
|
|
+ return n || defaultMaterialNameFromFile(item.file)
|
|
|
+ })
|
|
|
+ addForm.value.material_name = material_name
|
|
|
+ addLoading.value = true
|
|
|
+ try {
|
|
|
+ /**
|
|
|
+ * 单次请求:每张图一对 —— img[] 二进制、material_name[] 名称(同序,便于 PHP 遍历)
|
|
|
+ */
|
|
|
+ const formData = new FormData()
|
|
|
+ formData.append('Category_id', String(addForm.value.Category_id ?? ''))
|
|
|
+ formData.append('sys_id', String(userStore.userInfo?.nickName ?? ''))
|
|
|
+ for (const item of addFileList.value) {
|
|
|
+ const rawFile = toRaw(item.file) || item.file
|
|
|
+ if (!rawFile || !(rawFile instanceof Blob)) {
|
|
|
+ ElMessage.error('存在无效文件,请移除后重新选择')
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const name = (item.material_name || '').trim() || defaultMaterialNameFromFile(rawFile)
|
|
|
+ formData.append('img[]', rawFile, rawFile.name || 'image')
|
|
|
+ formData.append('material_name[]', name)
|
|
|
+ }
|
|
|
+ const res = await Material_Add(formData)
|
|
|
+ if (res.code === 0) {
|
|
|
+ const n = addFileList.value.length
|
|
|
+ ElMessage.success(res.msg || `成功上传 ${n} 张`)
|
|
|
+ addVisible.value = false
|
|
|
+ getList()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '上传失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Material_Add error:', e)
|
|
|
+ ElMessage.error('上传失败')
|
|
|
+ } finally {
|
|
|
+ addLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getCategoryList = async () => {
|
|
|
+ try {
|
|
|
+ const res = await Material_Category_List()
|
|
|
+ if (res.code === 0 && Array.isArray(res.data)) {
|
|
|
+ categoryList.value = res.data
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Material_Category_List error:', e)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const getList = async () => {
|
|
|
+ loading.value = true
|
|
|
+ try {
|
|
|
+ const params = {
|
|
|
+ search: searchKeyword.value?.trim() || '',
|
|
|
+ page: page.value,
|
|
|
+ pageSize: pageSize.value
|
|
|
+ }
|
|
|
+ if (selectedCategoryId.value != null && selectedCategoryId.value !== '') {
|
|
|
+ params.Category_id = selectedCategoryId.value
|
|
|
+ }
|
|
|
+ const res = await Material_List(params)
|
|
|
+ if (res.code === 0) {
|
|
|
+ tableData.value = Array.isArray(res.data) ? res.data : []
|
|
|
+ total.value = res.total ?? res.count ?? tableData.value.length
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '获取列表失败')
|
|
|
+ tableData.value = []
|
|
|
+ total.value = 0
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Material_List error:', e)
|
|
|
+ ElMessage.error('获取列表失败')
|
|
|
+ tableData.value = []
|
|
|
+ total.value = 0
|
|
|
+ } finally {
|
|
|
+ loading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const openEdit = (item) => {
|
|
|
+ editForm.value = {
|
|
|
+ id: item.id,
|
|
|
+ material_name: item.material_name ?? '',
|
|
|
+ Category_id: item.Category_id ?? item.category_id ?? null
|
|
|
+ }
|
|
|
+ editVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+const submitEdit = async () => {
|
|
|
+ if (!editFormRef.value) return
|
|
|
+ try {
|
|
|
+ await editFormRef.value.validate()
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ editLoading.value = true
|
|
|
+ try {
|
|
|
+ const payload = {
|
|
|
+ id: editForm.value.id,
|
|
|
+ material_name: editForm.value.material_name,
|
|
|
+ Category_id: editForm.value.Category_id ?? null
|
|
|
+ }
|
|
|
+ const res = await Material_Update(payload)
|
|
|
+ if (res.code === 0) {
|
|
|
+ ElMessage.success(res.msg || '修改成功')
|
|
|
+ editVisible.value = false
|
|
|
+ getList()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.msg || '修改失败')
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.error('Material_Update error:', e)
|
|
|
+ ElMessage.error('修改失败')
|
|
|
+ } finally {
|
|
|
+ editLoading.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const handleDelete = async (item) => {
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm(`确定删除素材「${item.material_name || '未命名'}」?`, '提示', {
|
|
|
+ type: 'warning'
|
|
|
+ })
|
|
|
+ } catch {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ const res = await materialDelete({ id: item.id })
|
|
|
+ if (res.code === 0) {
|
|
|
+ ElMessage.success(res.msg || '删除成功')
|
|
|
+ getList()
|
|
|
+ }
|
|
|
+ // code !== 0(如已被模板使用):全局 axios 拦截器已弹出 res.msg,此处不再重复提示
|
|
|
+ } catch (e) {
|
|
|
+ console.error('materialDelete error:', e)
|
|
|
+ // 业务失败(code≠0)走 resolve,已由全局拦截器提示;此处仅兜底网络等异常
|
|
|
+ ElMessage.error('删除失败')
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const previewImage = (item) => {
|
|
|
+ const url = formatImageUrl(item.material_url)
|
|
|
+ if (!url) return
|
|
|
+ const list = tableData.value.map((row) => formatImageUrl(row.material_url)).filter(Boolean)
|
|
|
+ const idx = list.indexOf(url)
|
|
|
+ previewList.value = list
|
|
|
+ previewIndex.value = idx >= 0 ? idx : 0
|
|
|
+ previewVisible.value = true
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await getCategoryList()
|
|
|
+ getList()
|
|
|
+})
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.material-library { padding: 0 12px; }
|
|
|
+.category-bar {
|
|
|
+ margin-bottom: 12px;
|
|
|
+ padding: 12px;
|
|
|
+ background: #fafafa;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+.category-row {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+.category-row:last-child { margin-bottom: 0; }
|
|
|
+.category-label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #606266;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.category-chip {
|
|
|
+ padding: 4px 12px;
|
|
|
+ font-size: 13px;
|
|
|
+ border-radius: 4px;
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+.category-chip:hover { border-color: #409eff; color: #409eff; }
|
|
|
+.category-chip.active { background: #409eff; border-color: #409eff; color: #fff; }
|
|
|
+/* 用 Grid 按行从左到右排;勿用 column-count,多列布局会先竖向填满一列再下一列 */
|
|
|
+.material-waterfall {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
|
+ gap: 16px;
|
|
|
+ padding: 12px 0;
|
|
|
+ min-height: 200px;
|
|
|
+}
|
|
|
+@media (max-width: 1800px) { .material-waterfall { grid-template-columns: repeat(5, minmax(0, 1fr)); } }
|
|
|
+@media (max-width: 1400px) { .material-waterfall { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
|
|
|
+@media (max-width: 900px) { .material-waterfall { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
|
|
|
+@media (max-width: 600px) { .material-waterfall { grid-template-columns: minmax(0, 1fr); } }
|
|
|
+.material-card {
|
|
|
+ min-width: 0;
|
|
|
+ border: 1px solid #ebeef5;
|
|
|
+ border-radius: 8px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #fff;
|
|
|
+ transition: box-shadow 0.2s;
|
|
|
+}
|
|
|
+.material-card:hover { box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); }
|
|
|
+.material-id-badge {
|
|
|
+ position: absolute;
|
|
|
+ top: 8px;
|
|
|
+ left: 8px;
|
|
|
+ z-index: 2;
|
|
|
+ padding: 2px 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.25;
|
|
|
+ color: #fff;
|
|
|
+ background: #333;
|
|
|
+ border-radius: 4px;
|
|
|
+ pointer-events: none;
|
|
|
+ max-width: calc(100% - 16px);
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.material-card-actions {
|
|
|
+ flex-shrink: 0;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 2px;
|
|
|
+ padding: 2px 0 2px 4px;
|
|
|
+}
|
|
|
+.material-card-actions .el-button { padding: 4px; }
|
|
|
+.material-preview {
|
|
|
+ position: relative;
|
|
|
+ aspect-ratio: 1;
|
|
|
+ background: #f5f7fa;
|
|
|
+ cursor: pointer;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.material-img {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: block;
|
|
|
+}
|
|
|
+.material-img-error {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 12px;
|
|
|
+ gap: 4px;
|
|
|
+}
|
|
|
+.material-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: flex-end;
|
|
|
+ justify-content: space-between;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 8px;
|
|
|
+ min-height: 52px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.material-info-text {
|
|
|
+ min-width: 0;
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+.material-name {
|
|
|
+ font-size: 13px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.material-meta {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #909399;
|
|
|
+ margin-top: 4px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.material-empty {
|
|
|
+ text-align: center;
|
|
|
+ padding: 60px;
|
|
|
+ color: #909399;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+.gva-pagination {
|
|
|
+ margin-top: 16px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+.add-upload-area { margin-bottom: 0; width: 100%; }
|
|
|
+.add-upload-fixed {
|
|
|
+ width: 100%;
|
|
|
+ height: 420px;
|
|
|
+ min-height: 420px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ overflow: hidden;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.add-upload-fixed .add-upload-drag {
|
|
|
+ flex-shrink: 0;
|
|
|
+ width: 100%;
|
|
|
+ height: 130px;
|
|
|
+ min-height: 130px;
|
|
|
+}
|
|
|
+.add-upload-fixed .add-file-list {
|
|
|
+ flex: 1;
|
|
|
+ min-height: 0;
|
|
|
+ min-width: 0;
|
|
|
+ width: 100%;
|
|
|
+ margin-top: 16px;
|
|
|
+ padding-top: 16px;
|
|
|
+ border-top: 1px solid #ebeef5;
|
|
|
+ overflow-y: auto !important;
|
|
|
+}
|
|
|
+:deep(.add-material-dialog .el-dialog__body) {
|
|
|
+ height: 520px;
|
|
|
+ min-height: 520px;
|
|
|
+ overflow: hidden;
|
|
|
+ width: 100%;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+:deep(.add-material-dialog .el-form-item__content) {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.add-upload-drag :deep(.el-upload) { width: 100%; height: 100%; }
|
|
|
+.add-upload-drag :deep(.el-upload-dragger) {
|
|
|
+ width: 100%;
|
|
|
+ height: 100%;
|
|
|
+ padding: 20px 24px;
|
|
|
+ border-radius: 8px;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
+.add-upload-icon { color: #c0c4cc; margin-bottom: 12px; }
|
|
|
+.add-upload-text { font-size: 14px; color: #606266; }
|
|
|
+.add-upload-text em { color: #409eff; font-style: normal; }
|
|
|
+.add-upload-hint { font-size: 12px; margin-top: 8px; color: #c0c4cc; }
|
|
|
+.add-file-list {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 12px;
|
|
|
+ margin-top: 12px;
|
|
|
+ width: 100%;
|
|
|
+ align-content: flex-start;
|
|
|
+ padding-bottom: 4px;
|
|
|
+}
|
|
|
+.add-file-item {
|
|
|
+ width: 128px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+.add-file-name-input {
|
|
|
+ margin-top: 6px;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+.add-file-name-input :deep(.el-input__wrapper) {
|
|
|
+ padding: 2px 8px;
|
|
|
+}
|
|
|
+.add-file-preview {
|
|
|
+ position: relative;
|
|
|
+ aspect-ratio: 1;
|
|
|
+ border-radius: 6px;
|
|
|
+ overflow: hidden;
|
|
|
+ background: #f5f7fa;
|
|
|
+}
|
|
|
+.add-file-preview .el-image { width: 100%; height: 100%; display: block; }
|
|
|
+.add-file-remove {
|
|
|
+ position: absolute;
|
|
|
+ top: 4px;
|
|
|
+ right: 4px;
|
|
|
+ width: 22px;
|
|
|
+ height: 22px;
|
|
|
+ line-height: 20px;
|
|
|
+ text-align: center;
|
|
|
+ font-size: 18px;
|
|
|
+ color: #fff;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ border-radius: 50%;
|
|
|
+ cursor: pointer;
|
|
|
+ opacity: 0;
|
|
|
+ transition: opacity 0.2s;
|
|
|
+}
|
|
|
+.add-file-item:hover .add-file-remove { opacity: 1; }
|
|
|
+.add-file-remove:hover { background: rgba(245, 108, 108, 0.9); }
|
|
|
+.add-file-size { font-size: 11px; color: #909399; margin-top: 4px; }
|
|
|
+</style>
|