diff --git a/web-react/src/components/authority-view/index.jsx b/web-react/src/components/authority-view/index.jsx
index e071a1b..2fcddd7 100644
--- a/web-react/src/components/authority-view/index.jsx
+++ b/web-react/src/components/authority-view/index.jsx
@@ -1,10 +1,241 @@
import React, { Component } from 'react'
+import { Checkbox, Descriptions, Empty, Spin, Tooltip } from 'antd'
+import { AntIcon } from 'components'
+import { EMPTY_ID } from 'util/global'
export default class AuthorityView extends Component {
+
+ state = {
+ loading: false,
+ dataSource: []
+ }
+
+ list = []
+
+ 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 () => { }
+ }
+
+ /**
+ * 自动加载数据
+ */
+ componentDidMount() {
+ if (this.autoLoad) {
+ this.onLoadData()
+ }
+ }
+
+ async onLoadData() {
+ this.setState({ loading: true })
+
+ const res = await this.loadData()
+
+ this.list = []
+ this.generateList(res)
+
+ if (this.props.defaultSelectedKeys) {
+ this.list.map(item => {
+ if (this.props.defaultSelectedKeys.includes(item.id) && (!item.children || !item.children.length)) {
+ this.onSelect(true, item)
+ }
+ })
+ }
+
+ this.setState({
+ dataSource: res,
+ loading: false
+ })
+ }
+
+ onReloadData() {
+ this.onLoadData()
+ }
+
+ onChange(e, item) {
+ this.onSelect(e.target.checked, item)
+
+ const visible = this.getVisible()
+
+ if (this.props.onSelect) {
+ this.props.onSelect(
+ // 返回所有选中
+ this.list.filter(item => item.checked).map(item => item.id),
+ // 返回所有选中和半选
+ this.list.filter(item => item.checked || item.indeterminate).map(item => item.id),
+ // 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选
+ visible
+ )
+ }
+ }
+
+ onSelect(check, item) {
+ item.checked = check
+ item.indeterminate = false
+ if (item.children && item.children.length) {
+ this.onChangeChildren(item.checked, item.children)
+ }
+ if (item.parentId) {
+ this.onChangeParent(item.checked, item.parentId)
+ }
+
+ this.setState({
+ dataSource: this.list.filter(item => item.parentId === EMPTY_ID)
+ })
+ }
+
+ onChangeParent(checked, parentId) {
+ const parent = this.list.find(p => p.id === parentId)
+ if (parent) {
+ const checkedCount = parent.children.filter(p => p.checked).length
+ const indeterminateCount = parent.children.filter(p => p.indeterminate).length
+ if (checkedCount === parent.children.length) {
+ // 全选
+ parent.checked = true
+ parent.indeterminate = false
+ } else if (!checkedCount && !indeterminateCount) {
+ // 全不选
+ parent.checked = false
+ parent.indeterminate = false
+ } else {
+ // 半选
+ parent.checked = false
+ parent.indeterminate = true
+ }
+ this.onChangeParent(checked, parent.parentId)
+ }
+ }
+
+ onChangeChildren(checked, children) {
+ children.forEach(p => {
+ p.checked = checked
+ p.indeterminate = false
+ if (p.children && p.children.length) {
+ this.onChangeChildren(checked, p.children)
+ }
+ })
+ }
+
+ generateList(data) {
+ data.forEach(item => {
+ if (item.children && item.children.length) {
+ this.generateList(item.children)
+ }
+ this.list.push(item)
+ })
+ }
+
+ getVisible() {
+ const checked = this.list.filter(item => item.checked)
+ const caseChildren = checked.filter(item => item.visibleParent || item.type != 2)
+ const visibleParents = []
+ // 递归寻找父级
+ const findVisibleParents = (children) => {
+ const parents = []
+ children.forEach(item => {
+ if (item.parentId) {
+ const parent = this.list.find(item => item.id === item.parentId)
+ if (parent) {
+ parents.push(parent)
+ visibleParents.push(parent)
+ }
+ }
+ })
+ if (parents.length) {
+ findVisibleParents(parents)
+ }
+ }
+
+ findVisibleParents(caseChildren)
+
+ const checkedIds = checked.map(item => item.id)
+ const visibleParentsIds = visibleParents.map(item => item.id)
+
+ const result = checkedIds
+ visibleParentsIds.forEach(item => {
+ if (!result.includes(item)) {
+ result.push(item)
+ }
+ })
+
+ return result
+ }
+
+ renderDescriptions(data) {
+ return data.map(item => {
+ return item.children && item.children.length ? this.renderItem(item) : this.renderCheckbox(item)
+ })
+ }
+
+ renderItem(data) {
+ return (
+
+ this.onChange(e, data)}
+ >{data.title}
+ }
+ >
+ {this.renderDescriptions(data.children)}
+
+
+ )
+ }
+
+ renderCheckbox(data) {
+ return (
+
-
+
+ }>
+ {
+ !this.state.loading ?
+
+ {
+ this.state.dataSource.map(item => {
+ return (
+ this.onChange(e, item)}
+ >{item.title}
+ }
+ >
+ {this.renderDescriptions(item.children)}
+
+ )
+ })
+ }
+
+ :
+
+ }
+
)
}
diff --git a/web-react/src/pages/system/role/data.jsx b/web-react/src/pages/system/role/data.jsx
new file mode 100644
index 0000000..f38b8a4
--- /dev/null
+++ b/web-react/src/pages/system/role/data.jsx
@@ -0,0 +1,169 @@
+import React, { Component } from 'react'
+import { Form, Select, Spin, TreeSelect } from 'antd'
+import { AntIcon } from 'components'
+import { cloneDeep } from 'lodash'
+import { api } from 'common/api'
+import getDicData from 'util/dic'
+
+const { SHOW_PARENT } = TreeSelect
+
+const initialValues = {}
+
+export default class data extends Component {
+
+ state = {
+ // 加载状态
+ loading: true,
+ dataScopeType: [],
+ orgTreeData: [],
+ arerTreeData: [],
+ orgCheckedKeys: [],
+
+ isDefine: false
+ }
+
+ // 表单实例
+ form = React.createRef()
+
+ // 初始化数据
+ record = {}
+
+ /**
+ * mount后回调
+ */
+ componentDidMount() {
+ this.props.created && this.props.created(this)
+ }
+
+ /**
+ * 填充数据
+ * 可以在设置this.record之后对其作出数据结构调整
+ * [异步,必要]
+ * @param {*} params
+ */
+ async fillData(params) {
+
+ this.record = cloneDeep(params.record)
+ //#region 从后端转换成前段所需格式
+ const { dataScopeType } = await getDicData('data_scope_type')
+ const orgTreeData = await this.onLoadOrgTreeData()
+ const arerTreeData = await this.onLoadAreaTreeData()
+ const orgCheckedKeys = await this.onLoadRoleOwn(this.record.id)
+ this.setState({
+ dataScopeType,
+ orgTreeData,
+ arerTreeData,
+ orgCheckedKeys
+ })
+ //#endregion
+ this.form.current.setFieldsValue({
+ dataScopeType: this.record.dataScopeType.toString()
+ })
+
+ this.onChange(this.record.dataScopeType)
+
+ this.setState({
+ loading: false
+ })
+ }
+
+ /**
+ * 获取数据
+ * 可以对postData进行数据结构调整
+ * [异步,必要]
+ * @returns
+ */
+ async getData() {
+ const form = this.form.current
+
+ const valid = await form.validateFields()
+ if (valid) {
+ const postData = form.getFieldsValue()
+ if (this.record) {
+ postData.id = this.record.id
+ }
+ //#region 从前段转换后端所需格式
+ //#endregion
+ return postData
+ }
+ }
+
+ //#region 自定义方法
+ async onLoadOrgTreeData() {
+ const { data } = await api.getOrgTree()
+ return data
+ }
+
+ async onLoadAreaTreeData() {
+ const { data } = await api.getAreaTree()
+ return data
+ }
+
+ async onLoadRoleOwn(id) {
+ const { data } = await api.sysRoleOwnData({ id })
+ return data
+ }
+
+ onChange(value) {
+ if (value == 5) {
+ this.setState({
+ isDefine: true
+ })
+ } else {
+ this.setState({
+ isDefine: false
+ })
+ }
+ }
+ //#endregion
+
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/web-react/src/pages/system/role/form.jsx b/web-react/src/pages/system/role/form.jsx
new file mode 100644
index 0000000..594deee
--- /dev/null
+++ b/web-react/src/pages/system/role/form.jsx
@@ -0,0 +1,104 @@
+import React, { Component } from 'react'
+import { Form, Input, InputNumber, Spin } from 'antd'
+import { AntIcon } from 'components'
+import { cloneDeep } from 'lodash'
+
+const initialValues = {
+ sort: 100
+}
+
+export default class form extends Component {
+
+ state = {
+ // 加载状态
+ loading: true,
+ }
+
+ // 表单实例
+ form = React.createRef()
+
+ // 初始化数据
+ record = {}
+
+ /**
+ * mount后回调
+ */
+ componentDidMount() {
+ this.props.created && this.props.created(this)
+ }
+
+ /**
+ * 填充数据
+ * 可以在设置this.record之后对其作出数据结构调整
+ * [异步,必要]
+ * @param {*} params
+ */
+ async fillData(params) {
+
+ this.record = cloneDeep(params.record)
+ //#region 从后端转换成前段所需格式
+ //#endregion
+ this.form.current.setFieldsValue(this.record)
+
+ this.setState({
+ loading: false
+ })
+ }
+
+ /**
+ * 获取数据
+ * 可以对postData进行数据结构调整
+ * [异步,必要]
+ * @returns
+ */
+ async getData() {
+ const form = this.form.current
+
+ const valid = await form.validateFields()
+ if (valid) {
+ const postData = form.getFieldsValue()
+ if (this.record) {
+ postData.id = this.record.id
+ }
+ //#region 从前段转换后端所需格式
+ //#endregion
+ return postData
+ }
+ }
+
+ //#region 自定义方法
+ //#endregion
+
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/web-react/src/pages/system/role/index.jsx b/web-react/src/pages/system/role/index.jsx
new file mode 100644
index 0000000..e9edb04
--- /dev/null
+++ b/web-react/src/pages/system/role/index.jsx
@@ -0,0 +1,250 @@
+import React, { Component } from 'react'
+import { Button, Card, Dropdown, Form, Input, Menu, message as Message, Popconfirm } from 'antd'
+import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components'
+import { api } from 'common/api'
+import auth from 'components/authorized/handler'
+import { isEqual } from 'lodash'
+import FormBody from './form'
+import MenuForm from './menu'
+import DataForm from './data'
+
+// 配置页面所需接口函数
+const apiAction = {
+ page: api.getRolePage,
+ add: api.sysRoleAdd,
+ edit: api.sysRoleEdit,
+ delete: api.sysRoleDelete,
+
+ grantMenu: api.sysRoleGrantMenu,
+ grantData: api.sysRoleGrantData
+}
+
+// 用于弹窗标题
+const name = '角色'
+
+export default class index extends Component {
+
+ // 表格实例
+ table = React.createRef()
+
+ // 新增窗口实例
+ addForm = React.createRef()
+ // 编辑窗口实例
+ editForm = React.createRef()
+
+ menuForm = React.createRef()
+ dataForm = React.createRef()
+
+ columns = [
+ {
+ title: '角色名',
+ dataIndex: 'name',
+ sorter: true,
+ },
+ {
+ title: '唯一编码',
+ dataIndex: 'code',
+ sorter: true,
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ sorter: true,
+ }
+ ]
+
+ /**
+ * 构造函数,在渲染前动态添加操作字段等
+ * @param {*} props
+ */
+ constructor(props) {
+ super(props)
+
+ const flag = auth({ sysRole: [['edit'], ['delete']] })
+
+ if (flag) {
+ this.columns.push({
+ title: '操作',
+ width: 150,
+ dataIndex: 'actions',
+ render: (text, record) => (
+
+ this.onOpen(this.editForm, record)}>编辑
+
+
+ this.onDelete(record)}
+ >
+ 删除
+
+
+
+
+
+
+ this.onOpen(this.menuForm, record)}>授权菜单
+
+
+
+
+ this.onOpen(this.dataForm, record)}>授权数据
+
+
+
+ }
+ >
+
+ 授权
+
+
+
+
+ )
+ })
+ }
+ }
+
+ /**
+ * 阻止外部组件引发的渲染,提升性能
+ * 可自行添加渲染条件
+ * [必要]
+ * @param {*} props
+ * @param {*} state
+ * @returns
+ */
+ shouldComponentUpdate(props, state) {
+ return !isEqual(this.state, state)
+ }
+
+ /**
+ * 调用加载数据接口,可在调用前对query进行处理
+ * [异步,必要]
+ * @param {*} params
+ * @param {*} query
+ * @returns
+ */
+ loadData = async (params, query) => {
+ const { data } = await apiAction.page({
+ ...params,
+ ...query,
+ })
+ return data
+ }
+
+ /**
+ * 打开新增/编辑弹窗
+ * @param {*} modal
+ * @param {*} record
+ */
+ onOpen(modal, record) {
+ modal.current.open({
+ record
+ })
+ }
+
+ /**
+ * 对表格上的操作进行统一处理
+ * [异步]
+ * @param {*} action
+ * @param {*} successMessage
+ */
+ async onAction(action, successMessage) {
+ this.table.current.onLoading()
+ try {
+ await action
+ Message.success(successMessage)
+ this.table.current.onReloadData()
+ } catch {
+ this.table.current.onLoaded()
+ }
+ }
+
+ /**
+ * 删除
+ * @param {*} record
+ */
+ onDelete(record) {
+ this.onAction(
+ apiAction.delete(record),
+ '删除成功'
+ )
+ }
+
+ //#region 自定义方法
+ //#endregion
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ }
+ operator={
+
+ }
+ onClick={() => this.onOpen(this.addForm)}
+ >新增{name}
+
+ }
+ />
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ )
+ }
+}
diff --git a/web-react/src/pages/system/role/menu.jsx b/web-react/src/pages/system/role/menu.jsx
new file mode 100644
index 0000000..6c3eb76
--- /dev/null
+++ b/web-react/src/pages/system/role/menu.jsx
@@ -0,0 +1,89 @@
+import React, { Component } from 'react'
+import { cloneDeep } from 'lodash'
+import { AntIcon, AuthorityView } from 'components'
+import { api } from 'common/api'
+import { Empty, Spin } from 'antd'
+
+export default class form extends Component {
+
+ state = {
+ // 加载状态
+ loading: true,
+ defaultSelectedKeys: []
+ }
+
+ selectedKeys = []
+
+ view = React.createRef()
+
+ // 初始化数据
+ record = {}
+
+ /**
+ * mount后回调
+ */
+ componentDidMount() {
+ this.props.created && this.props.created(this)
+ }
+
+ /**
+ * 填充数据
+ * 可以在设置this.record之后对其作出数据结构调整
+ * [异步,必要]
+ * @param {*} params
+ */
+ async fillData(params) {
+
+ this.record = cloneDeep(params.record)
+ //#region 从后端转换成前段所需格式
+ const { data } = await api.sysRoleOwnMenu({ id: this.record.id })
+ this.setState({
+ defaultSelectedKeys: data
+ })
+ this.view.current.onLoadData()
+ //#endregion
+
+ this.setState({
+ loading: false
+ })
+ }
+
+ /**
+ * 获取数据
+ * 可以对postData进行数据结构调整
+ * [异步,必要]
+ * @returns
+ */
+ async getData() {
+ const postData = {}
+ if (this.record) {
+ postData.id = this.record.id
+ }
+ //#region 从前段转换后端所需格式
+ postData.grantMenuIdList = this.selectedKeys
+ //#endregion
+ return postData
+ }
+
+ //#region 自定义方法
+ async loadData() {
+ const { data } = await api.SysMenuTreeForGrant()
+ return data
+ }
+ //#endregion
+
+ render() {
+ return (
+
}>
+
this.selectedKeys = s3}
+ ref={this.view}
+ />
+ { this.state.loading && }
+
+ )
+ }
+}