From 39b46e795919c5bd0cb395da3f0032b7f3ac9a6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=87=AA=E5=B8=A6=E5=A4=A7=E4=BD=AC=E6=B0=94=E5=9C=BA?=
<188633308@qq.com>
Date: Sun, 13 Jun 2021 19:24:46 +0800
Subject: [PATCH] =?UTF-8?q?update=20=E6=90=AC=E8=BF=81=E6=A0=91=E8=8A=82?=
=?UTF-8?q?=E7=82=B9=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
web-react/src/common/api/index.js | 26 +-
.../src/components/authorized/handler.js | 6 +
web-react/src/components/modal-form/index.jsx | 7 +-
.../components/query-tree-layout/index.jsx | 268 ++++++++++++++++-
web-react/src/pages/system/app/form.jsx | 26 +-
web-react/src/pages/system/app/index.jsx | 8 +-
web-react/src/pages/system/org/form.jsx | 226 ++++++++++++++
web-react/src/pages/system/org/index.jsx | 279 ++++++++++++++++++
web-react/src/store/reducer.js | 20 ++
web-react/src/util/format/index.js | 8 +-
10 files changed, 841 insertions(+), 33 deletions(-)
create mode 100644 web-react/src/pages/system/org/form.jsx
create mode 100644 web-react/src/pages/system/org/index.jsx
diff --git a/web-react/src/common/api/index.js b/web-react/src/common/api/index.js
index deb59ed..0f80ffc 100644
--- a/web-react/src/common/api/index.js
+++ b/web-react/src/common/api/index.js
@@ -135,25 +135,19 @@ for (let key in urls) {
}
})
.catch(({ response }) => {
- if (process.env.VUE_APP_NODE_ENV === 'development') {
- const { data, status } = response
- if (data.constructor === String) {
- errorNotification({
- message: data,
- code: status
- })
- } else {
- errorNotification(data)
- }
- if (data.code === STATUS.Unauthorized) {
- handlerUnauthorized()
- }
- reject(data)
- } else {
+ const { data, status } = response
+ if (data.constructor === String) {
errorNotification({
- message: '系统发生错误,请联系管理员'
+ message: data,
+ code: status
})
+ } else {
+ errorNotification(data)
}
+ if (data.code === STATUS.Unauthorized) {
+ handlerUnauthorized()
+ }
+ reject(data)
})
})
}
diff --git a/web-react/src/components/authorized/handler.js b/web-react/src/components/authorized/handler.js
index 28267b7..9e30dbb 100644
--- a/web-react/src/components/authorized/handler.js
+++ b/web-react/src/components/authorized/handler.js
@@ -19,6 +19,8 @@ const authByArray = (auth, permissions) => {
case Boolean:
flags.push([p, '&&'])
break
+ default:
+ break
}
})
@@ -77,6 +79,8 @@ const authByJson = (auth, permissions) => {
case Array:
flags.push(authByArray(deepName(app, key), permissions))
break
+ default:
+ break
}
}
@@ -122,6 +126,8 @@ const auth = (auth) => {
case Object:
flag = authByJson(auth, permissions)
break
+ default:
+ break
}
}
diff --git a/web-react/src/components/modal-form/index.jsx b/web-react/src/components/modal-form/index.jsx
index 040d6e2..defda6c 100644
--- a/web-react/src/components/modal-form/index.jsx
+++ b/web-react/src/components/modal-form/index.jsx
@@ -51,7 +51,7 @@ export default class ModalForm extends Component {
* 打开弹窗
* @param {*} data
*/
- open(data) {
+ open(data = {}) {
this.data = data
this.setState({ visible: true })
}
@@ -86,7 +86,10 @@ export default class ModalForm extends Component {
*/
async onClose() {
const body = this.childNode.current
- if (!body) return
+ if (!body) {
+ this.close()
+ return
+ }
if (this.compareOnClose) {
const formData = body.form.current.getFieldsValue()
diff --git a/web-react/src/components/query-tree-layout/index.jsx b/web-react/src/components/query-tree-layout/index.jsx
index acba62a..f7d4bf5 100644
--- a/web-react/src/components/query-tree-layout/index.jsx
+++ b/web-react/src/components/query-tree-layout/index.jsx
@@ -1,11 +1,271 @@
import React, { Component } from 'react'
+import { Breadcrumb, Empty, Input, Layout, Spin, Tooltip, Tree } from 'antd'
+import { AntIcon, Container } from 'components'
export default class QueryTreeLayout extends Component {
- render() {
- return (
-
-
+ state = {
+ loading: false,
+ dataSource: [],
+ expandedKeys: [],
+ selectedKey: '',
+ autoExpandParent: true,
+ searchValue: ''
+ }
+
+ list = []
+
+ defaultExpandedKeys = []
+
+ replaceFields = {
+ value: 'id',
+ title: 'title',
+ 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.defaultExpanded = typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false
+
+ if (this.props.replaceFields) {
+ this.replaceFields = this.props.replaceFields
+ }
+ }
+
+ /**
+ * 自动加载数据
+ */
+ componentDidMount() {
+ if (this.autoLoad) {
+ this.onLoadData()
+ }
+ }
+
+ /**
+ * 加载数据
+ * 调用外部传入的loadData函数,可在loadData中自行改变参数
+ */
+ async onLoadData() {
+ this.onLoading()
+
+ const res = await this.props.loadData()
+ const data = this.generateKey(res)
+ this.list = []
+ this.generateList(data)
+
+ let expandedKeys = []
+ if (this.defaultExpanded) {
+ expandedKeys = this.list.map(p => p.key)
+ }
+ this.setState({
+ dataSource: data,
+ expandedKeys
+ })
+ this.onLoaded()
+ }
+
+ /**
+ * 数据开始加载
+ */
+ onLoading() {
+ this.setState({
+ loading: {
+ indicator:
+ }
+ })
+ }
+
+ /**
+ * 数据加载完成
+ */
+ onLoaded() {
+ this.setState({ loading: false })
+ }
+
+ onExpand(expandedKeys) {
+ this.setState({
+ expandedKeys,
+ autoExpandParent: false
+ })
+ }
+
+ 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]
+ })
+ if (this.props.onSelect) {
+ this.props.onSelect(selectedIds[0])
+ }
+ }
+
+ onSearch(value) {
+ const expandedKeys = this.list
+ .map(p => {
+ if (p[this.replaceFields.title].indexOf(value) > -1) {
+ return this.getParentKey(p.key, this.state.dataSource)
+ }
+ return null
+ })
+ .filter((p, i, self) => p && self.indexOf(p) === i)
+
+ this.setState({
+ expandedKeys,
+ autoExpandParent: !!value,
+ searchValue: value
+ })
+ }
+
+ generateKey(data, level) {
+ const n = level || [0]
+ n.push(0)
+ data.forEach((p, i) => {
+ n[n.length - 1] = i
+ p.key = n.join('-')
+ p.scopedSlots = { title: 'title' }
+ if (p[this.replaceFields.children]) {
+ this.generateKey(p[this.replaceFields.children], Object.assign([], n))
+ }
+ })
+ return data
+ }
+
+ generateList(data) {
+ // 这里获取不到Key
+ for (let i = 0; i < data.length; i++) {
+ const { key } = data[i]
+ this.list.push({
+ key,
+ [this.replaceFields.value]: data[i][this.replaceFields.value],
+ [this.replaceFields.title]: data[i][this.replaceFields.title]
+ })
+ if (data[i][this.replaceFields.children]) {
+ this.generateList(data[i][this.replaceFields.children])
+ }
+ }
+ }
+
+ getParentKey(key, tree) {
+ let parentKey;
+ for (let i = 0; i < tree.length; i++) {
+ const node = tree[i]
+ if (node[this.replaceFields.children]) {
+ if (node[this.replaceFields.children].some(item => item.key === key)) {
+ parentKey = node.key
+ } else if (this.getParentKey(key, node[this.replaceFields.children])) {
+ parentKey = this.getParentKey(key, node[this.replaceFields.children])
+ }
+ }
+ }
+ return parentKey;
+ }
+
+ titleRender(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}>
+ }
+
+ renderBreadcrumbItem() {
+
+ const path = ['顶级']
+
+ const findPath = (data, level) => {
+ level = level || 1
+ for (let i = 0; i < data.length; i++) {
+ const item = data[i]
+
+ path[level] = item[this.replaceFields.title]
+
+ if (item[this.replaceFields.value] === this.state.selectedKey) {
+ path.length = level + 1
+ return true
+ }
+
+ if (item[this.replaceFields.children] && item[this.replaceFields.children].length) {
+ const found = findPath(item[this.replaceFields.children], level + 1)
+ if (found) {
+ return true
+ }
+ }
+ }
+ }
+
+ if (this.state.selectedKey) {
+ findPath(this.state.dataSource)
+ }
+
+ return path.map((p, i) => {p})
+ }
+
+ render() {
+
+ const props = {
+ treeData: this.state.dataSource,
+ expandedKeys: this.state.expandedKeys,
+ autoExpandParent: this.state.autoExpandParent
+ }
+
+ const on = {
+ onExpand: (expandedKeys) => this.onExpand(expandedKeys),
+ onSelect: (selectedKeys) => this.onSelect(selectedKeys)
+ }
+
+ return (
+
+
+
+
+ this.onSearch(value)}
+ />
+
+
+
+
+ this.setState({ expandedKeys: [] })} />
+
+
+
+ }>
+ {
+ !this.state.loading && !this.state.dataSource.length ?
+
+ :
+ this.titleRender(nodeData)}
+ />
+ }
+
+
+
+
+
+
+ {this.renderBreadcrumbItem()}
+
+
+ {this.props.children}
+
+
)
}
}
diff --git a/web-react/src/pages/system/app/form.jsx b/web-react/src/pages/system/app/form.jsx
index f857c23..1c5f440 100644
--- a/web-react/src/pages/system/app/form.jsx
+++ b/web-react/src/pages/system/app/form.jsx
@@ -1,9 +1,11 @@
import React, { Component } from 'react'
-import { Form, Input, Spin } from 'antd'
+import { Form, Input, InputNumber, Spin } from 'antd'
import { AntIcon } from 'components'
import { cloneDeep } from 'lodash'
-const initialValues = {}
+const initialValues = {
+ sort: 100
+}
export default class form extends Component {
@@ -29,12 +31,13 @@ export default class form extends Component {
* 填充数据
* 可以在设置this.record之后对其作出数据结构调整
* [异步,必要]
- * @param {*} record
+ * @param {*} params
*/
- async fillData(record) {
+ async fillData(params) {
- this.record = cloneDeep(record)
- /** */
+ this.record = cloneDeep(params.record)
+ //#region 从后端转换成前段所需格式
+ //#endregion
this.form.current.setFieldsValue(this.record)
this.setState({
@@ -57,7 +60,8 @@ export default class form extends Component {
if (this.record) {
postData.id = this.record.id
}
- /** */
+ //#region 从前段转换后端所需格式
+ //#endregion
return postData
}
}
@@ -79,6 +83,14 @@ export default class form extends Component {
+
+
+
diff --git a/web-react/src/pages/system/app/index.jsx b/web-react/src/pages/system/app/index.jsx
index e4e21a5..515fece 100644
--- a/web-react/src/pages/system/app/index.jsx
+++ b/web-react/src/pages/system/app/index.jsx
@@ -144,7 +144,7 @@ export default class index extends Component {
* @param {*} query
* @returns
*/
- async loadData(params, query) {
+ loadData = async (params, query) => {
const { data } = await apiAction.page({
...params,
...query,
@@ -176,7 +176,9 @@ export default class index extends Component {
* @param {*} record
*/
onOpen(modal, record) {
- modal.current.open(record)
+ modal.current.open({
+ record
+ })
}
/**
@@ -240,7 +242,7 @@ export default class index extends Component {
}
onClick={() => this.onOpen(this.addForm)}
- >新增应用
+ >新增{name}
}
/>
diff --git a/web-react/src/pages/system/org/form.jsx b/web-react/src/pages/system/org/form.jsx
new file mode 100644
index 0000000..d3fe0ef
--- /dev/null
+++ b/web-react/src/pages/system/org/form.jsx
@@ -0,0 +1,226 @@
+import React, { Component } from 'react'
+import { Cascader, Form, Input, InputNumber, Select, Spin, TreeSelect } from 'antd'
+import { AntIcon } from 'components'
+import { cloneDeep } from 'lodash'
+import getDicData from 'util/dic'
+import { EMPTY_ID } from 'util/global'
+import { api } from 'common/api'
+
+const initialValues = {
+ sort: 100
+}
+
+export default class form extends Component {
+
+ state = {
+ // 加载状态
+ loading: true,
+
+ codes: {
+ orgType: []
+ },
+
+ options: {
+ orgData: [],
+ areaData: []
+ }
+ }
+
+ // 表单实例
+ 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 orgData = await this.loadOrgData()
+ const areaData = await this.loadAreaData()
+
+ const codes = await getDicData('org_type')
+ this.setState({
+ codes,
+ options: {
+ orgData,
+ areaData
+ }
+ })
+
+ const areaCode = [];
+ const findCode = (data, level) => {
+ level = level || 0;
+ for (let i = 0; i < data.length; i++) {
+ const item = data[i];
+ areaCode[level] = item.code;
+
+ if (item.code === params.record.areaCode) {
+ areaCode.length = level + 1;
+ return true;
+ }
+
+ if (item.children && item.children.length) {
+ const found = findCode(item.children, level + 1);
+ if (found) {
+ return true;
+ }
+ }
+ }
+ };
+
+ if (params.record && params.record.areaCode) {
+ findCode(areaData);
+ }
+
+ this.record = {
+ pid: params.orgId,
+ ...this.record,
+ areaCode
+ }
+ this.record.areaCode = areaCode
+ //#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 从前段转换后端所需格式
+ postData.areaCode = postData.areaCode[postData.areaCode.length - 1]
+ //#endregion
+ return postData
+ }
+ }
+
+ //#region 自定义方法
+ async loadOrgData() {
+ const { data } = await api.getOrgTree()
+ return [{
+ id: EMPTY_ID,
+ parentId: undefined,
+ title: '顶级',
+ value: EMPTY_ID,
+ pid: undefined,
+ children: data,
+ }]
+ }
+
+ async loadAreaData() {
+ const { data } = await api.getAreaTree()
+ const clearChiildren = (data) => {
+ data.forEach((item) => {
+ if (item.children && item.children.length) {
+ clearChiildren(item.children);
+ } else {
+ delete item.children;
+ }
+ });
+ };
+ clearChiildren(data);
+ return data
+ }
+
+ onAreaCodeChange(selectedOptions) {
+ const data = selectedOptions[selectedOptions.length - 1]
+ this.form.current.setFieldsValue({
+ name: data.name,
+ code: data.code
+ })
+ }
+ //#endregion
+
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/web-react/src/pages/system/org/index.jsx b/web-react/src/pages/system/org/index.jsx
new file mode 100644
index 0000000..34c8a4d
--- /dev/null
+++ b/web-react/src/pages/system/org/index.jsx
@@ -0,0 +1,279 @@
+import React, { Component } from 'react'
+import { Button, Card, Form, Input, message as Message, Popconfirm } from 'antd'
+import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions, QueryTreeLayout } from 'components'
+import { api } from 'common/api'
+import auth from 'components/authorized/handler'
+import { toCamelCase } from 'util/format'
+import { isEqual } from 'lodash'
+import getDicData from 'util/dic'
+import FormBody from './form'
+
+const apiAction = {
+ tree: api.getOrgTree,
+ page: api.getOrgPage,
+ add: api.sysOrgAdd,
+ edit: api.sysOrgEdit,
+ delete: api.sysOrgDelete
+}
+
+const name = '机构'
+
+export default class index extends Component {
+
+ // 表格实例
+ table = React.createRef()
+
+ // 新增窗口实例
+ addForm = React.createRef()
+ // 编辑窗口实例
+ editForm = React.createRef()
+
+ // 树选中节点
+ selectId = undefined
+
+ // 表格字段
+ columns = [
+ {
+ title: '机构名称',
+ width: '400px',
+ dataIndex: 'name',
+ sorter: true,
+ },
+ {
+ title: '唯一编码',
+ width: '200px',
+ dataIndex: 'code',
+ sorter: true,
+ },
+ {
+ title: '机构类型',
+ dataIndex: 'type',
+ sorter: true,
+ render: text => (<>{this.bindCodeValue(text, 'org_type')}>)
+ },
+ {
+ title: '排序',
+ width: '80px',
+ dataIndex: 'sort',
+ sorter: true,
+ },
+ {
+ title: '备注',
+ dataIndex: 'remark',
+ sorter: true,
+ },
+ ]
+
+ /**
+ * 构造函数,在渲染前动态添加操作字段等
+ * @param {*} props
+ */
+ constructor(props) {
+ super(props)
+
+ const flag = auth({ sysOrg: [['edit'], ['delete']] })
+
+ if (flag) {
+ this.columns.push({
+ title: '操作',
+ width: 150,
+ dataIndex: 'actions',
+ render: (text, record) => (
+
+ this.onOpen(this.editForm, record)}>编辑
+
+
+ this.onDelete(record)}
+ >
+ 删除
+
+
+ )
+ })
+ }
+ }
+
+ /**
+ * 阻止外部组件引发的渲染,提升性能
+ * 可自行添加渲染条件
+ * [必要]
+ * @param {*} props
+ * @param {*} state
+ * @returns
+ */
+ shouldComponentUpdate(props, state) {
+ return !isEqual(this.state, state)
+ }
+
+ /**
+ * 加载字典数据,之后开始加载表格数据
+ * 如果必须要加载字典数据,可直接对表格设置autoLoad=true
+ */
+ componentDidMount() {
+ this.table.current.onLoading()
+ getDicData('org_type').then(res => {
+ this.setState({
+ codes: res
+ }, () => {
+ this.table.current.onLoadData()
+ })
+ })
+ }
+
+ /**
+ * 调用加载数据接口,可在调用前对query进行处理
+ * [异步,必要]
+ * @param {*} params
+ * @param {*} query
+ * @returns
+ */
+ loadData = async (params, query) => {
+
+ query = {
+ ...query,
+ pid: this.selectId
+ }
+
+ const { data } = await apiAction.page({
+ ...params,
+ ...query,
+ })
+ return data
+ }
+
+ /**
+ * 调用树结构数据接口
+ * [异步,必要]
+ * @returns
+ */
+ loadTreeData = async () => {
+ const { data } = await apiAction.tree()
+ return data
+ }
+
+ /**
+ * 树节点选中事件
+ * [必要]
+ * @param {*} id
+ */
+ onSelectTree(id) {
+ this.selectId = id
+ this.table.current.onReloadData()
+ }
+
+ /**
+ * 绑定字典数据
+ * @param {*} code
+ * @param {*} name
+ * @returns
+ */
+ bindCodeValue(code, name) {
+ name = toCamelCase(name)
+ const codes = this.state.codes[name]
+ if (codes) {
+ const c = codes.find((p) => p.code == code)
+ if (c) {
+ return c.value
+ }
+ }
+ return null
+ }
+
+ /**
+ * 打开新增/编辑弹窗
+ * @param {*} modal
+ * @param {*} record
+ */
+ onOpen(modal, record) {
+ modal.current.open({
+ orgId: this.selectId,
+ 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 (
+ this.onSelectTree(key)}
+ >
+
+
+
+
+
+
+
+ }
+ operator={
+ }
+ onClick={() => this.onOpen(this.addForm)}
+ >新增{name}
+ }
+ />
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ )
+ }
+}
diff --git a/web-react/src/store/reducer.js b/web-react/src/store/reducer.js
index de2c558..4bb1151 100644
--- a/web-react/src/store/reducer.js
+++ b/web-react/src/store/reducer.js
@@ -11,6 +11,25 @@ const user = (state = {}, action) => {
}
}
+const layout = (state = {}, action) => {
+ switch (action.type) {
+ // 打开窗口
+ case 'OPEN_WINDOW':
+ return state
+ // 关闭窗口
+ case 'CLOSE_WINDOW':
+ return state
+ // 重新加载窗口
+ case 'RELOAD_WINDOW':
+ return state
+ // 侧边收起状态
+ case 'TOGGLE_COLLAPSED':
+ return state
+ default:
+ return state
+ }
+}
+
const dicData = (state = {}, action) => {
switch (action.type) {
case 'ADD_DIC_DATA':
@@ -23,6 +42,7 @@ const dicData = (state = {}, action) => {
const combine = combineReducers({
user,
+ layout,
dicData
})
diff --git a/web-react/src/util/format/index.js b/web-react/src/util/format/index.js
index 86fd9b1..238c7fe 100644
--- a/web-react/src/util/format/index.js
+++ b/web-react/src/util/format/index.js
@@ -40,5 +40,11 @@ export const toCamelCase = (str) => {
* @param {String} str
*/
export const toUnderScoreCase = (str) => {
-
+ if (typeof str === 'string') {
+ str = str.replace(/[A-Z]/g, (match) => {
+ return `_${match}`
+ }).toLowerCase()
+ return str.startsWith('_') ? str.slice(1) : str
+ }
+ return str
}
\ No newline at end of file