feat: 添加字典管理
parent
09498ec02e
commit
1133555ca2
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,156 @@
|
|||
<template>
|
||||
<bread-page>
|
||||
<template #content>
|
||||
<div class="h-full w-full grid grid-cols-[auto_1fr] gap-4 p-4">
|
||||
<div class="bg-white w-[256px]">
|
||||
<div class="flex items-center justify-between gap-2 px-4 h-14">
|
||||
<span class="text-base">菜单列表</span>
|
||||
<div>
|
||||
<a-button>
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-plus"></i>
|
||||
</template>
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<a-tree
|
||||
:data="menus"
|
||||
:default-expand-all="true"
|
||||
:block-node="true"
|
||||
:field-names="{
|
||||
icon: undefined,
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
}"
|
||||
>
|
||||
<template #title="node">
|
||||
<div class="group flex-1 flex items-center justify-between gap-2">
|
||||
<div @click="onEdit(node)">
|
||||
<!-- <a-tag :color="MenuTypes.fmt(node.type, 'color')" size="small" :bordered="true">
|
||||
{{ MenuTypes.fmt(node.type) }}
|
||||
</a-tag> -->
|
||||
<i :class="node.icon" class="ml-2"></i>
|
||||
{{ node.name }}
|
||||
</div>
|
||||
<div class="hidden group-hover:block">
|
||||
<i
|
||||
v-if="node.type === MenuType.MENU"
|
||||
class="text-sm text-gray-400 hover:text-gray-700 icon-park-outline-plus"
|
||||
></i>
|
||||
<i class="text-sm text-gray-400 hover:text-gray-700 icon-park-outline-delete"></i>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-tree>
|
||||
</div>
|
||||
<div class="bg-white">
|
||||
<a-card title="菜单信息" :bordered="false">
|
||||
<Form ref="formRef" v-bind="form"></Form>
|
||||
</a-card>
|
||||
<a-divider :margin="0"></a-divider>
|
||||
<div class="px-4 mt-4">
|
||||
<btn-table></btn-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</bread-page>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { Menu, api } from "@/api";
|
||||
import { useForm, Form, useAniTable, FormInstance } from "@/components";
|
||||
import { MenuType, MenuTypes } from "@/constants/menu";
|
||||
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const menus = ref<any[]>([]);
|
||||
const treeEach = (tree: any[], fn: any) => {
|
||||
for (const item of tree) {
|
||||
if (item.children) {
|
||||
treeEach(item.children, fn);
|
||||
}
|
||||
fn(item);
|
||||
}
|
||||
};
|
||||
|
||||
const onEdit = (row: any) => {
|
||||
formRef.value?.setModel(row);
|
||||
(btn.props as any).data = row.buttons;
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const res = await api.menu.getMenus({ tree: true });
|
||||
const data = res.data.data ?? [];
|
||||
treeEach(data, (item: Menu) => {
|
||||
if (item.type === MenuType.BUTTON) {
|
||||
return;
|
||||
}
|
||||
if (item.type === MenuType.PAGE) {
|
||||
(item as any).buttons = (item as any).children;
|
||||
delete (item as any).children;
|
||||
}
|
||||
(item as any).iconRender = () => <i class={item.icon} />;
|
||||
});
|
||||
menus.value = data;
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
items: [
|
||||
{
|
||||
field: "name",
|
||||
label: "菜单名称",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "icon",
|
||||
label: "菜单图标",
|
||||
type: "input",
|
||||
},
|
||||
],
|
||||
async submit(arg) {
|
||||
console.log(arg);
|
||||
},
|
||||
});
|
||||
|
||||
const [btnTable, btn] = useAniTable({
|
||||
columns: [
|
||||
{
|
||||
title: " 名称",
|
||||
dataIndex: "name",
|
||||
},
|
||||
{
|
||||
title: "标识",
|
||||
dataIndex: "code",
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
type: "button",
|
||||
width: 140,
|
||||
buttons: [
|
||||
{
|
||||
type: "modify",
|
||||
text: "修改",
|
||||
},
|
||||
{
|
||||
text: "删除",
|
||||
type: "delete",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
create: {},
|
||||
modify: {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
|
||||
<route lang="json">
|
||||
{
|
||||
"meta": {
|
||||
"sort": 10302,
|
||||
"title": "菜单管理",
|
||||
"icon": "icon-park-outline-add-subtract"
|
||||
}
|
||||
}
|
||||
</route>
|
||||
|
|
@ -143,7 +143,7 @@ export const FormModal = defineComponent({
|
|||
}
|
||||
if (typeof props.trigger === "object") {
|
||||
content = (
|
||||
<Button type="primary" {...omit(props.trigger, "text")}>
|
||||
<Button type="primary" {...props.trigger.buttonProps}>
|
||||
{props.trigger?.text || "新增"}
|
||||
</Button>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
<template>
|
||||
<bread-page>
|
||||
<iframe
|
||||
src="https://apifox.com/apidoc/shared-f1ea65e6-cee8-4fe3-949f-288a7cd1af49"
|
||||
frameborder="0"
|
||||
class="w-full h-full"
|
||||
></iframe>
|
||||
<template #content>
|
||||
<iframe
|
||||
src="https://apifox.com/apidoc/shared-f1ea65e6-cee8-4fe3-949f-288a7cd1af49"
|
||||
frameborder="0"
|
||||
class="w-full h-full"
|
||||
></iframe>
|
||||
</template>
|
||||
</bread-page>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import doc from "@/dd.json";
|
||||
import doc from "./data.json";
|
||||
import editorModal from "./editor.vue";
|
||||
import ejs from "ejs";
|
||||
import template from "./page.ejs?raw";
|
||||
|
|
@ -85,8 +85,8 @@ const onChange = (value: string | number) => {
|
|||
|
||||
const onOpen = () => {
|
||||
const data = {
|
||||
tag: '',
|
||||
operationId: '',
|
||||
tag: "",
|
||||
operationId: "",
|
||||
create: {},
|
||||
select: {},
|
||||
modify: {},
|
||||
|
|
@ -106,7 +106,6 @@ const onOpen = () => {
|
|||
data.delete = route;
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
content.value = ejs.render(template, data);
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div class="w-[210px] h-full overflow-hidden grid grid-rows-[auto_1fr]">
|
||||
<div class="flex gap-2">
|
||||
<a-input-search allow-clear placeholder="字典名称..." class="mb-2"></a-input-search>
|
||||
<a-button @click="onCreateRow">
|
||||
<a-input-search allow-clear placeholder="字典类型" class="mb-2"></a-input-search>
|
||||
<a-button @click="formCtx.open">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-add"></i>
|
||||
</template>
|
||||
|
|
@ -13,12 +13,13 @@
|
|||
<ul class="pl-0 mt-0">
|
||||
<li
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
:key="item.code"
|
||||
:class="{ active: item.id === current?.id }"
|
||||
class="group flex items-center justify-between gap-1 h-8 rounded mb-2 pl-3 hover:bg-gray-100 cursor-pointer"
|
||||
>
|
||||
<div>
|
||||
<div class="flex-1 h-full flex items-center gap-2 overflow-hidden" @click="emit('change', item)">
|
||||
<i class="icon-park-outline-folder-close align-[-2px]"></i>
|
||||
{{ item.title }}
|
||||
<span class="flex-1 truncate">{{ item.name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<a-dropdown>
|
||||
|
|
@ -28,13 +29,13 @@
|
|||
</template>
|
||||
</a-button>
|
||||
<template #content>
|
||||
<a-doption @click="onModifyRow(item)">
|
||||
<a-doption @click="formCtx.open(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-edit"></i>
|
||||
</template>
|
||||
修改
|
||||
</a-doption>
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow">
|
||||
<a-doption class="!text-red-500" @click="onDeleteRow(item)">
|
||||
<template #icon>
|
||||
<i class="icon-park-outline-delete"></i>
|
||||
</template>
|
||||
|
|
@ -50,86 +51,77 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { DictType, api } from "@/api";
|
||||
import { useAniFormModal } from "@/components";
|
||||
import { delConfirm } from "@/utils";
|
||||
import { Message } from "@arco-design/web-vue";
|
||||
import { PropType } from "vue";
|
||||
|
||||
const data = [
|
||||
{
|
||||
id: 1,
|
||||
title: "用户性别",
|
||||
count: 23,
|
||||
defineProps({
|
||||
current: {
|
||||
type: Object as PropType<DictType>,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "微信头像",
|
||||
count: 52,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "文章封面",
|
||||
count: 19,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "山水诗画",
|
||||
count: 81,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: "虾米沙雕",
|
||||
count: 12,
|
||||
},
|
||||
];
|
||||
});
|
||||
|
||||
const list = ref(data);
|
||||
const emit = defineEmits(["change"]);
|
||||
const list = ref<DictType[]>([]);
|
||||
|
||||
const onModifyRow = (row: any) => {
|
||||
formCtx.props.title = "修改字典";
|
||||
formCtx.open(row);
|
||||
const updateDictTypes = async () => {
|
||||
const res = await api.dictType.getDictTypes({ size: 0 });
|
||||
list.value = res.data.data ?? [];
|
||||
list.value.length && emit("change", list.value[0]);
|
||||
};
|
||||
|
||||
const onCreateRow = () => {
|
||||
formCtx.props.title = "新建字典";
|
||||
formCtx.open();
|
||||
};
|
||||
onMounted(updateDictTypes);
|
||||
|
||||
const onDeleteRow = async () => {
|
||||
const onDeleteRow = async (row: DictType) => {
|
||||
await delConfirm();
|
||||
const res = await api.dictType.delDictType(row.id);
|
||||
Message.success(res.data.message);
|
||||
};
|
||||
|
||||
const [formModal, formCtx] = useAniFormModal({
|
||||
title: "修改分组",
|
||||
title: ({ model }) => (!model.id ? "新建字典类型" : "修改字典类型"),
|
||||
trigger: false,
|
||||
modalProps: {
|
||||
width: 432,
|
||||
width: 580,
|
||||
},
|
||||
model: {
|
||||
id: undefined,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "title",
|
||||
label: "分组名称",
|
||||
field: "name",
|
||||
label: "名称",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "code",
|
||||
label: "唯一编码",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
label: "备注信息",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
submit: async ({ model }) => {
|
||||
let res;
|
||||
if (model.id) {
|
||||
const item = list.value.find((i) => i.id === model.id);
|
||||
if (item) {
|
||||
item.title = model.title;
|
||||
}
|
||||
res = await api.dictType.setDictType(model.id, model);
|
||||
} else {
|
||||
const ids = list.value.map((i) => i.id);
|
||||
const maxId = Math.max.apply(null, ids);
|
||||
list.value.push({
|
||||
id: maxId,
|
||||
title: model.title,
|
||||
count: 0,
|
||||
});
|
||||
res = await api.dictType.addDictType(model);
|
||||
}
|
||||
updateDictTypes();
|
||||
return res;
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
||||
<style lang="less" scoped>
|
||||
.active {
|
||||
color: rgb(var(--primary-6));
|
||||
background-color: rgb(var(--primary-1));
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,56 +3,125 @@
|
|||
<div class="py-2 px-4 bg-white">
|
||||
<bread-crumb></bread-crumb>
|
||||
</div>
|
||||
<div class="grid grid-cols-[auto_auto_1fr] h-full overflow-hidden bg-white p-4 m-4 rounded">
|
||||
<div class="grid grid-cols-[auto_1fr] gap-4 overflow-hidden bg-white p-4 m-4 rounded">
|
||||
<div>
|
||||
<ani-group></ani-group>
|
||||
<ani-group :current="current" @change="onTypeChange"></ani-group>
|
||||
</div>
|
||||
<a-divider direction="vertical"></a-divider>
|
||||
<div>
|
||||
<a-alert :show-icon="false" class="mb-3 !border-brand-500">
|
||||
<span class="text-brand-500 font-bold">{{ current?.name }}</span>
|
||||
<div class="mt-1">描述:{{ current?.description }}</div>
|
||||
</a-alert>
|
||||
<dict-table></dict-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="tsx">
|
||||
import { DictType, api } from "@/api";
|
||||
import aniGroup from "./components/group.vue";
|
||||
import { useAniTable, useForm, Form } from "@/components";
|
||||
import { useAniTable, createColumn, updateColumn } from "@/components";
|
||||
|
||||
const form = useForm({
|
||||
items: [
|
||||
{
|
||||
field: "字典名",
|
||||
label: "字典名",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "字典名",
|
||||
label: "",
|
||||
type: "submit",
|
||||
},
|
||||
],
|
||||
});
|
||||
const current = ref<DictType>();
|
||||
const onTypeChange = (item: DictType) => {
|
||||
current.value = item;
|
||||
dict.refresh();
|
||||
};
|
||||
|
||||
const [dictTable, dict] = useAniTable({
|
||||
async data(search, paging) {
|
||||
return api.dict.getDicts({ ...search, ...paging, typeId: current.value?.id } as any);
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: "字典名",
|
||||
title: "字典项",
|
||||
dataIndex: "name",
|
||||
render: ({ record }) => {
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{record.name}: {record.code}
|
||||
</div>
|
||||
<div class="text-gray-400 text-xs">{record.description}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
createColumn,
|
||||
updateColumn,
|
||||
{
|
||||
title: "字典值",
|
||||
dataIndex: "name",
|
||||
title: "操作",
|
||||
type: "button",
|
||||
width: 140,
|
||||
buttons: [
|
||||
{
|
||||
type: "modify",
|
||||
text: "修改",
|
||||
},
|
||||
{
|
||||
type: "delete",
|
||||
text: "删除",
|
||||
onClick: ({ record }) => {
|
||||
return api.dict.delDict(record.id);
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
create: {
|
||||
search: {
|
||||
button: false,
|
||||
items: [
|
||||
{
|
||||
field: "字典名",
|
||||
field: "name",
|
||||
label: "名称",
|
||||
type: "search",
|
||||
searchable: true,
|
||||
enterable: true,
|
||||
nodeProps: {
|
||||
placeholder: "字典名称",
|
||||
},
|
||||
itemProps: {
|
||||
hideLabel: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
create: {
|
||||
title: '新增字典',
|
||||
model: {
|
||||
typeId: undefined,
|
||||
},
|
||||
modalProps: {
|
||||
width: 580,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
field: "name",
|
||||
label: "字典名",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "code",
|
||||
label: "字典指",
|
||||
type: "input",
|
||||
},
|
||||
{
|
||||
field: "description",
|
||||
label: "备注",
|
||||
type: "textarea",
|
||||
},
|
||||
],
|
||||
submit: async ({ model }) => {
|
||||
return api.dict.addDict({ ...model, typeId: current.value?.id });
|
||||
},
|
||||
},
|
||||
modify: {
|
||||
extend: true,
|
||||
title: "修改字典",
|
||||
submit: async ({ model }) => {
|
||||
return api.dict.setDict(model.id, { ...model, typeId: current.value?.id });
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ const menuArr = flatedMenus.map((i) => ({ label: i.title, value: i.id }));
|
|||
|
||||
const expanded = ref(false);
|
||||
const toggleExpand = () => {
|
||||
console.log(menu.tableRef.value);
|
||||
expanded.value = !expanded.value;
|
||||
menu.tableRef.value?.tableRef?.expandAll(expanded.value);
|
||||
};
|
||||
|
|
@ -64,12 +63,7 @@ const [menuTable, menu] = useAniTable({
|
|||
<span>{record.name ?? "无"}</span>
|
||||
<span class="text-gray-400 text-xs truncate">{id}</span>
|
||||
</div>
|
||||
<a-switch checked-color="#3c9" size="small">
|
||||
{{
|
||||
"checked-icon": () => <i class="icon-park-outline-check"></i>,
|
||||
"unchecked-icon": () => <i class="icon-park-outline-close"></i>,
|
||||
}}
|
||||
</a-switch>
|
||||
<a-switch checked-color="#3c9" size="small"></a-switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -102,13 +96,6 @@ const [menuTable, menu] = useAniTable({
|
|||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// title: "启用",
|
||||
// dataIndex: "createdAt",
|
||||
// width: 80,
|
||||
// align: "center",
|
||||
// render: ({ record }) => <a-switch checked-color="#3c9"></a-switch>,
|
||||
// },
|
||||
],
|
||||
search: {
|
||||
items: [
|
||||
|
|
@ -137,10 +124,9 @@ const [menuTable, menu] = useAniTable({
|
|||
initial: 0,
|
||||
label: "父级",
|
||||
type: "treeSelect",
|
||||
async options(arg) {
|
||||
async options() {
|
||||
const res = await api.menu.getMenus({ size: 0, tree: true });
|
||||
const data = res.data.data;
|
||||
console.log(arg);
|
||||
return [
|
||||
{
|
||||
id: 0,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ declare module '@vue/runtime-core' {
|
|||
ACheckbox: typeof import('@arco-design/web-vue')['Checkbox']
|
||||
ACheckboxGroup: typeof import('@arco-design/web-vue')['CheckboxGroup']
|
||||
AConfigProvider: typeof import('@arco-design/web-vue')['ConfigProvider']
|
||||
ADatePicker: typeof import('@arco-design/web-vue')['DatePicker']
|
||||
ADivider: typeof import('@arco-design/web-vue')['Divider']
|
||||
ADoption: typeof import('@arco-design/web-vue')['Doption']
|
||||
ADrawer: typeof import('@arco-design/web-vue')['Drawer']
|
||||
|
|
@ -68,6 +67,7 @@ declare module '@vue/runtime-core' {
|
|||
Editor: typeof import('./../components/editor/index.vue')['default']
|
||||
Header: typeof import('./../components/editor/panel-main/components/header.vue')['default']
|
||||
ImagePicker: typeof import('./../components/editor/components/ImagePicker.vue')['default']
|
||||
'Index.dev1': typeof import('./../components/breadcrumb/index.dev1.vue')['default']
|
||||
InputColor: typeof import('./../components/editor/components/InputColor.vue')['default']
|
||||
InputImage: typeof import('./../components/editor/components/InputImage.vue')['default']
|
||||
Marquee: typeof import('./../components/editor/blocks/text/marquee.vue')['default']
|
||||
|
|
@ -81,6 +81,7 @@ declare module '@vue/runtime-core' {
|
|||
Render: typeof import('./../components/editor/blocks/date/render.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
'Temp.dev1': typeof import('./../components/breadcrumb/temp.dev1.vue')['default']
|
||||
Texter: typeof import('./../components/editor/panel-main/components/texter.vue')['default']
|
||||
Toast: typeof import('./../components/toast/toast.vue')['default']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
/**
|
||||
* 列表转树结构
|
||||
* @param list 数组
|
||||
* @param id ID key
|
||||
* @param pid 父级key
|
||||
* @param cid 子项key
|
||||
* @returns
|
||||
*/
|
||||
export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "children") => {
|
||||
const map = list.reduce((res, v) => ((res[v[id]] = v), res), {});
|
||||
return list.filter((item) => {
|
||||
|
|
@ -10,11 +18,18 @@ export const listToTree = (list: any[], id = "id", pid = "parentId", cid = "chil
|
|||
});
|
||||
};
|
||||
|
||||
export function treeEach(tree: any[], fn: (item: any) => void) {
|
||||
/**
|
||||
* 遍历树结构
|
||||
* @param tree 数组
|
||||
* @param fn 函数
|
||||
* @param before 是否广度遍历
|
||||
*/
|
||||
export function treeEach(tree: any[], fn: (item: any) => void, before = true) {
|
||||
for (const item of tree) {
|
||||
fn(item);
|
||||
before && fn(item);
|
||||
if (item.children) {
|
||||
treeEach(item.children, fn);
|
||||
}
|
||||
!before && fn(item);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue