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 fa6ce44..6a37036 100644 --- a/Api/Ewide.Core/Filter/RequestActionFilter.cs +++ b/Api/Ewide.Core/Filter/RequestActionFilter.cs @@ -8,6 +8,8 @@ using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; using UAParser; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Ewide.Core { @@ -34,12 +36,29 @@ namespace Ewide.Core var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; var descAtt = Attribute.GetCustomAttribute(actionDescriptor.MethodInfo, typeof(DescriptionAttribute)) as DescriptionAttribute; + var message = "请求成功"; + if (isRequestSucceed) + { + var result = actionContext.Result; + var resultType = result.GetType(); + if (resultType.Name == "ContentResult") + { + var resultContent = ((Microsoft.AspNetCore.Mvc.ContentResult)actionContext.Result)?.Content; + var resultJson = JsonConvert.DeserializeObject(resultContent); + message = resultJson["message"]?.ToString(); + } + } + else + { + message = actionContext.Exception.Message; + } + var sysOpLog = new SysLogOp { Name = descAtt != null ? descAtt.Description : actionDescriptor.ActionName, OpType = 1, Success = isRequestSucceed, - //Message = isRequestSucceed ? "成功" : "失败", + Message = message, Ip = httpContext.GetRemoteIpAddressToIPv4(), Location = httpRequest.GetRequestUrlAddress(), Browser = clent.UA.Family + clent.UA.Major, @@ -48,13 +67,13 @@ namespace Ewide.Core ClassName = context.Controller.ToString(), MethodName = actionDescriptor.ActionName, ReqMethod = httpRequest.Method, - //Param = JsonSerializerUtility.Serialize(context.ActionArguments), - //Result = JsonSerializerUtility.Serialize(actionContext.Result), + Param = JsonConvert.SerializeObject(context.ActionArguments), + // Result = resultContent, ElapsedTime = sw.ElapsedMilliseconds, OpTime = DateTime.Now, Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) }; - await sysOpLog.InsertAsync(); + await sysOpLog.InsertNowAsync(); } } } diff --git a/Api/Ewide.Core/Service/Auth/AuthService.cs b/Api/Ewide.Core/Service/Auth/AuthService.cs index b9b3773..fba2662 100644 --- a/Api/Ewide.Core/Service/Auth/AuthService.cs +++ b/Api/Ewide.Core/Service/Auth/AuthService.cs @@ -101,6 +101,24 @@ namespace Ewide.Core.Service // 设置刷新Token令牌 _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken; + // 增加登录日志 + var loginOutput = user.Adapt(); + var clent = Parser.GetDefault().Parse(App.GetService().HttpContext.Request.Headers["User-Agent"]); + loginOutput.LastLoginBrowser = clent.UA.Family + clent.UA.Major; + loginOutput.LastLoginOs = clent.OS.Family + clent.OS.Major; + await new SysLogVis + { + Name = "登录", + Success = true, + Message = "登录成功", + Ip = loginOutput.LastLoginIp, + Browser = loginOutput.LastLoginBrowser, + Os = loginOutput.LastLoginOs, + VisType = 1, + VisTime = loginOutput.LastLoginTime, + Account = loginOutput.Account + }.InsertAsync(); + return accessToken; } @@ -163,20 +181,6 @@ namespace Ewide.Core.Service loginOutput.Menus = await _sysMenuService.GetLoginMenusAntDesign(userId, defaultActiveAppCode); } - // 增加登录日志 - //await new SysLogVis - //{ - // Name = "登录", - // Success = true, - // Message = "登录成功", - // Ip = loginOutput.LastLoginIp, - // Browser = loginOutput.LastLoginBrowser, - // Os = loginOutput.LastLoginOs, - // VisType = 1, - // VisTime = loginOutput.LastLoginTime, - // Account = loginOutput.Account - //}.InsertAsync(); - return loginOutput; } diff --git a/Api/Ewide.Core/Service/Log/SysOpLogService.cs b/Api/Ewide.Core/Service/Log/SysOpLogService.cs index 7c23226..5a6e346 100644 --- a/Api/Ewide.Core/Service/Log/SysOpLogService.cs +++ b/Api/Ewide.Core/Service/Log/SysOpLogService.cs @@ -1,4 +1,5 @@ -using Ewide.Core.Extension; +using Dapper; +using Ewide.Core.Extension; using Furion.DatabaseAccessor; using Furion.DatabaseAccessor.Extensions; using Furion.DependencyInjection; @@ -20,10 +21,12 @@ namespace Ewide.Core.Service public class SysOpLogService : ISysOpLogService, IDynamicApiController, ITransient { private readonly IRepository _sysOpLogRep; // 操作日志表仓储 + private readonly IDapperRepository _dapperRepository; - public SysOpLogService(IRepository sysOpLogRep) + public SysOpLogService(IRepository sysOpLogRep, IDapperRepository dapperRepository) { _sysOpLogRep = sysOpLogRep; + _dapperRepository = dapperRepository; } /// @@ -54,11 +57,7 @@ namespace Ewide.Core.Service [HttpPost("/sysOpLog/delete")] public async Task ClearOpLog() { - var opLogs = await _sysOpLogRep.Entities.ToListAsync(); - opLogs.ForEach(u => - { - u.Delete(); - }); + await _dapperRepository.ExecuteAsync("DELETE FROM sys_log_op"); } } } 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 fa73ffa..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,8 +83,8 @@ 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.Weight != (int)MenuWeight.DEFAULT_WEIGHT) + .Where(u => u.Type != (int)MenuType.FUNCTION) + .Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT) .OrderBy(u => u.Sort).ToListAsync(); } else @@ -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/package.json b/web-react/package.json index 502591d..8de0070 100644 --- a/web-react/package.json +++ b/web-react/package.json @@ -20,6 +20,7 @@ "photoswipe": "^4.1.3", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-json-view": "^1.21.3", "react-monaco-editor": "^0.43.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", @@ -58,4 +59,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} diff --git a/web-react/src/assets/style/lib/list.less b/web-react/src/assets/style/lib/list.less index f8a937f..2dbb2de 100644 --- a/web-react/src/assets/style/lib/list.less +++ b/web-react/src/assets/style/lib/list.less @@ -26,7 +26,7 @@ } } } - >.ant-pagination { + .ant-pagination { margin: @padding-md 0; } .ant-descriptions { @@ -44,6 +44,14 @@ } } } + &--scroll { + position: relative; + + overflow-x: auto; + } + .ant-list-items { + min-width: 1000px; + } .ant-list-item { transition: @animation-duration-slow; transition-property: background, border-bottom-color; @@ -52,4 +60,36 @@ background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent); } } + &-container { + position: relative; + &::before, + &::after { + position: absolute; + top: 0; + bottom: 0; + z-index: 3; + + width: 30px; + + content: ''; + transition: box-shadow @animation-duration-slow; + pointer-events: none; + } + &::before { + left: 0; + } + &::after { + right: 0; + } + &.yo-list--ping-left { + &::before { + box-shadow: inset 10px 0 8px -8px fade(@black, 15%); + } + } + &.yo-list--ping-right { + &::after { + box-shadow: inset -10px 0 8px -8px fade(@black, 15%); + } + } + } } diff --git a/web-react/src/assets/style/lib/table.less b/web-react/src/assets/style/lib/table.less index 9bde634..6ed273f 100644 --- a/web-react/src/assets/style/lib/table.less +++ b/web-react/src/assets/style/lib/table.less @@ -22,6 +22,14 @@ } } } +.ant-table { + .ant-table-container { + &::before, + &::after { + z-index: 3; + } + } +} .ant-table-thead { th.ant-table-column-has-sorters { &:hover { diff --git a/web-react/src/assets/style/lib/tree-layout.less b/web-react/src/assets/style/lib/tree-layout.less index 4b1cc97..a54090a 100644 --- a/web-react/src/assets/style/lib/tree-layout.less +++ b/web-react/src/assets/style/lib/tree-layout.less @@ -19,6 +19,20 @@ } } } + &--collapsed { + position: absolute; + top: 0; + left: 0; + bottom: 0; + z-index: 4; + + transform: translateX(-100%); + &.open { + transform: translateX(0); + + box-shadow: 2px 0 8px fade(@black , 20%); + } + } &--bar { line-height: 20px; 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/authority-view/index.jsx b/web-react/src/components/authority-view/index.jsx index adabbe9..e6c220f 100644 --- a/web-react/src/components/authority-view/index.jsx +++ b/web-react/src/components/authority-view/index.jsx @@ -17,11 +17,11 @@ function getVisible() { const caseChildren = checked.filter(item => item.visibleParent || item.type != 2) const visibleParents = [] // 递归寻找父级 - const findVisibleParents = (children) => { + const findVisibleParents = children => { const parents = [] children.forEach(item => { if (item.parentId) { - const parent = this.list.find(item => item.id === item.parentId) + const parent = this.list.find(p => p.id === item.parentId) if (parent) { parents.push(parent) visibleParents.push(parent) @@ -50,7 +50,9 @@ function getVisible() { function renderDescriptions(data) { return data.map(item => { - return item.children && item.children.length ? renderItem.call(this, item) : renderCheckbox.call(this, item) + return item.children && item.children.length + ? renderItem.call(this, item) + : renderCheckbox.call(this, item) }) } @@ -63,8 +65,10 @@ function renderItem(data) { value={data.id} checked={data.checked} indeterminate={data.indeterminate} - onChange={(e) => this.onChange(e, data)} - >{data.title} + onChange={e => this.onChange(e, data)} + > + {data.title} + } > {renderDescriptions.call(this, data.children)} @@ -76,26 +80,22 @@ function renderItem(data) { function renderCheckbox(data) { return (
- this.onChange(e, data)} - >{data.title} - { - data.visibleParent && data.type == 2 && + this.onChange(e, data)}> + {data.title} + + {data.visibleParent && data.type == 2 && ( - } + )}
) } export default class AuthorityView extends Component { - state = { loading: false, - dataSource: [] + dataSource: [], } list = [] @@ -104,7 +104,8 @@ export default class AuthorityView extends Component { super(props) this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true - this.loadData = typeof this.props.loadData === 'function' ? this.props.loadData : async () => { } + this.loadData = + typeof this.props.loadData === 'function' ? this.props.loadData : async () => {} } /** @@ -126,7 +127,10 @@ export default class AuthorityView extends Component { if (this.props.defaultSelectedKeys) { this.list.map(item => { - if (this.props.defaultSelectedKeys.includes(item.id) && (!item.children || !item.children.length)) { + if ( + this.props.defaultSelectedKeys.includes(item.id) && + (!item.children || !item.children.length) + ) { this.onSelect(true, item) } }) @@ -134,8 +138,10 @@ export default class AuthorityView extends Component { this.setState({ dataSource: res, - loading: false + loading: false, }) + + this.onChange() } onReloadData = () => { @@ -143,16 +149,18 @@ export default class AuthorityView extends Component { } onChange = (e, item) => { - this.onSelect(e.target.checked, item) + if (e && item) { + this.onSelect(e.target.checked, item) + } const visible = getVisible.call(this) if (this.props.onSelect) { this.props.onSelect( // 返回所有选中 - this.list.filter(item => item.checked).map(item => item.id), + this.list.filter(p => p.checked).map(p => p.id), // 返回所有选中和半选 - this.list.filter(item => item.checked || item.indeterminate).map(item => item.id), + this.list.filter(p => p.checked || p.indeterminate).map(p => p.id), // 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选 visible ) @@ -170,7 +178,7 @@ export default class AuthorityView extends Component { } this.setState({ - dataSource: this.list.filter(item => item.parentId === EMPTY_ID) + dataSource: this.list.filter(p => p.parentId === EMPTY_ID), }) } @@ -210,31 +218,30 @@ export default class AuthorityView extends Component { return (
}> - { - !this.state.loading ? - - { - this.state.dataSource.map(item => { - return ( - this.onChange(e, item)} - >{item.title} - } + {!this.state.loading ? ( + + {this.state.dataSource.map(item => { + return ( + this.onChange(e, item)} > - {renderDescriptions.call(this, item.children)} - - ) - }) - } - - : - - } + {item.title} + + } + > + {renderDescriptions.call(this, item.children)} + + ) + })} + + ) : ( + + )}
) 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/components/query-list/index.jsx b/web-react/src/components/query-list/index.jsx index e623731..c052306 100644 --- a/web-react/src/components/query-list/index.jsx +++ b/web-react/src/components/query-list/index.jsx @@ -43,6 +43,8 @@ export default class QueryList extends Component { state = { loading: false, dataSource: [], + + ping: [], } // 查询表单实例 @@ -91,6 +93,12 @@ export default class QueryList extends Component { if (this.autoLoad) { this.onLoadData() } + + window.addEventListener('resize', this.onResizeScroll) + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeScroll) } /** @@ -108,9 +116,14 @@ export default class QueryList extends Component { this.query ) - this.setState({ - dataSource: res.rows || res.data || res.items, - }) + this.setState( + { + dataSource: res.rows || res.data || res.items, + }, + () => { + this.onResizeScroll() + } + ) this.pagination.total = res.totalCount @@ -174,7 +187,26 @@ export default class QueryList extends Component { this.onLoadData() } + onResizeScroll = () => { + this.onListScrollX({ target: this.refs['scroll-x'] }) + } + + onListScrollX(e) { + const { offsetWidth, scrollWidth, scrollLeft } = e.target + const ping = [] + if (offsetWidth + scrollLeft < scrollWidth && scrollLeft > 0) { + ping.push(...['left', 'right']) + } else if (offsetWidth + scrollLeft < scrollWidth) { + ping.push('right') + } else if (offsetWidth + scrollLeft >= scrollWidth && offsetWidth < scrollWidth) { + ping.push('left') + } + this.setState({ ping }) + } + render() { + const { loading, dataSource, ping } = this.state + const attrs = {} Object.keys(this.props).forEach(key => { if (!propsMap.includes(key)) { @@ -206,9 +238,21 @@ export default class QueryList extends Component {
- }> - - {!!this.state.dataSource && !!this.state.dataSource.length && ( + }> +
`yo-list--ping-${p}`)] + .filter(p => p) + .join(' ')} + > +
this.onListScrollX(e)} + > + +
+
+ {!!dataSource && !!dataSource.length && ( { const c = [] if (type !== 'tree' && rowNumber & !expandable & !expandedRowRender) { - c.push(rowNoColumn) + //c.push(rowNoColumn) } c.push(...(columns || [])) return c.filter(p => !p.hidden) diff --git a/web-react/src/components/query-tree-layout/index.jsx b/web-react/src/components/query-tree-layout/index.jsx index d7c4466..fd65fb2 100644 --- a/web-react/src/components/query-tree-layout/index.jsx +++ b/web-react/src/components/query-tree-layout/index.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react' -import { Breadcrumb, Empty, Input, Layout, Spin, Tooltip, Tree } from 'antd' +import { Breadcrumb, Button, Col, Empty, Input, Layout, Row, Spin, Tooltip, Tree } from 'antd' import { AntIcon, Container } from 'components' +import { SIDER_BREAK_POINT } from 'util/global' function generateKey(data, level) { const n = level || [0] @@ -23,7 +24,7 @@ function generateList(data) { this.list.push({ key, [this.replaceFields.value]: data[i][this.replaceFields.value], - [this.replaceFields.title]: data[i][this.replaceFields.title] + [this.replaceFields.title]: data[i][this.replaceFields.title], }) if (data[i][this.replaceFields.children]) { generateList.call(this, data[i][this.replaceFields.children]) @@ -32,7 +33,7 @@ function generateList(data) { } function getParentKey(key, tree) { - let parentKey; + let parentKey for (let i = 0; i < tree.length; i++) { const node = tree[i] if (node[this.replaceFields.children]) { @@ -43,23 +44,26 @@ function getParentKey(key, tree) { } } } - return parentKey; + return parentKey } function renderTitle(nodeData) { const title = nodeData[this.replaceFields.title] if (this.state.searchValue && title.indexOf(this.state.searchValue) > -1) { - return <> - {title.substr(0, title.indexOf(this.state.searchValue))} - {this.state.searchValue} - {title.substr(title.indexOf(this.state.searchValue) + this.state.searchValue.length)} - + return ( + <> + {title.substr(0, title.indexOf(this.state.searchValue))} + {this.state.searchValue} + {title.substr( + title.indexOf(this.state.searchValue) + this.state.searchValue.length + )} + + ) } return <>{title} } function renderBreadcrumbItem() { - const path = ['顶级'] const findPath = (data, level) => { @@ -91,14 +95,16 @@ function renderBreadcrumbItem() { } export default class QueryTreeLayout extends Component { - state = { loading: false, dataSource: [], expandedKeys: [], selectedKey: '', autoExpandParent: true, - searchValue: '' + searchValue: '', + + collapsed: false, + showSider: false, } list = [] @@ -108,16 +114,18 @@ export default class QueryTreeLayout extends Component { replaceFields = { value: 'id', title: 'title', - children: 'children' + children: 'children', } constructor(props) { super(props) this.autoLoad = typeof this.props.autoLoad === 'boolean' ? this.props.autoLoad : true - this.loadData = typeof this.props.loadData === 'function' ? this.props.loadData : async () => { } + this.loadData = + typeof this.props.loadData === 'function' ? this.props.loadData : async () => {} - this.defaultExpanded = typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false + this.defaultExpanded = + typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false if (this.props.replaceFields) { this.replaceFields = this.props.replaceFields @@ -131,6 +139,13 @@ export default class QueryTreeLayout extends Component { if (this.autoLoad) { this.onLoadData() } + + window.addEventListener('resize', this.onResizeLayout) + this.onResizeLayout() + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeLayout) } /** @@ -151,7 +166,7 @@ export default class QueryTreeLayout extends Component { } this.setState({ dataSource: data, - expandedKeys + expandedKeys, }) this.onLoaded() } @@ -162,8 +177,8 @@ export default class QueryTreeLayout extends Component { onLoading = () => { this.setState({ loading: { - indicator: - } + indicator: , + }, }) } @@ -178,28 +193,28 @@ export default class QueryTreeLayout extends Component { this.onLoadData() } - onExpand = (expandedKeys) => { + onExpand = expandedKeys => { this.setState({ expandedKeys, - autoExpandParent: false + autoExpandParent: false, }) } - onSelect = (selectedKeys) => { + onSelect = selectedKeys => { const selectedIds = [] selectedKeys.forEach(p => { const data = this.list.find(m => m.key === p) selectedIds.push(data[this.replaceFields.value]) }) this.setState({ - selectedKey: selectedIds[0] + selectedKey: selectedIds[0], }) if (this.props.onSelect) { this.props.onSelect(selectedIds[0]) } } - onSearch = (value) => { + onSearch = value => { const expandedKeys = this.list .map(p => { if (p[this.replaceFields.title].indexOf(value) > -1) { @@ -212,34 +227,49 @@ export default class QueryTreeLayout extends Component { this.setState({ expandedKeys, autoExpandParent: !!value, - searchValue: value + searchValue: value, + }) + } + + onResizeLayout = () => { + this.setState({ + collapsed: window.innerWidth <= SIDER_BREAK_POINT, }) } render() { - - const { dataSource, expandedKeys, autoExpandParent } = this.state + const { loading, dataSource, expandedKeys, autoExpandParent, collapsed, showSider } = + this.state const props = { treeData: dataSource, expandedKeys, - autoExpandParent + autoExpandParent, } const on = { - onExpand: (expandedKeys) => this.onExpand(expandedKeys), - onSelect: (selectedKeys) => this.onSelect(selectedKeys) + onExpand: expandedKeys => this.onExpand(expandedKeys), + onSelect: selectedKeys => this.onSelect(selectedKeys), } return ( - + p) + .join(' ')} + onMouseLeave={() => this.setState({ showSider: false })} + >
this.onSearch(value)} + onSearch={value => this.onSearch(value)} />
@@ -248,37 +278,56 @@ export default class QueryTreeLayout extends Component { this.onReloadData()} /> - this.setState({ expandedKeys: [] })} /> + this.setState({ expandedKeys: [] })} + />
- } wrapperClassName="h-100-p"> - { - !this.state.loading && !this.state.dataSource.length ? - - -

暂无数据

-
- } - description={false} - /> - : - renderTitle.call(this, nodeData)} - /> - } + } + wrapperClassName="h-100-p" + > + {!loading && !dataSource.length ? ( + + +

暂无数据

+ + } + description={false} + /> + ) : ( + renderTitle.call(this, nodeData)} + /> + )}
- - {renderBreadcrumbItem.call(this)} - + + {collapsed && ( + + } diff --git a/web-react/src/pages/system/user/index.jsx b/web-react/src/pages/system/user/index.jsx index 7418764..f40c684 100644 --- a/web-react/src/pages/system/user/index.jsx +++ b/web-react/src/pages/system/user/index.jsx @@ -21,6 +21,7 @@ import getDictData from 'util/dic' import FormBody from './form' import RoleForm from './role' import DataForm from './data' +import auth from 'components/authorized/handler' // 配置页面所需接口函数 const apiAction = { @@ -209,25 +210,25 @@ export default class index extends Component { this.onResetPassword(record)}>重置密码 , - + - - + {auth('sysUser:grantRole') && ( + this.onOpen(this.roleForm, record)}> 授权角色 - - - + )} + {auth('sysUser:grantData') && ( + this.onOpen(this.dataForm, record)}> 授权额外数据 - + )} } > diff --git a/web-react/src/store/reducer/layout.js b/web-react/src/store/reducer/layout.js index 7f2fa41..1441e84 100644 --- a/web-react/src/store/reducer/layout.js +++ b/web-react/src/store/reducer/layout.js @@ -1,6 +1,21 @@ -const layout = (state = { - siderCollapsed: false -}, action) => { +import { SETTING_KEY } from "common/storage" +import { SIDER_BREAK_POINT } from "util/global" + +const defaultState = { + siderCollapsed: false, + allowSiderCollapsed: true +} + +const localStorageState = () => { + return JSON.parse(window.localStorage.getItem(SETTING_KEY)) || {} +} + +const mergeState = { + ...defaultState, + ...localStorageState() +} + +const layout = (state = mergeState, action) => { switch (action.type) { // 打开窗口 case 'OPEN_WINDOW': @@ -13,8 +28,23 @@ const layout = (state = { return state // 侧边收起状态 case 'TOGGLE_COLLAPSED': - const _state = { ...state, siderCollapsed: action.siderCollapsed } - return _state + { + if (window.innerWidth <= SIDER_BREAK_POINT) { + return state + } + const _state = { ...state, siderCollapsed: action.siderCollapsed } + window.localStorage.setItem(SETTING_KEY, JSON.stringify(_state)) + return _state + } + case 'AUTO_TOGGLE_COLLAPSED': + { + const _state = { + ...state, + siderCollapsed: localStorageState().siderCollapsed || action.siderCollapsed, + allowSiderCollapsed: !action.siderCollapsed + } + return _state + } default: return state } diff --git a/web-react/src/util/global/index.js b/web-react/src/util/global/index.js index 2543e20..918cb47 100644 --- a/web-react/src/util/global/index.js +++ b/web-react/src/util/global/index.js @@ -36,4 +36,6 @@ export const RSA_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQU /** * 城市名称 */ -export const CITY = '黄石市' \ No newline at end of file +export const CITY = '黄石市' + +export const SIDER_BREAK_POINT = 1366 \ No newline at end of file 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