diff --git a/Api/Ewide.Core/Enum/MenuType.cs b/Api/Ewide.Core/Enum/MenuType.cs index 80363cd..ddc74ac 100644 --- a/Api/Ewide.Core/Enum/MenuType.cs +++ b/Api/Ewide.Core/Enum/MenuType.cs @@ -20,9 +20,9 @@ namespace Ewide.Core MENU = 1, /// - /// 按钮 + /// 功能 /// - [Description("按钮")] - BTN = 2 + [Description("功能")] + FUNCTION = 2 } } diff --git a/Api/Ewide.Core/Ewide.Core.xml b/Api/Ewide.Core/Ewide.Core.xml index 51a582c..7e5c095 100644 --- a/Api/Ewide.Core/Ewide.Core.xml +++ b/Api/Ewide.Core/Ewide.Core.xml @@ -2417,9 +2417,9 @@ 菜单 - + - 按钮 + 功能 @@ -4933,7 +4933,7 @@ 路由元信息(路由附带扩展信息) - + 路径 @@ -4943,6 +4943,11 @@ 控制路由和子路由是否显示在 sidebar + + + 打开方式 + + 路由元信息内部类 @@ -5068,6 +5073,11 @@ 菜单类型(字典 0目录 1菜单 2按钮) + + + 打开方式(字典 0无 1组件 2内链 3外链) + + 菜单Id diff --git a/Api/Ewide.Core/Filter/RequestActionFilter.cs b/Api/Ewide.Core/Filter/RequestActionFilter.cs index de89d27..6a37036 100644 --- a/Api/Ewide.Core/Filter/RequestActionFilter.cs +++ b/Api/Ewide.Core/Filter/RequestActionFilter.cs @@ -73,7 +73,7 @@ namespace Ewide.Core OpTime = DateTime.Now, Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) }; - await sysOpLog.InsertAsync(); + await sysOpLog.InsertNowAsync(); } } } diff --git a/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs b/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs index adec4a9..8739f9e 100644 --- a/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs +++ b/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs @@ -38,12 +38,17 @@ /// /// 路径 /// - public string Path { get; set; } + public string Link { get; set; } /// /// 控制路由和子路由是否显示在 sidebar /// public bool Hidden { get; set; } + + /// + /// 打开方式 + /// + public int OpenType { get; set; } } /// diff --git a/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs b/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs index 4c86f61..eae1c9d 100644 --- a/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs +++ b/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs @@ -25,7 +25,7 @@ namespace Ewide.Core.Service /// /// 菜单类型(字典 0目录 1菜单 2按钮) /// - public virtual string Type { get; set; } + public virtual int Type { get; set; } /// /// 图标 @@ -55,7 +55,7 @@ namespace Ewide.Core.Service /// /// 打开方式(字典 0无 1组件 2内链 3外链) /// - public virtual string OpenType { get; set; } + public virtual int OpenType { get; set; } /// /// 是否可见(Y-是,N-否) @@ -99,7 +99,13 @@ namespace Ewide.Core.Service /// 菜单类型(字典 0目录 1菜单 2按钮) /// [Required(ErrorMessage = "菜单类型不能为空")] - public override string Type { get; set; } + public override int Type { get; set; } + + /// + /// 打开方式(字典 0无 1组件 2内链 3外链) + /// + [Required(ErrorMessage = "打开方式不能为空")] + public override int OpenType { get; set; } } public class DeleteMenuInput diff --git a/Api/Ewide.Core/Service/Menu/SysMenuService.cs b/Api/Ewide.Core/Service/Menu/SysMenuService.cs index 2a79991..b9d3572 100644 --- a/Api/Ewide.Core/Service/Menu/SysMenuService.cs +++ b/Api/Ewide.Core/Service/Menu/SysMenuService.cs @@ -53,7 +53,7 @@ namespace Ewide.Core.Service var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId); var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList); permissions = await _sysMenuRep.DetachedEntities.Where(u => menuIdList.Contains(u.Id)) - .Where(u => u.Type == (int)MenuType.BTN) + .Where(u => u.Type == (int)MenuType.FUNCTION) .Where(u => u.Status == (int)CommonStatus.ENABLE) .Select(u => u.Permission).ToListAsync(); #if DEBUG @@ -83,7 +83,7 @@ namespace Ewide.Core.Service sysMenuList = await _sysMenuRep.DetachedEntities .Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Application == appCode) - .Where(u => u.Type != (int)MenuType.BTN) + .Where(u => u.Type != (int)MenuType.FUNCTION) .Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT) .OrderBy(u => u.Sort).ToListAsync(); } @@ -96,7 +96,7 @@ namespace Ewide.Core.Service .Where(u => menuIdList.Contains(u.Id)) .Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Application == appCode) - .Where(u => u.Type != (int)MenuType.BTN) + .Where(u => u.Type != (int)MenuType.FUNCTION) .OrderBy(u => u.Sort).ToListAsync(); } // 转换成登录菜单 @@ -106,8 +106,9 @@ namespace Ewide.Core.Service Pid = u.Pid, Name = u.Code, Component = u.Component, - Redirect = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Redirect, - Path = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Router, + Redirect = u.Redirect, + Link = u.Link, + OpenType = u.OpenType, Meta = new Meta { Title = u.Name, @@ -185,7 +186,7 @@ namespace Ewide.Core.Service /// 增加和编辑时检查参数 /// /// - private static void CheckMenuParam(MenuInput input) + private async Task CheckMenuParam(MenuInput input) { var type = input.Type; var router = input.Router; @@ -195,17 +196,17 @@ namespace Ewide.Core.Service if (type.Equals((int)MenuType.DIR)) { - if (string.IsNullOrEmpty(router)) - throw Oops.Oh(ErrorCode.D4001); + //if (string.IsNullOrEmpty(router)) + // throw Oops.Oh(ErrorCode.D4001); } else if (type.Equals((int)MenuType.MENU)) { - if (string.IsNullOrEmpty(router)) - throw Oops.Oh(ErrorCode.D4001); - if (string.IsNullOrEmpty(openType)) - throw Oops.Oh(ErrorCode.D4002); + //if (string.IsNullOrEmpty(router)) + // throw Oops.Oh(ErrorCode.D4001); + //if (string.IsNullOrEmpty(openType)) + // throw Oops.Oh(ErrorCode.D4002); } - else if (type.Equals((int)MenuType.BTN)) + else if (type.Equals((int)MenuType.FUNCTION)) { if (string.IsNullOrEmpty(permission)) throw Oops.Oh(ErrorCode.D4003); @@ -217,10 +218,37 @@ namespace Ewide.Core.Service //if (!urlSet.Contains(permission.Replace(":","/"))) // throw Oops.Oh(ErrorCode.meu1005); } - //按钮可以设置绑定菜单 - if(!isVisibleParent && type.Equals((int)MenuType.BTN)) + + // 检查上级菜单的类型是否正确 + var pid = input.Pid; + var flag = true; + var empty = System.Guid.Empty.ToString(); + switch(type) { - throw Oops.Oh(ErrorCode.D4004); + // 目录必须在顶级下 + case (int)MenuType.DIR: + flag = pid.Equals(empty); + break; + // 菜单必须在顶级或目录下 + case (int)MenuType.MENU: + if (!pid.Equals(empty)) + { + var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid); + flag = parent.Type.Equals((int)MenuType.DIR); + } + break; + // 功能必须在菜单下 + case (int)MenuType.FUNCTION: + { + var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid); + flag = parent == null ? false : parent.Type.Equals((int)MenuType.MENU); + } + break; + } + + if (!flag) + { + throw Oops.Oh("父级菜单类型错误"); } } @@ -240,7 +268,7 @@ namespace Ewide.Core.Service } // 校验参数 - CheckMenuParam(input); + await CheckMenuParam(input); var menu = input.Adapt(); menu.Pids = await CreateNewPids(input.Pid); @@ -296,7 +324,7 @@ namespace Ewide.Core.Service } // 校验参数 - CheckMenuParam(input); + await CheckMenuParam(input); // 如果是编辑,父id不能为自己的子节点 var childIdList = await _sysMenuRep.DetachedEntities.Where(u => u.Pids.Contains(input.Id.ToString())) .Select(u => u.Id).ToListAsync(); @@ -360,7 +388,7 @@ namespace Ewide.Core.Service // 更新当前菜单 oldMenu = input.Adapt(); oldMenu.Pids = newPids; - await oldMenu.UpdateAsync(ignoreNullValues: true); + await oldMenu.UpdateExcludeAsync(new[] { nameof(SysMenu.Type) }, ignoreNullValues: true); // 清除缓存 await _sysCacheService.DelByPatternAsync(CommonConst.CACHE_KEY_MENU); diff --git a/web-react/src/assets/style/main.less b/web-react/src/assets/style/main.less index 7cf67c9..3abb0d5 100644 --- a/web-react/src/assets/style/main.less +++ b/web-react/src/assets/style/main.less @@ -300,6 +300,14 @@ opacity: 0; } + >iframe { + display: block; + + width: 100%; + height: 100%; + + border: 0; + } } } } diff --git a/web-react/src/components/modal-form/index.jsx b/web-react/src/components/modal-form/index.jsx index 5b47046..5b86f51 100644 --- a/web-react/src/components/modal-form/index.jsx +++ b/web-react/src/components/modal-form/index.jsx @@ -3,56 +3,62 @@ import { Button, Drawer, message as Message, Modal } from 'antd' import { cloneDeep, isEqual } from 'lodash' /** - * 渲染对话框 - * @param {*} props - * @param {*} on - * @param {*} childWithProps - * @returns - */ + * 渲染对话框 + * @param {*} props + * @param {*} on + * @param {*} childWithProps + * @returns + */ function renderModal(props, on, childWithProps) { - on = { ...on, - onCancel: () => this.onClose() + onCancel: () => this.onClose(), } - return - {childWithProps} - - + return ( + + {childWithProps} + + ) } /** * 渲染抽屉 - * @param {*} props - * @param {*} on - * @param {*} childWithProps - * @returns + * @param {*} props + * @param {*} on + * @param {*} childWithProps + * @returns */ function renderDrawer(props, on, childWithProps) { on = { ...on, - onClose: () => this.onClose() + onClose: () => this.onClose(), } - return -
- {childWithProps} -
-
- - -
-
+ // react在这里会对该组件不存在的props抛出异常 + ;['action', 'onSuccess', 'onOk', 'confirmLoading'].forEach(p => { + delete props[p] + }) + + return ( + on.onClose()}> +
{childWithProps}
+
+ + +
+
+ ) } export default class ModalForm extends Component { - state = { // 弹窗显示/隐藏 visible: false, // 提交状态 - confirmLoading: false + confirmLoading: false, } // 子元素实例 @@ -67,12 +73,11 @@ export default class ModalForm extends Component { snapshot = null // 完成操作 - action = async () => { } + action = async () => {} // 是否在关闭时校验数据改变 compareOnClose = true - constructor(props) { super(props) @@ -93,7 +98,7 @@ export default class ModalForm extends Component { /** * 打开弹窗 - * @param {*} data + * @param {*} data */ open = (data = {}) => { this.data = data @@ -110,7 +115,7 @@ export default class ModalForm extends Component { /** * 子元素创建后回调 * 对子元素数据进行填充,(如需关闭时对比)之后再获取结构调整后的数据快照 - * @returns + * @returns */ onCreated = async () => { const body = this.childNode.current @@ -126,7 +131,7 @@ export default class ModalForm extends Component { /** * 取消编辑 * (如需关闭时对比)获取当前数据结构与快照对比 - * @returns + * @returns */ onClose = async () => { const body = this.childNode.current @@ -143,7 +148,7 @@ export default class ModalForm extends Component { content: '当前内容已更改,是否确认不保存并且关闭', onOk: () => { this.close() - } + }, }) return } @@ -155,7 +160,7 @@ export default class ModalForm extends Component { /** * 完成编辑 * 校验并获取结构调整后的数据,调用this.action进行操作 - * @returns + * @returns */ onOk = async () => { const body = this.childNode.current @@ -175,39 +180,33 @@ export default class ModalForm extends Component { this.props.onSuccess(postData) } } - } finally { this.setState({ confirmLoading: false }) } } render() { - const props = { ...this.props, visible: this.state.visible, destroyOnClose: true, - confirmLoading: this.state.confirmLoading + confirmLoading: this.state.confirmLoading, } const on = { - onOk: () => this.onOk() + onOk: () => this.onOk(), } - const childWithProps = React.cloneElement( - React.Children.only(this.props.children), - { - created: childNode => { - this.childNode.current = childNode - this.onCreated() - } - } - ) + const childWithProps = React.cloneElement(React.Children.only(this.props.children), { + created: childNode => { + this.childNode.current = childNode + this.onCreated() + }, + }) - return this.type === 'modal' ? - renderModal.call(this, props, on, childWithProps) - : - renderDrawer.call(this, props, on, childWithProps) + return this.type === 'modal' + ? renderModal.call(this, props, on, childWithProps) + : renderDrawer.call(this, props, on, childWithProps) } } diff --git a/web-react/src/pages/system/menu/form.jsx b/web-react/src/pages/system/menu/form.jsx index 83f9b8f..bbc7dea 100644 --- a/web-react/src/pages/system/menu/form.jsx +++ b/web-react/src/pages/system/menu/form.jsx @@ -7,29 +7,31 @@ import { api } from 'common/api' import { EMPTY_ID } from 'util/global' const initialValues = { - type: '1', - openType: '1', + type: 1, + openType: 1, + weight: '2', visible: true, - sort: 100 + sort: 100, } export default class form extends Component { - state = { // 加载状态 loading: true, codes: { menuType: [], - openType: [] + openType: [], + menuWeight: [], }, options: { appList: [], - parentTreeData: [] + parentTreeData: [], }, + addType: [], type: initialValues.type, openType: initialValues.openType, - icon: '' + icon: '', } // 表单实例 @@ -51,45 +53,49 @@ export default class form extends Component { * 填充数据 * 可以在设置this.record之后对其作出数据结构调整 * [异步,必要] - * @param {*} params + * @param {*} params */ async fillData(params) { + const form = this.form.current this.record = cloneDeep(params.record) //#region 从后端转换成前段所需格式 - const { menuType, openType } = await getDictData('menu_type', 'open_type') + const codes = await getDictData('menu_type', 'open_type', 'menu_weight') const appList = await this.onLoadSysApplist() let parentTreeData = [] if (params.isParent) { parentTreeData = await this.onLoadMenuTree(params.parent.application) - } else if (params.record) { + } + + if (params.record) { parentTreeData = await this.onLoadMenuTree(params.record.application) + } else { + this.setState({ addType: params.addType }) + if (params.addType.length) { + form.setFieldsValue({ + type: params.addType[0], + }) + } } const icon = params.record && params.record.icon this.setState({ - codes: { - menuType, - openType - }, + codes, options: { appList, - parentTreeData + parentTreeData, }, - icon + icon, }) //#endregion - const form = this.form.current if (params.isParent) { form.setFieldsValue({ pid: params.parent.id, - application: params.parent.application + application: params.parent.application, }) } else { form.setFieldsValue(this.record) } - this.setState({ - loading: false - }) + this.setState({ loading: false }) this.onTypeChange() } @@ -98,7 +104,7 @@ export default class form extends Component { * 获取数据 * 可以对postData进行数据结构调整 * [异步,必要] - * @returns + * @returns */ async getData() { const form = this.form.current @@ -123,30 +129,31 @@ export default class form extends Component { async onLoadMenuTree(application) { const { data } = await api.getMenuTree({ application }) - return [{ - id: EMPTY_ID, - parentId: undefined, - title: '顶级', - value: EMPTY_ID, - pid: undefined, - children: data, - }] + return [ + { + id: EMPTY_ID, + parentId: undefined, + title: '顶级', + value: EMPTY_ID, + pid: undefined, + children: data, + }, + ] } - onTypeChange() { this.onTypeChangeGroup() - const form = this.form.current - const { type } = form.getFieldsValue() - if (['0', '2'].includes(type)) { - form.setFieldsValue({ - openType: '0' - }) - } else { - form.setFieldsValue({ - openType: '1' - }) - } + // const form = this.form.current + // const { type } = form.getFieldsValue() + // if ([0, 2].includes(type)) { + // form.setFieldsValue({ + // openType: 0, + // }) + // } else { + // form.setFieldsValue({ + // openType: 1, + // }) + // } } onOpenTypeChange() { @@ -168,178 +175,221 @@ export default class form extends Component { this.setState({ type, - openType + openType, }) } async onApplicationChange(value) { this.setState({ - loading: true + loading: true, }) const parentTreeData = await this.onLoadMenuTree(value) this.setState({ loading: false, options: { ...this.state.options, - parentTreeData - } + parentTreeData, + }, }) this.form.current.setFieldsValue({ - pid: undefined + pid: undefined, }) } onSelectIcon(icon) { this.form.current.setFieldsValue({ - icon + icon, }) this.setState({ icon }) } //#endregion render() { + const { loading, codes, options, addType, type, openType, icon } = this.state + return ( -
- }> + + }>

基本信息

- 目录:默认添加在顶级 -
菜单: -
按钮: + 目录:一级菜单,默认添加在顶级 +
+ 菜单:二级菜单 +
+ 按钮:菜单中对应到接口的功能 } rules={[{ required: true, message: '请选择菜单类型' }]} > - this.onTypeChange(e)}> - { - this.state.codes.menuType.map(item => { - return ( - {item.value} - ) - }) - } + this.onTypeChange(e)}> + {codes.menuType.map(item => { + return ( + + {item.value} + + ) + })}
- + - - this.onApplicationChange(value)} + > + {options.appList.map(item => { + return ( + + {item.name} + + ) + })} - { - this.state.type != 0 && - + {type != 0 && ( + - } + )} + + 系统权重:菜单/功能任何角色可用 +
+ 业务权重:菜单/功能为超级管理员不可用,可防止管理员误操作 + + } + rules={[{ required: true, message: '请选择权重' }]} + > + + {codes.menuWeight.map(item => { + return ( + + {item.value} + + ) + })} + +

扩展信息

- { - this.state.type == 1 && + {type == 1 && ( - this.onOpenTypeChange(e)}> - { - this.state.codes.openType.map(item => { - return ( - {item.value} - ) - }) - } + this.onOpenTypeChange(e)}> + {codes.openType.map(item => { + return ( + + {item.value} + + ) + })} - } - { - this.state.type == 1 && this.state.openType == 1 && - + )} + {type == 1 && openType == 1 && ( + - } - { - this.state.type == 1 && this.state.openType == 2 && - + )} + {type == 1 && openType == 2 && ( + - } - { - this.state.type == 1 && this.state.openType == 3 && - + )} + {type == 1 && openType == 3 && ( + - } - { - this.state.type == 2 && - + )} + {type == 2 && ( + - } - { - this.state.type == 2 && - + )} + {type == 2 && ( + - } + )} - { - this.state.type != 2 && + {type != 2 && ( - } + addonBefore={icon && } addonAfter={ - this - .iconSelector - .current - .open(this.form.current.getFieldValue('icon')) + this.iconSelector.current.open( + this.form.current.getFieldValue('icon') + ) } /> } /> - } + )} - this.onSelectIcon(icon)} /> + this.onSelectIcon(icon)} /> ) } diff --git a/web-react/src/pages/system/menu/index.jsx b/web-react/src/pages/system/menu/index.jsx index 27eaaf9..a7566f3 100644 --- a/web-react/src/pages/system/menu/index.jsx +++ b/web-react/src/pages/system/menu/index.jsx @@ -1,5 +1,5 @@ import React, { Component } from 'react' -import { Button, Table, Card, Popconfirm, message as Message, Row, Col, Tooltip } from 'antd' +import { Button, Table, Card, Popconfirm, message as Message, Row, Col, Tooltip, Tag } from 'antd' import { isEqual } from 'lodash' import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' import { api } from 'common/api' @@ -24,6 +24,7 @@ export default class index extends Component { codes: { menuType: [], menuWeight: [], + openType: [], }, } @@ -55,9 +56,36 @@ export default class index extends Component { render: text => text && , }, { - title: '前端组件', + title: '连接', width: 220, - dataIndex: 'component', + dataIndex: 'openType', + render: (text, record) => { + switch (text) { + case 1: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.component} + + ) + case 2: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.link} + + ) + case 3: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.redirect} + + ) + default: + return '' + } + }, }, { title: '排序', @@ -83,7 +111,9 @@ export default class index extends Component { render: (text, record) => ( - this.onOpen(this.editForm, record)}>编辑 + this.onOpen({ modal: this.editForm, record })}> + 编辑 + {record.type < 2 && ( - this.onOpen(this.addForm, record, true)}> + + this.onOpen({ + modal: this.addForm, + record, + isParent: true, + addType: record.type == 0 ? [1] : [2], + }) + } + > {record.type == 0 ? '新增子菜单' : '新增功能'} @@ -125,7 +164,7 @@ export default class index extends Component { */ componentDidMount() { this.table.current.onLoading() - getDictData('menu_type', 'menu_weight').then(res => { + getDictData('menu_type', 'menu_weight', 'open_type').then(res => { this.setState( { codes: res, @@ -162,7 +201,7 @@ export default class index extends Component { name = toCamelCase(name) const codes = this.state.codes[name] if (codes) { - const c = codes.find(p => p.code === code) + const c = codes.find(p => p.code == code) if (c) { return c.value } @@ -175,15 +214,17 @@ export default class index extends Component { * @param {*} modal * @param {*} record */ - onOpen(modal, record, isParent = false) { + onOpen({ modal, record, isParent = false, addType = [] }) { const params = isParent ? { parent: record, isParent, + addType, } : { record, isParent, + addType, } modal.current.open(params) @@ -246,7 +287,9 @@ export default class index extends Component { this.onOpen(this.editForm, item)} + onClick={() => + this.onOpen({ modal: this.editForm, record: item }) + } > @@ -280,9 +323,12 @@ export default class index extends Component { if (isFunction) { grids.push( this.onOpen(this.addForm, record, true)} + onClick={() => + this.onOpen({ modal: this.addForm, record, isParent: true, addType: [2] }) + } >
@@ -331,9 +377,11 @@ export default class index extends Component { } diff --git a/web-react/src/views/main/_layout/content/index.jsx b/web-react/src/views/main/_layout/content/index.jsx index 73f2ad5..e52b045 100644 --- a/web-react/src/views/main/_layout/content/index.jsx +++ b/web-react/src/views/main/_layout/content/index.jsx @@ -20,7 +20,6 @@ class ComponentDynamic extends Component { if (this.props.onRef) { this.props.onRef(this) } - return true } @@ -73,6 +72,40 @@ class ComponentDynamic extends Component { } } +class Iframe extends Component { + shouldComponentUpdate() { + if (this.props.onRef) { + this.props.onRef(this) + } + return true + } + + componentDidMount() { + if (this.props.onRef) { + this.props.onRef(this) + } + this.loadComponent() + } + + loadComponent() { + NProgress.start() + const iframe = this.refs.content + iframe.onload = () => { + NProgress.done() + } + iframe.onerror = () => { + NProgress.done() + } + iframe.src = this.props.src + } + + render() { + const { title } = this.props + + return