From fd9665c265ccad9160218677acc009a9124ab5a3 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: Fri, 25 Jun 2021 17:59:09 +0800 Subject: [PATCH 1/9] =?UTF-8?q?update=20=E5=93=8D=E5=BA=94=E5=BC=8F?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-react/src/assets/style/lib/list.less | 42 ++++- web-react/src/assets/style/lib/table.less | 8 + .../src/assets/style/lib/tree-layout.less | 14 ++ web-react/src/components/query-list/index.jsx | 56 ++++++- .../components/query-tree-layout/index.jsx | 157 ++++++++++++------ web-react/src/store/reducer/layout.js | 40 ++++- web-react/src/util/global/index.js | 4 +- .../src/views/main/_layout/header/index.jsx | 13 +- web-react/src/views/main/index.jsx | 13 +- 9 files changed, 275 insertions(+), 72 deletions(-) diff --git a/web-react/src/assets/style/lib/list.less b/web-react/src/assets/style/lib/list.less index f8a937f..2dbb2de 100644 --- a/web-react/src/assets/style/lib/list.less +++ b/web-react/src/assets/style/lib/list.less @@ -26,7 +26,7 @@ } } } - >.ant-pagination { + .ant-pagination { margin: @padding-md 0; } .ant-descriptions { @@ -44,6 +44,14 @@ } } } + &--scroll { + position: relative; + + overflow-x: auto; + } + .ant-list-items { + min-width: 1000px; + } .ant-list-item { transition: @animation-duration-slow; transition-property: background, border-bottom-color; @@ -52,4 +60,36 @@ background: linear-gradient(90deg, transparent 10%, @background-color-light 70%, transparent); } } + &-container { + position: relative; + &::before, + &::after { + position: absolute; + top: 0; + bottom: 0; + z-index: 3; + + width: 30px; + + content: ''; + transition: box-shadow @animation-duration-slow; + pointer-events: none; + } + &::before { + left: 0; + } + &::after { + right: 0; + } + &.yo-list--ping-left { + &::before { + box-shadow: inset 10px 0 8px -8px fade(@black, 15%); + } + } + &.yo-list--ping-right { + &::after { + box-shadow: inset -10px 0 8px -8px fade(@black, 15%); + } + } + } } diff --git a/web-react/src/assets/style/lib/table.less b/web-react/src/assets/style/lib/table.less index 9bde634..6ed273f 100644 --- a/web-react/src/assets/style/lib/table.less +++ b/web-react/src/assets/style/lib/table.less @@ -22,6 +22,14 @@ } } } +.ant-table { + .ant-table-container { + &::before, + &::after { + z-index: 3; + } + } +} .ant-table-thead { th.ant-table-column-has-sorters { &:hover { diff --git a/web-react/src/assets/style/lib/tree-layout.less b/web-react/src/assets/style/lib/tree-layout.less index 4b1cc97..a54090a 100644 --- a/web-react/src/assets/style/lib/tree-layout.less +++ b/web-react/src/assets/style/lib/tree-layout.less @@ -19,6 +19,20 @@ } } } + &--collapsed { + position: absolute; + top: 0; + left: 0; + bottom: 0; + z-index: 4; + + transform: translateX(-100%); + &.open { + transform: translateX(0); + + box-shadow: 2px 0 8px fade(@black , 20%); + } + } &--bar { line-height: 20px; diff --git a/web-react/src/components/query-list/index.jsx b/web-react/src/components/query-list/index.jsx index e623731..c052306 100644 --- a/web-react/src/components/query-list/index.jsx +++ b/web-react/src/components/query-list/index.jsx @@ -43,6 +43,8 @@ export default class QueryList extends Component { state = { loading: false, dataSource: [], + + ping: [], } // 查询表单实例 @@ -91,6 +93,12 @@ export default class QueryList extends Component { if (this.autoLoad) { this.onLoadData() } + + window.addEventListener('resize', this.onResizeScroll) + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeScroll) } /** @@ -108,9 +116,14 @@ export default class QueryList extends Component { this.query ) - this.setState({ - dataSource: res.rows || res.data || res.items, - }) + this.setState( + { + dataSource: res.rows || res.data || res.items, + }, + () => { + this.onResizeScroll() + } + ) this.pagination.total = res.totalCount @@ -174,7 +187,26 @@ export default class QueryList extends Component { this.onLoadData() } + onResizeScroll = () => { + this.onListScrollX({ target: this.refs['scroll-x'] }) + } + + onListScrollX(e) { + const { offsetWidth, scrollWidth, scrollLeft } = e.target + const ping = [] + if (offsetWidth + scrollLeft < scrollWidth && scrollLeft > 0) { + ping.push(...['left', 'right']) + } else if (offsetWidth + scrollLeft < scrollWidth) { + ping.push('right') + } else if (offsetWidth + scrollLeft >= scrollWidth && offsetWidth < scrollWidth) { + ping.push('left') + } + this.setState({ ping }) + } + render() { + const { loading, dataSource, ping } = this.state + const attrs = {} Object.keys(this.props).forEach(key => { if (!propsMap.includes(key)) { @@ -206,9 +238,21 @@ export default class QueryList extends Component {
- }> - - {!!this.state.dataSource && !!this.state.dataSource.length && ( + }> +
`yo-list--ping-${p}`)] + .filter(p => p) + .join(' ')} + > +
this.onListScrollX(e)} + > + +
+
+ {!!dataSource && !!dataSource.length && ( -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.substr(0, title.indexOf(this.state.searchValue))} + {this.state.searchValue} + {title.substr( + title.indexOf(this.state.searchValue) + this.state.searchValue.length + )} + + ) } return <>{title} } function renderBreadcrumbItem() { - const path = ['顶级'] const findPath = (data, level) => { @@ -91,14 +95,16 @@ function renderBreadcrumbItem() { } export default class QueryTreeLayout extends Component { - state = { loading: false, dataSource: [], expandedKeys: [], selectedKey: '', autoExpandParent: true, - searchValue: '' + searchValue: '', + + collapsed: false, + showSider: false, } list = [] @@ -108,16 +114,18 @@ export default class QueryTreeLayout extends Component { replaceFields = { value: 'id', title: 'title', - children: 'children' + 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.loadData = + typeof this.props.loadData === 'function' ? this.props.loadData : async () => {} - this.defaultExpanded = typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false + this.defaultExpanded = + typeof this.props.defaultExpanded === 'boolean' ? this.props.defaultExpanded : false if (this.props.replaceFields) { this.replaceFields = this.props.replaceFields @@ -131,6 +139,13 @@ export default class QueryTreeLayout extends Component { if (this.autoLoad) { this.onLoadData() } + + window.addEventListener('resize', this.onResizeLayout) + this.onResizeLayout() + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeLayout) } /** @@ -151,7 +166,7 @@ export default class QueryTreeLayout extends Component { } this.setState({ dataSource: data, - expandedKeys + expandedKeys, }) this.onLoaded() } @@ -162,8 +177,8 @@ export default class QueryTreeLayout extends Component { onLoading = () => { this.setState({ loading: { - indicator: - } + indicator: , + }, }) } @@ -178,28 +193,28 @@ export default class QueryTreeLayout extends Component { this.onLoadData() } - onExpand = (expandedKeys) => { + onExpand = expandedKeys => { this.setState({ expandedKeys, - autoExpandParent: false + autoExpandParent: false, }) } - onSelect = (selectedKeys) => { + 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] + selectedKey: selectedIds[0], }) if (this.props.onSelect) { this.props.onSelect(selectedIds[0]) } } - onSearch = (value) => { + onSearch = value => { const expandedKeys = this.list .map(p => { if (p[this.replaceFields.title].indexOf(value) > -1) { @@ -212,34 +227,49 @@ export default class QueryTreeLayout extends Component { this.setState({ expandedKeys, autoExpandParent: !!value, - searchValue: value + searchValue: value, + }) + } + + onResizeLayout = () => { + this.setState({ + collapsed: window.innerWidth <= SIDER_BREAK_POINT, }) } render() { - - const { dataSource, expandedKeys, autoExpandParent } = this.state + const { loading, dataSource, expandedKeys, autoExpandParent, collapsed, showSider } = + this.state const props = { treeData: dataSource, expandedKeys, - autoExpandParent + autoExpandParent, } const on = { - onExpand: (expandedKeys) => this.onExpand(expandedKeys), - onSelect: (selectedKeys) => this.onSelect(selectedKeys) + onExpand: expandedKeys => this.onExpand(expandedKeys), + onSelect: selectedKeys => this.onSelect(selectedKeys), } return ( - + p) + .join(' ')} + onMouseLeave={() => this.setState({ showSider: false })} + >
this.onSearch(value)} + onSearch={value => this.onSearch(value)} />
@@ -248,37 +278,56 @@ export default class QueryTreeLayout extends Component { this.onReloadData()} /> - this.setState({ expandedKeys: [] })} /> + this.setState({ expandedKeys: [] })} + />
- } wrapperClassName="h-100-p"> - { - !this.state.loading && !this.state.dataSource.length ? - - -

暂无数据

-
- } - description={false} - /> - : - renderTitle.call(this, nodeData)} - /> - } + } + wrapperClassName="h-100-p" + > + {!loading && !dataSource.length ? ( + + +

暂无数据

+ + } + description={false} + /> + ) : ( + renderTitle.call(this, nodeData)} + /> + )}
- - {renderBreadcrumbItem.call(this)} - + + {collapsed && ( + + - - - + // react在这里会对该组件不存在的props抛出异常 + ;['action', 'onSuccess', 'onOk', 'confirmLoading'].forEach(p => { + delete props[p] + }) + + return ( + on.onClose()}> +
{childWithProps}
+
+ + +
+
+ ) } export default class ModalForm extends Component { - state = { // 弹窗显示/隐藏 visible: false, // 提交状态 - confirmLoading: false + confirmLoading: false, } // 子元素实例 @@ -67,12 +73,11 @@ export default class ModalForm extends Component { snapshot = null // 完成操作 - action = async () => { } + action = async () => {} // 是否在关闭时校验数据改变 compareOnClose = true - constructor(props) { super(props) @@ -93,7 +98,7 @@ export default class ModalForm extends Component { /** * 打开弹窗 - * @param {*} data + * @param {*} data */ open = (data = {}) => { this.data = data @@ -110,7 +115,7 @@ export default class ModalForm extends Component { /** * 子元素创建后回调 * 对子元素数据进行填充,(如需关闭时对比)之后再获取结构调整后的数据快照 - * @returns + * @returns */ onCreated = async () => { const body = this.childNode.current @@ -126,7 +131,7 @@ export default class ModalForm extends Component { /** * 取消编辑 * (如需关闭时对比)获取当前数据结构与快照对比 - * @returns + * @returns */ onClose = async () => { const body = this.childNode.current @@ -143,7 +148,7 @@ export default class ModalForm extends Component { content: '当前内容已更改,是否确认不保存并且关闭', onOk: () => { this.close() - } + }, }) return } @@ -155,7 +160,7 @@ export default class ModalForm extends Component { /** * 完成编辑 * 校验并获取结构调整后的数据,调用this.action进行操作 - * @returns + * @returns */ onOk = async () => { const body = this.childNode.current @@ -175,39 +180,33 @@ export default class ModalForm extends Component { this.props.onSuccess(postData) } } - } finally { this.setState({ confirmLoading: false }) } } render() { - const props = { ...this.props, visible: this.state.visible, destroyOnClose: true, - confirmLoading: this.state.confirmLoading + confirmLoading: this.state.confirmLoading, } const on = { - onOk: () => this.onOk() + onOk: () => this.onOk(), } - const childWithProps = React.cloneElement( - React.Children.only(this.props.children), - { - created: childNode => { - this.childNode.current = childNode - this.onCreated() - } - } - ) + const childWithProps = React.cloneElement(React.Children.only(this.props.children), { + created: childNode => { + this.childNode.current = childNode + this.onCreated() + }, + }) - return this.type === 'modal' ? - renderModal.call(this, props, on, childWithProps) - : - renderDrawer.call(this, props, on, childWithProps) + return this.type === 'modal' + ? renderModal.call(this, props, on, childWithProps) + : renderDrawer.call(this, props, on, childWithProps) } } diff --git a/web-react/src/pages/system/menu/form.jsx b/web-react/src/pages/system/menu/form.jsx index 83f9b8f..bbc7dea 100644 --- a/web-react/src/pages/system/menu/form.jsx +++ b/web-react/src/pages/system/menu/form.jsx @@ -7,29 +7,31 @@ import { api } from 'common/api' import { EMPTY_ID } from 'util/global' const initialValues = { - type: '1', - openType: '1', + type: 1, + openType: 1, + weight: '2', visible: true, - sort: 100 + sort: 100, } export default class form extends Component { - state = { // 加载状态 loading: true, codes: { menuType: [], - openType: [] + openType: [], + menuWeight: [], }, options: { appList: [], - parentTreeData: [] + parentTreeData: [], }, + addType: [], type: initialValues.type, openType: initialValues.openType, - icon: '' + icon: '', } // 表单实例 @@ -51,45 +53,49 @@ export default class form extends Component { * 填充数据 * 可以在设置this.record之后对其作出数据结构调整 * [异步,必要] - * @param {*} params + * @param {*} params */ async fillData(params) { + const form = this.form.current this.record = cloneDeep(params.record) //#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() let parentTreeData = [] if (params.isParent) { parentTreeData = await this.onLoadMenuTree(params.parent.application) - } else if (params.record) { + } + + if (params.record) { 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 this.setState({ - codes: { - menuType, - openType - }, + codes, options: { appList, - parentTreeData + parentTreeData, }, - icon + icon, }) //#endregion - const form = this.form.current if (params.isParent) { form.setFieldsValue({ pid: params.parent.id, - application: params.parent.application + application: params.parent.application, }) } else { form.setFieldsValue(this.record) } - this.setState({ - loading: false - }) + this.setState({ loading: false }) this.onTypeChange() } @@ -98,7 +104,7 @@ export default class form extends Component { * 获取数据 * 可以对postData进行数据结构调整 * [异步,必要] - * @returns + * @returns */ async getData() { const form = this.form.current @@ -123,30 +129,31 @@ export default class form extends Component { async onLoadMenuTree(application) { const { data } = await api.getMenuTree({ application }) - return [{ - id: EMPTY_ID, - parentId: undefined, - title: '顶级', - value: EMPTY_ID, - pid: undefined, - children: data, - }] + return [ + { + id: EMPTY_ID, + parentId: undefined, + title: '顶级', + value: EMPTY_ID, + pid: undefined, + children: data, + }, + ] } - onTypeChange() { this.onTypeChangeGroup() - const form = this.form.current - const { type } = form.getFieldsValue() - if (['0', '2'].includes(type)) { - form.setFieldsValue({ - openType: '0' - }) - } else { - form.setFieldsValue({ - openType: '1' - }) - } + // const form = this.form.current + // const { type } = form.getFieldsValue() + // if ([0, 2].includes(type)) { + // form.setFieldsValue({ + // openType: 0, + // }) + // } else { + // form.setFieldsValue({ + // openType: 1, + // }) + // } } onOpenTypeChange() { @@ -168,178 +175,221 @@ export default class form extends Component { this.setState({ type, - openType + openType, }) } async onApplicationChange(value) { this.setState({ - loading: true + loading: true, }) const parentTreeData = await this.onLoadMenuTree(value) this.setState({ loading: false, options: { ...this.state.options, - parentTreeData - } + parentTreeData, + }, }) this.form.current.setFieldsValue({ - pid: undefined + pid: undefined, }) } onSelectIcon(icon) { this.form.current.setFieldsValue({ - icon + icon, }) this.setState({ icon }) } //#endregion render() { + const { loading, codes, options, addType, type, openType, icon } = this.state + return ( -
- }> + + }>

基本信息

- 目录:默认添加在顶级 -
菜单: -
按钮: + 目录:一级菜单,默认添加在顶级 +
+ 菜单:二级菜单 +
+ 按钮:菜单中对应到接口的功能 } rules={[{ required: true, message: '请选择菜单类型' }]} > - this.onTypeChange(e)}> - { - this.state.codes.menuType.map(item => { - return ( - {item.value} - ) - }) - } + this.onTypeChange(e)}> + {codes.menuType.map(item => { + return ( + + {item.value} + + ) + })}
- + - - this.onApplicationChange(value)} + > + {options.appList.map(item => { + return ( + + {item.name} + + ) + })} - { - this.state.type != 0 && - + {type != 0 && ( + - } + )} + + 系统权重:菜单/功能任何角色可用 +
+ 业务权重:菜单/功能为超级管理员不可用,可防止管理员误操作 + + } + rules={[{ required: true, message: '请选择权重' }]} + > + + {codes.menuWeight.map(item => { + return ( + + {item.value} + + ) + })} + +

扩展信息

- { - this.state.type == 1 && + {type == 1 && ( - this.onOpenTypeChange(e)}> - { - this.state.codes.openType.map(item => { - return ( - {item.value} - ) - }) - } + this.onOpenTypeChange(e)}> + {codes.openType.map(item => { + return ( + + {item.value} + + ) + })} - } - { - this.state.type == 1 && this.state.openType == 1 && - + )} + {type == 1 && openType == 1 && ( + - } - { - this.state.type == 1 && this.state.openType == 2 && - + )} + {type == 1 && openType == 2 && ( + - } - { - this.state.type == 1 && this.state.openType == 3 && - + )} + {type == 1 && openType == 3 && ( + - } - { - this.state.type == 2 && - + )} + {type == 2 && ( + - } - { - this.state.type == 2 && - + )} + {type == 2 && ( + - } + )} - { - this.state.type != 2 && + {type != 2 && ( - } + addonBefore={icon && } addonAfter={ - this - .iconSelector - .current - .open(this.form.current.getFieldValue('icon')) + this.iconSelector.current.open( + this.form.current.getFieldValue('icon') + ) } /> } /> - } + )} - this.onSelectIcon(icon)} /> + this.onSelectIcon(icon)} /> ) } diff --git a/web-react/src/pages/system/menu/index.jsx b/web-react/src/pages/system/menu/index.jsx index 27eaaf9..a7566f3 100644 --- a/web-react/src/pages/system/menu/index.jsx +++ b/web-react/src/pages/system/menu/index.jsx @@ -1,5 +1,5 @@ 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 { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' import { api } from 'common/api' @@ -24,6 +24,7 @@ export default class index extends Component { codes: { menuType: [], menuWeight: [], + openType: [], }, } @@ -55,9 +56,36 @@ export default class index extends Component { render: text => text && , }, { - title: '前端组件', + title: '连接', width: 220, - dataIndex: 'component', + dataIndex: 'openType', + render: (text, record) => { + switch (text) { + case 1: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.component} + + ) + case 2: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.link} + + ) + case 3: + return ( + <> + {this.bindCodeValue(text, 'open_type')}{' '} + {record.redirect} + + ) + default: + return '' + } + }, }, { title: '排序', @@ -83,7 +111,9 @@ export default class index extends Component { render: (text, record) => ( - this.onOpen(this.editForm, record)}>编辑 + this.onOpen({ modal: this.editForm, record })}> + 编辑 + {record.type < 2 && ( - this.onOpen(this.addForm, record, true)}> + + this.onOpen({ + modal: this.addForm, + record, + isParent: true, + addType: record.type == 0 ? [1] : [2], + }) + } + > {record.type == 0 ? '新增子菜单' : '新增功能'} @@ -125,7 +164,7 @@ export default class index extends Component { */ componentDidMount() { this.table.current.onLoading() - getDictData('menu_type', 'menu_weight').then(res => { + getDictData('menu_type', 'menu_weight', 'open_type').then(res => { this.setState( { codes: res, @@ -162,7 +201,7 @@ export default class index extends Component { name = toCamelCase(name) const codes = this.state.codes[name] if (codes) { - const c = codes.find(p => p.code === code) + const c = codes.find(p => p.code == code) if (c) { return c.value } @@ -175,15 +214,17 @@ export default class index extends Component { * @param {*} modal * @param {*} record */ - onOpen(modal, record, isParent = false) { + onOpen({ modal, record, isParent = false, addType = [] }) { const params = isParent ? { parent: record, isParent, + addType, } : { record, isParent, + addType, } modal.current.open(params) @@ -246,7 +287,9 @@ export default class index extends Component { this.onOpen(this.editForm, item)} + onClick={() => + this.onOpen({ modal: this.editForm, record: item }) + } > @@ -280,9 +323,12 @@ export default class index extends Component { if (isFunction) { grids.push( this.onOpen(this.addForm, record, true)} + onClick={() => + this.onOpen({ modal: this.addForm, record, isParent: true, addType: [2] }) + } >
@@ -331,9 +377,11 @@ export default class index extends Component { } diff --git a/web-react/src/views/main/_layout/content/index.jsx b/web-react/src/views/main/_layout/content/index.jsx index 73f2ad5..e52b045 100644 --- a/web-react/src/views/main/_layout/content/index.jsx +++ b/web-react/src/views/main/_layout/content/index.jsx @@ -20,7 +20,6 @@ class ComponentDynamic extends Component { if (this.props.onRef) { this.props.onRef(this) } - 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