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
-600
View File
@@ -1,600 +0,0 @@
<template>
<div class="setting-container">
<!-- 主容器 -->
<el-card class="main-container" shadow="never">
<!-- 搜索和操作栏 -->
<div class="filter-section">
<div class="filter-content">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="配置组">
<el-select v-model="queryParams.group_id" placeholder="请选择配置组" clearable style="width: 200px" @change="handleQuery">
<el-option
v-for="group in groupList"
:key="group.id"
:label="group.name"
:value="group.id"
/>
</el-select>
</el-form-item>
<el-form-item label="关键词筛选">
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增配置
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</div>
</div>
<!-- 配置列表 -->
<div class="table-section">
<el-table
v-loading="loading"
:data="settingList"
@selection-change="handleSelectionChange"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="150" />
<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>{{ row.value }}</span>
</template>
</el-table-column>
<el-table-column prop="type" label="类型" width="100">
<template #default="{ row }">
<el-tag :type="getTypeColor(row.type)">
{{ row.type || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="settingGroupID" label="配置组" width="150" />
<el-table-column label="是否开放" width="100">
<template #default="{ row }">
<el-switch
v-model="row.open"
@change="handleToggleOpen(row)"
:disabled="toggleLoading === row.id"
/>
</template>
</el-table-column>
<el-table-column prop="note" label="备注" min-width="200" show-overflow-tooltip />
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</div>
</el-card>
<!-- 配置表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="600px"
destroy-on-close
>
<el-form
ref="settingFormRef"
:model="settingForm"
:rules="settingRules"
label-width="120px"
>
<el-form-item label="配置组" prop="settingGroupID">
<el-select v-model="settingForm.settingGroupID" placeholder="请选择配置组" style="width: 100%">
<el-option
v-for="group in groupList"
:key="group.id"
:label="group.name"
:value="group.id"
/>
</el-select>
</el-form-item>
<el-form-item label="名称" prop="name">
<el-input v-model="settingForm.name" placeholder="请输入配置名称" />
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="settingForm.type" placeholder="请选择类型" style="width: 100%" @change="handleTypeChange">
<el-option label="字符串 (string)" value="string" />
<el-option label="整数 (int)" value="int" />
<el-option label="浮点数 (float)" value="float" />
<el-option label="布尔值 (bool)" value="bool" />
</el-select>
</el-form-item>
<el-form-item label="值" prop="value">
<el-input
v-if="settingForm.type === 'string'"
v-model="settingForm.value"
type="textarea"
:rows="3"
placeholder="请输入配置值"
/>
<el-input-number
v-else-if="settingForm.type === 'int'"
v-model="settingForm.value"
:controls="false"
placeholder="请输入整数"
style="width: 100%"
/>
<el-input-number
v-else-if="settingForm.type === 'float'"
v-model="settingForm.value"
:controls="false"
:precision="2"
placeholder="请输入浮点数"
style="width: 100%"
/>
<el-switch
v-else-if="settingForm.type === 'bool'"
v-model="settingForm.value"
/>
<el-input
v-else
v-model="settingForm.value"
placeholder="请输入配置值"
/>
</el-form-item>
<el-form-item label="是否开放访问">
<el-switch v-model="settingForm.open" />
<span style="margin-left: 10px; color: #909399; font-size: 12px;">
开启后允许公开访问
</span>
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input
v-model="settingForm.note"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete } from '@element-plus/icons-vue'
import {
getSettingList,
getSettingInfo,
createSetting,
updateSetting,
setSettingOpen,
deleteSetting
} from '@/api/admin/setting'
import { getSettingGroupList } from '@/api/admin/setting'
// 查询参数
const queryParams = reactive({
group_id: undefined,
key: '',
page: 1,
count: 10
})
// 配置表单
const settingForm = reactive({
id: undefined,
name: '',
value: '',
type: 'string',
settingGroupID: undefined,
open: false,
note: ''
})
const settingRules = {
name: [
{ required: true, message: '请输入配置名称', trigger: 'blur' }
],
value: [
{ required: true, message: '请输入配置值', trigger: 'blur' }
],
type: [
{ required: true, message: '请选择配置类型', trigger: 'change' }
],
settingGroupID: [
{ required: true, message: '请选择配置组', trigger: 'change' }
]
}
// 状态数据
const loading = ref(false)
const settingList = ref([])
const groupList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增配置')
const settingFormRef = ref(null)
const toggleLoading = ref(null)
// 格式化日期时间
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 获取类型颜色
const getTypeColor = (type) => {
const colorMap = {
'string': 'primary',
'int': 'success',
'float': 'warning',
'bool': 'info'
}
return colorMap[type] || ''
}
// 获取配置组列表
const fetchGroupList = async () => {
try {
const res = await getSettingGroupList({ page: 1, count: 1000 })
if (res.data.code === 200) {
groupList.value = res.data.data.data || []
}
} catch (error) {
console.error('获取配置组列表失败:', error)
}
}
// 获取配置列表
const fetchSettingList = async () => {
loading.value = true
try {
const params = { ...queryParams }
if (!params.group_id) {
delete params.group_id
}
const res = await getSettingList(params)
console.log('配置列表数据:', res.data)
if (res.data.code === 200) {
settingList.value = res.data.data.data || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取配置列表失败:', error)
ElMessage.error('获取配置列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchSettingList()
}
// 重置查询
const resetQuery = () => {
queryParams.group_id = undefined
queryParams.key = ''
queryParams.page = 1
fetchSettingList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchSettingList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchSettingList()
}
// 类型变化
const handleTypeChange = (type) => {
// 根据类型重置值
if (type === 'bool') {
settingForm.value = false
} else if (type === 'int' || type === 'float') {
settingForm.value = 0
} else {
settingForm.value = ''
}
}
// 新增配置
const handleAdd = () => {
dialogTitle.value = '新增配置'
Object.assign(settingForm, {
id: undefined,
name: '',
value: '',
type: 'string',
setting_group_id: undefined,
open: false,
note: ''
})
dialogVisible.value = true
}
// 编辑配置
const handleEdit = async (row) => {
dialogTitle.value = '编辑配置'
try {
const res = await getSettingInfo({ id: row.id })
console.log('配置详情数据:', res)
if (res.data.code === 200) {
const data = res.data.data
Object.assign(settingForm, {
id: data.id,
name: data.name || '',
value: data.value,
type: data.type || 'string',
settingGroupID: data.settingGroupID,
open: data.open || false,
note: data.note || ''
})
console.log('配置详情数据:', settingForm)
// 根据类型转换值
if (data.type === 'bool') {
settingForm.value = data.value === true || data.value === 'true' || data.value === 1
} else if (data.type === 'int') {
settingForm.value = parseInt(data.value) || 0
} else if (data.type === 'float') {
settingForm.value = parseFloat(data.value) || 0
}
dialogVisible.value = true
}
} catch (error) {
console.error('获取配置详情失败:', error)
ElMessage.error('获取配置详情失败')
}
}
// 切换开放状态
const handleToggleOpen = async (row) => {
toggleLoading.value = row.id
try {
const res = await setSettingOpen({
id: row.id,
open: row.open
})
if (res.data.code === 200) {
ElMessage.success('修改成功')
} else {
// 恢复原状态
row.open = !row.open
ElMessage.error(res.data.message || '修改失败')
}
} catch (error) {
// 恢复原状态
row.open = !row.open
console.error('修改失败:', error)
ElMessage.error(error.response?.data?.message || '修改失败')
} finally {
toggleLoading.value = null
}
}
// 删除配置
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除配置 "${row.name}" 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteSetting({ id: row.id })
console.log('删除配置响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchSettingList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const deletePromises = selectedRows.value.map(row =>
deleteSetting({ id: row.id })
)
await Promise.all(deletePromises)
ElMessage.success('批量删除成功')
fetchSettingList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
settingFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
name: settingForm.name,
value: String(settingForm.value),
type: settingForm.type,
setting_group_id: settingForm.settingGroupID,
open: settingForm.open,
note: settingForm.note
}
if (settingForm.id) {
submitData.id = settingForm.id
}
console.log('提交配置数据:', submitData)
const res = settingForm.id
? await updateSetting(submitData)
: await createSetting(submitData)
if (res.data.code === 200) {
ElMessage.success(settingForm.id ? '修改成功' : '创建成功')
dialogVisible.value = false
fetchSettingList()
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error(error.response?.data?.message || '提交失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
fetchSettingList()
})
</script>
<style scoped>
.setting-container {
padding: 0;
}
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.search-form {
margin: 0;
flex: 1;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.search-form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-section {
padding: 0;
}
.pagination {
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
</style>
-409
View File
@@ -1,409 +0,0 @@
<template>
<div class="setting-group-container">
<!-- 主容器 -->
<el-card class="main-container" shadow="never">
<!-- 搜索和操作栏 -->
<div class="filter-section">
<div class="filter-content">
<el-form :inline="true" :model="queryParams" class="search-form">
<el-form-item label="关键词筛选">
<el-input v-model="queryParams.key" placeholder="请输入关键词" clearable style="width: 200px" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<el-icon><Search /></el-icon>查询
</el-button>
<el-button @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<div class="action-bar">
<el-button type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>新增配置组
</el-button>
<el-button type="danger" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>批量删除
</el-button>
</div>
</div>
</div>
<!-- 配置组列表 -->
<div class="table-section">
<el-table
v-loading="loading"
:data="groupList"
@selection-change="handleSelectionChange"
style="width: 100%"
:header-cell-style="{ background: '#fafafa', color: '#333', fontWeight: 600 }"
>
<el-table-column type="selection" width="55" />
<el-table-column prop="id" label="ID" width="80" />
<el-table-column prop="name" label="名称" min-width="200" />
<el-table-column prop="note" label="备注" min-width="250" show-overflow-tooltip />
<el-table-column label="创建时间" width="180">
<template #default="{ row }">
{{ formatDate(row.CreatedAt) }}
</template>
</el-table-column>
<el-table-column label="更新时间" width="180">
<template #default="{ row }">
{{ formatDate(row.UpdatedAt) }}
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }">
<el-button type="primary" link @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" link @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
v-model:current-page="queryParams.page"
v-model:page-size="queryParams.count"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
background
class="pagination"
/>
</div>
</el-card>
<!-- 配置组表单对话框 -->
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="500px"
destroy-on-close
>
<el-form
ref="groupFormRef"
:model="groupForm"
:rules="groupRules"
label-width="100px"
>
<el-form-item label="名称" prop="name">
<el-input v-model="groupForm.name" placeholder="请输入配置组名称" />
</el-form-item>
<el-form-item label="备注" prop="note">
<el-input
v-model="groupForm.note"
type="textarea"
:rows="3"
placeholder="请输入备注信息"
/>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search, Plus, Delete } from '@element-plus/icons-vue'
import {
getSettingGroupList,
getSettingGroupInfo,
createSettingGroup,
updateSettingGroup,
deleteSettingGroup
} from '@/api/admin/setting'
// 查询参数
const queryParams = reactive({
key: '',
page: 1,
count: 10
})
// 配置组表单
const groupForm = reactive({
id: undefined,
name: '',
note: ''
})
const groupRules = {
name: [
{ required: true, message: '请输入配置组名称', trigger: 'blur' }
]
}
// 状态数据
const loading = ref(false)
const groupList = ref([])
const total = ref(0)
const selectedRows = ref([])
const dialogVisible = ref(false)
const dialogTitle = ref('新增配置组')
const groupFormRef = ref(null)
// 格式化日期时间
const formatDate = (dateString) => {
if (!dateString) return '-'
const date = new Date(dateString)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
// 获取配置组列表
const fetchGroupList = async () => {
loading.value = true
try {
const res = await getSettingGroupList(queryParams)
console.log('配置组列表数据:', res.data)
if (res.data.code === 200) {
groupList.value = res.data.data.data || []
total.value = res.data.data.all_count || 0
}
} catch (error) {
console.error('获取配置组列表失败:', error)
ElMessage.error('获取配置组列表失败')
} finally {
loading.value = false
}
}
// 查询
const handleQuery = () => {
queryParams.page = 1
fetchGroupList()
}
// 重置查询
const resetQuery = () => {
queryParams.key = ''
queryParams.page = 1
fetchGroupList()
}
// 选择项变化
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
// 分页
const handleSizeChange = (size) => {
queryParams.count = size
fetchGroupList()
}
const handleCurrentChange = (page) => {
queryParams.page = page
fetchGroupList()
}
// 新增配置组
const handleAdd = () => {
dialogTitle.value = '新增配置组'
Object.assign(groupForm, {
id: undefined,
name: '',
note: ''
})
dialogVisible.value = true
}
// 编辑配置组
const handleEdit = async (row) => {
dialogTitle.value = '编辑配置组'
try {
const res = await getSettingGroupInfo({ setting_group_id: row.id })
console.log('配置组详情数据:', res.data)
if (res.data.code === 200) {
Object.assign(groupForm, {
id: res.data.data.id,
name: res.data.data.name || '',
note: res.data.data.note || ''
})
dialogVisible.value = true
}
} catch (error) {
console.error('获取配置组详情失败:', error)
ElMessage.error('获取配置组详情失败')
}
}
// 删除配置组
const handleDelete = (row) => {
ElMessageBox.confirm(`确认删除配置组 "${row.name}" 吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const res = await deleteSettingGroup({ setting_group_id: row.id })
console.log('删除配置组响应:', res.data)
if (res.data.code === 200) {
ElMessage.success('删除成功')
fetchGroupList()
}
} catch (error) {
console.error('删除失败:', error)
ElMessage.error(error.response?.data?.message || '删除失败')
}
}).catch(() => {})
}
// 批量删除
const handleBatchDelete = () => {
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的 ${selectedRows.value.length} 条记录吗?`, '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
const deletePromises = selectedRows.value.map(row =>
deleteSettingGroup({ setting_group_id: row.id })
)
await Promise.all(deletePromises)
ElMessage.success('批量删除成功')
fetchGroupList()
} catch (error) {
console.error('批量删除失败:', error)
ElMessage.error('批量删除失败')
}
}).catch(() => {})
}
// 提交表单
const submitForm = () => {
groupFormRef.value?.validate(async (valid) => {
if (valid) {
try {
const submitData = {
name: groupForm.name,
note: groupForm.note
}
if (groupForm.id) {
submitData.id = groupForm.id
}
console.log('提交配置组数据:', submitData)
const res = groupForm.id
? await updateSettingGroup(submitData)
: await createSettingGroup(submitData)
if (res.data.code === 200) {
ElMessage.success(groupForm.id ? '修改成功' : '创建成功')
dialogVisible.value = false
fetchGroupList()
}
} catch (error) {
console.error('提交失败:', error)
ElMessage.error(error.response?.data?.message || '提交失败')
}
}
})
}
// 初始化
onMounted(() => {
fetchGroupList()
})
</script>
<style scoped>
.setting-group-container {
padding: 0;
}
.main-container {
border: 1px solid #e1e8ed;
background: #ffffff;
}
.filter-section {
padding: 0;
border-bottom: 1px solid #e1e8ed;
background: #fafbfc;
}
.filter-content {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
gap: 20px;
flex-wrap: wrap;
}
.search-form {
margin: 0;
flex: 1;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.search-form :deep(.el-form-item) {
margin-bottom: 0;
margin-right: 12px;
}
.action-bar {
display: flex;
gap: 12px;
flex-shrink: 0;
}
.table-section {
padding: 0;
}
.pagination {
margin-top: 20px;
padding: 16px 20px;
border-top: 1px solid #e1e8ed;
background: #fafbfc;
justify-content: flex-end;
}
/* 表格样式优化 */
:deep(.el-table) {
border: none;
color: #2c3e50;
}
:deep(.el-table__header) {
background: #f8f9fa;
}
:deep(.el-table th) {
background: #f8f9fa !important;
border-bottom: 2px solid #e1e8ed;
color: #2c3e50;
font-weight: 600;
font-size: 13px;
}
:deep(.el-table td) {
border-bottom: 1px solid #f0f2f5;
color: #34495e;
}
:deep(.el-table tr:hover > td) {
background-color: #f8f9fa !important;
}
:deep(.el-card__body) {
padding: 0;
}
</style>
+520 -4
View File
@@ -128,6 +128,40 @@
<el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip> <el-table-column prop="value" label="值" min-width="200" show-overflow-tooltip>
<template #default="{ row }"> <template #default="{ row }">
<span v-if="row.type === 'bool'">{{ row.value ? '' : '' }}</span> <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> <span v-else>{{ row.value }}</span>
</template> </template>
</el-table-column> </el-table-column>
@@ -185,6 +219,7 @@
:title="groupDialogTitle" :title="groupDialogTitle"
width="500px" width="500px"
destroy-on-close destroy-on-close
class="dialog-scrollable"
> >
<el-form <el-form
ref="groupFormRef" ref="groupFormRef"
@@ -216,6 +251,7 @@
:title="settingDialogTitle" :title="settingDialogTitle"
width="600px" width="600px"
destroy-on-close destroy-on-close
class="dialog-scrollable"
> >
<el-form <el-form
ref="settingFormRef" ref="settingFormRef"
@@ -242,6 +278,9 @@
<el-option label="整数 (int)" value="int" /> <el-option label="整数 (int)" value="int" />
<el-option label="浮点数 (float)" value="float" /> <el-option label="浮点数 (float)" value="float" />
<el-option label="布尔值 (bool)" value="bool" /> <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-select>
</el-form-item> </el-form-item>
<el-form-item label="值" prop="value"> <el-form-item label="值" prop="value">
@@ -271,6 +310,91 @@
v-else-if="settingForm.type === 'bool'" v-else-if="settingForm.type === 'bool'"
v-model="settingForm.value" 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 <el-input
v-else v-else
v-model="settingForm.value" v-model="settingForm.value"
@@ -304,7 +428,7 @@
import { ref, reactive, onMounted, watch } from 'vue' import { ref, reactive, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' 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 { import {
getSettingGroupList, getSettingGroupList,
getSettingGroupInfo, getSettingGroupInfo,
@@ -318,6 +442,7 @@ import {
setSettingOpen, setSettingOpen,
deleteSetting deleteSetting
} from '@/api/admin/setting' } from '@/api/admin/setting'
import { uploadFile } from '@/api/admin/file'
const route = useRoute() const route = useRoute()
@@ -393,6 +518,10 @@ const settingDialogVisible = ref(false)
const settingDialogTitle = ref('新增配置') const settingDialogTitle = ref('新增配置')
const settingFormRef = ref(null) const settingFormRef = ref(null)
const toggleLoading = ref(null) const toggleLoading = ref(null)
const fileInfo = ref(null)
const fileUploading = ref(false)
const fileListInfo = ref([])
const newStringItem = ref('')
// 格式化日期时间 // 格式化日期时间
const formatDate = (dateString) => { const formatDate = (dateString) => {
@@ -413,7 +542,10 @@ const getTypeColor = (type) => {
'string': 'primary', 'string': 'primary',
'int': 'success', 'int': 'success',
'float': 'warning', 'float': 'warning',
'bool': 'info' 'bool': 'info',
'file': 'danger',
'file_list': 'danger',
'string_list': 'primary'
} }
return colorMap[type] || '' return colorMap[type] || ''
} }
@@ -630,11 +762,134 @@ const handleTypeChange = (type) => {
settingForm.value = false settingForm.value = false
} else if (type === 'int' || type === 'float') { } else if (type === 'int' || type === 'float') {
settingForm.value = 0 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 { } else {
settingForm.value = '' 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 () => { const handleAddSetting = async () => {
settingDialogTitle.value = '新增配置' settingDialogTitle.value = '新增配置'
// 刷新配置组列表以确保数据最新 // 刷新配置组列表以确保数据最新
@@ -648,6 +903,9 @@ const handleAddSetting = async () => {
open: false, open: false,
note: '' note: ''
}) })
fileInfo.value = null
fileListInfo.value = []
newStringItem.value = ''
settingDialogVisible.value = true settingDialogVisible.value = true
} }
@@ -672,6 +930,12 @@ const handleEditSetting = async (row) => {
settingForm.value = parseInt(data.value) || 0 settingForm.value = parseInt(data.value) || 0
} else if (data.type === 'float') { } else if (data.type === 'float') {
settingForm.value = parseFloat(data.value) || 0 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 settingDialogVisible.value = true
} }
@@ -888,8 +1152,71 @@ onMounted(() => {
background-color: #f8f9fa !important; background-color: #f8f9fa !important;
} }
:deep(.el-card__body) { :deep(.dialog-scrollable .el-dialog) {
padding: 0; 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) { :deep(.el-tabs__header) {
@@ -918,4 +1245,193 @@ onMounted(() => {
:deep(.el-tabs__active-bar) { :deep(.el-tabs__active-bar) {
background-color: #2c3e50; 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> </style>
+53 -5
View File
@@ -3,13 +3,61 @@
自行访问下面的接口地址内容 自行访问下面的接口地址内容
1.添加了参数show_home 用于控制商品套餐是否在首页显示,http://localhost:5174/product/list在该页面的编辑弹窗和新增弹窗添加show_home参数,是否展示按钮,这是个boolean值,true为展示,false为不展示 ✅已完成 1.- 管理员 配置信息类型 type参数新增 file 类型,先点击上传图片的位置上传文件请求文件上传接口后将获取的响应数据中的id赋值到value,用户端返回信息会返回url ✅已完成
创建商品套餐
POST /api/v1/admin/good/plan/create
接口ID412068533
接口地址:https://app.apifox.com/link/project/5145887/apis/api-412068533
- 管理员 配置信息类型
- 新增 file 类型,上传文件后,设置value为id
- 新增 file_list 类型,允许上传多个文件,value 为文件列表id,使用 , 分割
- 新增 string_list 类型,value 为字符串列表,使用 , 分割
创建配置
POST /api/v1/admin/server/setting/create
接口ID361964543
接口地址:https://app.apifox.com/link/project/5145887/apis/api-361964543
以下为响应数据格式:
{
"code": 200,
"message": "Success",
"data": {
"id": 5,
"name": "测试111",
"value": "1459",
"note": "测试",
"type": "file",
"settingGroup": null,
"settingGroupID": 4,
"open": true,
"CreatedAt": "2026-03-10T11:06:05.531505043+08:00",
"UpdatedAt": "2026-03-10T11:06:05.531505043+08:00",
"omitempty": null
}
}
文件上传
POST /api/v1/tools/file/upload
接口ID223275082
接口地址:https://app.apifox.com/link/project/5145887/apis/api-223275082
以下是响应数据格式:
{
"code": 200,
"message": "Success",
"data": [
{
"id": 1459,
"realName": "312",
"saveName": "17731118324",
"savePath": "static/files/2026-03-10/17731118324",
"size": 323351,
"type": "work_order",
"content": "",
"userId": 4,
"openDow": true,
"CreatedAt": "2026-03-10T11:03:52.910463212+08:00",
"UpdatedAt": "2026-03-10T11:03:52.910463212+08:00",
"omitempty": null
}
]
}
-----------------------------------------------------------------------------------------------需要解决 -----------------------------------------------------------------------------------------------需要解决
规则限制: 规则限制: