Browse Source

🎟 构建平台业务模块

master
wangxiang 3 years ago
parent
commit
407f8ac125
  1. 4
      kicc-platform/kicc-platform-api/kicc-system-api/src/main/java/com/cloud/kicc/system/api/entity/Menu.java
  2. 1
      kicc-platform/kicc-platform-biz/kicc-system-biz/src/main/java/com/cloud/kicc/system/controller/MenuController.java
  3. 1
      kicc-platform/kicc-platform-biz/kicc-system-biz/src/main/java/com/cloud/kicc/system/service/impl/MenuServiceImpl.java
  4. 1
      kicc-ui/src/store/modules/user.ts
  5. 10
      kicc-ui/src/utils/dateUtil.ts
  6. 10
      kicc-ui/src/utils/helper/treeHelper.ts
  7. 91
      kicc-ui/src/views/system/menu/MenuModal.vue
  8. 117
      kicc-ui/src/views/system/menu/index.vue
  9. 209
      kicc-ui/src/views/system/menu/menu.data.ts
  10. 10
      kicc-ui/types/index.d.ts

4
kicc-platform/kicc-platform-api/kicc-system-api/src/main/java/com/cloud/kicc/system/api/entity/Menu.java

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.cloud.kicc.common.data.entity.CommonEntity; import com.cloud.kicc.common.data.entity.CommonEntity;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@ -87,6 +88,7 @@ public class Menu extends CommonEntity {
* 子菜单 * 子菜单
*/ */
@TableField(exist = false) @TableField(exist = false)
private List<Menu> children = new ArrayList<Menu>(); @JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<Menu> children = new ArrayList();
} }

1
kicc-platform/kicc-platform-biz/kicc-system-biz/src/main/java/com/cloud/kicc/system/controller/MenuController.java

@ -41,6 +41,7 @@ public class MenuController {
return new LambdaQueryWrapper<Menu>() return new LambdaQueryWrapper<Menu>()
.like(StrUtil.isNotBlank(menu.getName()), Menu::getName, menu.getName()) .like(StrUtil.isNotBlank(menu.getName()), Menu::getName, menu.getName())
.eq(StrUtil.isNotBlank(menu.getHideMenu()), Menu::getHideMenu, menu.getHideMenu()) .eq(StrUtil.isNotBlank(menu.getHideMenu()), Menu::getHideMenu, menu.getHideMenu())
.between(StrUtil.isNotBlank(menu.getBeginTime()) && StrUtil.isNotBlank(menu.getEndTime()), Menu::getCreateTime, menu.getBeginTime(), menu.getEndTime())
.orderByAsc(Menu::getSort); .orderByAsc(Menu::getSort);
} }

1
kicc-platform/kicc-platform-biz/kicc-system-biz/src/main/java/com/cloud/kicc/system/service/impl/MenuServiceImpl.java

@ -46,6 +46,7 @@ public class MenuServiceImpl extends ServiceImpl<MenuMapper, Menu> implements Me
menuVo.setComponent(menu.getComponent()); menuVo.setComponent(menu.getComponent());
// 检测是否是根节点,0:代表根节点 // 检测是否是根节点,0:代表根节点
if (MenuEnum.MENU_0.getValue().equals(menu.getParentId()) && !ReUtil.isMatch(RegexConstants.MATCHER_URL, menu.getPath())) { if (MenuEnum.MENU_0.getValue().equals(menu.getParentId()) && !ReUtil.isMatch(RegexConstants.MATCHER_URL, menu.getPath())) {
menuVo.setComponent("Layout");
menuVo.setPath("/" + menu.getPath()); menuVo.setPath("/" + menu.getPath());
} else { } else {
menuVo.setPath(menu.getPath()); menuVo.setPath(menu.getPath());

1
kicc-ui/src/store/modules/user.ts

@ -71,6 +71,7 @@ export const useUserStore = defineStore({
}, },
setPermissions(permissions: string[]) { setPermissions(permissions: string[]) {
this.permissions = permissions; this.permissions = permissions;
setAuthCache(PERMISSIONS_KEY, permissions);
}, },
setAccessToken(accessToken: string) { setAccessToken(accessToken: string) {
this.access_token = accessToken; this.access_token = accessToken;

10
kicc-ui/src/utils/dateUtil.ts

@ -21,4 +21,14 @@ export function formatToDate(date: moment.MomentInput = undefined, format = DATE
return moment(date).format(format); return moment(date).format(format);
} }
/** 添加日期范围 */
export function convertDateRange(params: Recordable, dateRangeField: string): Recordable {
const search = params, dateRange = search[dateRangeField];
if (null != dateRange && dateRange?.length > 0) {
search.beginTime = dateRange[0];
search.endTime = dateRange[1];
}
return search;
}
export const dateUtil = moment; export const dateUtil = moment;

10
kicc-ui/src/utils/helper/treeHelper.ts

@ -24,20 +24,16 @@ export function listToTree<T = any>(list: any[], config: Partial<TreeHelperConfi
const nodeMap = new Map(); const nodeMap = new Map();
const result: T[] = []; const result: T[] = [];
const { id, children, parentId } = conf; const { id, children, parentId } = conf;
for (const node of list) nodeMap.set(node[id], node);
for (const node of list) {
node[children] = node[children] || [];
nodeMap.set(node[id], node);
}
for (const node of list) { for (const node of list) {
const parent = nodeMap.get(node[parentId]); const parent = nodeMap.get(node[parentId]);
(parent ? parent.children : result).push(node); (parent ? parent[children] || (parent[children] = []) : result).push(node);
} }
return result; return result;
} }
/** 树形转换集合 */ /** 树形转换集合 */
export function treeToList<T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T { export function treeToList<T = any>(tree: any[], config: Partial<TreeHelperConfig> = {}): T {
config = getConfig(config); config = getConfig(config);
const { children } = config; const { children } = config;
const result: any = [...tree]; const result: any = [...tree];

91
kicc-ui/src/views/system/menu/MenuModal.vue

@ -1,72 +1,75 @@
<template> <template>
<BasicModal <BasicModal width="720px"
v-bind="$attrs" v-bind="$attrs"
@register="registerDrawer" @ok="handleSubmit"
showFooter @register="registerModal"
:title="getTitle"
width="50%"
@ok="handleSubmit"
> >
<BasicForm @register="registerForm" /> <BasicForm @register="registerForm"/>
</BasicModal> </BasicModal>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, computed, unref } from 'vue'; /**
* Copyright © 2020-2022 <a href="http://www.entfrm.com/">entfrm</a> All rights reserved.
* author entfrm开发团队-王翔
*/
import { ref, unref } from 'vue';
import { BasicForm, useForm } from '/@/components/Form/index'; import { BasicForm, useForm } from '/@/components/Form/index';
import { formSchema } from './menu.data'; import { formSchema } from './menu.data';
import { BasicModal, useModalInner } from '/@/components/Modal'; import { BasicModal, ModalProps, useModalInner } from '/@/components/Modal';
import { listMenu, addMenu, editMenu } from '/@/api/system/menu'; import { listMenu, addMenu, editMenu, getMenu } from '/@/api/system/menu';
import { listToTree } from '/@/utils/helper/treeHelper';
// emit const emits = defineEmits(['success', 'register']);
const emit = defineEmits(['success', 'register']);
//props const tag = ref('');
const props = defineProps({
updateFlag: Boolean
});
var isUpdate = ref(props.updateFlag);//
const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({ const [registerForm, { resetFields, setFieldsValue, updateSchema, validate }] = useForm({
labelWidth: 100, labelWidth: 100,
schemas: formSchema, schemas: formSchema,
showActionButtonGroup: false, showActionButtonGroup: false,
actionColOptions: { baseColProps: { span: 24 }
span: 23,
},
}); });
const [registerDrawer, { setModalProps, closeModal }] = useModalInner(async (data) => { const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data: ModalInnerData = {}) => {
resetFields(); await resetFields();
setModalProps({ confirmLoading: false }); listMenu().then(data => {
isUpdate.value = !!data?.isUpdate; updateSchema({
field: 'parentId',
if (unref(isUpdate)) { componentProps: {
setFieldsValue({ treeData: listToTree(data)
...data.record, }
}); });
}
const treeData = await menuList();
treeData.unshift({ id: '0', name: '顶级菜单' });
updateSchema({
field: 'parentId',
componentProps: { treeData },
}); });
tag.value = data._tag;
const menuId = data.record?.id;
const props: ModalProps = { confirmLoading: false };
switch (unref(tag)) {
case 'add':
props.title = '新增菜单';
menuId && await setFieldsValue({ parentId: menuId });
break;
case 'edit':
props.title = '编辑菜单';
await setFieldsValue(await getMenu({ id: menuId }) || {});
break;
}
setModalProps(props);
}); });
const getTitle = computed(() => (!unref(isUpdate) ? '新增菜单' : '编辑菜单'));
async function handleSubmit() { async function handleSubmit() {
try { try {
const values = await validate(); const formData = await validate();
setModalProps({ confirmLoading: true }); setModalProps({ confirmLoading: true });
if(unref(isUpdate)) { switch (unref(tag)) {
await menuUpdate(values); case 'add':
}else{ await addMenu(formData);
await menuAdd(values); break;
case 'edit':
await editMenu(formData);
break;
} }
emits('success');
closeModal(); closeModal();
emit('success');
} finally { } finally {
setModalProps({ confirmLoading: false }); setModalProps({ confirmLoading: false });
} }

117
kicc-ui/src/views/system/menu/index.vue

@ -1,51 +1,71 @@
<template> <template>
<div> <div>
<BasicTable @register="registerTable" @fetch-success="onFetchSuccess"> <BasicTable @register="registerTable">
<template #toolbar> <template #toolbar>
<a-button type="primary" @click="handleCreate">新增菜单</a-button> <a-button v-auth="['menu_add']"
<a-button type="default" @click="expandAll">展开全部</a-button> type="primary"
<a-button type="default" @click="collapseAll">折叠全部</a-button> @click="handleAdd"
>新增菜单</a-button>
<a-button type="default"
@click="expandAll"
>展开全部</a-button>
<a-button type="default"
@click="collapseAll"
>折叠全部</a-button>
</template> </template>
<template #action="{ record }"> <template #action="{ record }">
<TableAction <TableAction :actions="[
:actions="[ {
{ label: '编辑',
icon: 'clarity:note-edit-line', icon: 'fa6-regular:pen-to-square',
onClick: handleEdit.bind(null, record), auth: ['menu_edit'],
onClick: handleEdit.bind(null, record)
},
{
label: '新增',
icon: 'ant-design:plus-circle-outlined',
auth: ['menu_add'],
onClick: handleAdd.bind(null, record),
},
{
label: '删除',
icon: 'ant-design:delete-outlined',
auth: ['menu_del'],
color: 'error',
popConfirm: {
title: '是否确认删除',
confirm: handleDel.bind(null, record),
}, },
{ }]"
icon: 'ant-design:delete-outlined',
color: 'error',
popConfirm: {
title: '是否确认删除',
confirm: handleDelete.bind(null, record),
},
},
]"
/> />
</template> </template>
</BasicTable> </BasicTable>
<MenuModal @register="registerDrawer" @success="handleSuccess" /> <MenuModal @register="registerModal" @success="handleSuccess"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { nextTick, ref } from 'vue'; /**
* Copyright © 2020-2022 <a href="http://www.entfrm.com/">entfrm</a> All rights reserved.
* author entfrm开发团队-王翔
*/
import { BasicTable, useTable, TableAction } from '/@/components/Table'; import { BasicTable, useTable, TableAction } from '/@/components/Table';
import { getMenu, listMenu, delMenu } from '/@/api/system/menu'; import { listMenu, delMenu } from '/@/api/system/menu';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import MenuModal from './MenuModal.vue'; import { listToTree } from '/@/utils/helper/treeHelper';
import { columns, searchFormSchema } from './menu.data'; import { columns, searchFormSchema } from './menu.data';
import { useMessage } from '/@/hooks/web/useMessage'; import { useMessage } from '/@/hooks/web/useMessage';
import { convertDateRange } from '/@/utils/dateUtil';
import MenuModal from './MenuModal.vue';
const { createMessage } = useMessage(); const { createMessage } = useMessage();
const [registerDrawer, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
const [registerTable, { reload, expandAll,collapseAll }] = useTable({ const [registerTable, { reload, expandAll,collapseAll }] = useTable({
title: '菜单列表', title: '菜单列表',
api: listMenu, api: listMenu,
columns, columns,
formConfig: { formConfig: {
labelWidth: 120, labelWidth: 120,
schemas: searchFormSchema, schemas: searchFormSchema
}, },
isTreeTable: true, isTreeTable: true,
pagination: false, pagination: false,
@ -56,58 +76,37 @@
showIndexColumn: false, showIndexColumn: false,
canResize: false, canResize: false,
actionColumn: { actionColumn: {
width: 180, width: 220,
title: '操作', title: '操作',
dataIndex: 'action', dataIndex: 'action',
slots: { customRender: 'action' }, slots: { customRender: 'action' },
fixed: undefined, fixed: false
}, },
afterFetch: (result) => listToTree(result),
handleSearchInfoFn: (queryParams) => convertDateRange(queryParams, 'dateRange')
}); });
var updateFlag = ref(true); /** 新增按钮操作,行内新增与工具栏局域新增通用 */
function handleAdd(record: Recordable) {
//expose openModal(true,{ _tag: 'add', record });
defineExpose({
updateFlag
});
/**
* 创建菜单
*/
function handleCreate() {
openModal(true, {
isUpdate: false,
});
} }
/** /** 编辑按钮操作,行内编辑 */
* 编辑菜单
*/
function handleEdit(record: Recordable) { function handleEdit(record: Recordable) {
openModal(true, { openModal(true, { _tag: 'edit', record });
record,
isUpdate: true,
});
} }
/** /** 删除按钮操作,行内删除 */
* 删除菜单 async function handleDel(record: Recordable) {
*/ await delMenu({ id: record.id });
async function handleDelete(record: Recordable) {
await menuDel({ id: record.id });
createMessage.success('删除成功!'); createMessage.success('删除成功!');
handleSuccess(); handleSuccess();
} }
/** /** 处理表单提交成功 */
* 成功后重载表格
*/
function handleSuccess() { function handleSuccess() {
debugger
reload(); reload();
} }
function onFetchSuccess() {
//
nextTick(expandAll);
}
</script> </script>

209
kicc-ui/src/views/system/menu/menu.data.ts

@ -1,3 +1,10 @@
/**
* @program: kicc-ui
* @description:
* @author: entfrm开发团队-
* @create: 2022/4/21
*/
import { BasicColumn } from '/@/components/Table'; import { BasicColumn } from '/@/components/Table';
import { FormSchema } from '/@/components/Table'; import { FormSchema } from '/@/components/Table';
import { h } from 'vue'; import { h } from 'vue';
@ -8,52 +15,58 @@ export const columns: BasicColumn[] = [
{ {
title: '菜单名称', title: '菜单名称',
dataIndex: 'name', dataIndex: 'name',
width: 200, align: 'left'
align: 'left',
}, },
{ {
title: '图标', title: '图标',
dataIndex: 'icon', dataIndex: 'icon',
width: 50, width: 80,
customRender: ({ record }) => { customRender: ({ record }) => {
return h(Icon, { icon: record.icon }); return h(Icon, { icon: record.icon });
}, }
},
{
title: '排序',
dataIndex: 'sort',
width: 80
}, },
{ {
title: '权限标识', title: '权限标识',
dataIndex: 'permission', dataIndex: 'permission',
width: 180, customRender: ({record}) => {
return record.permission || h(Tag, { color: 'red' }, () => '暂无数据');
}
}, },
{ {
title: '组件', title: '组件',
dataIndex: 'component', dataIndex: 'component',
customRender: ({record}) => {
return record.component || h(Tag, { color: 'red' }, () => '暂无数据');
}
}, },
{ {
title: '排序', title: '是否隐藏',
dataIndex: 'sort', dataIndex: 'hideMenu',
width: 50,
},
{
title: '状态',
dataIndex: 'status',
width: 80, width: 80,
customRender: ({ record }) => { customRender: ({ record }) => {
const status = record.status; const hideMenu = record.hideMenu;
const enable = ~~status === 0; // 采用二进制~~取反,只要为null或者0等等一些其他的空元素都会转为0
// 第一次取反会运算为-1,在后一次取反会运算为0
const enable = ~~hideMenu === 0;
const color = enable ? 'green' : 'red'; const color = enable ? 'green' : 'red';
const text = enable ? '启用' : '停用'; const text = enable ? '显示' : '隐藏';
return h(Tag, { color: color }, () => text); return h(Tag, { color: color }, () => text);
}, }
}, },
{ {
title: '创建时间', title: '创建时间',
dataIndex: 'createTime', dataIndex: 'createTime'
width: 180, }
},
]; ];
const isDir = (type: string) => type === 'M'; /** 菜单类型 */
const isMenu = (type: string) => type === 'C'; const isMenu = (type: string) => type === 'C';
/** 按钮类型 */
const isButton = (type: string) => type === 'F'; const isButton = (type: string) => type === 'F';
export const searchFormSchema: FormSchema[] = [ export const searchFormSchema: FormSchema[] = [
@ -62,22 +75,33 @@ export const searchFormSchema: FormSchema[] = [
label: '菜单名', label: '菜单名',
component: 'Input', component: 'Input',
componentProps: { componentProps: {
placeholder: '请输入名称', placeholder: '请输入菜单名称'
}, },
colProps: { span: 8 }, colProps: { span: 8 }
}, },
{ {
field: 'beginTime', field: 'hideMenu',
label: '起始时间', label: '是否隐藏',
component: 'DatePicker', component: 'Select',
colProps: { span: 8 }, componentProps: {
options: [
{ label: '显示', value: '0' },
{ label: '隐藏', value: '1' }
]
},
colProps: { span: 7 }
}, },
{ {
field: 'endTime', field: 'dateRange',
label: '截止时间', label: '创建时间',
component: 'DatePicker', component: 'RangePicker',
colProps: { span: 8 }, componentProps: {
}, style: { width:'100%' },
format: 'yyyy-MM-dd',
placeholder: ['开始日期','结束日期']
},
colProps: { span: 8 }
}
]; ];
export const formSchema: FormSchema[] = [ export const formSchema: FormSchema[] = [
@ -85,29 +109,8 @@ export const formSchema: FormSchema[] = [
field: 'id', field: 'id',
label: 'ID', label: 'ID',
component: 'Input', component: 'Input',
show: false, show: false
},
{
field: 'type',
label: '菜单类型',
component: 'RadioButtonGroup',
defaultValue: 'M',
componentProps: {
options: [
{ label: '目录', value: 'M' },
{ label: '菜单', value: 'C' },
{ label: '按钮', value: 'F' },
],
},
colProps: { lg: 24, md: 24 },
},
{
field: 'name',
label: '菜单名称',
component: 'Input',
required: true,
}, },
{ {
field: 'parentId', field: 'parentId',
label: '上级菜单', label: '上级菜单',
@ -116,71 +119,80 @@ export const formSchema: FormSchema[] = [
replaceFields: { replaceFields: {
title: 'name', title: 'name',
key: 'id', key: 'id',
value: 'id', value: 'id'
}, },
getPopupContainer: () => document.body, getPopupContainer: () => document.body
}, }
}, },
{ {
field: 'sort', field: 'type',
label: '排序', label: '菜单类型',
component: 'InputNumber', component: 'RadioButtonGroup',
required: true, defaultValue: 'M',
componentProps: {
options: [
{ label: '目录', value: 'M' },
{ label: '菜单', value: 'C' },
{ label: '按钮', value: 'F' }
]
}
}, },
{ {
field: 'icon', field: 'icon',
label: '图标', label: '菜单图标',
component: 'IconPicker', component: 'IconPicker',
required: true, required: true,
ifShow: ({ values }) => !isButton(values.type), ifShow: ({ values }) => !isButton(values.type),
}, },
{
field: 'name',
label: '菜单名称',
component: 'Input',
required: true,
colProps: {
span: 12
}
},
{ {
field: 'path', field: 'path',
label: '路由地址', label: '路由地址',
component: 'Input', component: 'Input',
required: true, required: true,
ifShow: ({ values }) => !isButton(values.type), colProps: {
span: 12
},
ifShow: ({ values }) => !isButton(values.type)
}, },
{ {
field: 'component', field: 'component',
label: '组件路径', label: '组件路径',
component: 'Input', component: 'Input',
ifShow: ({ values }) => !isButton(values.type), colProps: {
span: 12
},
ifShow: ({ values }) => isMenu(values.type)
}, },
{ {
field: 'permission', field: 'permission',
label: '权限标识', label: '权限标识',
component: 'Input', component: 'Input',
ifShow: ({ values }) => isButton(values.type), colProps: {
}, span: 12
{
field: 'status',
label: '状态',
component: 'RadioButtonGroup',
defaultValue: '0',
componentProps: {
options: [
{ label: '启用', value: '0' },
{ label: '禁用', value: '1' },
],
}, },
ifShow: ({ values }) => isButton(values.type)
}, },
{ {
field: 'target', field: 'sort',
label: '是否外链', label: '显示排序',
component: 'RadioButtonGroup', component: 'InputNumber',
defaultValue: '0',
componentProps: { componentProps: {
options: [ style: { width:'100%' }
{ label: '否', value: '0' },
{ label: '是', value: '1' },
],
}, },
ifShow: ({ values }) => !isButton(values.type), required: true,
colProps: {
span: 12
}
}, },
{ {
field: 'keepalive', field: 'keepalive',
label: '是否缓存', label: '是否缓存',
@ -192,20 +204,25 @@ export const formSchema: FormSchema[] = [
{ label: '是', value: '1' }, { label: '是', value: '1' },
], ],
}, },
ifShow: ({ values }) => isMenu(values.type), colProps: {
span: 12
},
ifShow: ({ values }) => isMenu(values.type)
}, },
{ {
field: 'show', field: 'hideMenu',
label: '是否显示', label: '是否隐藏',
component: 'RadioButtonGroup', component: 'RadioButtonGroup',
defaultValue: '0', defaultValue: '0',
componentProps: { componentProps: {
options: [ options: [
{ label: '', value: '0' }, { label: '', value: '0' },
{ label: '', value: '1' }, { label: '', value: '1' },
], ]
}, },
ifShow: ({ values }) => !isButton(values.type), colProps: {
}, span: 12
},
ifShow: ({ values }) => !isButton(values.type)
}
]; ];

10
kicc-ui/types/index.d.ts vendored

@ -32,3 +32,13 @@ declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null; declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null;
declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>; declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
/** 模态框传递数据通用类型定义 */
declare type ModalInnerData = {
// 模态框标签类型
_tag: string;
// 表格记录行数
record: Recordable;
[key: string]: any;
};

Loading…
Cancel
Save