perf: 顶部菜单自定义

pull/229/head
xiaojunnuo 2024-10-27 02:51:56 +08:00
parent 661293c189
commit 54d136cc6a
12 changed files with 192 additions and 98 deletions

View File

@ -1,6 +1,6 @@
<template>
<a-dropdown class="fs-locale-picker">
<div>
<div style="display: block">
<fs-iconify icon="ion-globe-outline" @click.prevent></fs-iconify>
</div>

View File

@ -89,11 +89,12 @@ export default defineComponent({
}
}
const title: any = () => {
if (sub?.meta?.icon) {
const icon = sub.icon || sub?.meta?.icon;
if (icon) {
// @ts-ignore
return (
<div class={"menu-item-title"}>
<fsIcon class={"anticon"} icon={sub.meta.icon} />
<fsIcon class={"anticon"} icon={icon} />
<span>{sub.title}</span>
</div>
);

View File

@ -41,6 +41,8 @@
<!-- >-->
<!-- Button-->
<!-- </button>-->
<fs-menu class="header-menu" mode="horizontal" :expand-selected="false" :selectable="false" :menus="settingStore.getHeaderMenus" />
<fs-menu
v-if="!settingStore?.isAgent && !settingStore.isComm"
class="header-menu"

View File

@ -68,20 +68,20 @@ export const sysResources = [
permission: "sys:settings:view"
}
},
// {
// title: "顶部菜单设置",
// name: "HeaderMenus",
// path: "/sys/settings/header-menus",
// component: "/sys/settings/header-menus/index.vue",
// meta: {
// show: () => {
// const settingStore = useSettingStore();
// return settingStore.isComm;
// },
// icon: "ion:document-text-outline",
// permission: "sys:settings:view"
// }
// },
{
title: "顶部菜单设置",
name: "HeaderMenus",
path: "/sys/settings/header-menus",
component: "/sys/settings/header-menus/index.vue",
meta: {
show: () => {
const settingStore = useSettingStore();
return settingStore.isComm;
},
icon: "ion:document-text-outline",
permission: "sys:settings:view"
}
},
{
title: "系统级授权",
name: "SysAccess",

View File

@ -1,6 +1,6 @@
import { defineStore } from "pinia";
import { Modal, notification, theme } from "ant-design-vue";
import _ from "lodash-es";
import _, { cloneDeep } from "lodash-es";
// @ts-ignore
import { LocalStorage } from "/src/utils/util.storage";
@ -10,6 +10,7 @@ import { useUserStore } from "/@/store/modules/user";
import { mitter } from "/@/utils/util.mitt";
import { env } from "/@/utils/util.env";
import { toRef } from "vue";
import { util } from "/@/utils";
export type ThemeToken = {
token: {
@ -120,6 +121,9 @@ export const useSettingStore = defineStore({
comm: "商业版"
};
return vipLabelMap[this.plusInfo?.vipType || "free"];
},
getHeaderMenus() {
return this.headerMenus?.menus || [];
}
},
actions: {
@ -137,6 +141,7 @@ export const useSettingStore = defineStore({
_.merge(this.installInfo, allSettings.installInfo || {});
_.merge(this.siteEnv, allSettings.siteEnv || {});
_.merge(this.plusInfo, allSettings.plusInfo || {});
_.merge(this.headerMenus, allSettings.headerMenus || {});
//@ts-ignore
this.initSiteInfo(allSettings.siteInfo || {});
},

View File

@ -63,3 +63,8 @@
}
}
}
.settings-form {
width: 800px;
margin: 20px;
}

View File

@ -235,7 +235,7 @@ h1, h2, h3, h4, h5, h6 {
}
}
.settings-form {
width: 800px;
margin: 20px;
.fs-16{
font-size: 16px;
}

View File

@ -4,11 +4,13 @@ import * as storages from "./util.storage";
import commons from "./util.common";
import * as mitt from "./util.mitt";
import router from "/util.router";
import { treeUtils } from "./util.tree";
export const util = {
...envs,
...sites,
...storages,
...commons,
...mitt,
...router
...router,
tree: treeUtils
};

View File

@ -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
};

View File

@ -1,99 +1,124 @@
import { useI18n } from "vue-i18n";
import { Ref, ref } from "vue";
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 { cloneDeep } from "lodash-es";
import { cloneDeep, find, merge, remove } from "lodash-es";
import { nanoid } from "nanoid";
import { SettingsSave } from "../api";
export default function ({ crudExpose, context }: CreateCrudOptionsProps): CreateCrudOptionsRet {
const { crudBinding } = crudExpose;
const router = useRouter();
const { t } = useI18n();
const settingStore = useSettingStore();
const menusRef = ref(cloneDeep(settingStore.headerMenus?.menus || []));
const selectedRowKeys: Ref<any[]> = ref([]);
context.selectedRowKeys = selectedRowKeys;
async function saveMenus() {
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 {
crudOptions: {
settings: {
plugins: {
//这里使用行选择插件生成行选择crudOptions配置最终会与crudOptions合并
rowSelection: {
enabled: true,
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 });
}
}
}
request: {
pageRequest,
addRequest,
editRequest,
delRequest
},
search: {
show: false
},
toolbar: {
buttons: {
refresh: {
show: false
}
}
},
mode: {
name: "local",
isMergeWhenUpdate: true,
isAppendWhenAdd: true
},
table: {
defaultExpandAllRows: true,
expandRowByClick: true,
editable: {
enabled: true,
mode: "row",
activeDefault: true,
showAction: true,
rowKey: "id"
defaultExpandAllRows: true,
expandedRowKeys: expandedRowKeys,
"onUpdate:expandedRowKeys": (val: string[]) => {
expandedRowKeys.value = val;
}
},
pagination: { show: false, pageSize: 9999999 },
rowHandle: {
width: 300,
fixed: "right",
group: {
editRow: {
buttons: {
addChild: {
text: "添加子菜单",
title: "添加子菜单",
text: null,
type: "link",
icon: "ion:add-circle-outline",
click: ({ row }) => {
if (row.children == null) {
row.children = [];
}
row.children.push({ id: nanoid() });
crudExpose.openAdd({
row: {
parentId: row.id
}
});
}
}
}
@ -104,7 +129,8 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
key: "id",
type: "text",
column: {
width: 200
width: 200,
show: false
},
form: {
show: false
@ -115,20 +141,60 @@ export default function ({ crudExpose, context }: CreateCrudOptionsProps): Creat
type: "text",
column: {
width: 300
},
form: {
rules: [
{
required: true,
message: "请输入标题"
}
]
}
},
icon: {
title: "图标",
type: "text",
column: {
width: 300
width: 300,
cellRender: ({ row }) => {
return <fs-icon class={"fs-16"} icon={row.icon}></fs-icon>;
}
},
link: {
form: {
component: {
placeholder: "ion:add-circle"
},
helper: {
render: () => {
return (
<span>
<a href="https://icon-sets.iconify.design/" target="_blank">
</a>
<span>--------</span>
</span>
);
}
}
}
},
path: {
title: "链接",
type: "text",
type: "link",
column: {
width: 300
},
form: {
rules: [
{
required: true,
message: "请输入链接"
},
{
type: "url",
message: "请输入正确的链接"
}
]
}
}
}

View File

@ -12,7 +12,7 @@ import { onMounted } from "vue";
import { useFs } from "@fast-crud/fast-crud";
import createCrudOptions from "./crud";
import { useSettingStore } from "/@/store/modules/settings";
import { cloneDeep } from "lodash-es";
defineOptions({
name: "SettingsHeaderMenus"
});
@ -21,7 +21,7 @@ const { crudBinding, crudRef, crudExpose, context } = useFs({ createCrudOptions
const settingStore = useSettingStore();
//
onMounted(() => {
crudBinding.value.data = cloneDeep(settingStore.headerMenus.menus || []);
crudExpose.doRefresh();
});
</script>
<style lang="less"></style>

View File

@ -64,6 +64,7 @@ import * as api from "./api";
import { notification } from "ant-design-vue";
import { useSettingStore } from "/src/store/modules/settings";
import { useUserStore } from "/@/store/modules/user";
import { merge } from "lodash-es";
defineOptions({
name: "SiteSetting"
@ -85,7 +86,7 @@ async function loadSysSiteSettings() {
if (data == null) {
return;
}
Object.assign(formState, data);
merge(formState, data);
}
const saveLoading = ref(false);
loadSysSiteSettings();