This commit is contained in:
扶桑花间 2025-08-25 22:45:07 +08:00
parent 907eb67dc7
commit 5acd35774f
9 changed files with 567 additions and 453 deletions

View File

@ -16,9 +16,9 @@ class ConfigController extends BaseController
'group'=> $this->request->get('group/s','') 'group'=> $this->request->get('group/s','')
]); ]);
$list = CurdService::getList($this->request, $model)->toArray(); $data = CurdService::getList($this->request, $model)->toArray();
$data = list_build_tree($list); // $data = list_build_tree($list);
return $this->writeSuccess('success', $data); return $this->writeSuccess('success', $data);
} }
@ -43,9 +43,9 @@ class ConfigController extends BaseController
'comments' => '', 'comments' => '',
'group'=> '' 'group'=> ''
]); ]);
$data['item_bind'] = $this->request->post('itemBind'); $data['item_bind'] = $this->request->post('itemBind','{}');
$data['item_style'] = $this->request->post('itemStyle'); $data['item_style'] = $this->request->post('itemStyle','');
$data['item_class'] = $this->request->post('itemClass'); $data['item_class'] = $this->request->post('itemClass','');
$user = new SysConfig(); $user = new SysConfig();

View File

@ -19,7 +19,7 @@ export async function configData(params: ConfigParam) {
/** /**
* *
*/ */
export async function listConfig(params?: ConfigParam) { export async function listConfigs(params?: ConfigParam) {
const res = await request.get<ApiResult<Config[]>>('/system/config', { const res = await request.get<ApiResult<Config[]>>('/system/config', {
params params
}); });
@ -29,7 +29,6 @@ export async function listConfig(params?: ConfigParam) {
return Promise.reject(new Error(res.data.message)); return Promise.reject(new Error(res.data.message));
} }
/** /**
* *
*/ */
@ -56,19 +55,23 @@ export async function updateConfig(data: Config) {
* *
*/ */
export async function updateConfigStatus(id: number, status: number) { export async function updateConfigStatus(id: number, status: number) {
const res = await request.put<ApiResult<unknown>>('/system/config/status', {id, status}); const res = await request.put<ApiResult<unknown>>('/system/config/status', {
id,
status
});
if (res.data.code === 0) { if (res.data.code === 0) {
return res.data.message; return res.data.message;
} }
return Promise.reject(new Error(res.data.message)); return Promise.reject(new Error(res.data.message));
} }
/** /**
* *
*/ */
export async function removeConfigs(ids: number[]) { export async function removeConfigs(ids: number[]) {
const res = await request.delete<ApiResult<unknown>>('/system/config/batch', {data: ids}); const res = await request.delete<ApiResult<unknown>>('/system/config/batch', {
data: ids
});
if (res.data.code === 0) { if (res.data.code === 0) {
return res.data.message; return res.data.message;
} }

View File

@ -4,12 +4,17 @@ import type { PageParam } from '@/api';
* *
*/ */
export interface Config { export interface Config {
id?: number;
pid?: number;
title?: string;
type?: string;
comments?: string;
itemStyle?: string;
itemBind?: string;
itemClass?: string;
} }
/** /**
* *
*/ */
export interface ConfigParam extends PageParam { export interface ConfigParam extends PageParam {}
}

View File

@ -2,50 +2,51 @@
* *
*/ */
export enum ItemType { export enum ItemType {
SEPARATOR = "-", SEPARATOR = 'separator',
ALERT = "alert", ALERT = 'alert',
CARD = "card", CARD = 'card',
COLLAPSE = "collapse", COLLAPSE = 'collapse',
TABS = "tabs", TABS = 'tabs',
TEXT = "text", TABS_ITEM = 'tabs_item',
TEXTAREA = "textarea", TEXT = 'text',
PASSWORD = "password", TEXTAREA = 'textarea',
CHECKBOX = "checkbox", PASSWORD = 'password',
RADIO = "radio", CHECKBOX = 'checkbox',
DATE = "date", RADIO = 'radio',
DATETIME = "datetime", DATE = 'date',
TIME = "time", DATETIME = 'datetime',
SWITCH = "switch", TIME = 'time',
HIDDEN = "hidden", SWITCH = 'switch',
RANGE = "range", HIDDEN = 'hidden',
DATE_RANGE = "date_range", RANGE = 'range',
DATETIME_RANGE = "datetime_range", DATE_RANGE = 'date_range',
TIME_RANGE = "time_range", DATETIME_RANGE = 'datetime_range',
NUMBER = "number" TIME_RANGE = 'time_range',
NUMBER = 'number'
} }
export const ItemLabels: Record<ItemType, string> = { export const ItemLabels: Record<ItemType, string> = {
[ItemType.SEPARATOR]: "分割线", [ItemType.SEPARATOR]: '分割线',
[ItemType.ALERT]: "提示", [ItemType.ALERT]: '提示',
[ItemType.CARD]: "卡片", [ItemType.CARD]: '卡片',
[ItemType.COLLAPSE]: "折叠面板", [ItemType.COLLAPSE]: '折叠面板',
[ItemType.TABS]: "Tabs", [ItemType.TABS]: 'Tabs',
[ItemType.TEXT]: "单行文本", [ItemType.TABS_ITEM]: 'Tabs子项',
[ItemType.TEXTAREA]: "多行文本", [ItemType.TEXT]: '单行文本',
[ItemType.PASSWORD]: "密码", [ItemType.TEXTAREA]: '多行文本',
[ItemType.CHECKBOX]: "复选框", [ItemType.PASSWORD]: '密码',
[ItemType.RADIO]: "单选按钮", [ItemType.CHECKBOX]: '复选框',
[ItemType.DATE]: "日期", [ItemType.RADIO]: '单选按钮',
[ItemType.DATETIME]: "日期+时间", [ItemType.DATE]: '日期',
[ItemType.TIME]: "时间", [ItemType.DATETIME]: '日期+时间',
[ItemType.SWITCH]: "开关", [ItemType.TIME]: '时间',
[ItemType.HIDDEN]: "隐藏", [ItemType.SWITCH]: '开关',
[ItemType.TIME_SECOND]: "时间", [ItemType.HIDDEN]: '隐藏',
[ItemType.RANGE]: "范围", [ItemType.RANGE]: '范围',
[ItemType.DATE_RANGE]: "日期范围", [ItemType.DATE_RANGE]: '日期范围',
[ItemType.DATETIME_RANGE]: "日期时间范围", [ItemType.DATETIME_RANGE]: '日期时间范围',
[ItemType.TIME_RANGE]: "时间范围", [ItemType.TIME_RANGE]: '时间范围',
[ItemType.NUMBER]: "数字", [ItemType.NUMBER]: '数字'
}; };
export function getItemLabel(type: ItemType): string { export function getItemLabel(type: ItemType): string {

View File

@ -1,34 +1,54 @@
<template> <template>
<template v-for="config in configList"> <template v-for="config in configList">
<template v-if="config.type === '-'"> <template v-if="config.type === 'separator'">
<el-divider v-bind="config.bind">{{config.title}}</el-divider> <el-divider v-bind="config.bind">{{ config.title }}</el-divider>
</template> </template>
<template v-else-if="config.type === 'alert'"> <template v-else-if="config.type === 'alert'">
<el-alert :title="config.title" v-bind="config.bind" /> <el-alert :title="config.title" v-bind="config.bind" />
</template> </template>
<template v-else-if="config.type === 'card'"> <template v-else-if="config.type === 'card'">
<ele-card shadow="always" :header="config.title" v-bind="config.bind"> <ele-card shadow="always" :header="config.title" v-bind="config.bind">
<config-form-list :config-list="config.children" v-if="config.children && config.children.length > 0" /> <config-form-list
:config-list="config.children"
v-if="config.children && config.children.length > 0"
/>
</ele-card> </ele-card>
</template> </template>
<template v-else-if="config.type === 'collapse'"> <template v-else-if="config.type === 'collapse'">
<el-collapse v-bind="config.bind"> <el-collapse v-bind="config.bind">
<el-collapse-item :title="config.title"> <el-collapse-item :title="config.title">
<config-form-list :config-list="config.children" v-if="config.children && config.children.length > 0" /> <config-form-list
:config-list="config.children"
v-if="config.children && config.children.length > 0"
/>
</el-collapse-item> </el-collapse-item>
</el-collapse> </el-collapse>
</template> </template>
<template v-else-if="config.type === 'tabs'"> <template v-else-if="config.type === 'tabs'">
<el-tabs <el-tabs type="card" :model-value="defaultTabs(config)">
type="card" <el-tab-pane
:model-value="defaultTabs(config)" v-for="tabPane in config.children"
> :label="tabPane.title"
<el-tab-pane v-for="(label, name) in config.option" :label="label" :name="name">{{label}}</el-tab-pane> :name="tabPane.id"
:key="tabPane.id"
>
<config-form-list
:config-list="tabPane.children"
v-if="tabPane.children && tabPane.children.length > 0"
/>
</el-tab-pane>
</el-tabs> </el-tabs>
</template> </template>
<template v-else> <template v-else>
<config-form-item :name="config.name" :title="config.title" :type="config.type" :value="config.value" <config-form-item
:option="config.option" :item-bind="config.bind"/> :name="config.name"
:title="config.title"
:type="config.type"
:value="config.value"
:option="config.option"
:item-bind="config.bind"
:key="config.id"
/>
<el-form-item v-if="config.tips"> <el-form-item v-if="config.tips">
<ele-text type="placeholder" style="line-height: 1.6"> <ele-text type="placeholder" style="line-height: 1.6">
{{ config.tips }} {{ config.tips }}
@ -39,15 +59,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import ConfigFormItem from "../components/config-form-item.vue"; import ConfigFormItem from '../components/config-form-item.vue';
const props = defineProps<{ const props = defineProps<{
configList: any; configList: any;
}>(); }>();
const defaultTabs = (cfg: any)=>{ const defaultTabs = (cfg: any) => {
let keys = Object.keys(cfg.option) let keys = Object.keys(cfg.option);
if(keys.length > 0) { if (keys.length > 0) {
return keys[0]; return keys[0];
} }
return null; return null;
} };
</script> </script>

View File

@ -9,14 +9,11 @@
> >
刷新 刷新
</el-button> </el-button>
<el-button <el-button type="danger" class="ele-btn-icon" :icon="Refresh">
type="danger"
class="ele-btn-icon"
:icon="Refresh"
>
同步配置 同步配置
</el-button> </el-button>
</el-card> </el-card>
<ele-card :body-style="{ paddingTop: '8px' }" v-loading="configLoading"> <ele-card :body-style="{ paddingTop: '8px' }" v-loading="configLoading">
<template #header> <template #header>
<ele-tabs <ele-tabs
@ -32,7 +29,10 @@
label-position="top" label-position="top"
@submit.prevent="" @submit.prevent=""
> >
<config-form-list :config-list="configList" v-if="configList.length > 0" /> <config-form-list
:config-list="configList"
v-if="configList.length > 0"
/>
</el-form> </el-form>
</ele-card> </ele-card>
</ele-page> </ele-page>
@ -40,12 +40,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, ref} from 'vue'; import {onMounted, ref} from 'vue';
import {Refresh} from "@element-plus/icons-vue" import {Refresh} from '@element-plus/icons-vue';
import {listConfig} from "@/api/system/config"; import {listConfigs} from '@/api/system/config';
import {getSysConfig, strToBind, strToOption} from "@/utils/sys-config"; import {getSysConfig, strToBind, strToOption} from '@/utils/sys-config';
import {useFormData} from "@/utils/use-form-data"; import {useFormData} from '@/utils/use-form-data';
import type {Config} from "@/api/system/config/model"; import type {Config} from '@/api/system/config/model';
import ConfigFormList from "./components/config-form-list.vue"; import ConfigFormList from './components/config-form-list.vue';
import {toTree} from 'ele-admin-plus';
defineOptions({name: 'SystemConfigSet'}); defineOptions({name: 'SystemConfigSet'});
@ -56,129 +57,37 @@ const configLoading = ref(false);
/** 搜索 */ /** 搜索 */
const reload = () => { const reload = () => {
configLoading.value = true; configLoading.value = true;
listConfig({group: configGroup.value, limit: 999}).then((data) => { listConfigs({group: configGroup.value, limit: 999}).then((data) => {
const configs = data.filter(d => d.type !== 'hidden'); const configs = data.filter((d) => d.type !== 'hidden');
configList.value = configs.map(d=>{ const lists = configs.map((d) => {
// vBinditem // vBinditem
d.bind = strToBind(d.type, d.itemBind); d.bind = strToBind(d.type, d.itemBind);
d.option = strToOption(d.type, d.option); d.option = strToOption(d.type, d.option);
if(d.type == "tabs") {
d.children = [
{
"id": 6,
"pid": 0,
"name": "page1",
"title": "YEM1",
"group": "base",
"type": "tab-item",
"value": "123456",
"options": "",
"tips": "",
"sort": 100,
"status": 1,
"vBind": null,
"createTime": "2025-08-22 16:50:23",
"updateTime": "2025-08-22 16:50:23",
"deleteTime": null,
},
{
"id": 6,
"pid": 0,
"name": "page2",
"title": "YEM2",
"group": "base",
"type": "tab-item",
"value": "123456",
"options": "",
"tips": "",
"sort": 100,
"status": 1,
"vBind": null,
"createTime": "2025-08-22 16:50:23",
"updateTime": "2025-08-22 16:50:23",
"deleteTime": null
},
];
}
if(d.type =='card' || d.type == 'collapse'){
d.children = [
{
"id": 6,
"pid": 0,
"name": "test_password",
"title": "系统密码",
"group": "base",
"type": "password",
"value": "123456",
"options": "",
"tips": "",
"sort": 100,
"status": 1,
"vBind": null,
"createTime": "2025-08-22 16:50:23",
"updateTime": "2025-08-22 16:50:23",
"deleteTime": null
},
{
"id": 4,
"pid": 0,
"name": "test_textarea",
"title": "测试配置",
"group": "base",
"type": "textarea",
"value": "测试配置",
"options": "",
"tips": "",
"sort": 100,
"status": 1,
"vBind": null,
"createTime": "2025-08-22 15:40:19",
"updateTime": "2025-08-22 16:40:31",
"deleteTime": null
},
{
"id": 3,
"pid": 0,
"name": "test_text",
"title": "站点名称",
"group": "base",
"type": "text",
"value": "Ele Admin Plus",
"options": "",
"tips": "站点名称显示在<header>的title上",
"sort": 100,
"status": 1,
"vBind": null,
"createTime": "2025-08-22 14:11:22",
"updateTime": "2025-08-22 16:50:32",
"deleteTime": null
}
];
for(let a in d.children){
d.children[a].bind = strToBind(d.children[a].type, d.children[a].itemBind);
d.children[a].option = strToOption(d.children[a].type, d.children[a].option);
}
}
return d; return d;
}); });
configList.value = toTree({
data: lists,
idField: 'id',
parentIdField: 'pid'
});
configLoading.value = false; configLoading.value = false;
}); });
}; };
const groupItems = ref([]); const groupItems = ref([]);
const configGroup = ref(""); const configGroup = ref('');
onMounted(async () => { onMounted(async () => {
// //
const group = []; const group = [];
const {valueData} = await getSysConfig("config_group"); const {valueData} = await getSysConfig('config_group');
for (const name in valueData) { for (const name in valueData) {
group.push({name: name, label: valueData[name]}) group.push({name: name, label: valueData[name]});
} }
groupItems.value = group groupItems.value = group;
if (group.length > 0) { if (group.length > 0) {
configGroup.value = group[0].name configGroup.value = group[0].name;
} }
reload(); reload();
}) });
</script> </script>

View File

@ -3,7 +3,7 @@
<ele-modal <ele-modal
form form
destroy-on-close destroy-on-close
:width="460" :width="960"
v-model="visible" v-model="visible"
:title="isUpdate ? '修改配置' : '添加配置'" :title="isUpdate ? '修改配置' : '添加配置'"
> >
@ -14,93 +14,108 @@
label-width="80px" label-width="80px"
@submit.prevent="" @submit.prevent=""
> >
<el-form-item label="类型" prop="type"> <el-row>
<el-select <el-col :span="12">
clearable <el-form-item label="上级" prop="pid">
class="ele-fluid" <config-select :model-value="form.pid" :group="form.group" disabled />
placeholder="请选择类型" </el-form-item>
v-model="form.type" <el-form-item label="类型" prop="type">
> <el-select
<el-option value="-" label="分割线" /> clearable
<el-option value="alert" label="提示" /> class="ele-fluid"
<el-option value="card" label="卡片" /> placeholder="请选择类型"
<el-option value="collapse" label="折叠面板" /> v-model="form.type"
<el-option value="tabs" label="Tabs" /> :disabled="form._type_readonly"
<el-option value="tabs_item" label="Tabs子页" /> >
<el-option value="text" label="单行文本" /> <el-option
<el-option value="textarea" label="多行文本" /> v-for="label in configItemType"
<el-option value="password" label="密码" /> :value="label"
<el-option value="checkbox" label="复选框" /> :label="getItemLabel(label)"
<el-option value="radio" label="单选按钮" /> :key="label"
<el-option value="date" label="日期" /> />
<el-option value="datetime" label="日期+时间" /> </el-select>
<el-option value="time" label="时间" /> </el-form-item>
<el-option value="switch" label="开关" /> <el-form-item label="配置标题" prop="title">
<el-option value="hidden" label="隐藏" /> <el-input
<el-option value="range" label="范围" /> clearable
<el-option value="date_range" label="日期范围" /> :maxlength="20"
<el-option value="datetime_range" label="日期时间范围" /> v-model="form.title"
<el-option value="time_range" label="时间范围" /> placeholder="请输入配置标题(一般由中文组成,仅用于显示)"
<el-option value="number" label="数字" /> />
</el-select> </el-form-item>
</el-form-item> <template v-if="form.type">
<el-form-item label="配置PID" prop="pid"> <el-form-item
<el-input-number label="配置名称"
v-model="form.pid" prop="name"
/> v-if="HIDE_FORM_ITEM_WHERE.name()"
</el-form-item> >
<el-form-item label="配置标题" prop="title"> <el-input
<el-input clearable
clearable :maxlength="20"
:maxlength="20" v-model="form.name"
v-model="form.title" placeholder="请输入配置名称"
placeholder="请输入配置标题(一般由中文组成,仅用于显示)" />
/> </el-form-item>
</el-form-item> <el-form-item
<el-form-item label="配置名称" prop="name"> label="配置值"
<el-input v-if="HIDE_FORM_ITEM_WHERE.value()"
clearable >
:maxlength="20" <el-input
v-model="form.name" :rows="4"
placeholder="请输入配置名称" type="textarea"
/> v-model="form.value"
</el-form-item> placeholder="请输入配置值"
/>
</el-form-item>
<el-form-item
label="配置项"
v-if="HIDE_FORM_ITEM_WHERE.option()"
>
<el-input
:rows="4"
type="textarea"
v-model="form.option"
placeholder="请输入配置项"
/>
</el-form-item>
<el-form-item label="配置说明">
<el-input
:rows="4"
type="textarea"
v-model="form.tips"
placeholder="请输入配置说明"
/>
</el-form-item>
</template>
</el-col>
<el-col :span="12">
<el-form-item label="BIND">
<el-input
:rows="4"
type="textarea"
v-model="form.itemBind"
placeholder=""
/>
</el-form-item>
<el-form-item label="Class">
<el-input
type="text"
v-model="form.itemClass"
placeholder="请输入渲染框CSS样式类"
/>
</el-form-item>
<el-form-item label="Style">
<el-input
:rows="4"
type="textarea"
v-model="form.itemStyle"
placeholder="请输入渲染框Style样式"
/>
</el-form-item>
</el-col>
</el-row>
<!-- //线 web_site_titleconfig('web_site_title')--> <el-form-item label="备注" v-if="false">
<el-form-item label="配置值">
<el-input
:rows="4"
type="textarea"
v-model="form.value"
placeholder="请输入配置值"
/>
</el-form-item>
<el-form-item label="配置项">
<el-input
:rows="4"
type="textarea"
v-model="form.option"
placeholder="请输入配置项"
/>
</el-form-item>
<el-form-item label="Bind属性">
<el-input
:rows="4"
type="textarea"
v-model="form.bind"
placeholder="请输入Bind属性"
/>
</el-form-item>
<el-form-item label="配置说明">
<el-input
:rows="4"
type="textarea"
v-model="form.tips"
placeholder="请输入配置说明"
/>
</el-form-item>
<el-form-item label="备注">
<el-input <el-input
:rows="4" :rows="4"
type="textarea" type="textarea"
@ -119,16 +134,51 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, reactive, watch } from 'vue'; import { ref, reactive, watch, computed } from 'vue';
import type { FormInstance, FormRules } from 'element-plus'; import type { FormInstance, FormRules } from 'element-plus';
import { EleMessage } from 'ele-admin-plus'; import { EleMessage } from 'ele-admin-plus';
import { useFormData } from '@/utils/use-form-data'; import { useFormData } from '@/utils/use-form-data';
import type { Config } from '@/api/system/config/model'; import type { Config } from '@/api/system/config/model';
import {addConfig, updateConfig} from "@/api/system/config"; import { addConfig, updateConfig } from '@/api/system/config';
import ConfigSelect from './config-select.vue';
import { ItemType, getItemLabel } from '@/enum/config-item-type.ts';
const HIDE_FORM_ITEM_WHERE: any = {
name: () => {
return ['tabs', 'tabs_item','alert','card','separator'].indexOf(<string>form.type) == -1;
},
value: ()=>{
return ['tabs', 'tabs_item'].indexOf(<string>form.type) == -1
},
option: ()=>{
return ['tabs', 'tabs_item'].indexOf(<string>form.type) == -1
}
};
const configItemType: any = computed(() => {
let list = {};
if (props.parent) {
if (props.parent.type == 'tabs') {
list[ItemType.TABS_ITEM] = ItemType.TABS_ITEM;
} else if (props.parent.type == 'tabs_item') {
for (const name in ItemType) {
if (
[ItemType.TABS_ITEM, ItemType.TABS].indexOf(ItemType[name]) == -1
) {
list[name] = ItemType[name];
}
}
}
}
if (Object.keys(list).length == 0) {
return ItemType;
} else {
return list;
}
});
const props = defineProps<{ const props = defineProps<{
/** 修改回显的数据 */ /** 修改回显的数据 */
data?: Config | null; data?: Config | null;
/** 父级元素 */
parent?: Config | null;
/** 配置所属组 */ /** 配置所属组 */
group?: string | null; group?: string | null;
}>(); }>();
@ -149,18 +199,26 @@
/** 表单实例 */ /** 表单实例 */
const formRef = ref<FormInstance | null>(null); const formRef = ref<FormInstance | null>(null);
interface ConfigEdit extends Config {
_type_readonly: boolean;
}
/** 表单数据 */ /** 表单数据 */
const [form, resetFields, assignFields] = useFormData<Config>({ const [form, resetFields, assignFields] = useFormData<ConfigEdit>({
id: void 0, id: void 0,
pid: 0, pid: 0,
_type_readonly: false,
type: '',
comments: '', comments: '',
group: '', group: '',
title: '', title: '',
name: '', name: '',
type: '',
tips: '', tips: '',
option: '', option: '',
value: '', value: '',
itemBind: '',
itemStyle: '',
itemClass: ''
}); });
/** 表单验证规则 */ /** 表单验证规则 */
@ -219,6 +277,17 @@
} else { } else {
resetFields(); resetFields();
isUpdate.value = false; isUpdate.value = false;
if (props.parent) {
let data: any = { pid: props.parent.id };
if (props.parent.type == 'tabs') {
data.type = 'tabs_item';
data._type_readonly = true;
}
assignFields(data);
}
}
if (form.type == 'tabs_item') {
form._type_readonly = true;
} }
form.group = props.group; form.group = props.group;
} }

View File

@ -0,0 +1,72 @@
<!-- 上级配置选择下拉框 -->
<template>
<el-tree-select
clearable
filterable
:data="configData"
check-strictly
default-expand-all
node-key="id"
:props="{ label: 'title', disabled: 'disabled' }"
:placeholder="placeholder"
:model-value="modelValue || void 0"
class="ele-fluid"
:popper-options="{ strategy: 'fixed' }"
@update:modelValue="updateValue"
>
<template #default="{ data }">
<span>{{ data.title }}</span>
</template>
</el-tree-select>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { EleMessage, toTree } from 'ele-admin-plus';
import { listConfigs } from '@/api/system/config';
import type { Config } from '@/api/system/config/model';
const props = withDefaults(
defineProps<{
/** 选中的配置 */
modelValue?: number | string;
/** 提示信息 */
placeholder?: string;
/** 配置所属组 */
group?: string | null;
}>(),
{
placeholder: '请选择上级配置'
}
);
const emit = defineEmits<{
(e: 'update:modelValue', value: number | string): void;
}>();
/** 配置数据 */
const configData = ref<Config[]>([]);
/** 更新选中数据 */
const updateValue = (value: number | string) => {
emit('update:modelValue', value || 0);
};
/** 获取配置数据 */
listConfigs({ group: props.group, limit: 999 })
.then((list) => {
const data: any = list.map((d: Config) => {
d.disabled = ['tabs', 'tabs_item'].indexOf(d.type) === -1;
return d;
});
configData.value = toTree({
data: data.filter(d => !d.disabled),
idField: 'id',
parentIdField: 'pid'
});
})
.catch((e) => {
EleMessage.error({ message: e.message, plain: true });
});
</script>

View File

@ -1,10 +1,10 @@
<template> <template>
<ele-page> <ele-page>
<!-- 搜索表单 --> <!-- 搜索表单 -->
<config-search @search="reload"/> <config-search @search="reload" />
<ele-tabs <ele-tabs
type="card" type="tag"
v-model="configGroup" v-model="configGroup"
:items="groupItems" :items="groupItems"
@tabChange="reload()" @tabChange="reload()"
@ -23,6 +23,7 @@
:pagination="false" :pagination="false"
cache-key="systemRoleTable" cache-key="systemRoleTable"
:load-on-created="false" :load-on-created="false"
height="66vh"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
> >
<template #toolbar> <template #toolbar>
@ -35,22 +36,25 @@
添加 添加
</el-button> </el-button>
</template> </template>
<template #action="d"> <template #action="{ row }">
{{ d }} <template v-if="['tabs', 'tabs_item', 'collapse', 'card'].indexOf(row.type) != -1">
<!-- <template v-if="['tabs'].indexOf(row.type) != -1">--> <el-link
<!-- {{ row }}--> type="primary"
<!-- <el-link type="primary" underline="never" @click="openEdit(row)">添加子项</el-link>--> underline="never"
<!-- <el-divider direction="vertical"/>--> @click="openEdit(null, row)"
<!-- </template>--> >添加子项</el-link
<!-- <el-link type="primary" underline="never" @click="openEdit(row)">--> >
<!-- 修改--> <el-divider direction="vertical" />
<!-- </el-link>--> </template>
<!-- <template v-if="row.group != 'system'">--> <el-link type="primary" underline="never" @click="openEdit(row)">
<!-- <el-divider direction="vertical"/>--> 修改
<!-- <el-link type="danger" underline="never" @click="remove(row)">--> </el-link>
<!-- 删除--> <template v-if="row.group != 'system'">
<!-- </el-link>--> <el-divider direction="vertical" />
<!-- </template>--> <el-link type="danger" underline="never" @click="remove(row)">
删除
</el-link>
</template>
</template> </template>
<template #status="{ row }"> <template #status="{ row }">
<el-switch <el-switch
@ -59,173 +63,204 @@
@change="(checked: boolean) => editStatus(checked, row)" @change="(checked: boolean) => editStatus(checked, row)"
/> />
</template> </template>
<template #type="{ row }">
<span :title="row.type">{{ getItemLabel(row.type) }}</span>
</template>
<template #sort="{ row }">
<el-input-number :controls="false" v-model="row.sort" :min="1" :max="9999" style="width: 100%;"></el-input-number>
</template>
</ele-pro-table> </ele-pro-table>
</ele-card> </ele-card>
<!-- 编辑弹窗 --> <!-- 编辑弹窗 -->
<config-edit v-model="showEdit" :data="current" @done="reload" :group="configGroup"/> <config-edit
v-model="showEdit"
:data="current"
:parent="parent"
@done="reload"
:group="configGroup"
/>
</ele-page> </ele-page>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, ref} from 'vue'; import { onMounted, ref } from 'vue';
import {ElMessageBox} from 'element-plus'; import { ElMessageBox } from 'element-plus';
import {EleMessage} from 'ele-admin-plus'; import {EleMessage, toTree} from 'ele-admin-plus';
import type {EleProTable} from 'ele-admin-plus'; import type { EleProTable } from 'ele-admin-plus';
import type { import type {
DatasourceFunction, DatasourceFunction,
Columns Columns
} from 'ele-admin-plus/es/ele-pro-table/types'; } from 'ele-admin-plus/es/ele-pro-table/types';
import {PlusOutlined,} from '@/components/icons'; import { PlusOutlined } from '@/components/icons';
import ConfigSearch from './components/config-search.vue'; import ConfigSearch from './components/config-search.vue';
import ConfigEdit from './components/config-edit.vue'; import ConfigEdit from './components/config-edit.vue';
import type {Config, ConfigParam} from '@/api/system/config/model'; import type { Config, ConfigParam } from '@/api/system/config/model';
import {listConfig, updateConfigStatus, removeConfigs} from "@/api/system/config"; import {
import {getSysConfig} from "@/utils/sys-config"; listConfigs,
updateConfigStatus,
removeConfigs
} from '@/api/system/config';
import { getSysConfig } from '@/utils/sys-config';
import { ItemType, getItemLabel } from '@/enum/config-item-type.ts';
defineOptions({name: 'SystemConfig'}); defineOptions({ name: 'SystemConfig' });
/** 表格实例 */ /** 表格实例 */
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null); const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
/** 表格列配置 */ /** 表格列配置 */
const columns = ref<Columns>([ const columns = ref<Columns>([
{ {
type: 'index', type: 'index',
columnKey: 'index', columnKey: 'index',
width: 50, width: 50,
align: 'center' align: 'center'
}, },
{ {
prop: 'name', prop: 'title',
label: '名称', label: '标题',
minWidth: 120 minWidth: 120
}, },
{ {
prop: 'title', prop: 'name',
label: '标题', label: '名称',
minWidth: 120 minWidth: 120
}, },
{ {
prop: 'type', prop: 'type',
label: '类型', slot: 'type',
minWidth: 120 label: '类型',
}, width: 120
{ },
prop: 'status', {
label: '状态', prop: 'status',
width: 90, label: '状态',
align: 'center', width: 90,
slot: 'status', align: 'center',
formatter: (row) => (row.status == 0 ? '正常' : '冻结') slot: 'status',
}, formatter: (row) => (row.status == 0 ? '正常' : '冻结')
{ },
prop: 'sort', {
label: '排序', prop: 'sort',
minWidth: 140 slot: 'sort',
}, label: '排序',
{ align: 'center',
prop: 'comments', width: 140
label: '备注', },
minWidth: 140 {
}, prop: 'comments',
{ label: '备注',
columnKey: 'action', minWidth: 140
label: '操作', },
width: 200, {
align: 'right' /* , columnKey: 'action',
label: '操作',
width: 200,
align: 'right' /* ,
fixed: 'right' */, fixed: 'right' */,
slot: 'action', slot: 'action',
hideInPrint: true, hideInPrint: true,
hideInExport: true hideInExport: true
} }
]); ]);
/** 表格选中数据 */ /** 表格选中数据 */
const selections = ref<Config[]>([]); const selections = ref<Config[]>([]);
/** 当前编辑数据 */ /** 当前编辑数据 */
const current = ref<Config | null>(null); const current = ref<Config | null>(null);
/** 是否显示编辑弹窗 */ const parent = ref<Config | null>(null);
const showEdit = ref(false);
/** 是否显示编辑弹窗 */
const showEdit = ref(false);
/** 表格数据源 */ /** 表格数据源 */
const datasource: DatasourceFunction = ({pages, where, orders}) => { const datasource: DatasourceFunction = async ({ pages, where, orders }) => {
return listConfig({...where, ...orders, ...pages, group: configGroup.value, limit: 999}); const data = await listConfigs({
}; ...where,
...orders,
...pages,
group: configGroup.value,
limit: 999
});
return toTree({
data,
idField: 'id',
parentIdField: 'pid'
});
};
/** 搜索 */ /** 搜索 */
const reload = (where?: ConfigParam) => { const reload = (where?: ConfigParam) => {
selections.value = []; selections.value = [];
tableRef.value?.reload?.({page: 1, where}); tableRef.value?.reload?.({ page: 1, where });
}; };
/** 打开编辑弹窗 */ /** 打开编辑弹窗 */
const openEdit = (row?: Config) => { const openEdit = (row?: Config, parentRow?: Config) => {
current.value = row ?? null; current.value = row ?? null;
showEdit.value = true; showEdit.value = true;
}; parent.value = parentRow;
};
/** 删除单个 */
/** 删除单个 */ const remove = (row?: Config) => {
const remove = (row?: Config) => { const rows = row == null ? selections.value : [row];
const rows = row == null ? selections.value : [row]; if (!rows.length) {
if (!rows.length) { EleMessage.error({ message: '请至少选择一条数据', plain: true });
EleMessage.error({message: '请至少选择一条数据', plain: true}); return;
return; }
} ElMessageBox.confirm(
ElMessageBox.confirm( '确定要删除“' + rows.map((d) => d.roleName).join(', ') + '”吗?',
'确定要删除“' + rows.map((d) => d.roleName).join(', ') + '”吗?', '系统提示',
'系统提示', { type: 'warning', draggable: true }
{type: 'warning', draggable: true} )
) .then(() => {
.then(() => { const loading = EleMessage.loading({
const loading = EleMessage.loading({ message: '请求中..',
message: '请求中..', plain: true
plain: true
});
removeConfigs(rows.map((d) => d.id))
.then((msg) => {
loading.close();
EleMessage.success({message: msg, plain: true});
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({message: e.message, plain: true});
}); });
}) removeConfigs(rows.map((d) => d.id))
.catch(() => { .then((msg) => {
}); loading.close();
}; EleMessage.success({ message: msg, plain: true });
reload();
})
.catch((e) => {
loading.close();
EleMessage.error({ message: e.message, plain: true });
});
})
.catch(() => {});
};
/** 修改配置状态 */ /** 修改配置状态 */
const editStatus = (checked: boolean, row: Config) => { const editStatus = (checked: boolean, row: Config) => {
const status = checked ? 1 : 0; const status = checked ? 1 : 0;
updateConfigStatus(row.id, status) updateConfigStatus(row.id, status)
.then((msg) => { .then((msg) => {
row.status = status; row.status = status;
EleMessage.success({message: msg, plain: true}); EleMessage.success({ message: msg, plain: true });
}) })
.catch((e) => { .catch((e) => {
EleMessage.error({message: e.message, plain: true}); EleMessage.error({ message: e.message, plain: true });
}); });
}; };
const groupItems = ref([]); const groupItems = ref([]);
const configGroup = ref(""); const configGroup = ref('');
onMounted(async () => { onMounted(async () => {
// //
const group = []; const group = [];
const {valueData} = await getSysConfig("config_group"); const { valueData } = await getSysConfig('config_group');
for (const name in valueData) { for (const name in valueData) {
group.push({name: name, label: valueData[name]}) group.push({ name: name, label: valueData[name] });
} }
groupItems.value = group groupItems.value = group;
if (group.length > 0) { if (group.length > 0) {
configGroup.value = group[0].name configGroup.value = group[0].name;
} }
reload(); reload();
}) });
</script> </script>