2025-11-18 18:46:14 +08:00
|
|
|
|
<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>
|
2025-12-24 12:06:59 +08:00
|
|
|
|
|
|
|
|
|
|
<!-- 图片上传 -->
|
|
|
|
|
|
<el-form-item
|
|
|
|
|
|
v-else-if="getRenderType(item) === 'imgupload'"
|
|
|
|
|
|
:label="item.configName || item.configKey"
|
|
|
|
|
|
:prop="item.configKey"
|
|
|
|
|
|
>
|
2025-12-24 14:12:44 +08:00
|
|
|
|
<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格式'"
|
|
|
|
|
|
/>
|
2025-12-24 12:06:59 +08:00
|
|
|
|
</el-form-item>
|
2025-11-18 18:46:14 +08:00
|
|
|
|
</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">
|
2025-12-24 14:12:44 +08:00
|
|
|
|
import { ref, reactive, onMounted } from 'vue';
|
2025-11-18 18:46:14 +08:00
|
|
|
|
import { AdminLayout } from '@/views/admin';
|
|
|
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
|
|
|
import { configApi } from '@/apis/system';
|
|
|
|
|
|
import type { ConfigItem } from '@/types/system/config';
|
2025-12-24 14:12:44 +08:00
|
|
|
|
import FileUpload from '@/components/file/FileUpload.vue';
|
2025-11-18 18:46:14 +08:00
|
|
|
|
|
|
|
|
|
|
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';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2025-11-28 17:16:17 +08:00
|
|
|
|
* 解析 options 字符串(支持JSON数组和逗号分隔字符串)
|
|
|
|
|
|
* @param optionsStr JSON 字符串或逗号分隔的字符串
|
2025-11-18 18:46:14 +08:00
|
|
|
|
* @returns 选项数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
function parseOptions(optionsStr?: string): Array<{ label: string; value: any }> {
|
|
|
|
|
|
if (!optionsStr) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
2025-11-28 17:16:17 +08:00
|
|
|
|
|
2025-11-18 18:46:14 +08:00
|
|
|
|
try {
|
2025-11-28 17:16:17 +08:00
|
|
|
|
// 尝试解析为JSON
|
2025-11-18 18:46:14 +08:00
|
|
|
|
const parsed = JSON.parse(optionsStr);
|
2025-11-28 17:16:17 +08:00
|
|
|
|
if (Array.isArray(parsed)) {
|
|
|
|
|
|
return parsed;
|
|
|
|
|
|
}
|
2025-11-18 18:46:14 +08:00
|
|
|
|
return [];
|
2025-11-28 17:16:17 +08:00
|
|
|
|
} 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
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
2025-11-18 18:46:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 加载配置数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
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> = {
|
2025-12-24 12:06:59 +08:00
|
|
|
|
'基础配置': '基础配置',
|
|
|
|
|
|
'爬虫配置': '爬虫配置',
|
|
|
|
|
|
'Dify配置': 'Dify配置',
|
|
|
|
|
|
'邮件配置': '邮件配置',
|
|
|
|
|
|
'短信配置': '短信配置',
|
|
|
|
|
|
'存储配置': '存储配置',
|
|
|
|
|
|
'系统参数': '系统参数'
|
2025-11-18 18:46:14 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 按分组组织配置
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 12:06:59 +08:00
|
|
|
|
/**
|
2025-12-24 14:12:44 +08:00
|
|
|
|
* 处理封面URL更新
|
2025-12-24 12:06:59 +08:00
|
|
|
|
*/
|
2025-12-24 14:12:44 +08:00
|
|
|
|
function handleCoverUpdate(url: string, groupKey: string, configKey: string) {
|
|
|
|
|
|
configData[groupKey][configKey] = url;
|
2025-12-24 12:06:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-18 18:46:14 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 保存指定分组的配置
|
|
|
|
|
|
*/
|
|
|
|
|
|
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
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
2025-11-26 14:47:02 +08:00
|
|
|
|
// 调用API保存配置
|
|
|
|
|
|
const result = await configApi.saveConfigs(saveData);
|
2025-11-18 18:46:14 +08:00
|
|
|
|
|
2025-11-26 14:47:02 +08:00
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
ElMessage.error(result.message || '配置保存失败');
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-11-18 18:46:14 +08:00
|
|
|
|
|
|
|
|
|
|
ElMessage.success('配置保存成功');
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('保存配置失败:', error);
|
|
|
|
|
|
ElMessage.error('保存配置失败');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
saving.value = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.system-config {
|
2025-11-28 17:16:17 +08:00
|
|
|
|
background-color: #FFFFFF;
|
2025-11-18 18:46:14 +08:00
|
|
|
|
padding: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
:deep(.el-tabs--border-card) {
|
|
|
|
|
|
border: none;
|
2025-11-28 17:16:17 +08:00
|
|
|
|
// box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
2025-11-18 18:46:14 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
: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>
|