Files
schoolNews/schoolNewsWeb/src/views/admin/manage/system/SystemConfigView.vue

445 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>
<!-- 图片上传 -->
<el-form-item
v-else-if="getRenderType(item) === 'imgupload'"
:label="item.configName || item.configKey"
:prop="item.configKey"
>
<FileUpload
list-type="cover"
:cover-url="configData[group.groupKey][item.configKey]"
@update:cover-url="(url) => handleCoverUpdate(url, group.groupKey, item.configKey)"
accept="image/*"
:max-size="5"
module="system"
:as-dialog="false"
:tip="item.remark || '点击上传图片支持jpg、png格式'"
/>
</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';
import FileUpload from '@/components/file/FileUpload.vue';
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 {
// 尝试解析为JSON
const parsed = JSON.parse(optionsStr);
if (Array.isArray(parsed)) {
return parsed;
}
return [];
} catch (error) {
// 如果JSON解析失败尝试作为逗号分隔的字符串处理
// 格式:'aliyun,tencent' 或 'aliyun:阿里云,tencent:腾讯云'
const items = optionsStr.split(',').map(item => item.trim()).filter(item => item);
return items.map(item => {
// 检查是否包含冒号分隔符 (value:label 格式)
const parts = item.split(':');
if (parts.length === 2) {
return {
value: parts[0].trim(),
label: parts[1].trim()
};
}
// 如果没有冒号value和label相同
return {
value: item,
label: item
};
});
}
}
/**
* 加载配置数据
*/
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> = {
'基础配置': '基础配置',
'爬虫配置': '爬虫配置',
'Dify配置': 'Dify配置',
'邮件配置': '邮件配置',
'短信配置': '短信配置',
'存储配置': '存储配置',
'系统参数': '系统参数'
};
// 按分组组织配置
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;
}
}
/**
* 处理封面URL更新
*/
function handleCoverUpdate(url: string, groupKey: string, configKey: string) {
configData[groupKey][configKey] = url;
}
/**
* 保存指定分组的配置
*/
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
}));
// 调用API保存配置
const result = await configApi.saveConfigs(saveData);
if (!result.success) {
ElMessage.error(result.message || '配置保存失败');
return;
}
ElMessage.success('配置保存成功');
} catch (error) {
console.error('保存配置失败:', error);
ElMessage.error('保存配置失败');
} finally {
saving.value = false;
}
}
</script>
<style lang="scss" scoped>
.system-config {
background-color: #FFFFFF;
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>