feat: 管理员 配置信息类型新增file,file_list,string_list类型
Build and Deploy Vue3 / build (push) Successful in 1m34s
Build and Deploy Vue3 / deploy (push) Successful in 1m0s

This commit is contained in:
2026-03-10 13:08:49 +08:00
parent fe29a8b3d0
commit cdd8f86b92
4 changed files with 573 additions and 1018 deletions
+520 -4
View File
@@ -128,6 +128,40 @@
<el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip>
<template #default="{ row }">
<span v-if="row.type === 'bool'">{{ row.value ? '' : '' }}</span>
<span v-else-if="row.type === 'file'">
<el-link v-if="row.value" type="primary" @click="previewFile(row.value)">
文件ID: {{ row.value }}
</el-link>
<span v-else>-</span>
</span>
<span v-else-if="row.type === 'file_list'">
<div v-if="row.value" class="file-list-display">
<el-link
v-for="(fileId, index) in getFileList(row.value)"
:key="fileId"
type="primary"
@click="previewFile(fileId)"
class="file-link"
>
文件{{ index + 1 }}: {{ fileId }}
</el-link>
</div>
<span v-else>-</span>
</span>
<span v-else-if="row.type === 'string_list'">
<div v-if="row.value" class="string-list-display">
<el-tag
v-for="(item, index) in getStringList(row.value)"
:key="index"
type="primary"
size="small"
class="string-tag"
>
{{ item }}
</el-tag>
</div>
<span v-else>-</span>
</span>
<span v-else>{{ row.value }}</span>
</template>
</el-table-column>
@@ -185,6 +219,7 @@
:title="groupDialogTitle"
width="500px"
destroy-on-close
class="dialog-scrollable"
>
<el-form
ref="groupFormRef"
@@ -216,6 +251,7 @@
:title="settingDialogTitle"
width="600px"
destroy-on-close
class="dialog-scrollable"
>
<el-form
ref="settingFormRef"
@@ -242,6 +278,9 @@
<el-option label="整数 (int)" value="int" />
<el-option label="浮点数 (float)" value="float" />
<el-option label="布尔值 (bool)" value="bool" />
<el-option label="文件 (file)" value="file" />
<el-option label="多文件 (file_list)" value="file_list" />
<el-option label="字符串列表 (string_list)" value="string_list" />
</el-select>
</el-form-item>
<el-form-item label="值" prop="value">
@@ -271,6 +310,91 @@
v-else-if="settingForm.type === 'bool'"
v-model="settingForm.value"
/>
<div v-else-if="settingForm.type === 'file'" class="file-upload-section">
<div class="file-info-display" v-if="settingForm.value && fileInfo">
<div class="file-details">
<span class="file-name" :title="fileInfo.realName || fileInfo.saveName">{{ fileInfo.realName || fileInfo.saveName }}</span>
<span class="file-id">文件ID: {{ settingForm.value }}</span>
<span class="file-size">{{ formatFileSize(fileInfo.size) }}</span>
</div>
<el-button type="danger" size="small" @click="clearFile">删除</el-button>
</div>
<el-upload
v-else
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileChange"
accept="*/*"
drag
class="file-uploader"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持任意文件类型文件上传后将自动保存并获取文件ID
</div>
</template>
</el-upload>
</div>
<div v-else-if="settingForm.type === 'file_list'" class="file-list-section">
<div class="file-list-info-display" v-if="getFileList(settingForm.value).length > 0">
<div class="file-list-details">
<div v-for="(fileInfo, index) in fileListInfo" :key="fileInfo.id" class="file-item">
<div class="file-details">
<span class="file-name" :title="fileInfo.realName || fileInfo.saveName">{{ fileInfo.realName || fileInfo.saveName }}</span>
<span class="file-id">文件ID: {{ fileInfo.id }}</span>
<span class="file-size">{{ formatFileSize(fileInfo.size) }}</span>
</div>
<el-button type="danger" size="small" @click="removeFile(index)">删除</el-button>
</div>
</div>
</div>
<el-upload
:auto-upload="false"
:show-file-list="false"
:on-change="handleFileListChange"
accept="*/*"
drag
class="file-uploader"
>
<el-icon class="el-icon--upload"><UploadFilled /></el-icon>
<div class="el-upload__text">
将文件拖到此处<em>点击上传</em>
</div>
<template #tip>
<div class="el-upload__tip">
支持上传多个文件文件ID将用逗号分隔存储
</div>
</template>
</el-upload>
</div>
<div v-else-if="settingForm.type === 'string_list'" class="string-list-section">
<div class="string-list-display" v-if="getStringList(settingForm.value).length > 0">
<div class="string-list-items">
<el-tag
v-for="(item, index) in getStringList(settingForm.value)"
:key="index"
closable
@close="removeStringItem(index)"
class="string-item"
>
{{ item }}
</el-tag>
</div>
</div>
<div class="string-list-input">
<el-input
v-model="newStringItem"
placeholder="请输入字符串,按回车添加"
@keyup.enter="addStringItem"
style="width: 100%"
/>
<el-button type="primary" @click="addStringItem" style="margin-top: 10px">添加</el-button>
</div>
</div>
<el-input
v-else
v-model="settingForm.value"
@@ -304,7 +428,7 @@
import { ref, reactive, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete } from '@element-plus/icons-vue'
import { Search, Plus, Delete, UploadFilled } from '@element-plus/icons-vue'
import {
getSettingGroupList,
getSettingGroupInfo,
@@ -318,6 +442,7 @@ import {
setSettingOpen,
deleteSetting
} from '@/api/admin/setting'
import { uploadFile } from '@/api/admin/file'
const route = useRoute()
@@ -393,6 +518,10 @@ const settingDialogVisible = ref(false)
const settingDialogTitle = ref('新增配置')
const settingFormRef = ref(null)
const toggleLoading = ref(null)
const fileInfo = ref(null)
const fileUploading = ref(false)
const fileListInfo = ref([])
const newStringItem = ref('')
// 格式化日期时间
const formatDate = (dateString) => {
@@ -413,7 +542,10 @@ const getTypeColor = (type) => {
'string': 'primary',
'int': 'success',
'float': 'warning',
'bool': 'info'
'bool': 'info',
'file': 'danger',
'file_list': 'danger',
'string_list': 'primary'
}
return colorMap[type] || ''
}
@@ -630,11 +762,134 @@ const handleTypeChange = (type) => {
settingForm.value = false
} else if (type === 'int' || type === 'float') {
settingForm.value = 0
} else if (type === 'file') {
settingForm.value = ''
fileInfo.value = null
} else if (type === 'file_list') {
settingForm.value = ''
fileListInfo.value = []
} else if (type === 'string_list') {
settingForm.value = ''
newStringItem.value = ''
} else {
settingForm.value = ''
}
}
// 文件变化处理
const handleFileChange = async (file) => {
fileUploading.value = true
try {
const formData = new FormData()
formData.append('file_names', file.name) // Add file_names as an array
formData.append('files', file.raw)
formData.append('update_type','cover')
formData.append('open_down','true')
const res = await uploadFile(formData)
if (res.data.code === 200 && res.data.data.length > 0) {
const uploadedFile = res.data.data[0]
settingForm.value = String(uploadedFile.id)
fileInfo.value = uploadedFile
ElMessage.success('文件上传成功')
} else {
ElMessage.error(res.data.message || '文件上传失败')
}
} catch (error) {
console.error('文件上传失败:', error)
ElMessage.error('文件上传失败')
} finally {
fileUploading.value = false
}
}
// 清除文件
const clearFile = () => {
settingForm.value = ''
fileInfo.value = null
}
// 格式化文件大小
const formatFileSize = (bytes) => {
if (!bytes) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
}
// 预览文件
const previewFile = (fileId) => {
// 这里可以根据需要实现文件预览逻辑
ElMessage.info(`文件ID: ${fileId}`)
}
// 获取文件列表
const getFileList = (value) => {
if (!value) return []
return value.split(',').filter(id => id.trim())
}
// 获取字符串列表
const getStringList = (value) => {
if (!value) return []
return value.split(',').filter(str => str.trim())
}
// 文件列表变化处理
const handleFileListChange = async (file) => {
fileUploading.value = true
try {
const formData = new FormData()
formData.append('file_names', file.name)
formData.append('files', file.raw)
formData.append('update_type','cover')
formData.append('open_down','true')
const res = await uploadFile(formData)
if (res.data.code === 200 && res.data.data.length > 0) {
const uploadedFile = res.data.data[0]
const currentFileIds = getFileList(settingForm.value)
currentFileIds.push(String(uploadedFile.id))
settingForm.value = currentFileIds.join(',')
fileListInfo.value.push(uploadedFile)
ElMessage.success('文件上传成功')
} else {
ElMessage.error(res.data.message || '文件上传失败')
}
} catch (error) {
console.error('文件上传失败:', error)
ElMessage.error('文件上传失败')
} finally {
fileUploading.value = false
}
}
// 移除文件
const removeFile = (index) => {
const currentFileIds = getFileList(settingForm.value)
currentFileIds.splice(index, 1)
settingForm.value = currentFileIds.join(',')
fileListInfo.value.splice(index, 1)
}
// 添加字符串项
const addStringItem = () => {
if (newStringItem.value.trim()) {
const currentItems = getStringList(settingForm.value)
currentItems.push(newStringItem.value.trim())
settingForm.value = currentItems.join(',')
newStringItem.value = ''
}
}
// 移除字符串项
const removeStringItem = (index) => {
const currentItems = getStringList(settingForm.value)
currentItems.splice(index, 1)
settingForm.value = currentItems.join(',')
}
const handleAddSetting = async () => {
settingDialogTitle.value = '新增配置'
// 刷新配置组列表以确保数据最新
@@ -648,6 +903,9 @@ const handleAddSetting = async () => {
open: false,
note: ''
})
fileInfo.value = null
fileListInfo.value = []
newStringItem.value = ''
settingDialogVisible.value = true
}
@@ -672,6 +930,12 @@ const handleEditSetting = async (row) => {
settingForm.value = parseInt(data.value) || 0
} else if (data.type === 'float') {
settingForm.value = parseFloat(data.value) || 0
} else if (data.type === 'file') {
fileInfo.value = null // 如果需要获取文件信息,可以调用文件详情接口
} else if (data.type === 'file_list') {
fileListInfo.value = [] // 如果需要获取文件信息,可以调用文件详情接口
} else if (data.type === 'string_list') {
newStringItem.value = ''
}
settingDialogVisible.value = true
}
@@ -888,8 +1152,71 @@ onMounted(() => {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
:deep(.dialog-scrollable .el-dialog) {
max-height: 90vh;
display: flex;
flex-direction: column;
}
:deep(.dialog-scrollable .el-dialog__body) {
max-height: calc(90vh - 120px);
overflow-y: auto;
padding: 20px;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE and Edge */
}
/* 完全隐藏滚动条但保持滚动功能 */
:deep(.dialog-scrollable .el-dialog__body)::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
/* 确保弹窗内容区域正确布局 */
:deep(.dialog-scrollable .el-dialog__header) {
flex-shrink: 0;
}
:deep(.dialog-scrollable .el-dialog__footer) {
flex-shrink: 0;
padding: 20px;
border-top: 1px solid #e4e7ed;
}
/* 移动端适配 */
@media (max-width: 768px) {
:deep(.dialog-scrollable .el-dialog) {
max-height: 95vh;
width: 95vw !important;
margin: 0 auto;
}
:deep(.dialog-scrollable .el-dialog__body) {
max-height: calc(95vh - 140px);
padding: 15px;
}
:deep(.dialog-scrollable .el-dialog__footer) {
padding: 15px;
}
}
/* 确保表单在弹窗中正确显示 */
:deep(.dialog-scrollable .el-form) {
margin: 0;
}
:deep(.dialog-scrollable .el-form-item) {
margin-bottom: 20px;
}
/* 文件上传组件在弹窗中的样式优化 */
:deep(.dialog-scrollable .file-uploader) {
margin-bottom: 10px;
}
:deep(.dialog-scrollable .file-info-display),
:deep(.dialog-scrollable .file-list-info-display) {
margin-bottom: 15px;
}
:deep(.el-tabs__header) {
@@ -918,4 +1245,193 @@ onMounted(() => {
:deep(.el-tabs__active-bar) {
background-color: #2c3e50;
}
/* 文件上传相关样式 */
.file-upload-section {
width: 100%;
}
.file-uploader {
width: 100%;
}
:deep(.file-uploader .el-upload) {
width: 100%;
}
:deep(.file-uploader .el-upload-dragger) {
width: 100%;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: border-color 0.3s;
}
:deep(.file-uploader .el-upload-dragger:hover) {
border-color: #409eff;
}
.file-info-display {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background-color: #f5f7fa;
}
.file-details {
display: flex;
flex-direction: column;
gap: 4px;
}
.file-name {
font-weight: 500;
color: #303133;
max-width: 200px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-block;
}
.file-id {
font-size: 12px;
color: #909399;
}
.file-size {
font-size: 12px;
color: #909399;
}
/* 文件列表相关样式 */
.file-list-section {
width: 100%;
}
.file-list-info-display {
margin-bottom: 16px;
}
.file-list-details {
display: flex;
flex-direction: column;
gap: 8px;
}
.file-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
border: 1px solid #e4e7ed;
border-radius: 6px;
background-color: #f5f7fa;
}
.file-item .file-details {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.file-item .file-name {
font-weight: 500;
color: #303133;
max-width: 180px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-block;
cursor: pointer;
transition: color 0.3s;
}
.file-item .file-name:hover {
color: #409eff;
}
/* 字符串列表相关样式 */
.string-list-section {
width: 100%;
}
.string-list-display {
margin-bottom: 16px;
}
.string-list-items {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.string-item {
margin: 0;
}
.string-list-input {
display: flex;
flex-direction: column;
gap: 8px;
}
/* 表格中的文件列表和字符串列表样式 */
.file-list-display {
display: flex;
flex-direction: column;
gap: 4px;
}
.file-link {
font-size: 12px;
margin-right: 8px;
max-width: 150px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-block;
vertical-align: middle;
}
.string-list-display {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.string-tag {
margin: 0;
max-width: 120px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
display: inline-block;
}
/* 响应式调整 */
@media (max-width: 768px) {
.file-name {
max-width: 120px !important;
}
.file-item .file-name {
max-width: 100px !important;
}
.file-link {
max-width: 100px !important;
}
.string-tag {
max-width: 80px !important;
}
}
</style>