update 迁移table和modal-form组件,迁移app管理
This commit is contained in:
131
web-react/src/components/authorized/handler.js
Normal file
131
web-react/src/components/authorized/handler.js
Normal file
@@ -0,0 +1,131 @@
|
||||
import store from 'store'
|
||||
|
||||
const { getState } = store
|
||||
|
||||
const stroePath = 'user'
|
||||
|
||||
const authByArray = (auth, permissions) => {
|
||||
|
||||
const flags = []
|
||||
|
||||
auth.forEach(p => {
|
||||
switch (p.constructor) {
|
||||
case String:
|
||||
flags.push([permissions.includes(p), '&&'])
|
||||
break
|
||||
case Array:
|
||||
flags.push([authByArray(p, permissions), '||'])
|
||||
break
|
||||
case Boolean:
|
||||
flags.push([p, '&&'])
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
let result
|
||||
|
||||
flags.forEach((p, i) => {
|
||||
if (p[1] === '&&') {
|
||||
if (i === 0) {
|
||||
result = true
|
||||
}
|
||||
if (result) {
|
||||
result = p[0]
|
||||
}
|
||||
} else {
|
||||
if (i === 0) {
|
||||
result = false
|
||||
}
|
||||
if (!result) {
|
||||
result = p[0]
|
||||
}
|
||||
}
|
||||
//result = p[1] === '&&' ? result && p[0] : result || p[0]
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const authByJson = (auth, permissions) => {
|
||||
|
||||
let result = true
|
||||
|
||||
const flags = []
|
||||
|
||||
const deepName = (arr, key) => {
|
||||
arr.forEach((p, i) => {
|
||||
switch (p.constructor) {
|
||||
case String:
|
||||
arr[i] = `${key}:${p}`
|
||||
break
|
||||
case Array:
|
||||
p = deepName(p, key)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
return arr
|
||||
}
|
||||
|
||||
for (let key in auth) {
|
||||
const app = auth[key]
|
||||
switch (app.constructor) {
|
||||
case String:
|
||||
flags.push(permissions.includes(`${key}:${app}`))
|
||||
break
|
||||
case Array:
|
||||
flags.push(authByArray(deepName(app, key), permissions))
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
flags.forEach(p => {
|
||||
result = result && p
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
}
|
||||
|
||||
const auth = (auth) => {
|
||||
|
||||
let info = this
|
||||
|
||||
if (!info || !Object.keys(info).length) {
|
||||
info = getState(stroePath)
|
||||
}
|
||||
|
||||
if (!info) {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 超级管理员
|
||||
*/
|
||||
if (info.adminType === 1) {
|
||||
return true
|
||||
}
|
||||
|
||||
const permissions = info.permissions
|
||||
|
||||
let flag = false
|
||||
|
||||
if (auth) {
|
||||
switch (auth.constructor) {
|
||||
case String:
|
||||
flag = permissions.includes(auth)
|
||||
break
|
||||
case Array:
|
||||
flag = authByArray(auth, permissions)
|
||||
break
|
||||
case Object:
|
||||
flag = authByJson(auth, permissions)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return flag
|
||||
}
|
||||
|
||||
export default auth
|
||||
@@ -1,11 +1,74 @@
|
||||
/**
|
||||
* auth: 允许的权限
|
||||
* authExclude: 排除的权限
|
||||
*
|
||||
* auth的几种传值方式
|
||||
* 1.String
|
||||
* 例: auth="sysApp:page"
|
||||
* 直接传入字符串,对单项权限进行验证
|
||||
*
|
||||
* 2.Array
|
||||
* 2.1.单项权限
|
||||
* 例: auth={['sysApp:page']}
|
||||
* 2.2.并且关系多项权限
|
||||
* 例: auth={['sysApp:page', 'sysApp:add']}
|
||||
* 数组中传入多个字符串
|
||||
* 此时验证的是同时拥有"sysApp:page"和"sysApp:add"两项权限才会渲染
|
||||
* 2.3.或者关系多项权限
|
||||
* 例: auth={[['sysApp:page', 'sysApp:add'], ['sysApp:edit']]}
|
||||
* 二维数组结构,内部数组之间为并且关系
|
||||
* 此时验证的是"sysApp:page"&"sysApp:add"||"sysApp:edit"
|
||||
* 注意:或者的条件必须包括在数组中,暴露在外则判定为并且
|
||||
* 2.4.可直接传入布尔值
|
||||
* 例: auth={['sysApp:page', 1 === 1]}
|
||||
* auth={[['sysApp:page', 'sysApp:add'], [1 === 1]]}
|
||||
*
|
||||
* 3.Json
|
||||
* 如果觉得多项权限时每次都要写应用编号比较繁琐,可对Array形式进行简化
|
||||
* 3.1.单项权限
|
||||
* 例: auth={{ sysApp: 'page' }}
|
||||
* 3.2.并且关系多项权限
|
||||
* 例: auth={{ sysApp: ['page', 'add'] }}
|
||||
* 3.3.或者关系多项权限
|
||||
* 例: auth={{ sysApp: [['page', 'add'], ['edit']]}}
|
||||
* 3.4.可直接传入布尔值
|
||||
* 例: auth={{ sysApp: ['page', 1 === 1] }}
|
||||
* auth={{ sysApp: [['page', 'add'], [1 === 1]] }}
|
||||
*
|
||||
*/
|
||||
|
||||
import React, { Component } from 'react'
|
||||
import store from 'store'
|
||||
import auth from './handler'
|
||||
|
||||
const { getState, subscribe } = store
|
||||
|
||||
const stroePath = 'user'
|
||||
|
||||
export default class Auth extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
state = getState(stroePath)
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.unsubscribe = subscribe(stroePath, () => {
|
||||
this.setState(getState(stroePath))
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe()
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const flag = auth.call(this.state, this.props.auth)
|
||||
|
||||
if (flag) {
|
||||
return this.props.children
|
||||
}
|
||||
|
||||
return <></>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,13 @@
|
||||
import AntIcon from './ant-icon'
|
||||
import AuthorityView from './authority-view'
|
||||
import Auth from './authorized'
|
||||
import ComponentDynamic from './component-dynamic'
|
||||
import Container from './container'
|
||||
import IconSelector from './icon-selector'
|
||||
import Image from './image'
|
||||
import ModalForm from './modal-form'
|
||||
import PhotoSwipe from './photo-swipe'
|
||||
import QueryList from './query-list'
|
||||
import QueryTable from './query-table'
|
||||
import QueryTableActions from './query-table-actions'
|
||||
import QueryTreeLayout from './query-tree-layout'
|
||||
|
||||
const components = {
|
||||
AntIcon,
|
||||
AuthorityView,
|
||||
Auth,
|
||||
ComponentDynamic,
|
||||
Container,
|
||||
IconSelector,
|
||||
Image,
|
||||
ModalForm,
|
||||
PhotoSwipe,
|
||||
QueryList,
|
||||
QueryTable,
|
||||
QueryTableActions,
|
||||
QueryTreeLayout
|
||||
}
|
||||
|
||||
export default components
|
||||
export { default as AntIcon } from 'components/ant-icon'
|
||||
export { default as AuthorityView } from './authority-view'
|
||||
export { default as Auth } from './authorized'
|
||||
export { default as ComponentDynamic } from './component-dynamic'
|
||||
export { default as Container } from './container'
|
||||
export { default as IconSelector } from './icon-selector'
|
||||
export { default as Image } from './image'
|
||||
export { default as ModalForm } from './modal-form'
|
||||
export { default as PhotoSwipe } from './photo-swipe'
|
||||
export { default as QueryList } from './query-list'
|
||||
export { default as QueryTable } from './query-table'
|
||||
export { default as QueryTableActions } from './query-table-actions'
|
||||
export { default as QueryTreeLayout } from './query-tree-layout'
|
||||
@@ -1,11 +1,195 @@
|
||||
import React, { Component } from 'react'
|
||||
import { message as Message, Modal } from 'antd'
|
||||
import { cloneDeep, isEqual } from 'lodash'
|
||||
|
||||
export default class ModalForm extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
state = {
|
||||
// 弹窗显示/隐藏
|
||||
visible: false,
|
||||
// 提交状态
|
||||
confirmLoading: false
|
||||
}
|
||||
|
||||
// 子元素实例
|
||||
childNode = React.createRef()
|
||||
|
||||
// 弹窗类型
|
||||
type = 'modal'
|
||||
|
||||
// 从外部传入的数据
|
||||
data = null
|
||||
// 数据结构调整后的快照
|
||||
snapshot = null
|
||||
|
||||
// 完成操作
|
||||
action = async () => { }
|
||||
|
||||
// 是否在关闭时校验数据改变
|
||||
compareOnClose = true
|
||||
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
if (this.props.type) {
|
||||
if (!['modal', 'drawer'].includes(this.props.type))
|
||||
throw new Error('props [type] error')
|
||||
this.type = this.props.type
|
||||
}
|
||||
|
||||
if (this.props.action) {
|
||||
this.action = this.props.action
|
||||
}
|
||||
|
||||
if (typeof this.props.compareOnClose === 'boolean') {
|
||||
this.compareOnClose = this.props.compareOnClose
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param {*} data
|
||||
*/
|
||||
open(data) {
|
||||
this.data = data
|
||||
this.setState({ visible: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭弹窗
|
||||
*/
|
||||
close() {
|
||||
this.setState({ visible: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 子元素创建后回调
|
||||
* 对子元素数据进行填充,(如需关闭时对比)之后再获取结构调整后的数据快照
|
||||
* @returns
|
||||
*/
|
||||
async onCreated() {
|
||||
const body = this.childNode.current
|
||||
if (!body || !body.fillData) return
|
||||
|
||||
await body.fillData(this.data)
|
||||
// 保存此时的form内容为快照
|
||||
if (this.compareOnClose) {
|
||||
this.snapshot = cloneDeep(body.form.current.getFieldsValue())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消编辑
|
||||
* (如需关闭时对比)获取当前数据结构与快照对比
|
||||
* @returns
|
||||
*/
|
||||
async onClose() {
|
||||
const body = this.childNode.current
|
||||
if (!body) return
|
||||
|
||||
if (this.compareOnClose) {
|
||||
const formData = body.form.current.getFieldsValue()
|
||||
if (!isEqual(this.snapshot, formData)) {
|
||||
Modal.confirm({
|
||||
title: '是否确认关闭',
|
||||
content: '当前内容已更改,是否确认不保存并且关闭',
|
||||
onOk: () => {
|
||||
this.close()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* 完成编辑
|
||||
* 校验并获取结构调整后的数据,调用this.action进行操作
|
||||
* @returns
|
||||
*/
|
||||
async onOk() {
|
||||
const body = this.childNode.current
|
||||
if (!body || !this.action || !body.getData) return
|
||||
|
||||
this.setState({ confirmLoading: true })
|
||||
try {
|
||||
const postData = await body.getData()
|
||||
|
||||
const { success } = await this.action(postData)
|
||||
if (success) {
|
||||
Message.success(this.props.successMessage || '保存成功')
|
||||
this.close()
|
||||
if (typeof this.props.onSuccess === 'function') {
|
||||
this.props.onSuccess(postData)
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
this.setState({ confirmLoading: false })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染对话框
|
||||
* @param {*} props
|
||||
* @param {*} on
|
||||
* @param {*} childWithProps
|
||||
* @returns
|
||||
*/
|
||||
renderModal(props, on, childWithProps) {
|
||||
|
||||
on = {
|
||||
...on,
|
||||
onCancel: () => this.onClose(this.compareOnClose)
|
||||
}
|
||||
|
||||
return <Modal className="yo-modal-form" {...props} {...on}>
|
||||
{childWithProps}
|
||||
</Modal>
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染抽屉
|
||||
* @param {*} props
|
||||
* @param {*} on
|
||||
* @param {*} childWithProps
|
||||
* @returns
|
||||
*/
|
||||
renderDrawer(props, on, childWithProps) {
|
||||
return <div></div>
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const props = {
|
||||
...this.props,
|
||||
visible: this.state.visible,
|
||||
destroyOnClose: true,
|
||||
|
||||
confirmLoading: this.state.confirmLoading
|
||||
}
|
||||
|
||||
const on = {
|
||||
onOk: () => this.onOk()
|
||||
}
|
||||
|
||||
const childWithProps = React.cloneElement(
|
||||
React.Children.only(this.props.children),
|
||||
{
|
||||
created: childNode => {
|
||||
this.childNode.current = childNode
|
||||
this.onCreated()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return this.type === 'modal' ?
|
||||
this.renderModal(props, on, childWithProps)
|
||||
:
|
||||
this.renderDrawer(props, on, childWithProps)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,28 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Divider } from 'antd'
|
||||
|
||||
export default class QueryTableActions extends Component {
|
||||
|
||||
renderActions() {
|
||||
const { children } = this.props
|
||||
const actions = []
|
||||
|
||||
Array.isArray(children) ? children.forEach((action, i) => {
|
||||
actions.push(action)
|
||||
if (i < this.props.children.length - 1) {
|
||||
actions.push(<Divider type="vertical" key={i} />)
|
||||
}
|
||||
}) : (actions.push(children))
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
<div className="yo-table-actions">
|
||||
<div className="yo-table-actions--inner">
|
||||
{this.renderActions()}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,11 +1,248 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Form, Button, Table } from 'antd'
|
||||
import { AntIcon } from 'components'
|
||||
|
||||
const clearChildren = (data) => {
|
||||
data.forEach(p => {
|
||||
if (p.children) {
|
||||
if (p.children.length) {
|
||||
p.children = clearChildren(p.children)
|
||||
} else {
|
||||
delete p.children
|
||||
}
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export default class QueryTable extends Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
|
||||
state = {
|
||||
// 加载状态
|
||||
loading: false,
|
||||
// 表格类型
|
||||
type: '',
|
||||
// 数据
|
||||
dataSource: []
|
||||
}
|
||||
|
||||
// 查询表单实例
|
||||
form = React.createRef()
|
||||
|
||||
// 查询值
|
||||
query = {}
|
||||
|
||||
// 分页器配置
|
||||
pagination = {
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 0,
|
||||
size: 'small',
|
||||
showSizeChanger: true,
|
||||
showQuickJumper: true,
|
||||
showTotal: (total) => `总共${total}条数据`
|
||||
}
|
||||
|
||||
// 默认选中页码
|
||||
pageIndex = 1
|
||||
// 默认页面尺寸
|
||||
pageSize = 10
|
||||
|
||||
// 排序字段
|
||||
sorter = {
|
||||
sortField: '',
|
||||
sortOrder: '',
|
||||
}
|
||||
|
||||
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 () => { }
|
||||
|
||||
if (this.props.pageIndex) {
|
||||
this.pageIndex = this.props.pageIndex
|
||||
this.pagination.current = this.pageIndex
|
||||
}
|
||||
if (this.props.pageSize) {
|
||||
this.pageSize = this.props.pageSize
|
||||
this.pagination.pageSize = this.pageSize
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动加载数据
|
||||
*/
|
||||
componentDidMount() {
|
||||
if (this.autoLoad) {
|
||||
this.onLoadData()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载数据
|
||||
* 调用外部传入的loadData函数,可在loadData中自行改变参数
|
||||
*/
|
||||
async onLoadData() {
|
||||
this.onLoading()
|
||||
|
||||
const res = await this.props.loadData({
|
||||
pageIndex: this.pagination.current,
|
||||
pageSize: this.pagination.pageSize,
|
||||
...this.sorter
|
||||
}, this.query)
|
||||
if (res.rows || res.data || res.items) {
|
||||
this.setState({
|
||||
type: 'table',
|
||||
dataSource: res.rows || res.data || res.items
|
||||
})
|
||||
|
||||
this.pagination.total = res.totalCount
|
||||
} else if (res) {
|
||||
this.setState({
|
||||
type: 'tree',
|
||||
dataSource: clearChildren(res)
|
||||
})
|
||||
|
||||
this.pagination = false
|
||||
}
|
||||
|
||||
this.onLoaded()
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据开始加载
|
||||
*/
|
||||
onLoading() {
|
||||
this.setState({
|
||||
loading: {
|
||||
indicator: <AntIcon type="loading" />
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据加载完成
|
||||
*/
|
||||
onLoaded() {
|
||||
this.setState({ loading: false })
|
||||
}
|
||||
|
||||
/**
|
||||
* 进行查询
|
||||
* 返回表单字段值,加载数据,并且返回到第一页
|
||||
* @param {*} values
|
||||
*/
|
||||
onQuery(values) {
|
||||
this.query = values
|
||||
this.onReloadData(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置查询
|
||||
* 初始化表单字段值,加载数据,并返回到第一页
|
||||
*/
|
||||
onResetQuery() {
|
||||
this.form.current.resetFields()
|
||||
this.query = {}
|
||||
this.onReloadData(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新加载表格数据
|
||||
* @param {Boolean} resetPage 是否重置页码
|
||||
*/
|
||||
onReloadData(resetPage = false) {
|
||||
if (resetPage) {
|
||||
this.pagination = {
|
||||
...this.pagination,
|
||||
current: this.pageIndex
|
||||
}
|
||||
}
|
||||
this.onLoadData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格分页/筛选/排序
|
||||
* @param {*} pagination
|
||||
* @param {*} filters
|
||||
* @param {*} sorter
|
||||
*/
|
||||
onTableChange(pagination, filters, sorter) {
|
||||
this.pagination = pagination
|
||||
this.sorter = {
|
||||
sortField: sorter.field,
|
||||
sortOrder: sorter.order,
|
||||
}
|
||||
this.onLoadData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染查询栏
|
||||
* @returns
|
||||
*/
|
||||
renderQueryBar() {
|
||||
|
||||
const { query, moreQuery } = this.props
|
||||
|
||||
return (
|
||||
<div className="yo-query-bar">
|
||||
<Form
|
||||
layout="inline"
|
||||
ref={this.form}
|
||||
onFinish={(value) => this.onQuery(value)}
|
||||
>
|
||||
{query}
|
||||
<Form.Item>
|
||||
<Button.Group className="mr-xs">
|
||||
<Button htmlType="submit" type="primary">查询</Button>
|
||||
<Button onClick={() => this.onResetQuery()}>重置</Button>
|
||||
</Button.Group>
|
||||
{
|
||||
moreQuery && <Button>更多查询条件</Button>
|
||||
}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { loading, dataSource } = this.state
|
||||
|
||||
const { query, operator, columns } = this.props
|
||||
|
||||
const props = {
|
||||
loading,
|
||||
pagination: this.pagination,
|
||||
dataSource,
|
||||
columns: (columns || []).filter(p => !p.hidden),
|
||||
bordered: true,
|
||||
size: 'middle',
|
||||
rowKey: record => record.id || Math.random().toString(16).slice(2),
|
||||
scroll: { x: 'max-content' }
|
||||
}
|
||||
|
||||
const on = {
|
||||
onChange: (...args) => this.onTableChange.apply(this, args)
|
||||
}
|
||||
|
||||
return (
|
||||
<section>
|
||||
{query && this.renderQueryBar()}
|
||||
<div className="yo-action-bar">
|
||||
<div className="yo-action-bar--actions">
|
||||
{operator}
|
||||
</div>
|
||||
<div className="yo-action-bar--actions">
|
||||
<Button.Group>
|
||||
<Button>刷新</Button>
|
||||
</Button.Group>
|
||||
</div>
|
||||
</div>
|
||||
<Table className="yo-table" {...props} {...on} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user