update 强化菜单

This commit is contained in:
2021-06-28 17:23:47 +08:00
parent f1ae3d5b2a
commit 6dceac060d
14 changed files with 491 additions and 243 deletions

View File

@@ -20,9 +20,9 @@ namespace Ewide.Core
MENU = 1, MENU = 1,
/// <summary> /// <summary>
/// 按钮 /// 功能
/// </summary> /// </summary>
[Description("按钮")] [Description("功能")]
BTN = 2 FUNCTION = 2
} }
} }

View File

@@ -2417,9 +2417,9 @@
菜单 菜单
</summary> </summary>
</member> </member>
<member name="F:Ewide.Core.MenuType.BTN"> <member name="F:Ewide.Core.MenuType.FUNCTION">
<summary> <summary>
按钮 功能
</summary> </summary>
</member> </member>
<member name="T:Ewide.Core.MenuWeight"> <member name="T:Ewide.Core.MenuWeight">
@@ -4933,7 +4933,7 @@
路由元信息(路由附带扩展信息) 路由元信息(路由附带扩展信息)
</summary> </summary>
</member> </member>
<member name="P:Ewide.Core.Service.AntDesignTreeNode.Path"> <member name="P:Ewide.Core.Service.AntDesignTreeNode.Link">
<summary> <summary>
路径 路径
</summary> </summary>
@@ -4943,6 +4943,11 @@
控制路由和子路由是否显示在 sidebar 控制路由和子路由是否显示在 sidebar
</summary> </summary>
</member> </member>
<member name="P:Ewide.Core.Service.AntDesignTreeNode.OpenType">
<summary>
打开方式
</summary>
</member>
<member name="T:Ewide.Core.Service.Meta"> <member name="T:Ewide.Core.Service.Meta">
<summary> <summary>
路由元信息内部类 路由元信息内部类
@@ -5068,6 +5073,11 @@
菜单类型(字典 0目录 1菜单 2按钮 菜单类型(字典 0目录 1菜单 2按钮
</summary> </summary>
</member> </member>
<member name="P:Ewide.Core.Service.AddMenuInput.OpenType">
<summary>
打开方式(字典 0无 1组件 2内链 3外链
</summary>
</member>
<member name="P:Ewide.Core.Service.DeleteMenuInput.Id"> <member name="P:Ewide.Core.Service.DeleteMenuInput.Id">
<summary> <summary>
菜单Id 菜单Id

View File

@@ -73,7 +73,7 @@ namespace Ewide.Core
OpTime = DateTime.Now, OpTime = DateTime.Now,
Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT)
}; };
await sysOpLog.InsertAsync(); await sysOpLog.InsertNowAsync();
} }
} }
} }

View File

@@ -38,12 +38,17 @@
/// <summary> /// <summary>
/// 路径 /// 路径
/// </summary> /// </summary>
public string Path { get; set; } public string Link { get; set; }
/// <summary> /// <summary>
/// 控制路由和子路由是否显示在 sidebar /// 控制路由和子路由是否显示在 sidebar
/// </summary> /// </summary>
public bool Hidden { get; set; } public bool Hidden { get; set; }
/// <summary>
/// 打开方式
/// </summary>
public int OpenType { get; set; }
} }
/// <summary> /// <summary>

View File

@@ -25,7 +25,7 @@ namespace Ewide.Core.Service
/// <summary> /// <summary>
/// 菜单类型(字典 0目录 1菜单 2按钮 /// 菜单类型(字典 0目录 1菜单 2按钮
/// </summary> /// </summary>
public virtual string Type { get; set; } public virtual int Type { get; set; }
/// <summary> /// <summary>
/// 图标 /// 图标
@@ -55,7 +55,7 @@ namespace Ewide.Core.Service
/// <summary> /// <summary>
/// 打开方式(字典 0无 1组件 2内链 3外链 /// 打开方式(字典 0无 1组件 2内链 3外链
/// </summary> /// </summary>
public virtual string OpenType { get; set; } public virtual int OpenType { get; set; }
/// <summary> /// <summary>
/// 是否可见Y-是N-否) /// 是否可见Y-是N-否)
@@ -99,7 +99,13 @@ namespace Ewide.Core.Service
/// 菜单类型(字典 0目录 1菜单 2按钮 /// 菜单类型(字典 0目录 1菜单 2按钮
/// </summary> /// </summary>
[Required(ErrorMessage = "菜单类型不能为空")] [Required(ErrorMessage = "菜单类型不能为空")]
public override string Type { get; set; } public override int Type { get; set; }
/// <summary>
/// 打开方式(字典 0无 1组件 2内链 3外链
/// </summary>
[Required(ErrorMessage = "打开方式不能为空")]
public override int OpenType { get; set; }
} }
public class DeleteMenuInput public class DeleteMenuInput

View File

@@ -53,7 +53,7 @@ namespace Ewide.Core.Service
var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId); var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId);
var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList); var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList);
permissions = await _sysMenuRep.DetachedEntities.Where(u => menuIdList.Contains(u.Id)) 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) .Where(u => u.Status == (int)CommonStatus.ENABLE)
.Select(u => u.Permission).ToListAsync(); .Select(u => u.Permission).ToListAsync();
#if DEBUG #if DEBUG
@@ -83,7 +83,7 @@ namespace Ewide.Core.Service
sysMenuList = await _sysMenuRep.DetachedEntities sysMenuList = await _sysMenuRep.DetachedEntities
.Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Status == (int)CommonStatus.ENABLE)
.Where(u => u.Application == appCode) .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) .Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT)
.OrderBy(u => u.Sort).ToListAsync(); .OrderBy(u => u.Sort).ToListAsync();
} }
@@ -96,7 +96,7 @@ namespace Ewide.Core.Service
.Where(u => menuIdList.Contains(u.Id)) .Where(u => menuIdList.Contains(u.Id))
.Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Status == (int)CommonStatus.ENABLE)
.Where(u => u.Application == appCode) .Where(u => u.Application == appCode)
.Where(u => u.Type != (int)MenuType.BTN) .Where(u => u.Type != (int)MenuType.FUNCTION)
.OrderBy(u => u.Sort).ToListAsync(); .OrderBy(u => u.Sort).ToListAsync();
} }
// 转换成登录菜单 // 转换成登录菜单
@@ -106,8 +106,9 @@ namespace Ewide.Core.Service
Pid = u.Pid, Pid = u.Pid,
Name = u.Code, Name = u.Code,
Component = u.Component, Component = u.Component,
Redirect = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Redirect, Redirect = u.Redirect,
Path = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Router, Link = u.Link,
OpenType = u.OpenType,
Meta = new Meta Meta = new Meta
{ {
Title = u.Name, Title = u.Name,
@@ -185,7 +186,7 @@ namespace Ewide.Core.Service
/// 增加和编辑时检查参数 /// 增加和编辑时检查参数
/// </summary> /// </summary>
/// <param name="input"></param> /// <param name="input"></param>
private static void CheckMenuParam(MenuInput input) private async Task CheckMenuParam(MenuInput input)
{ {
var type = input.Type; var type = input.Type;
var router = input.Router; var router = input.Router;
@@ -195,17 +196,17 @@ namespace Ewide.Core.Service
if (type.Equals((int)MenuType.DIR)) if (type.Equals((int)MenuType.DIR))
{ {
if (string.IsNullOrEmpty(router)) //if (string.IsNullOrEmpty(router))
throw Oops.Oh(ErrorCode.D4001); // throw Oops.Oh(ErrorCode.D4001);
} }
else if (type.Equals((int)MenuType.MENU)) else if (type.Equals((int)MenuType.MENU))
{ {
if (string.IsNullOrEmpty(router)) //if (string.IsNullOrEmpty(router))
throw Oops.Oh(ErrorCode.D4001); // throw Oops.Oh(ErrorCode.D4001);
if (string.IsNullOrEmpty(openType)) //if (string.IsNullOrEmpty(openType))
throw Oops.Oh(ErrorCode.D4002); // throw Oops.Oh(ErrorCode.D4002);
} }
else if (type.Equals((int)MenuType.BTN)) else if (type.Equals((int)MenuType.FUNCTION))
{ {
if (string.IsNullOrEmpty(permission)) if (string.IsNullOrEmpty(permission))
throw Oops.Oh(ErrorCode.D4003); throw Oops.Oh(ErrorCode.D4003);
@@ -217,10 +218,37 @@ namespace Ewide.Core.Service
//if (!urlSet.Contains(permission.Replace(":","/"))) //if (!urlSet.Contains(permission.Replace(":","/")))
// throw Oops.Oh(ErrorCode.meu1005); // 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<SysMenu>(); var menu = input.Adapt<SysMenu>();
menu.Pids = await CreateNewPids(input.Pid); menu.Pids = await CreateNewPids(input.Pid);
@@ -296,7 +324,7 @@ namespace Ewide.Core.Service
} }
// 校验参数 // 校验参数
CheckMenuParam(input); await CheckMenuParam(input);
// 如果是编辑父id不能为自己的子节点 // 如果是编辑父id不能为自己的子节点
var childIdList = await _sysMenuRep.DetachedEntities.Where(u => u.Pids.Contains(input.Id.ToString())) var childIdList = await _sysMenuRep.DetachedEntities.Where(u => u.Pids.Contains(input.Id.ToString()))
.Select(u => u.Id).ToListAsync(); .Select(u => u.Id).ToListAsync();
@@ -360,7 +388,7 @@ namespace Ewide.Core.Service
// 更新当前菜单 // 更新当前菜单
oldMenu = input.Adapt<SysMenu>(); oldMenu = input.Adapt<SysMenu>();
oldMenu.Pids = newPids; oldMenu.Pids = newPids;
await oldMenu.UpdateAsync(ignoreNullValues: true); await oldMenu.UpdateExcludeAsync(new[] { nameof(SysMenu.Type) }, ignoreNullValues: true);
// 清除缓存 // 清除缓存
await _sysCacheService.DelByPatternAsync(CommonConst.CACHE_KEY_MENU); await _sysCacheService.DelByPatternAsync(CommonConst.CACHE_KEY_MENU);

View File

@@ -300,6 +300,14 @@
opacity: 0; opacity: 0;
} }
>iframe {
display: block;
width: 100%;
height: 100%;
border: 0;
}
} }
} }
} }

View File

@@ -3,23 +3,23 @@ import { Button, Drawer, message as Message, Modal } from 'antd'
import { cloneDeep, isEqual } from 'lodash' import { cloneDeep, isEqual } from 'lodash'
/** /**
* 渲染对话框 * 渲染对话框
* @param {*} props * @param {*} props
* @param {*} on * @param {*} on
* @param {*} childWithProps * @param {*} childWithProps
* @returns * @returns
*/ */
function renderModal(props, on, childWithProps) { function renderModal(props, on, childWithProps) {
on = { on = {
...on, ...on,
onCancel: () => this.onClose() onCancel: () => this.onClose(),
} }
return <Modal className="yo-modal-form" {...props} {...on}> return (
{childWithProps} <Modal className="yo-modal-form" {...props} {...on}>
</Modal> {childWithProps}
</Modal>
)
} }
/** /**
@@ -32,27 +32,33 @@ function renderModal(props, on, childWithProps) {
function renderDrawer(props, on, childWithProps) { function renderDrawer(props, on, childWithProps) {
on = { on = {
...on, ...on,
onClose: () => this.onClose() onClose: () => this.onClose(),
} }
return <Drawer className="yo-drawer-form" {...props} {...on}> // react在这里会对该组件不存在的props抛出异常
<div class="yo-drawer-form--body"> ;['action', 'onSuccess', 'onOk', 'confirmLoading'].forEach(p => {
{childWithProps} delete props[p]
</div> })
<div className="ant-drawer-footer">
<Button onClick={on.onClose}>取消</Button> return (
<Button loading={this.state.confirmLoading} onClick={on.onOk} type="primary">确定</Button> <Drawer className="yo-drawer-form" {...props} onClose={() => on.onClose()}>
</div> <div className="yo-drawer-form--body">{childWithProps}</div>
</Drawer> <div className="ant-drawer-footer">
<Button onClick={on.onClose}>取消</Button>
<Button loading={this.state.confirmLoading} onClick={on.onOk} type="primary">
确定
</Button>
</div>
</Drawer>
)
} }
export default class ModalForm extends Component { export default class ModalForm extends Component {
state = { state = {
// 弹窗显示/隐藏 // 弹窗显示/隐藏
visible: false, visible: false,
// 提交状态 // 提交状态
confirmLoading: false confirmLoading: false,
} }
// 子元素实例 // 子元素实例
@@ -67,12 +73,11 @@ export default class ModalForm extends Component {
snapshot = null snapshot = null
// 完成操作 // 完成操作
action = async () => { } action = async () => {}
// 是否在关闭时校验数据改变 // 是否在关闭时校验数据改变
compareOnClose = true compareOnClose = true
constructor(props) { constructor(props) {
super(props) super(props)
@@ -143,7 +148,7 @@ export default class ModalForm extends Component {
content: '当前内容已更改,是否确认不保存并且关闭', content: '当前内容已更改,是否确认不保存并且关闭',
onOk: () => { onOk: () => {
this.close() this.close()
} },
}) })
return return
} }
@@ -175,39 +180,33 @@ export default class ModalForm extends Component {
this.props.onSuccess(postData) this.props.onSuccess(postData)
} }
} }
} finally { } finally {
this.setState({ confirmLoading: false }) this.setState({ confirmLoading: false })
} }
} }
render() { render() {
const props = { const props = {
...this.props, ...this.props,
visible: this.state.visible, visible: this.state.visible,
destroyOnClose: true, destroyOnClose: true,
confirmLoading: this.state.confirmLoading confirmLoading: this.state.confirmLoading,
} }
const on = { const on = {
onOk: () => this.onOk() onOk: () => this.onOk(),
} }
const childWithProps = React.cloneElement( const childWithProps = React.cloneElement(React.Children.only(this.props.children), {
React.Children.only(this.props.children), created: childNode => {
{ this.childNode.current = childNode
created: childNode => { this.onCreated()
this.childNode.current = childNode },
this.onCreated() })
}
}
)
return this.type === 'modal' ? return this.type === 'modal'
renderModal.call(this, props, on, childWithProps) ? renderModal.call(this, props, on, childWithProps)
: : renderDrawer.call(this, props, on, childWithProps)
renderDrawer.call(this, props, on, childWithProps)
} }
} }

View File

@@ -7,29 +7,31 @@ import { api } from 'common/api'
import { EMPTY_ID } from 'util/global' import { EMPTY_ID } from 'util/global'
const initialValues = { const initialValues = {
type: '1', type: 1,
openType: '1', openType: 1,
weight: '2',
visible: true, visible: true,
sort: 100 sort: 100,
} }
export default class form extends Component { export default class form extends Component {
state = { state = {
// 加载状态 // 加载状态
loading: true, loading: true,
codes: { codes: {
menuType: [], menuType: [],
openType: [] openType: [],
menuWeight: [],
}, },
options: { options: {
appList: [], appList: [],
parentTreeData: [] parentTreeData: [],
}, },
addType: [],
type: initialValues.type, type: initialValues.type,
openType: initialValues.openType, openType: initialValues.openType,
icon: '' icon: '',
} }
// 表单实例 // 表单实例
@@ -54,42 +56,46 @@ export default class form extends Component {
* @param {*} params * @param {*} params
*/ */
async fillData(params) { async fillData(params) {
const form = this.form.current
this.record = cloneDeep(params.record) this.record = cloneDeep(params.record)
//#region 从后端转换成前段所需格式 //#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() const appList = await this.onLoadSysApplist()
let parentTreeData = [] let parentTreeData = []
if (params.isParent) { if (params.isParent) {
parentTreeData = await this.onLoadMenuTree(params.parent.application) parentTreeData = await this.onLoadMenuTree(params.parent.application)
} else if (params.record) { }
if (params.record) {
parentTreeData = await this.onLoadMenuTree(params.record.application) 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 const icon = params.record && params.record.icon
this.setState({ this.setState({
codes: { codes,
menuType,
openType
},
options: { options: {
appList, appList,
parentTreeData parentTreeData,
}, },
icon icon,
}) })
//#endregion //#endregion
const form = this.form.current
if (params.isParent) { if (params.isParent) {
form.setFieldsValue({ form.setFieldsValue({
pid: params.parent.id, pid: params.parent.id,
application: params.parent.application application: params.parent.application,
}) })
} else { } else {
form.setFieldsValue(this.record) form.setFieldsValue(this.record)
} }
this.setState({ this.setState({ loading: false })
loading: false
})
this.onTypeChange() this.onTypeChange()
} }
@@ -123,30 +129,31 @@ export default class form extends Component {
async onLoadMenuTree(application) { async onLoadMenuTree(application) {
const { data } = await api.getMenuTree({ application }) const { data } = await api.getMenuTree({ application })
return [{ return [
id: EMPTY_ID, {
parentId: undefined, id: EMPTY_ID,
title: '顶级', parentId: undefined,
value: EMPTY_ID, title: '顶级',
pid: undefined, value: EMPTY_ID,
children: data, pid: undefined,
}] children: data,
},
]
} }
onTypeChange() { onTypeChange() {
this.onTypeChangeGroup() this.onTypeChangeGroup()
const form = this.form.current // const form = this.form.current
const { type } = form.getFieldsValue() // const { type } = form.getFieldsValue()
if (['0', '2'].includes(type)) { // if ([0, 2].includes(type)) {
form.setFieldsValue({ // form.setFieldsValue({
openType: '0' // openType: 0,
}) // })
} else { // } else {
form.setFieldsValue({ // form.setFieldsValue({
openType: '1' // openType: 1,
}) // })
} // }
} }
onOpenTypeChange() { onOpenTypeChange() {
@@ -168,178 +175,221 @@ export default class form extends Component {
this.setState({ this.setState({
type, type,
openType openType,
}) })
} }
async onApplicationChange(value) { async onApplicationChange(value) {
this.setState({ this.setState({
loading: true loading: true,
}) })
const parentTreeData = await this.onLoadMenuTree(value) const parentTreeData = await this.onLoadMenuTree(value)
this.setState({ this.setState({
loading: false, loading: false,
options: { options: {
...this.state.options, ...this.state.options,
parentTreeData parentTreeData,
} },
}) })
this.form.current.setFieldsValue({ this.form.current.setFieldsValue({
pid: undefined pid: undefined,
}) })
} }
onSelectIcon(icon) { onSelectIcon(icon) {
this.form.current.setFieldsValue({ this.form.current.setFieldsValue({
icon icon,
}) })
this.setState({ icon }) this.setState({ icon })
} }
//#endregion //#endregion
render() { render() {
const { loading, codes, options, addType, type, openType, icon } = this.state
return ( return (
<Form <Form initialValues={initialValues} ref={this.form} className="yo-form">
initialValues={initialValues} <Spin spinning={loading} indicator={<AntIcon type="loading" />}>
ref={this.form}
className="yo-form"
>
<Spin spinning={this.state.loading} indicator={<AntIcon type="loading" />}>
<div className="yo-form-group"> <div className="yo-form-group">
<h3 className="h3">基本信息</h3> <h3 className="h3">基本信息</h3>
<div className="yo-form-group"> <div className="yo-form-group">
<Form.Item <Form.Item
label="菜单类型" label="菜单类型"
name="type" name="type"
help={ tooltip={
<> <>
目录默认添加在顶级 目录一级菜单默认添加在顶级
<br />菜单 <br />
<br />按钮 菜单二级菜单
<br />
按钮菜单中对应到接口的功能
</> </>
} }
rules={[{ required: true, message: '请选择菜单类型' }]} rules={[{ required: true, message: '请选择菜单类型' }]}
> >
<Radio.Group onChange={(e) => this.onTypeChange(e)}> <Radio.Group onChange={e => this.onTypeChange(e)}>
{ {codes.menuType.map(item => {
this.state.codes.menuType.map(item => { return (
return ( <Radio.Button
<Radio.Button key={item.code}
key={item.code} value={+item.code}
value={item.code} disabled={!addType.includes(+item.code)}
>{item.value}</Radio.Button> >
) {item.value}
}) </Radio.Button>
} )
})}
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item label="名称" name="name" rules={[{ required: true, message: '请输入名称' }]}> <Form.Item
label="名称"
name="name"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input autoComplete="off" placeholder="请输入名称" /> <Input autoComplete="off" placeholder="请输入名称" />
</Form.Item> </Form.Item>
<Form.Item label="唯一编码" name="code"> <Form.Item label="唯一编码" name="code">
<Input autoComplete="off" placeholder="请输入唯一编码" /> <Input autoComplete="off" placeholder="请输入唯一编码" />
</Form.Item> </Form.Item>
<Form.Item label="所属应用" name="application" rules={[{ required: true, message: '请选择所属应用' }]}> <Form.Item
<Select placeholder="请选择所属应用" onChange={(value) => this.onApplicationChange(value)}> label="所属应用"
{ name="application"
this.state.options.appList.map(item => { rules={[{ required: true, message: '请选择所属应用' }]}
return ( >
<Select.Option <Select
key={item.code} placeholder="请选择所属应用"
value={item.code} onChange={value => this.onApplicationChange(value)}
>{item.name}</Select.Option> >
) {options.appList.map(item => {
}) return (
} <Select.Option key={item.code} value={item.code}>
{item.name}
</Select.Option>
)
})}
</Select> </Select>
</Form.Item> </Form.Item>
{ {type != 0 && (
this.state.type != 0 && <Form.Item
<Form.Item label="父级菜单" name="pid" rules={[{ required: true, message: '请选择父级' }]}> label="父级菜单"
name="pid"
rules={[{ required: true, message: '请选择父级' }]}
>
<TreeSelect <TreeSelect
dropdownStyle={{ maxHeight: '300px', overflow: 'auto' }} dropdownStyle={{ maxHeight: '300px', overflow: 'auto' }}
treeData={this.state.options.parentTreeData} treeData={options.parentTreeData}
placeholder="请选择父级菜单" placeholder="请选择父级菜单"
/> />
</Form.Item> </Form.Item>
} )}
<Form.Item
label="权重"
name="weight"
tooltip={
<>
系统权重菜单/功能任何角色可用
<br />
业务权重菜单/功能为超级管理员不可用可防止管理员误操作
</>
}
rules={[{ required: true, message: '请选择权重' }]}
>
<Radio.Group>
{codes.menuWeight.map(item => {
return (
<Radio.Button key={item.code} value={item.code}>
{item.value}
</Radio.Button>
)
})}
</Radio.Group>
</Form.Item>
</div> </div>
<h3 className="h3">扩展信息</h3> <h3 className="h3">扩展信息</h3>
<div className="yo-form-group"> <div className="yo-form-group">
{ {type == 1 && (
this.state.type == 1 &&
<Form.Item label="打开方式" name="openType"> <Form.Item label="打开方式" name="openType">
<Radio.Group onChange={(e) => this.onOpenTypeChange(e)}> <Radio.Group onChange={e => this.onOpenTypeChange(e)}>
{ {codes.openType.map(item => {
this.state.codes.openType.map(item => { return (
return ( <Radio.Button key={item.code} value={+item.code}>
<Radio.Button {item.value}
key={item.code} </Radio.Button>
value={item.code} )
>{item.value}</Radio.Button> })}
)
})
}
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
} )}
{ {type == 1 && openType == 1 && (
this.state.type == 1 && this.state.openType == 1 && <Form.Item
<Form.Item label="前端组件" name="component" rules={[{ required: true, message: '请输入前端组件' }]}> label="前端组件"
name="component"
tooltip="打开新页签并渲染前端组件"
rules={[{ required: true, message: '请输入前端组件' }]}
>
<Input autoComplete="off" placeholder="请输入前端组件" /> <Input autoComplete="off" placeholder="请输入前端组件" />
</Form.Item> </Form.Item>
} )}
{ {type == 1 && openType == 2 && (
this.state.type == 1 && this.state.openType == 2 && <Form.Item
<Form.Item label="内链地址" name="router" rules={[{ required: true, message: '请输入内链地址' }]}> label="内链地址"
name="link"
tooltip="打开新页签并使用iframe加载页面"
rules={[{ required: true, message: '请输入内链地址' }]}
>
<Input autoComplete="off" placeholder="请输入内链地址" /> <Input autoComplete="off" placeholder="请输入内链地址" />
</Form.Item> </Form.Item>
} )}
{ {type == 1 && openType == 3 && (
this.state.type == 1 && this.state.openType == 3 && <Form.Item
<Form.Item label="外链地址" name="link" rules={[{ required: true, message: '请输入外链地址' }]}> label="外链地址"
name="redirect"
tooltip="打开新的浏览器窗口"
rules={[{ required: true, message: '请输入外链地址' }]}
>
<Input autoComplete="off" placeholder="请输入外链地址" /> <Input autoComplete="off" placeholder="请输入外链地址" />
</Form.Item> </Form.Item>
} )}
{ {type == 2 && (
this.state.type == 2 && <Form.Item
<Form.Item label="权限标识" name="permission" rules={[{ required: true, message: '请输入权限标识' }]}> label="权限标识"
name="permission"
rules={[{ required: true, message: '请输入权限标识' }]}
>
<Input autoComplete="off" placeholder="请输入权限标识" /> <Input autoComplete="off" placeholder="请输入权限标识" />
</Form.Item> </Form.Item>
} )}
{ {type == 2 && (
this.state.type == 2 && <Form.Item
<Form.Item label="关联上级菜单显示" name="visibleParent" valuePropName="checked"> label="关联上级菜单显示"
name="visibleParent"
valuePropName="checked"
>
<Switch /> <Switch />
</Form.Item> </Form.Item>
} )}
<Form.Item label="可见性" name="visible" valuePropName="checked"> <Form.Item label="可见性" name="visible" valuePropName="checked">
<Switch /> <Switch />
</Form.Item> </Form.Item>
{ {type != 2 && (
this.state.type != 2 &&
<Form.Item label="图标" name="icon"> <Form.Item label="图标" name="icon">
<Input <Input
disabled disabled
placeholder="请选择图标" placeholder="请选择图标"
addonBefore={ addonBefore={icon && <AntIcon type={icon} />}
this.state.icon &&
<AntIcon type={this.state.icon} />
}
addonAfter={ addonAfter={
<AntIcon <AntIcon
type="setting" type="setting"
onClick={() => onClick={() =>
this this.iconSelector.current.open(
.iconSelector this.form.current.getFieldValue('icon')
.current )
.open(this.form.current.getFieldValue('icon'))
} }
/> />
} }
/> />
</Form.Item> </Form.Item>
} )}
<Form.Item label="排序" name="sort"> <Form.Item label="排序" name="sort">
<InputNumber <InputNumber
max={1000} max={1000}
@@ -355,7 +405,7 @@ export default class form extends Component {
</div> </div>
</Spin> </Spin>
<IconSelector ref={this.iconSelector} onSelect={(icon) => this.onSelectIcon(icon)} /> <IconSelector ref={this.iconSelector} onSelect={icon => this.onSelectIcon(icon)} />
</Form> </Form>
) )
} }

View File

@@ -1,5 +1,5 @@
import React, { Component } from 'react' 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 { isEqual } from 'lodash'
import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components'
import { api } from 'common/api' import { api } from 'common/api'
@@ -24,6 +24,7 @@ export default class index extends Component {
codes: { codes: {
menuType: [], menuType: [],
menuWeight: [], menuWeight: [],
openType: [],
}, },
} }
@@ -55,9 +56,36 @@ export default class index extends Component {
render: text => text && <AntIcon type={text} />, render: text => text && <AntIcon type={text} />,
}, },
{ {
title: '前端组件', title: '连接',
width: 220, width: 220,
dataIndex: 'component', dataIndex: 'openType',
render: (text, record) => {
switch (text) {
case 1:
return (
<>
<Tag color="green">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.component}
</>
)
case 2:
return (
<>
<Tag color="orange">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.link}
</>
)
case 3:
return (
<>
<Tag color="red">{this.bindCodeValue(text, 'open_type')}</Tag>{' '}
{record.redirect}
</>
)
default:
return ''
}
},
}, },
{ {
title: '排序', title: '排序',
@@ -83,7 +111,9 @@ export default class index extends Component {
render: (text, record) => ( render: (text, record) => (
<QueryTableActions> <QueryTableActions>
<Auth auth="sysMenu:edit"> <Auth auth="sysMenu:edit">
<a onClick={() => this.onOpen(this.editForm, record)}>编辑</a> <a onClick={() => this.onOpen({ modal: this.editForm, record })}>
编辑
</a>
</Auth> </Auth>
<Auth auth="sysMenu:delete"> <Auth auth="sysMenu:delete">
<Popconfirm <Popconfirm
@@ -96,7 +126,16 @@ export default class index extends Component {
</Auth> </Auth>
{record.type < 2 && ( {record.type < 2 && (
<Auth auth="sysMenu:add"> <Auth auth="sysMenu:add">
<a onClick={() => this.onOpen(this.addForm, record, true)}> <a
onClick={() =>
this.onOpen({
modal: this.addForm,
record,
isParent: true,
addType: record.type == 0 ? [1] : [2],
})
}
>
{record.type == 0 ? '新增子菜单' : '新增功能'} {record.type == 0 ? '新增子菜单' : '新增功能'}
</a> </a>
</Auth> </Auth>
@@ -125,7 +164,7 @@ export default class index extends Component {
*/ */
componentDidMount() { componentDidMount() {
this.table.current.onLoading() this.table.current.onLoading()
getDictData('menu_type', 'menu_weight').then(res => { getDictData('menu_type', 'menu_weight', 'open_type').then(res => {
this.setState( this.setState(
{ {
codes: res, codes: res,
@@ -162,7 +201,7 @@ export default class index extends Component {
name = toCamelCase(name) name = toCamelCase(name)
const codes = this.state.codes[name] const codes = this.state.codes[name]
if (codes) { if (codes) {
const c = codes.find(p => p.code === code) const c = codes.find(p => p.code == code)
if (c) { if (c) {
return c.value return c.value
} }
@@ -175,15 +214,17 @@ export default class index extends Component {
* @param {*} modal * @param {*} modal
* @param {*} record * @param {*} record
*/ */
onOpen(modal, record, isParent = false) { onOpen({ modal, record, isParent = false, addType = [] }) {
const params = isParent const params = isParent
? { ? {
parent: record, parent: record,
isParent, isParent,
addType,
} }
: { : {
record, record,
isParent, isParent,
addType,
} }
modal.current.open(params) modal.current.open(params)
@@ -246,7 +287,9 @@ export default class index extends Component {
<Tooltip title="编辑"> <Tooltip title="编辑">
<a <a
className="link-gray" className="link-gray"
onClick={() => this.onOpen(this.editForm, item)} onClick={() =>
this.onOpen({ modal: this.editForm, record: item })
}
> >
<AntIcon type="edit" /> <AntIcon type="edit" />
</a> </a>
@@ -280,9 +323,12 @@ export default class index extends Component {
if (isFunction) { if (isFunction) {
grids.push( grids.push(
<Card.Grid <Card.Grid
key={0}
style={{ padding: '18px 12px', cursor: 'pointer' }} style={{ padding: '18px 12px', cursor: 'pointer' }}
className="text-center" className="text-center"
onClick={() => this.onOpen(this.addForm, record, true)} onClick={() =>
this.onOpen({ modal: this.addForm, record, isParent: true, addType: [2] })
}
> >
<div> <div>
<AntIcon type="plus" className="text-normal h2" /> <AntIcon type="plus" className="text-normal h2" />
@@ -331,9 +377,11 @@ export default class index extends Component {
<Auth auth="sysMenu:add"> <Auth auth="sysMenu:add">
<Button <Button
icon={<AntIcon type="plus" />} icon={<AntIcon type="plus" />}
onClick={() => this.onOpen(this.addForm)} onClick={() =>
this.onOpen({ modal: this.addForm, addType: [0, 1] })
}
> >
新增{name} 新增目录/菜单
</Button> </Button>
</Auth> </Auth>
} }

View File

@@ -20,7 +20,6 @@ class ComponentDynamic extends Component {
if (this.props.onRef) { if (this.props.onRef) {
this.props.onRef(this) this.props.onRef(this)
} }
return true 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 <iframe ref="content" title={title} />
}
}
export default class index extends Component { export default class index extends Component {
state = { state = {
actived: '', actived: '',
@@ -191,14 +224,23 @@ export default class index extends Component {
' yo-tab-external-tabpane' ' yo-tab-external-tabpane'
} }
> >
<ComponentDynamic {pane.openType === 1 ? (
path={pane.path} <ComponentDynamic
id={pane.key} path={pane.path}
key={pane.key} id={pane.key}
param={pane.param} key={pane.key}
paneActived={pane.key === actived} param={pane.param}
onRef={p => this.panes.push(p)} paneActived={pane.key === actived}
/> onRef={p => this.panes.push(p)}
/>
) : pane.openType === 2 ? (
<Iframe
src={pane.path}
title={pane.key}
id={pane.key}
onRef={p => this.panes.push(p)}
/>
) : null}
</div> </div>
) )
})} })}

View File

@@ -78,7 +78,7 @@ export default class search extends Component {
} }
onSelect(value, option) { onSelect(value, option) {
const { id, meta, component } = option.menu const { id, meta, component, link, redirect, openType } = option.menu
// 选中时清空输入框内容,并失去焦点 // 选中时清空输入框内容,并失去焦点
this.setState({ searchValue: '' }) this.setState({ searchValue: '' })
@@ -88,7 +88,19 @@ export default class search extends Component {
key: id, key: id,
title: meta.title, title: meta.title,
icon: meta.icon, icon: meta.icon,
path: component, path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
}) })
} }

View File

@@ -50,11 +50,25 @@ export default class index extends Component {
} }
onOpenContentWindow = menu => { onOpenContentWindow = menu => {
const { id, meta, component, link, redirect, openType } = menu
window.openContentWindow({ window.openContentWindow({
key: menu.id, key: id,
title: menu.meta.title, title: meta.title,
icon: menu.meta.icon, icon: meta.icon,
path: menu.component, path: (() => {
switch (openType) {
case 1:
return component
case 2:
return link
case 3:
return redirect
default:
return null
}
})(),
openType,
}) })
} }

View File

@@ -105,7 +105,31 @@ export default class index extends Component {
return return
} }
const path = settings.path.startsWith('/') ? settings.path : `/${settings.path}` let path = (p => {
if (p.startsWith('http')) return p
if (p.startsWith('/')) return p
else return `/${p}`
})(settings.path)
if ([2, 3].includes(settings.openType)) {
const param = (p => {
const arr = []
if (p && p.constructor === Object) {
Object.keys(p).forEach(key => {
arr.push(`${key}=${(p[key] || '').toString()}`)
})
}
return arr.join('&')
})(settings.param)
path += param ? `?${param}` : ''
if (settings.openType === 3) {
// 打开新的浏览器窗口
window.open(path)
return
}
}
/** /**
* 向标签页队列中添加一个新的标签页 * 向标签页队列中添加一个新的标签页
@@ -120,7 +144,9 @@ export default class index extends Component {
path, path,
param: settings.param, param: settings.param,
loaded: false, loaded: false,
openType: settings.openType || 1,
} }
this.setState({ this.setState({
panes: [...this.state.panes, newPane], panes: [...this.state.panes, newPane],
}) })