)
}
diff --git a/web-react/src/components/query-table/index.jsx b/web-react/src/components/query-table/index.jsx
index 6eeae05..83f3fa6 100644
--- a/web-react/src/components/query-table/index.jsx
+++ b/web-react/src/components/query-table/index.jsx
@@ -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 (
-
+ 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:
+ }
+ })
+ }
+
+ /**
+ * 数据加载完成
+ */
+ 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 (
+
+
+
+
+
+
+ {
+ moreQuery &&
+ }
+
+
)
}
+
+ 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 (
+
+ {query && this.renderQueryBar()}
+
+
+ {operator}
+
+
+
+
+
+
+
+
+
+ )
+ }
}
diff --git a/web-react/src/pages/business/house/info/form/base/index.jsx b/web-react/src/pages/business/house/info/form/base/index.jsx
index 54e38c2..b16c947 100644
--- a/web-react/src/pages/business/house/info/form/base/index.jsx
+++ b/web-react/src/pages/business/house/info/form/base/index.jsx
@@ -2,9 +2,7 @@ import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import { Row, Col, Card, Anchor } from 'antd'
import { defaultsDeep } from 'lodash'
-import Components from 'components'
-
-const { ComponentDynamic, Container } = Components
+import { ComponentDynamic, Container } from 'components'
const parts = [
{
diff --git a/web-react/src/pages/business/house/info/form/index.jsx b/web-react/src/pages/business/house/info/form/index.jsx
index ae25b41..9c4eeba 100644
--- a/web-react/src/pages/business/house/info/form/index.jsx
+++ b/web-react/src/pages/business/house/info/form/index.jsx
@@ -1,9 +1,7 @@
import React, { Component } from 'react'
import { Tabs, Button, message } from 'antd'
import { defaultsDeep } from 'lodash'
-import Components from 'components'
-
-const { ComponentDynamic, Container } = Components
+import { ComponentDynamic, Container } from 'components'
const tabs = [
{
diff --git a/web-react/src/pages/home/index.jsx b/web-react/src/pages/home/index.jsx
index f6c7aba..f2b5a96 100644
--- a/web-react/src/pages/home/index.jsx
+++ b/web-react/src/pages/home/index.jsx
@@ -2,14 +2,12 @@ import React, { Component } from 'react'
import { Row, Col, Divider } from 'antd'
import { isEqual } from 'lodash'
import store from 'store'
-import Components from 'components'
+import { Container, Image, AntIcon } from 'components'
import moment from 'moment'
import './index.less'
const { getState, subscribe } = store
-const { Container, Image, AntIcon } = Components
-
const storePath = 'user'
export default class index extends Component {
diff --git a/web-react/src/pages/system/app/form.jsx b/web-react/src/pages/system/app/form.jsx
new file mode 100644
index 0000000..f857c23
--- /dev/null
+++ b/web-react/src/pages/system/app/form.jsx
@@ -0,0 +1,87 @@
+import React, { Component } from 'react'
+import { Form, Input, Spin } from 'antd'
+import { AntIcon } from 'components'
+import { cloneDeep } from 'lodash'
+
+const initialValues = {}
+
+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 {*} record
+ */
+ async fillData(record) {
+
+ this.record = cloneDeep(record)
+ /** */
+ 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
+ }
+ /** */
+ return postData
+ }
+ }
+
+ //#region 自定义方法
+ //#endregion
+
+ render() {
+ return (
+
+ )
+ }
+}
diff --git a/web-react/src/pages/system/app/index.jsx b/web-react/src/pages/system/app/index.jsx
new file mode 100644
index 0000000..e4e21a5
--- /dev/null
+++ b/web-react/src/pages/system/app/index.jsx
@@ -0,0 +1,268 @@
+import React, { Component } from 'react'
+import { Button, Card, Form, Input, Popconfirm, message as Message } from 'antd'
+import { isEqual } from 'lodash'
+import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components'
+import { api } from 'common/api'
+import getDicData from 'util/dic'
+import auth from 'components/authorized/handler'
+import { toCamelCase } from 'util/format'
+import FormBody from './form'
+
+// 配置页面所需接口函数
+const apiAction = {
+ page: api.getAppPage,
+ add: api.sysAppAdd,
+ edit: api.sysAppEdit,
+ delete: api.sysAppDelete,
+
+ setDefault: api.sysAppSetAsDefault
+}
+
+// 用于弹窗标题
+const name = '应用'
+
+export default class index extends Component {
+
+ // 表格实例
+ table = React.createRef()
+
+ // 新增窗口实例
+ addForm = React.createRef()
+ // 编辑窗口实例
+ editForm = React.createRef()
+
+ // 表格字段
+ columns = [
+ {
+ title: '应用名称',
+ dataIndex: 'name',
+ sorter: true,
+ },
+ {
+ title: '唯一编码',
+ dataIndex: 'code',
+ sorter: true,
+ },
+ {
+ title: '是否默认',
+ dataIndex: 'active',
+ sorter: true,
+ render: (text, record) => (<>
+ {text ? '是' : '否'}
+ {
+ !record.active &&
+
+
+ this.onSetDefault(record)}
+ >
+ 设为默认
+
+
+
+ }
+ >)
+ },
+ {
+ title: '状态',
+ dataIndex: 'status',
+ sorter: true,
+ render: text => (<>{this.bindCodeValue(text, 'common_status')}>)
+ },
+ {
+ title: '排序',
+ dataIndex: 'sort',
+ sorter: true,
+ },
+ ]
+
+ /**
+ * 构造函数,在渲染前动态添加操作字段等
+ * @param {*} props
+ */
+ constructor(props) {
+ super(props)
+
+ const flag = auth({ sysApp: [['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('common_status').then(res => {
+ this.setState({
+ codes: res
+ }, () => {
+ this.table.current.onLoadData()
+ })
+ })
+ }
+
+ /**
+ * 调用加载数据接口,可在调用前对query进行处理
+ * [异步,必要]
+ * @param {*} params
+ * @param {*} query
+ * @returns
+ */
+ async loadData(params, query) {
+ const { data } = await apiAction.page({
+ ...params,
+ ...query,
+ })
+ return data
+ }
+
+ /**
+ * 绑定字典数据
+ * @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(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 自定义方法
+ async onSetDefault(record) {
+ this.onAction(
+ apiAction.setDefault(record),
+ '设置成功'
+ )
+ }
+ //#endregion
+
+ render() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ }
+ operator={
+ }
+ onClick={() => this.onOpen(this.addForm)}
+ >新增应用
+ }
+ />
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ this.table.current.onReloadData()}
+ >
+
+
+
+ )
+ }
+}
diff --git a/web-react/src/store/index.js b/web-react/src/store/index.js
index 9771438..101e479 100644
--- a/web-react/src/store/index.js
+++ b/web-react/src/store/index.js
@@ -2,16 +2,16 @@ import { createStore } from 'redux'
import { cloneDeep, result, isEqual } from 'lodash'
import reducer from './reducer'
-const store = createStore(reducer)
+const _store = createStore(reducer)
-const exStore = cloneDeep(store)
+const store = cloneDeep(_store)
/**
* 允许传入第一个参数path,只监听指定属性路径的对象
* @param {...any} args
* @returns
*/
-exStore.subscribe = (...args) => {
+store.subscribe = (...args) => {
let path,
listener,
snapshot
@@ -19,13 +19,13 @@ exStore.subscribe = (...args) => {
if (typeof args[0] === 'string' && typeof args[1] === 'function') {
path = args[0]
listener = args[1]
- snapshot = cloneDeep(result(store.getState(), path))
+ snapshot = cloneDeep(result(_store.getState(), path))
} else {
listener = args[0]
}
- return store.subscribe((...args) => {
- const state = store.getState()
+ return _store.subscribe((...args) => {
+ const state = _store.getState()
if (path) {
const resultState = cloneDeep(result(state, path))
if (!isEqual(snapshot, resultState)) {
@@ -43,12 +43,12 @@ exStore.subscribe = (...args) => {
* @param {*} path
* @returns
*/
-exStore.getState = (path) => {
+store.getState = (path) => {
if (path) {
- return result(store.getState(), path)
+ return result(_store.getState(), path)
}
- return store.getState()
+ return _store.getState()
}
-export default exStore
\ No newline at end of file
+export default store
\ No newline at end of file
diff --git a/web-react/src/store/reducer.js b/web-react/src/store/reducer.js
index 16202aa..de2c558 100644
--- a/web-react/src/store/reducer.js
+++ b/web-react/src/store/reducer.js
@@ -14,7 +14,8 @@ const user = (state = {}, action) => {
const dicData = (state = {}, action) => {
switch (action.type) {
case 'ADD_DIC_DATA':
- return state
+ const _state = { ...state, ...action.value }
+ return _state
default:
return state
}
diff --git a/web-react/src/util/dic/index.js b/web-react/src/util/dic/index.js
new file mode 100644
index 0000000..10a6763
--- /dev/null
+++ b/web-react/src/util/dic/index.js
@@ -0,0 +1,42 @@
+import { api } from 'common/api'
+import { mapKeys } from 'lodash'
+import store from 'store'
+import { toCamelCase } from 'util/format'
+
+const { getState, dispatch } = store
+
+const getDicData = async (...args) => {
+ const dicData = getState('dicData')
+ let result = {}
+ const code = []
+ for (let i = 0; i < args.length; i++) {
+ const codeName = toCamelCase(args[i])
+ if (!dicData.hasOwnProperty(codeName)) {
+ code.push(args[i])
+ } else {
+ result[codeName] = dicData[codeName]
+ }
+ }
+
+ if (code.length) {
+ try {
+ const value = mapKeys((await api.sysDictTypeDropDowns({ code })).data, (value, key) => {
+ return toCamelCase(key)
+ })
+
+ dispatch({
+ type: 'ADD_DIC_DATA',
+ value
+ })
+
+ result = { ...result, ...value }
+
+ return result
+ }
+ catch { }
+ }
+
+ return dicData
+}
+
+export default getDicData
\ No newline at end of file
diff --git a/web-react/src/util/format/index.js b/web-react/src/util/format/index.js
index 3b0a568..86fd9b1 100644
--- a/web-react/src/util/format/index.js
+++ b/web-react/src/util/format/index.js
@@ -16,4 +16,29 @@ export const numberToChinese = (val) => {
const chinanum = overWan ? getWan(overWan) + '万' + getWan(noWan) : getWan(num)
return chinanum
+}
+
+/**
+ * 下划线转驼峰
+ * @param {String} str
+ */
+export const toCamelCase = (str) => {
+ if (typeof str === 'string') {
+ return str.toLowerCase().split('_').map((p, i) => {
+ if (i > 0) {
+ return p[0].toUpperCase() + p.slice(1)
+ } else {
+ return p
+ }
+ }).join('')
+ }
+ return str
+}
+
+/**
+ * 驼峰转下划线
+ * @param {String} str
+ */
+export const toUnderScoreCase = (str) => {
+
}
\ No newline at end of file
diff --git a/web-react/src/views/main/_layout/header/index.jsx b/web-react/src/views/main/_layout/header/index.jsx
index 1d423d3..a25b9e4 100644
--- a/web-react/src/views/main/_layout/header/index.jsx
+++ b/web-react/src/views/main/_layout/header/index.jsx
@@ -1,11 +1,9 @@
import React, { Component } from 'react'
import { Layout, Badge } from 'antd'
-import Components from 'components'
+import { AntIcon, Container } from 'components'
import Logo from '../logo'
import User from './user'
-const { AntIcon, Container } = Components
-
export default class index extends Component {
render() {
return (
diff --git a/web-react/src/views/main/_layout/header/user.jsx b/web-react/src/views/main/_layout/header/user.jsx
index 4926212..77d00d8 100644
--- a/web-react/src/views/main/_layout/header/user.jsx
+++ b/web-react/src/views/main/_layout/header/user.jsx
@@ -5,12 +5,10 @@ import { isEqual } from 'lodash'
import store from 'store'
import { api } from 'common/api'
import { token } from 'common/token'
-import Components from 'components'
+import { AntIcon, Image } from 'components'
const { getState, subscribe } = store
-const { AntIcon, Image } = Components
-
const storePath = 'user'
let userOpenTimer, userCloseTimer