mirror of https://github.com/certd/certd
perf: 顶部菜单自定义
parent
661293c189
commit
54d136cc6a
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<a-dropdown class="fs-locale-picker">
|
<a-dropdown class="fs-locale-picker">
|
||||||
<div>
|
<div style="display: block">
|
||||||
<fs-iconify icon="ion-globe-outline" @click.prevent></fs-iconify>
|
<fs-iconify icon="ion-globe-outline" @click.prevent></fs-iconify>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -89,11 +89,12 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const title: any = () => {
|
const title: any = () => {
|
||||||
if (sub?.meta?.icon) {
|
const icon = sub.icon || sub?.meta?.icon;
|
||||||
|
if (icon) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
return (
|
return (
|
||||||
<div class={"menu-item-title"}>
|
<div class={"menu-item-title"}>
|
||||||
<fsIcon class={"anticon"} icon={sub.meta.icon} />
|
<fsIcon class={"anticon"} icon={icon} />
|
||||||
<span>{sub.title}</span>
|
<span>{sub.title}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
<!-- >-->
|
<!-- >-->
|
||||||
<!-- Button-->
|
<!-- Button-->
|
||||||
<!-- </button>-->
|
<!-- </button>-->
|
||||||
|
<fs-menu class="header-menu" mode="horizontal" :expand-selected="false" :selectable="false" :menus="settingStore.getHeaderMenus" />
|
||||||
|
|
||||||
<fs-menu
|
<fs-menu
|
||||||
v-if="!settingStore?.isAgent && !settingStore.isComm"
|
v-if="!settingStore?.isAgent && !settingStore.isComm"
|
||||||
class="header-menu"
|
class="header-menu"
|
||||||
|
|
|
@ -68,20 +68,20 @@ export const sysResources = [
|
||||||
permission: "sys:settings:view"
|
permission: "sys:settings:view"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// {
|
{
|
||||||
// title: "顶部菜单设置",
|
title: "顶部菜单设置",
|
||||||
// name: "HeaderMenus",
|
name: "HeaderMenus",
|
||||||
// path: "/sys/settings/header-menus",
|
path: "/sys/settings/header-menus",
|
||||||
// component: "/sys/settings/header-menus/index.vue",
|
component: "/sys/settings/header-menus/index.vue",
|
||||||
// meta: {
|
meta: {
|
||||||
// show: () => {
|
show: () => {
|
||||||
// const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
// return settingStore.isComm;
|
return settingStore.isComm;
|
||||||
// },
|
},
|
||||||
// icon: "ion:document-text-outline",
|
icon: "ion:document-text-outline",
|
||||||
// permission: "sys:settings:view"
|
permission: "sys:settings:view"
|
||||||
// }
|
}
|
||||||
// },
|
},
|
||||||
{
|
{
|
||||||
title: "系统级授权",
|
title: "系统级授权",
|
||||||
name: "SysAccess",
|
name: "SysAccess",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { defineStore } from "pinia";
|
import { defineStore } from "pinia";
|
||||||
import { Modal, notification, theme } from "ant-design-vue";
|
import { Modal, notification, theme } from "ant-design-vue";
|
||||||
import _ from "lodash-es";
|
import _, { cloneDeep } from "lodash-es";
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { LocalStorage } from "/src/utils/util.storage";
|
import { LocalStorage } from "/src/utils/util.storage";
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import { useUserStore } from "/@/store/modules/user";
|
||||||
import { mitter } from "/@/utils/util.mitt";
|
import { mitter } from "/@/utils/util.mitt";
|
||||||
import { env } from "/@/utils/util.env";
|
import { env } from "/@/utils/util.env";
|
||||||
import { toRef } from "vue";
|
import { toRef } from "vue";
|
||||||
|
import { util } from "/@/utils";
|
||||||
|
|
||||||
export type ThemeToken = {
|
export type ThemeToken = {
|
||||||
token: {
|
token: {
|
||||||
|
@ -120,6 +121,9 @@ export const useSettingStore = defineStore({
|
||||||
comm: "商业版"
|
comm: "商业版"
|
||||||
};
|
};
|
||||||
return vipLabelMap[this.plusInfo?.vipType || "free"];
|
return vipLabelMap[this.plusInfo?.vipType || "free"];
|
||||||
|
},
|
||||||
|
getHeaderMenus() {
|
||||||
|
return this.headerMenus?.menus || [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -137,6 +141,7 @@ export const useSettingStore = defineStore({
|
||||||
_.merge(this.installInfo, allSettings.installInfo || {});
|
_.merge(this.installInfo, allSettings.installInfo || {});
|
||||||
_.merge(this.siteEnv, allSettings.siteEnv || {});
|
_.merge(this.siteEnv, allSettings.siteEnv || {});
|
||||||
_.merge(this.plusInfo, allSettings.plusInfo || {});
|
_.merge(this.plusInfo, allSettings.plusInfo || {});
|
||||||
|
_.merge(this.headerMenus, allSettings.headerMenus || {});
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
this.initSiteInfo(allSettings.siteInfo || {});
|
this.initSiteInfo(allSettings.siteInfo || {});
|
||||||
},
|
},
|
||||||
|
|
|
@ -62,4 +62,9 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-form {
|
||||||
|
width: 800px;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
|
|
@ -235,7 +235,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form {
|
|
||||||
width: 800px;
|
.fs-16{
|
||||||
margin: 20px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,13 @@ import * as storages from "./util.storage";
|
||||||
import commons from "./util.common";
|
import commons from "./util.common";
|
||||||
import * as mitt from "./util.mitt";
|
import * as mitt from "./util.mitt";
|
||||||
import router from "/util.router";
|
import router from "/util.router";
|
||||||
|
import { treeUtils } from "./util.tree";
|
||||||
export const util = {
|
export const util = {
|
||||||
...envs,
|
...envs,
|
||||||
...sites,
|
...sites,
|
||||||
...storages,
|
...storages,
|
||||||
...commons,
|
...commons,
|
||||||
...mitt,
|
...mitt,
|
||||||
...router
|
...router,
|
||||||
|
tree: treeUtils
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
export function eachTree(tree: any[], callback: (item: any) => void) {
|
||||||
|
tree.forEach((item) => {
|
||||||
|
callback(item);
|
||||||
|
if (item.children) {
|
||||||
|
eachTree(item.children, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const treeUtils = {
|
||||||
|
eachTree
|
||||||
|
};
|
|
@ -1,99 +1,124 @@
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { Ref, ref } from "vue";
|
import { Ref, ref } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { compute, CreateCrudOptionsProps, CreateCrudOptionsRet } from "@fast-crud/fast-crud";
|
import { AddReq, compute, CreateCrudOptionsProps, CreateCrudOptionsRet, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import { cloneDeep } from "lodash-es";
|
import { cloneDeep, find, merge, remove } from "lodash-es";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
|
import { SettingsSave } from "../api";
|
||||||
|
|
||||||
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
|
||||||
const { crudBinding } = crudExpose;
|
const { crudBinding } = crudExpose;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
const menusRef = ref(cloneDeep(settingStore.headerMenus?.menus || []));
|
|
||||||
|
|
||||||
const selectedRowKeys: Ref<any[]> = ref([]);
|
async function saveMenus() {
|
||||||
context.selectedRowKeys = selectedRowKeys;
|
const menus = settingStore.headerMenus;
|
||||||
|
await SettingsSave("sys.header.menus", menus);
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachTree(tree: any[], callback: (item: any) => void) {
|
||||||
|
tree.forEach((item) => {
|
||||||
|
callback(item);
|
||||||
|
if (item.children) {
|
||||||
|
eachTree(item.children, callback);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandedRowKeys = ref<string[]>([]);
|
||||||
|
const pageRequest = async (query: UserPageQuery): Promise<UserPageRes> => {
|
||||||
|
const records = cloneDeep(settingStore.headerMenus?.menus || []);
|
||||||
|
expandedRowKeys.value = [];
|
||||||
|
eachTree(records, (item) => {
|
||||||
|
if (item.children && item.children.length > 0) {
|
||||||
|
expandedRowKeys.value.push(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
records: records,
|
||||||
|
total: records.length,
|
||||||
|
limit: 9999999,
|
||||||
|
offset: 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
const editRequest = async ({ form, row }: EditReq) => {
|
||||||
|
form.id = row.id;
|
||||||
|
let found: any = undefined;
|
||||||
|
eachTree(settingStore.headerMenus?.menus || [], (item) => {
|
||||||
|
if (item.id === row.id) {
|
||||||
|
merge(item, form);
|
||||||
|
found = item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await saveMenus();
|
||||||
|
return found;
|
||||||
|
};
|
||||||
|
const delRequest = async ({ row }: DelReq) => {
|
||||||
|
eachTree([{ children: settingStore.headerMenus?.menus }], (item) => {
|
||||||
|
if (item.children) {
|
||||||
|
remove(item.children, (child) => child.id === row.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await saveMenus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addRequest = async ({ form }: AddReq) => {
|
||||||
|
form.id = nanoid();
|
||||||
|
if (form.parentId) {
|
||||||
|
eachTree(settingStore.headerMenus?.menus || [], (item) => {
|
||||||
|
if (item.id === form.parentId) {
|
||||||
|
if (!item.children) {
|
||||||
|
item.children = [];
|
||||||
|
}
|
||||||
|
item.children.push(form);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
settingStore.headerMenus?.menus.push(form);
|
||||||
|
}
|
||||||
|
parent.value = null;
|
||||||
|
await saveMenus();
|
||||||
|
return form;
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
crudOptions: {
|
crudOptions: {
|
||||||
settings: {
|
request: {
|
||||||
plugins: {
|
pageRequest,
|
||||||
//这里使用行选择插件,生成行选择crudOptions配置,最终会与crudOptions合并
|
addRequest,
|
||||||
rowSelection: {
|
editRequest,
|
||||||
enabled: true,
|
delRequest
|
||||||
order: -2,
|
|
||||||
before: true,
|
|
||||||
// handle: (pluginProps,useCrudProps)=>CrudOptions,
|
|
||||||
props: {
|
|
||||||
multiple: true,
|
|
||||||
crossPage: true,
|
|
||||||
selectedRowKeys
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actionbar: {
|
|
||||||
buttons: {
|
|
||||||
add: {
|
|
||||||
show: false
|
|
||||||
},
|
|
||||||
addRow: {
|
|
||||||
show: true,
|
|
||||||
click: () => {
|
|
||||||
crudBinding.value.data.push({ id: nanoid() });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
save: {
|
|
||||||
text: "保存菜单",
|
|
||||||
type: "primary",
|
|
||||||
click: async () => {
|
|
||||||
await settingStore.saveHeaderMenus({ menus: menusRef.value });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
toolbar: {
|
|
||||||
buttons: {
|
|
||||||
refresh: {
|
|
||||||
show: false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mode: {
|
|
||||||
name: "local",
|
|
||||||
isMergeWhenUpdate: true,
|
|
||||||
isAppendWhenAdd: true
|
|
||||||
},
|
|
||||||
table: {
|
table: {
|
||||||
defaultExpandAllRows: true,
|
|
||||||
expandRowByClick: true,
|
expandRowByClick: true,
|
||||||
editable: {
|
defaultExpandAllRows: true,
|
||||||
enabled: true,
|
expandedRowKeys: expandedRowKeys,
|
||||||
mode: "row",
|
"onUpdate:expandedRowKeys": (val: string[]) => {
|
||||||
activeDefault: true,
|
expandedRowKeys.value = val;
|
||||||
showAction: true,
|
|
||||||
rowKey: "id"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pagination: { show: false, pageSize: 9999999 },
|
pagination: { show: false, pageSize: 9999999 },
|
||||||
rowHandle: {
|
rowHandle: {
|
||||||
width: 300,
|
width: 300,
|
||||||
fixed: "right",
|
fixed: "right",
|
||||||
group: {
|
buttons: {
|
||||||
editRow: {
|
addChild: {
|
||||||
addChild: {
|
title: "添加子菜单",
|
||||||
text: "添加子菜单",
|
text: null,
|
||||||
click: ({ row }) => {
|
type: "link",
|
||||||
if (row.children == null) {
|
icon: "ion:add-circle-outline",
|
||||||
row.children = [];
|
click: ({ row }) => {
|
||||||
|
crudExpose.openAdd({
|
||||||
|
row: {
|
||||||
|
parentId: row.id
|
||||||
}
|
}
|
||||||
row.children.push({ id: nanoid() });
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +129,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
key: "id",
|
key: "id",
|
||||||
type: "text",
|
type: "text",
|
||||||
column: {
|
column: {
|
||||||
width: 200
|
width: 200,
|
||||||
|
show: false
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
show: false
|
show: false
|
||||||
|
@ -115,20 +141,60 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
|
||||||
type: "text",
|
type: "text",
|
||||||
column: {
|
column: {
|
||||||
width: 300
|
width: 300
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入标题"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: {
|
icon: {
|
||||||
title: "图标",
|
title: "图标",
|
||||||
type: "text",
|
type: "text",
|
||||||
column: {
|
column: {
|
||||||
width: 300
|
width: 300,
|
||||||
|
cellRender: ({ row }) => {
|
||||||
|
return <fs-icon class={"fs-16"} icon={row.icon}></fs-icon>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
component: {
|
||||||
|
placeholder: "ion:add-circle"
|
||||||
|
},
|
||||||
|
helper: {
|
||||||
|
render: () => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<a href="https://icon-sets.iconify.design/" target="_blank">
|
||||||
|
图标库
|
||||||
|
</a>
|
||||||
|
<span>--搜索--选择图标--复制名称--填到这里</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
link: {
|
path: {
|
||||||
title: "链接",
|
title: "链接",
|
||||||
type: "text",
|
type: "link",
|
||||||
column: {
|
column: {
|
||||||
width: 300
|
width: 300
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: "请输入链接"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "url",
|
||||||
|
message: "请输入正确的链接"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { onMounted } from "vue";
|
||||||
import { useFs } from "@fast-crud/fast-crud";
|
import { useFs } from "@fast-crud/fast-crud";
|
||||||
import createCrudOptions from "./crud";
|
import createCrudOptions from "./crud";
|
||||||
import { useSettingStore } from "/@/store/modules/settings";
|
import { useSettingStore } from "/@/store/modules/settings";
|
||||||
import { cloneDeep } from "lodash-es";
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "SettingsHeaderMenus"
|
name: "SettingsHeaderMenus"
|
||||||
});
|
});
|
||||||
|
@ -21,7 +21,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions
|
||||||
const settingStore = useSettingStore();
|
const settingStore = useSettingStore();
|
||||||
// 页面打开后获取列表数据
|
// 页面打开后获取列表数据
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
crudBinding.value.data = cloneDeep(settingStore.headerMenus.menus || []);
|
crudExpose.doRefresh();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="less"></style>
|
<style lang="less"></style>
|
||||||
|
|
|
@ -64,6 +64,7 @@ import * as api from "./api";
|
||||||
import { notification } from "ant-design-vue";
|
import { notification } from "ant-design-vue";
|
||||||
import { useSettingStore } from "/src/store/modules/settings";
|
import { useSettingStore } from "/src/store/modules/settings";
|
||||||
import { useUserStore } from "/@/store/modules/user";
|
import { useUserStore } from "/@/store/modules/user";
|
||||||
|
import { merge } from "lodash-es";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "SiteSetting"
|
name: "SiteSetting"
|
||||||
|
@ -85,7 +86,7 @@ async function loadSysSiteSettings() {
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Object.assign(formState, data);
|
merge(formState, data);
|
||||||
}
|
}
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
loadSysSiteSettings();
|
loadSysSiteSettings();
|
||||||
|
|
Loading…
Reference in New Issue