系统配置

This commit is contained in:
2025-11-18 18:46:14 +08:00
parent 9f3176194b
commit ca756dcfd7
23 changed files with 1039 additions and 49 deletions

View File

@@ -0,0 +1,390 @@
<template>
<AdminLayout title="系统配置" subtitle="系统参数配置管理">
<div class="system-config">
<!-- 配置分类标签 - 动态生成 -->
<el-tabs v-model="activeTab" type="border-card" v-loading="loading">
<el-tab-pane
v-for="group in configGroups"
:key="group.groupKey"
:label="group.groupName"
:name="group.groupKey"
>
<el-form
:model="configData[group.groupKey]"
label-width="140px"
class="config-form"
>
<!-- 动态渲染配置项 -->
<template v-for="item in group.items" :key="item.configKey">
<!-- 文本输入框 -->
<el-form-item
v-if="getRenderType(item) === 'input'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-input
v-model="configData[group.groupKey][item.configKey]"
:placeholder="item.placeholder || `请输入${item.configName || item.configKey}`"
/>
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
<!-- 多行文本框 -->
<el-form-item
v-else-if="getRenderType(item) === 'textarea'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-input
v-model="configData[group.groupKey][item.configKey]"
type="textarea"
:rows="item.rows || 3"
:placeholder="item.placeholder || `请输入${item.configName || item.configKey}`"
/>
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
<!-- 数字输入框 -->
<el-form-item
v-else-if="getRenderType(item) === 'number'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-input-number
v-model="configData[group.groupKey][item.configKey]"
:min="item.minValue"
:max="item.maxValue"
:placeholder="item.placeholder"
/>
<span v-if="item.unit" class="form-item-unit">{{ item.unit }}</span>
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
<!-- 开关 -->
<el-form-item
v-else-if="getRenderType(item) === 'switch'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-switch v-model="configData[group.groupKey][item.configKey]" />
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
<!-- 下拉选择 -->
<el-form-item
v-else-if="getRenderType(item) === 'select'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-select
v-model="configData[group.groupKey][item.configKey]"
:placeholder="item.placeholder || `请选择${item.configName || item.configKey}`"
>
<el-option
v-for="option in parseOptions(item.options)"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
<!-- 密码输入框 -->
<el-form-item
v-else-if="getRenderType(item) === 'password'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<el-input
v-model="configData[group.groupKey][item.configKey]"
type="password"
show-password
:placeholder="item.placeholder || `请输入${item.configName || item.configKey}`"
/>
<span v-if="item.remark" class="form-item-remark">{{ item.remark }}</span>
</el-form-item>
</template>
<!-- 操作按钮 -->
<el-form-item>
<el-button
type="primary"
@click="saveConfig(group.groupKey)"
:loading="saving"
>
保存配置
</el-button>
<el-button @click="loadConfigs">重置</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
</el-tabs>
</div>
</AdminLayout>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue';
import { AdminLayout } from '@/views/admin';
import { ElMessage } from 'element-plus';
import { configApi } from '@/apis/system';
import type { ConfigItem } from '@/types/system/config';
defineOptions({
name: 'SystemConfigView'
});
// 配置分组接口定义
interface ConfigGroup {
groupKey: string;
groupName: string;
orderNum: number;
items: ConfigItem[];
}
const loading = ref(false);
const saving = ref(false);
const activeTab = ref('');
// 配置分组列表
const configGroups = ref<ConfigGroup[]>([]);
// 配置数据对象,按分组存储
const configData = reactive<Record<string, Record<string, any>>>({});
onMounted(() => {
loadConfigs();
});
/**
* 获取配置项的渲染类型
* @param item 配置项
* @returns 渲染类型input/textarea/number/switch/select/password
*/
function getRenderType(item: ConfigItem): string {
// 如果明确指定了 renderType直接使用
if (item.renderType) {
return item.renderType;
}
// 根据 configType 自动推断渲染类型
switch (item.configType?.toLowerCase()) {
case 'boolean':
return 'switch';
case 'number':
case 'integer':
case 'long':
case 'double':
case 'float':
return 'number';
case 'json':
return 'textarea';
case 'string':
default:
// String 类型根据其他属性进一步判断
if (item.options) {
return 'select';
}
if (item.rows && item.rows > 1) {
return 'textarea';
}
if (item.configKey?.includes('password') || item.configKey?.includes('secret')) {
return 'password';
}
return 'input';
}
}
/**
* 解析 options JSON 字符串
* @param optionsStr JSON 字符串
* @returns 选项数组
*/
function parseOptions(optionsStr?: string): Array<{ label: string; value: any }> {
if (!optionsStr) {
return [];
}
try {
const parsed = JSON.parse(optionsStr);
return Array.isArray(parsed) ? parsed : [];
} catch (error) {
console.warn('解析 options 失败:', error);
return [];
}
}
/**
* 加载配置数据
*/
async function loadConfigs() {
try {
loading.value = true;
// 调用API获取配置
const result = await configApi.getConfigs();
if (!result.success) {
ElMessage.error(result.message || '加载配置失败');
return;
}
// 后端返回的列表数据在 dataList 字段中
const configItems: ConfigItem[] = (result.dataList || []) as unknown as ConfigItem[];
// 定义分组名称映射
const groupNames: Record<string, string> = {
basic: '基本配置',
email: '邮件配置',
storage: '存储配置',
system: '系统参数'
};
// 按分组组织配置
const groupMap = new Map<string, ConfigGroup>();
configItems.forEach((item: ConfigItem) => {
if (!groupMap.has(item.configGroup)) {
groupMap.set(item.configGroup, {
groupKey: item.configGroup,
groupName: groupNames[item.configGroup] || item.configGroup,
orderNum: Object.keys(groupNames).indexOf(item.configGroup),
items: []
});
configData[item.configGroup] = {};
}
const group = groupMap.get(item.configGroup)!;
group.items.push(item);
// 初始化配置值,根据 configType 转换数据类型
let value: any = item.configValue;
switch (item.configType?.toLowerCase()) {
case 'number':
case 'integer':
case 'long':
case 'double':
case 'float':
value = Number(value);
break;
case 'boolean':
value = value === 'true' || value === true || value === 1;
break;
case 'json':
try {
value = JSON.parse(value);
} catch {
value = value;
}
break;
default:
// string 类型保持原样
break;
}
configData[item.configGroup][item.configKey] = value;
});
// 转换为数组并排序
configGroups.value = Array.from(groupMap.values()).sort(
(a, b) => a.orderNum - b.orderNum
);
// 每个分组内的配置项也排序
configGroups.value.forEach(group => {
group.items.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
});
// 设置默认激活的标签页
if (configGroups.value.length > 0) {
activeTab.value = configGroups.value[0].groupKey;
}
} catch (error) {
console.error('加载配置失败:', error);
ElMessage.error('加载配置失败');
} finally {
loading.value = false;
}
}
/**
* 保存指定分组的配置
*/
async function saveConfig(groupKey: string) {
try {
saving.value = true;
const groupData = configData[groupKey];
const configItems = configGroups.value
.find(g => g.groupKey === groupKey)
?.items || [];
// 构建保存数据
const saveData = configItems.map(item => ({
configKey: item.configKey,
configValue: String(groupData[item.configKey]),
configGroup: groupKey
}));
// TODO: 调用API保存配置
// await configApi.saveConfigs(saveData);
console.log('保存配置:', saveData);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success('配置保存成功');
} catch (error) {
console.error('保存配置失败:', error);
ElMessage.error('保存配置失败');
} finally {
saving.value = false;
}
}
</script>
<style lang="scss" scoped>
.system-config {
padding: 20px;
:deep(.el-tabs--border-card) {
border: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
:deep(.el-tabs__content) {
padding: 24px;
}
}
.config-form {
max-width: 800px;
:deep(.el-form-item__label) {
font-weight: 500;
}
:deep(.el-input),
:deep(.el-select),
:deep(.el-input-number) {
width: 100%;
}
:deep(.el-textarea__inner) {
font-family: inherit;
}
.form-item-remark {
display: inline-block;
margin-left: 10px;
color: #909399;
font-size: 13px;
}
.form-item-unit {
display: inline-block;
margin-left: 10px;
color: #606266;
font-size: 14px;
}
}
</style>