From a828a62d209e87fa50e5a42f0fdbc8535a4a7ca7 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: Thu, 24 Jun 2021 15:49:20 +0800 Subject: [PATCH 01/23] =?UTF-8?q?update=20=E8=8F=9C=E5=8D=95=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-react/src/assets/style/lib/table.less | 339 +++++++++--------- .../src/assets/style/lib/visibility.less | 6 + web-react/src/assets/style/public.less | 9 + .../src/components/query-table/index.jsx | 4 +- web-react/src/pages/system/menu/index.jsx | 157 ++++---- 5 files changed, 272 insertions(+), 243 deletions(-) diff --git a/web-react/src/assets/style/lib/table.less b/web-react/src/assets/style/lib/table.less index 0e748c7..2e3d24e 100644 --- a/web-react/src/assets/style/lib/table.less +++ b/web-react/src/assets/style/lib/table.less @@ -1,5 +1,4 @@ @import (reference) '../extend.less'; - .yo-query-bar { margin-bottom: @padding-xs; .ant-form-inline { @@ -8,220 +7,224 @@ } } } - .yo-action-bar { - display: flex; - justify-content: space-between; + display: flex; + justify-content: space-between; - margin-bottom: @padding-md; - - &--actions { - > .ant-btn, - > .ant-btn-group { - + .ant-btn, - + .ant-btn-group { - margin-left: @padding-xs; - } - } + margin-bottom: @padding-md; + &--actions { + >.ant-btn, + >.ant-btn-group { + +.ant-btn, + +.ant-btn-group { + margin-left: @padding-xs; + } } + } } - .ant-table-thead { - th.ant-table-column-has-sorters { - &:hover { - background-color: darken(@background-color-base, 5%); - } + th.ant-table-column-has-sorters { + &:hover { + background-color: darken(@background-color-base, 5%); } + } } - .ant-table-tbody { - > tr { - > td { - transition-property: background, border-bottom-color; - } + >tr { + >td { + transition-property: background, border-bottom-color; } - - > tr.ant-table-row:hover { - > td { - border-bottom-color: lighten(@primary-color, 30%); - } + } + >tr.ant-table-row:hover { + >td { + border-bottom-color: lighten(@primary-color, 30%); } + } } - .ant-table-small { - > .ant-table-content { - > .ant-table-body { - margin: 0; - - > table { - > .ant-table-thead { - > tr { - > th { - background-color: @table-selected-row-bg; - } - } - } + >.ant-table-content { + >.ant-table-body { + margin: 0; + >table { + >.ant-table-thead { + >tr { + >th { + background-color: @table-selected-row-bg; } + } } + } } + } } - .ant-table-thead { - > tr { - > th { - font-weight: bold; - } + >tr { + >th { + font-weight: bold; } + } } - .ant-table-sticky-scroll { display: none; } - .yo-table { - .ant-table { - margin: 0 !important; + .ant-table { + margin: 0 !important; + } + .border-right-none { + border-right-width: 0 !important; + &:last-child { + border-right-width: 1px !important; } - - .border-right-none() { - border-right-width: 0 !important; - - &:last-child { - border-right-width: 1px !important; + } + .ant-table-content { + .ant-table-body { + overflow-x: auto !important; + >table { + >.ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } } + >.ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } } - - .ant-table-content { - .ant-table-body { - overflow-x: auto !important; - - > table { - > .ant-table-thead { - > tr { - > th { - .border-right-none(); - } - } - } - - > .ant-table-tbody { - > tr { - > td { - .border-right-none(); - } - } - } - } + .ant-table-fixed-left { + .ant-table-thead { + >tr { + >th { + border-right-width: 0 !important; + } } - - .ant-table-fixed-left { - .ant-table-thead { - > tr { - > th { - border-right-width: 0 !important; - } - } - } - - .ant-table-tbody { - > tr { - > td { - border-right-width: 0 !important; - } - } - } - } - - .ant-table-fixed-right { - .ant-table-fixed { - border-left-width: 0 !important; - } - - .ant-table-thead { - > tr { - > th { - .border-right-none(); - } - } - } - - .ant-table-tbody { - > tr { - > td { - .border-right-none(); - } - } - } + } + .ant-table-tbody { + >tr { + >td { + border-right-width: 0 !important; + } } + } } - + .ant-table-fixed-right { + .ant-table-fixed { + border-left-width: 0 !important; + } + .ant-table-thead { + >tr { + >th { + .border-right-none(); + } + } + } + .ant-table-tbody { + >tr { + >td { + .border-right-none(); + } + } + } + } + } .ant-table-bordered { >.ant-table-container { border-top: @border-width-base @border-style-base @table-border-color; } } - &--row-no { background-color: @table-header-bg; } } - .yo-table-actions { - display: inline-block; + display: inline-block; - vertical-align: middle; + vertical-align: middle; + &--inner { + display: flex; + align-items: center; - &--inner { - display: flex; - align-items: center; - - height: 18px; - } + height: 18px; + } } - .yo-table--column-setting { - width: 240px; + width: 240px; + .ant-dropdown-menu-item { + display: flex; + align-items: center; + justify-content: space-between; + } + .anticon-pushpin { + transition: @animation-duration-slow; + transform: rotate(45deg); - .ant-dropdown-menu-item { - display: flex; - justify-content: space-between; - align-items: center; - } - - .anticon-pushpin { - color: darken(@white, 40%); - transition: @animation-duration-slow; - transform: rotate(45deg); - } - - .yo-table--fixed { - transform: rotate(-45deg); - } + color: darken(@white, 40%); + } + .yo-table--fixed { + transform: rotate(-45deg); + } } +.yo-menu-table { + .ant-table { + .ant-table-expand-icon-col { + width: 28px; + } + .ant-table-row-expand-icon-cell { + z-index: 1; -.yo-inner-table { - .ant-table { - .ant-table-tbody { - > .ant-table-expanded-row-level-1 > td { - padding: 0; - border-right: none !important; - .ant-table-wrapper { - border: none; - .ant-table { - margin: 0 !important; - } - .ant-table-container { - border: none; - .ant-table-cell:first-child { - padding-left: 25px; - } - .ant-table-expanded-row-level-1 > td { - border-right: @border-width-base @border-style-base @table-border-color !important; - padding: @padding-sm @padding-xs @padding-sm @padding-xl; - } - } - } + padding-right: 0 !important; + + border-right: none !important; + +.ant-table-cell { + padding-left: 0; + } + } + .ant-table-tbody { + >.ant-table-expanded-row-level-1>td { + padding: 0; + + border-right: none !important; + .ant-table-wrapper { + border: none; + .ant-table { + margin: 0 !important; + } + .ant-table-container { + border: none; + .ant-table-row-expand-icon-cell { + .ant-table-row-expand-icon { + left: @padding-md; + } + +.ant-table-cell { + padding-left: @padding-md; + } } + .ant-table-expanded-row-level-1>td { + padding: @padding-sm @padding-xs @padding-sm @padding-xl; + + border-right: @border-width-base @border-style-base @table-border-color !important; + .ant-card { + max-width: fit-content; + margin-bottom: 0; + + background: none; + .ant-card-grid { + width: 300px; + padding: @padding-xs @padding-sm; + + background-color: @white; + } + } + } + } } + } } + } } diff --git a/web-react/src/assets/style/lib/visibility.less b/web-react/src/assets/style/lib/visibility.less index 85c9124..a782155 100644 --- a/web-react/src/assets/style/lib/visibility.less +++ b/web-react/src/assets/style/lib/visibility.less @@ -20,3 +20,9 @@ .flex { display: flex; } +.ellipsis { + overflow: hidden; + + white-space: nowrap; + text-overflow: ellipsis; +} diff --git a/web-react/src/assets/style/public.less b/web-react/src/assets/style/public.less index d935b12..7714511 100644 --- a/web-react/src/assets/style/public.less +++ b/web-react/src/assets/style/public.less @@ -33,3 +33,12 @@ background: url('~assets/image/adorn/house-top-01.png') no-repeat bottom right; } } +a.link-gray { + color: fade(@black, 50%); + &:hover { + color: @link-hover-color; + } + &:active { + color: @link-active-color; + } +} diff --git a/web-react/src/components/query-table/index.jsx b/web-react/src/components/query-table/index.jsx index d0028bf..62dc0bd 100644 --- a/web-react/src/components/query-table/index.jsx +++ b/web-react/src/components/query-table/index.jsx @@ -277,7 +277,7 @@ export default class QueryTable extends Component { const { loading, dataSource, type } = this.state - const { query, operator, columns } = this.props + const { query, operator, columns, expandable, expandedRowRender } = this.props const attrs = {} Object.keys(this.props).forEach(key => { @@ -292,7 +292,7 @@ export default class QueryTable extends Component { dataSource, columns: (() => { const c = [] - if (type !== 'tree' && rowNumber) { + if (type !== 'tree' && rowNumber & !expandable & !expandedRowRender) { c.push(rowNoColumn) } c.push(...(columns || [])) diff --git a/web-react/src/pages/system/menu/index.jsx b/web-react/src/pages/system/menu/index.jsx index c4f44d8..27eaaf9 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, Form, Input, Popconfirm, message as Message, Tag } from 'antd' +import { Button, Table, Card, Popconfirm, message as Message, Row, Col, Tooltip } from 'antd' import { isEqual } from 'lodash' import { AntIcon, Auth, Container, ModalForm, QueryTable, QueryTableActions } from 'components' import { api } from 'common/api' @@ -58,13 +58,6 @@ export default class index extends Component { title: '前端组件', width: 220, dataIndex: 'component', - ellipsis: true, - }, - { - title: '权限标识', - width: 220, - dataIndex: 'permission', - ellipsis: true, }, { title: '排序', @@ -85,17 +78,10 @@ export default class index extends Component { if (flag) { this.columns.push({ title: '操作', - width: 150, + width: 220, dataIndex: 'actions', render: (text, record) => ( - {record.type < 2 && ( - - this.onOpen(this.addForm, record, true)}> - 新增子菜单 - - - )} this.onOpen(this.editForm, record)}>编辑 @@ -108,6 +94,13 @@ export default class index extends Component { 删除 + {record.type < 2 && ( + + this.onOpen(this.addForm, record, true)}> + {record.type == 0 ? '新增子菜单' : '新增功能'} + + + )} ), }) @@ -156,27 +149,8 @@ export default class index extends Component { ...params, ...query, }) - return this.onfilterdata(data) - //return data - } - - onfilterdata(data) { - this.findlastChildren(data) return data } - findlastChildren(data) { - data.forEach(s => { - if (s.children && s.children.length > 0) { - s.rowExpandable = true - s.children.forEach(p => { - if (p.children && p.children.length === 0) { - p.rowExpandable = false - } - }) - this.findlastChildren(s.children) - } - }) - } /** * 绑定字典数据 @@ -240,64 +214,101 @@ export default class index extends Component { this.onAction(apiAction.delete(record), '删除成功') } + //#region 自定义方法 /** * 绘制新的扩展行 * @param {*} record */ onRowRender(record) { - var arr = [] - var istag = false - record.children.map(s => { - if (!s.rowExpandable && s.type == 2) { - istag = true - var temp = ( - - {s.name} |{' '} - - this.onOpen(this.editForm, s)}>编辑 - {' '} - |{' '} - - this.onDelete(s)} - > - 删除 - - - + const grids = [] + let isFunction = false + record.children.map(item => { + if (!(item.children && item.children.length) && item.type == 2) { + isFunction = true + const grid = ( + +

+ {item.visibleParent && ( + + + + )} + {item.name} +

+ {item.permission} +
+ + + + this.onOpen(this.editForm, item)} + > + + + + + + this.onDelete(item)} + > + + + + + + + + +
+
) - arr.push(temp) - } else if (s.rowExpandable || (!s.rowExpandable && s.type == 1)) { - arr.push(s) + grids.push(grid) + } else if ( + (item.children && item.children.length) || + (!(item.children && item.children.length) && item.type == 1) + ) { + grids.push(item) } }) - if (istag) { - return arr + if (isFunction) { + grids.push( + this.onOpen(this.addForm, record, true)} + > +
+ +
+ 新增功能 +
+ ) + return {grids} } else { return ( record.id} expandable={{ expandedRowRender: record => this.onRowRender(record), - rowExpandable: record => record.rowExpandable === true, + rowExpandable: record => record.children && record.children.length, }} - childrenColumnName="11" + childrenColumnName="none" bordered={true} pagination={false} /> ) } } - - //#region 自定义方法 - async onSetDefault(record) { - this.onAction(apiAction.setDefault(record), '设置成功') - } //#endregion render() { @@ -306,15 +317,15 @@ export default class index extends Component {
this.onRowRender(record), - rowExpandable: record => record.rowExpandable === true, + rowExpandable: record => record.children && record.children.length, }} operator={ From 64bb1c3352f7c824200e408356397620eb790084 Mon Sep 17 00:00:00 2001 From: ky_yusj <2655568377@qq.com> Date: Thu, 24 Jun 2021 16:22:37 +0800 Subject: [PATCH 02/23] =?UTF-8?q?fix=20=E5=8C=BA=E5=9F=9F=E6=A0=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HouseProjectInfoService.cs | 1 + .../Service/Area/AreaCodeService.cs | 10 +++++++-- .../src/components/query-table/index.jsx | 5 +++++ .../src/pages/business/house/project/form.jsx | 4 +++- .../pages/business/house/project/index.jsx | 22 ++++++++++++++++++- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs index 3efac64..60048d3 100644 --- a/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs +++ b/Api/Ewide.Application/Service/HouseSafety/HouseProjectInfo/HouseProjectInfoService.cs @@ -88,6 +88,7 @@ namespace Ewide.Application.Service.HouseProjectInfo var areaCodeRep = Db.GetRepository(); var projects = await _houseProjectInfoRep.DetachedEntities .Join(areaCodeRep.DetachedEntities, p => p.AreaCode, a => a.Code, (p, a) => new { p, AreaName = a.Name }) + .Where(input.Type>0, x => x.p.Type == input.Type) .Where(!string.IsNullOrEmpty(input.Name), x => x.p.Name.Contains(input.Name)) .Where(!string.IsNullOrEmpty(input.Note), x => x.p.Note.Contains(input.Note)) .Where(!string.IsNullOrEmpty(input.AreaCode), x => x.p.AreaCode == input.AreaCode) diff --git a/Api/Ewide.Core/Service/Area/AreaCodeService.cs b/Api/Ewide.Core/Service/Area/AreaCodeService.cs index 66bf906..337431e 100644 --- a/Api/Ewide.Core/Service/Area/AreaCodeService.cs +++ b/Api/Ewide.Core/Service/Area/AreaCodeService.cs @@ -93,11 +93,17 @@ namespace Ewide.Core.Service.Area #endif } var query = cachedAreaCodes.Where(p => p.LevelType <= level); + var resAreaCode = new List(); if (areaCodeList != null) { - query = query.Where(p => areaCodeList.Contains(p.Code)); + foreach (var code in areaCodeList) + { + var queryRes = query.Where(p => p.Code.StartsWith(code)); + resAreaCode.AddRange(queryRes); + } + cachedAreaCodes = resAreaCode.Distinct().ToList(); } - cachedAreaCodes = query.ToList(); + return new TreeBuildUtil().DoTreeBuild(cachedAreaCodes.Select(u => new AreaTreeNode { Code = u.Code, diff --git a/web-react/src/components/query-table/index.jsx b/web-react/src/components/query-table/index.jsx index 62dc0bd..01a3b19 100644 --- a/web-react/src/components/query-table/index.jsx +++ b/web-react/src/components/query-table/index.jsx @@ -1,6 +1,7 @@ import React, { Component } from 'react' import { Form, Button, Table, Tooltip } from 'antd' import { AntIcon } from 'components' +import { isEqual } from 'lodash' const propsMap = ['columns', 'autoLoad', 'loadData', 'pageIndex', 'pageSize'] @@ -157,6 +158,10 @@ export default class QueryTable extends Component { onLoadData = async () => { this.onLoading() + if (isEqual(this.query, {})) { + this.query = this.props.queryInitialValues + } + const res = await this.loadData( { pageIndex: this.pagination.current, diff --git a/web-react/src/pages/business/house/project/form.jsx b/web-react/src/pages/business/house/project/form.jsx index 750743b..31a7ea1 100644 --- a/web-react/src/pages/business/house/project/form.jsx +++ b/web-react/src/pages/business/house/project/form.jsx @@ -87,7 +87,8 @@ export default class form extends Component { this.record = { pid: params.pid, ...this.record, - areaCode: areaCode.length == 4 ? areaCode : [], + areaCode: + areaCode.length > 0 && areaCode[areaCode.length - 1].length == 12 ? areaCode : [], } //#endregion @@ -213,6 +214,7 @@ export default class form extends Component { rules={[{ required: true, message: '请选择所属区域' }]} > { + if (values.hasOwnProperty('type')) { + this.setState({ type: values.type }) + } + }} query={ + + + 全部 + + 住宅 + + + 非住宅 + + + From 5bf0cb5a126f515e30d86f9980a411f250d11836 Mon Sep 17 00:00:00 2001 From: ky_yusj <2655568377@qq.com> Date: Thu, 24 Jun 2021 18:15:58 +0800 Subject: [PATCH 03/23] update housecode --- .../HouseCode/Dto/HouseCodeOutput.cs | 1 + .../pages/business/house/code/form/index.jsx | 2 + .../pages/business/house/code/form/part.jsx | 66 +++++++++++++------ .../src/pages/business/house/code/index.jsx | 2 +- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs index 0316d8b..de2cda4 100644 --- a/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs +++ b/Api/Ewide.Application/Service/HouseSafety/HouseCode/Dto/HouseCodeOutput.cs @@ -29,6 +29,7 @@ namespace Ewide.Application public class GetHouseCodeOutput { public string Id { get; set; } + public string HouseCode { get; set; } public int Type { get; set; } public int Industry { get; set; } public string AreaCode { get; set; } diff --git a/web-react/src/pages/business/house/code/form/index.jsx b/web-react/src/pages/business/house/code/form/index.jsx index ddd7aa4..1583fb5 100644 --- a/web-react/src/pages/business/house/code/form/index.jsx +++ b/web-react/src/pages/business/house/code/form/index.jsx @@ -36,6 +36,8 @@ export default class index extends Component { loading: false, }) }) + } else { + this.setState({ loading: false }) } } diff --git a/web-react/src/pages/business/house/code/form/part.jsx b/web-react/src/pages/business/house/code/form/part.jsx index 2494ff3..e7ddb17 100644 --- a/web-react/src/pages/business/house/code/form/part.jsx +++ b/web-react/src/pages/business/house/code/form/part.jsx @@ -19,6 +19,7 @@ import { cloneDeep, isEqual } from 'lodash' import getDictData from 'util/dic' import { api } from 'common/api' import { CITY } from 'util/global' +import auth from 'components/authorized/handler' const initialValues = { type: 1, @@ -85,6 +86,8 @@ export default class form extends Component { //#region 从后端转换成前段所需格式 await this.initMap() const options = { ...this.state.options } + const { data: areaTree } = await api.getAreaTree() + options.areaTree = areaTree // 有数据 if (this.record) { const { type, areaCode } = this.record @@ -95,6 +98,16 @@ export default class form extends Component { areaCode.substr(0, 9), areaCode, ] + switch (areaTree[0].adCode.length) { + case 6: + this.record.areaCode = this.record.areaCode.slice(1) + break + case 9: + this.record.areaCode = this.record.areaCode.slice(2) + break + default: + break + } // 获取项目和片区列表 const data = await this.getProjectsAndZones({ areaCode: this.record.areaCode, @@ -107,12 +120,12 @@ export default class form extends Component { this.map.setCenter(position) } const codes = await getDictData('house_type', 'house_industry') - const { data: areaTree } = await api.getAreaTree() - options.areaTree = areaTree + this.setState({ codes, options, }) + this.showHouseCode() //#endregion this.form.current.setFieldsValue(this.record) @@ -136,7 +149,7 @@ export default class form extends Component { const postData = form.getFieldsValue() //#region 从前段转换后端所需格式 if (postData.areaCode) { - postData.areaCode = postData.areaCode[3] + postData.areaCode = postData.areaCode[postData.areaCode.length - 1] } //#endregion return postData @@ -282,21 +295,22 @@ export default class form extends Component { type = value.type } - if (!(areaCode && areaCode.length === 4)) return null + if (!(areaCode && areaCode.length > 0 && areaCode[areaCode.length - 1].length === 12)) + return null try { const result = {} if (mode.includes('projects')) { const { data: projects } = await api.houseProjectList({ - areaCode: areaCode[3], + areaCode: areaCode[areaCode.length - 1], type, }) result.projects = projects } if (mode.includes('zones')) { const { data: zones } = await api.houseZoneList({ - areaCode: areaCode[3], + areaCode: areaCode[areaCode.length - 1], }) result.zones = zones } @@ -323,8 +337,14 @@ export default class form extends Component { }) } else if (values) { const { type, industry, areaCode, projectId, no } = values - if (areaCode && areaCode.length === 4 && projectId && no) { - let houseCode = areaCode[3] + if ( + areaCode && + areaCode.length > 0 && + areaCode[areaCode.length - 1].length === 12 && + projectId && + no + ) { + let houseCode = areaCode[areaCode.length - 1] const projectSort = this.state.options.projects.find(p => p.id === projectId).sort houseCode += projectSort.toString().padStart(3, '0') houseCode += no.toString().padStart(3, '0') @@ -368,17 +388,20 @@ export default class form extends Component { // 所属区域 else if (changedValues.hasOwnProperty('areaCode')) { - this.setState({ loading: true }) - const data = await this.getProjectsAndZones() - form.setFieldsValue({ projectId: undefined, zoneId: undefined }) - this.setState({ - options: { - ...this.state.options, - ...data, - }, - loading: false, - }) - this.showHouseCode(form.getFieldsValue()) + const flag = auth({ houseCode: 'getNextNoByCode' }) + if (flag) { + this.setState({ loading: true }) + const data = await this.getProjectsAndZones() + form.setFieldsValue({ projectId: undefined, zoneId: undefined }) + this.setState({ + options: { + ...this.state.options, + ...data, + }, + loading: false, + }) + this.showHouseCode(form.getFieldsValue()) + } } // 所属项目 @@ -474,7 +497,10 @@ export default class form extends Component { Date: Fri, 25 Jun 2021 11:37:43 +0800 Subject: [PATCH 04/23] =?UTF-8?q?update=20=E9=80=89=E6=88=BF=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/business/house/member/form.jsx | 252 +++++++++++++++++- .../src/pages/business/house/member/index.jsx | 2 +- 2 files changed, 250 insertions(+), 4 deletions(-) diff --git a/web-react/src/pages/business/house/member/form.jsx b/web-react/src/pages/business/house/member/form.jsx index 70ee907..1857200 100644 --- a/web-react/src/pages/business/house/member/form.jsx +++ b/web-react/src/pages/business/house/member/form.jsx @@ -1,11 +1,257 @@ import React, { Component } from 'react' +import { Form, Input, DatePicker, Select, Radio, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' +import moment from 'moment' + +const initialValues = { + sex: 0, + sysEmpParam: {}, +} export default class form extends Component { + state = { + // 加载状态 + loading: true, + lockRole: false, + options: { + orgData: [], + roleData: [], + }, + } + + // 表单实例 + 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 roleData = await this.LoadRoleData() + + // 日期特殊处理 + if (this.record.birthday) { + this.record.birthday = moment(this.record.birthday) + } + + // 提交的时候是"param",而获取下来却是"info",在这里转换一下 + if (this.record.sysEmpInfo) { + this.record.sysEmpParam = this.record.sysEmpInfo + delete this.record.sysEmpInfo + } else if (!this.record.sysEmpParam) { + this.record.sysEmpParam = { + extIds: [], + } + } + + if (params.orgId) { + this.record.sysEmpParam.orgId = params.orgId + } + const defaultRole = params.record + ? await this.loadOwnRole(params.record.id) + : params.orgId + ? await this.loadDefaultRole(params.orgId) + : [] + + if (defaultRole.constructor === Array) { + this.record.roleId = defaultRole.length > 0 ? defaultRole[0] : '' + this.setState({ + lockRole: true, + }) + } else { + this.record.roleId = defaultRole.id + this.setState({ + lockRole: defaultRole.code === 'zone_manager', + }) + } + + this.setState({ + options: { + ...this.state.options, + orgData, + roleData, + }, + }) + + this.record = { + ...this.record, + } + //#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 从前段转换后端所需格式 + //console.log(postData) + //#endregion + return postData + } + } + + //#region 自定义方法 + async loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async LoadRoleData() { + const { data } = await api.houseMemberDefaultRoleList() + return data + } + async loadOwnRole(id) { + const { data } = await api.houseMemberOwnRole({ id }) + return data + } + async loadDefaultRole(orgId) { + const { data } = await api.houseMemberDefaultRole({ orgId }) + return data + } render() { return ( -
- 1 -
+
+ }> +
+ + + + + + + + + + + + + {this.props.mode == 'add' && ( + <> + + + + + + + + )} + + + + + + + + + + + 保密 + + + + + + + + + + + + + + + + + + + + +
+
+ ) } } diff --git a/web-react/src/pages/business/house/member/index.jsx b/web-react/src/pages/business/house/member/index.jsx index 1b67dae..3780bdc 100644 --- a/web-react/src/pages/business/house/member/index.jsx +++ b/web-react/src/pages/business/house/member/index.jsx @@ -34,7 +34,7 @@ const apiAction = { } // 用于弹窗标题 -const name = '用户' +const name = '人员' export default class index extends Component { state = { From 87d303afb6aacaa08a94aaf7be420c470b453055 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 15:49:06 +0800 Subject: [PATCH 05/23] =?UTF-8?q?add=20=E6=9C=8D=E5=8A=A1=E7=9B=91?= =?UTF-8?q?=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Ewide.Core/Util/MachineUtil.cs | 93 +++++- web-react/src/assets/style/lib/table.less | 2 + web-react/src/pages/system/machine/base.jsx | 33 ++ .../src/pages/system/machine/disk-charts.jsx | 107 +++++++ web-react/src/pages/system/machine/index.jsx | 44 +++ .../src/pages/system/machine/use-charts.jsx | 297 ++++++++++++++++++ .../src/views/main/_layout/content/index.jsx | 7 +- 7 files changed, 569 insertions(+), 14 deletions(-) create mode 100644 web-react/src/pages/system/machine/base.jsx create mode 100644 web-react/src/pages/system/machine/disk-charts.jsx create mode 100644 web-react/src/pages/system/machine/index.jsx create mode 100644 web-react/src/pages/system/machine/use-charts.jsx diff --git a/Api/Ewide.Core/Util/MachineUtil.cs b/Api/Ewide.Core/Util/MachineUtil.cs index fdb12a5..b083552 100644 --- a/Api/Ewide.Core/Util/MachineUtil.cs +++ b/Api/Ewide.Core/Util/MachineUtil.cs @@ -1,5 +1,6 @@ using Furion.RemoteRequest.Extensions; using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -26,10 +27,8 @@ namespace Ewide.Core var ramInfo = GetRamInfo(); return new { - TotalRam = Math.Ceiling(ramInfo.Total / 1024).ToString() + " GB", // 总内存 RamRate = Math.Ceiling(100 * ramInfo.Used / ramInfo.Total), // 内存使用率 CpuRate = Math.Ceiling(double.Parse(GetCPURate())), // cpu使用率 - RunTime = GetRunTime() }; } @@ -40,20 +39,28 @@ namespace Ewide.Core public static async Task GetMachineBaseInfo() { var assemblyName = typeof(Furion.App).Assembly.GetName(); - //var networkInfo = NetworkInfo.GetNetworkInfo(); - //var (Received, Send) = networkInfo.GetInternetSpeed(1000); return new { WanIp = await GetWanIpFromPCOnline(), // 外网IP - SendAndReceived = "",// "上行" + Math.Round(networkInfo.SendLength / 1024.0 / 1024 / 1024, 2) + "GB 下行" + Math.Round(networkInfo.ReceivedLength / 1024.0 / 1024 / 1024, 2) + "GB", // 上下行流量统计 - LanIp = "",//networkInfo.AddressIpv4.ToString(), // 局域网IP + LanIp = Dns.GetHostAddresses(string.Empty).Last().ToString(), // 局域网IP IpMac = "",//networkInfo.Mac, // Mac地址 HostName = Environment.MachineName, // HostName + CurrentDirectory = Environment.CurrentDirectory, // 系统路径 SystemOs = RuntimeInformation.OSDescription, // 系统名称 OsArchitecture = Environment.OSVersion.Platform.ToString() + " " + RuntimeInformation.OSArchitecture.ToString(), // 系统架构 - ProcessorCount = Environment.ProcessorCount.ToString() + "核", // CPU核心数 + ProcessorCount = Environment.ProcessorCount, // CPU核心数 FrameworkDescription = RuntimeInformation.FrameworkDescription + " + " + assemblyName.Name.ToString() + assemblyName.Version.ToString(), // .NET和Furion版本 - NetworkSpeed = ""//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度 + NetworkSpeed = "",//"上行" + Send / 1024 + "kb/s 下行" + Received / 1024 + "kb/s" // 网络速度 + // Cpu名称 + CpuName = GetCPUName(), + // Cpu基准速度 + CpuBaseSpeed = GetCPUSpeed(), + // 内存总量 + TotalRam = GetRamInfo().Total, + // 运行时间 + RunTime = (long)(DateTime.Now - Process.GetCurrentProcess().StartTime).TotalMilliseconds, + // 磁盘信息 + DiskInfo = GetDiskInfo(), }; } @@ -94,24 +101,86 @@ namespace Ewide.Core return RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux); } + private static string GetCPUName() + { + string result; + if (IsUnix()) + { + // ?????? + var output = ShellUtil.Bash(""); + result = output.Trim(); + } + else + { + var output = ShellUtil.Cmd("wmic", "cpu get Name"); + result = output.Replace("Name", string.Empty).Trim(); + } + return result; + } + + private static string GetCPUSpeed() + { + string result; + if (IsUnix()) + { + // ?????? + var output = ShellUtil.Bash(""); + result = output.Trim(); + } + else + { + var output = ShellUtil.Cmd("wmic", "cpu get CurrentClockSpeed"); + result = output.Replace("CurrentClockSpeed", string.Empty).Trim(); + } + return result; + } + + private static List> GetDiskInfo() + { + var result = new List>(); + if (IsUnix()) + { + // ?????? + var output = ShellUtil.Bash(""); + } + else + { + var output = ShellUtil.Cmd("wmic", "LOGICALDISK get Name,Description,FileSystem,Size,FreeSpace"); + var strArray = output.Replace("\r", "").Trim().Split('\n') + .Select(p => System.Text.RegularExpressions.Regex.Replace(p.Trim(), @"\s+", ",").Split(',')); + var keyArray = strArray.First(); + var valueArray = strArray.Where((p, i) => i > 0).ToArray(); + foreach(var value in valueArray) + { + var dict = new Dictionary(); + for(var i = 0;i /// 获取CPU使用率 /// /// private static string GetCPURate() { - string cpuRate; + string result; if (IsUnix()) { var output = ShellUtil.Bash("top -b -n1 | grep \"Cpu(s)\" | awk '{print $2 + $4}'"); - cpuRate = output.Trim(); + result = output.Trim(); } else { var output = ShellUtil.Cmd("wmic", "cpu get LoadPercentage"); - cpuRate = output.Replace("LoadPercentage", string.Empty).Trim(); + result = output.Replace("LoadPercentage", string.Empty).Trim(); } - return cpuRate; + return result; } /// diff --git a/web-react/src/assets/style/lib/table.less b/web-react/src/assets/style/lib/table.less index 2e3d24e..9bde634 100644 --- a/web-react/src/assets/style/lib/table.less +++ b/web-react/src/assets/style/lib/table.less @@ -139,6 +139,8 @@ } } &--row-no { + width: 30px !important; + background-color: @table-header-bg; } } diff --git a/web-react/src/pages/system/machine/base.jsx b/web-react/src/pages/system/machine/base.jsx new file mode 100644 index 0000000..9437b73 --- /dev/null +++ b/web-react/src/pages/system/machine/base.jsx @@ -0,0 +1,33 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions } from 'antd' + +export default class base extends Component { + render() { + const { base } = this.props + + const { hostName, systemOs, wanIp, lanIp, osArchitecture, frameworkDescription } = base + + return ( + <> +
+ + + {hostName} + {systemOs} + {wanIp} + {lanIp} + {osArchitecture} + + {frameworkDescription} + + + + + + ) + } +} diff --git a/web-react/src/pages/system/machine/disk-charts.jsx b/web-react/src/pages/system/machine/disk-charts.jsx new file mode 100644 index 0000000..0024161 --- /dev/null +++ b/web-react/src/pages/system/machine/disk-charts.jsx @@ -0,0 +1,107 @@ +import React, { Component } from 'react' +import { Card, Col, Row } from 'antd' +import * as echarts from 'echarts' + +export default class diskCharts extends Component { + diskInfo = [] + + diskChart1 = [] + diskChart2 = [] + diskChart3 = [] + diskChart4 = [] + + constructor(props) { + super(props) + + const { base } = props + + this.diskInfo = base.diskInfo + } + + componentDidMount() { + this.diskInfo.forEach(({ size, freeSpace }, i) => { + const dom = this.refs[`disk-chart-${i}`] + this[`diskChart${i}`] = echarts.init(dom) + + const usedSpace = size - freeSpace + const sizeGB = (size / 1024 / 1024 / 1024).toFixed(1) + const usedGB = (usedSpace / 1024 / 1024 / 1024).toFixed(1) + const freeGB = (freeSpace / 1024 / 1024 / 1024).toFixed(1) + + const option = { + tooltip: false, + series: [ + { + name: '磁盘使用量', + type: 'pie', + radius: ['70%', '100%'], + label: { + show: true, + fontSize: '14', + position: 'center', + formatter: `共 ${sizeGB} GB`, + }, + emphasis: { + label: { + show: true, + fontSize: '14', + formatter: '{b}{c}GB({d}%)', + }, + }, + labelLine: { + show: false, + }, + data: [ + { + value: usedGB, + name: '已用', + itemStyle: { + color: usedGB / sizeGB >= 0.85 ? '#ff4d4f' : '#007bff', + }, + }, + { + value: freeGB, + name: '可用', + itemStyle: { color: '#e0e0e0' }, + }, + ], + hoverAnimation: false, + animation: false, + }, + ], + } + this[`diskChart${i}`].setOption(option) + }) + + window.addEventListener('resize', this.onResizeCharts) + } + + componentWillUnmount() { + window.removeEventListener('resize', this.onResizeCharts) + } + + onResizeCharts = () => { + this.diskInfo.forEach((item, i) => { + this[`diskChart${i}`].resize() + }) + } + + render() { + const { diskInfo } = this + + return ( + <> + {diskInfo.map((item, i) => ( + + +
+ {item.description}({item.name}) +
+
+
+ + ))} + + ) + } +} diff --git a/web-react/src/pages/system/machine/index.jsx b/web-react/src/pages/system/machine/index.jsx new file mode 100644 index 0000000..fa6a8a9 --- /dev/null +++ b/web-react/src/pages/system/machine/index.jsx @@ -0,0 +1,44 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions, Row, Statistic } from 'antd' +import { api } from 'common/api' +import { Container } from 'components' +import { isEqual } from 'lodash' +import moment from 'moment' + +import Base from './base' +import UseCharts from './use-charts' +import DiskCharts from './disk-charts' + +export default class index extends Component { + state = { + loading: true, + base: {}, + network: {}, + } + + shouldComponentUpdate(props, state) { + return !isEqual(this.state, state) || this.props.paneActived !== props.paneActived + } + + async componentDidMount() { + const { data: base } = await api.sysMachineBase() + this.setState({ loading: false, base }) + } + + render() { + const { paneActived } = this.props + + const { loading } = this.state + + return ( + +
+ + {!loading && } + {!loading && } + {!loading && } + +
+ ) + } +} diff --git a/web-react/src/pages/system/machine/use-charts.jsx b/web-react/src/pages/system/machine/use-charts.jsx new file mode 100644 index 0000000..2693159 --- /dev/null +++ b/web-react/src/pages/system/machine/use-charts.jsx @@ -0,0 +1,297 @@ +import React, { Component } from 'react' +import { Card, Col, Descriptions, Row, Statistic } from 'antd' +import * as echarts from 'echarts' +import moment from 'moment' +import { api } from 'common/api' + +export default class useCharts extends Component { + state = { + use: {}, + + nowMoment: moment(), + } + + timer = null + timerMoment = null + + systemStart = moment() + + now = Date.now() + + cpuChart = null + cpuData = [] + + ramChart = null + ramData = [] + + shouldComponentUpdate(props) { + // 当前页签未选中时停止获取状态 + if (this.props.actived !== props.actived) { + if (props.actived) { + this.start() + } else { + this.stop() + } + } + return true + } + + componentDidMount() { + this.systemStart = moment().add(-this.props.base.runTime) + this.initCpuChart() + this.initRamChart() + + this.start() + + window.addEventListener('resize', this.onResizeCharts) + } + + componentWillUnmount() { + this.stop() + + window.removeEventListener('resize', this.onResizeCharts) + } + + start() { + this.timer = setInterval(() => { + this.refreshData() + }, 3000) + this.refreshData() + this.timerMoment = setInterval(() => { + this.setState({ nowMoment: moment() }) + }, 1000) + } + + stop() { + clearInterval(this.timer) + clearInterval(this.timerMoment) + } + + async refreshData() { + const { data: use } = await api.sysMachineUse() + + this.now = Date.now() + + this.cpuData.shift() + this.cpuData.push({ + name: this.now, + value: [this.now, use.cpuRate], + }) + this.cpuChart.setOption({ + series: [{ data: this.cpuData }], + }) + + this.ramData.shift() + this.ramData.push({ + name: this.now, + value: [this.now, use.ramRate], + }) + this.ramChart.setOption({ + series: [{ data: this.ramData }], + }) + + this.setState({ use }) + } + + initCpuChart() { + for (let i = 0; i < 60; i++) { + const past = this.now - (60 - i) * 1000 + this.cpuData.push({ + name: past, + value: [past, -1], + }) + } + + const dom = this.refs['cpu-chart'] + this.cpuChart = echarts.init(dom) + const option = { + grid: { + show: true, + top: 0, + left: 0, + right: 0, + bottom: 0, + borderColor: 'rgba(0, 123, 255, 1)', + borderWidth: 2, + zlevel: 2, + }, + tooltip: false, + xAxis: { + type: 'time', + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + }, + yAxis: { + type: 'value', + max: 100, + min: 0, + axisLabel: { + show: false, + }, + }, + series: [ + { + type: 'line', + showSymbol: false, + hoverAnimation: false, + animation: false, + data: this.cpuData, + lineStyle: { + width: 1, + color: 'rgba(0, 123, 255, .8)', + }, + areaStyle: { + color: 'rgba(0, 123, 255, .3)', + }, + }, + ], + } + this.cpuChart.setOption(option) + } + + initRamChart() { + for (let i = 0; i < 60; i++) { + const past = this.now - (60 - i) * 1000 + this.ramData.push({ + name: past, + value: [past, -1], + }) + } + + const dom = this.refs['ram-chart'] + this.ramChart = echarts.init(dom) + const option = { + grid: { + show: true, + top: 0, + left: 0, + right: 0, + bottom: 0, + borderColor: 'rgba(83, 29, 171, 1)', + borderWidth: 2, + zlevel: 2, + }, + tooltip: false, + xAxis: { + type: 'time', + axisTick: { + show: false, + }, + axisLabel: { + show: false, + }, + axisLine: { + show: false, + }, + }, + yAxis: { + type: 'value', + max: 100, + min: 0, + axisLabel: { + show: false, + }, + }, + series: [ + { + type: 'line', + showSymbol: false, + hoverAnimation: false, + animation: false, + data: this.ramData, + lineStyle: { + width: 1, + color: 'rgba(83, 29, 171, .8)', + }, + areaStyle: { + color: 'rgba(83, 29, 171, .3)', + }, + }, + ], + } + this.ramChart.setOption(option) + } + + onResizeCharts = () => { + this.cpuChart.resize() + this.ramChart.resize() + } + + render() { + const { base } = this.props + const { use, nowMoment } = this.state + const { cpuName, cpuBaseSpeed, processorCount, totalRam } = base + const { cpuRate, ramRate } = use + + const diffDays = nowMoment.diff(this.systemStart, 'days') + const diff = + diffDays + ':' + moment(nowMoment.diff(this.systemStart)).utc().format('HH:mm:ss') + + return ( + <> + + + +
CPU
+
{cpuName}
+
+
+ +
+ + + + + + + {((cpuBaseSpeed || 0) / 1000).toFixed(2)} GHz + + + {processorCount || 0} + + + + + + + + + +
内存
+
{((totalRam || 0) / 1024).toFixed(1)} GB
+
+
+ +
+ + + + + + + + ) + } +} diff --git a/web-react/src/views/main/_layout/content/index.jsx b/web-react/src/views/main/_layout/content/index.jsx index f9aadc7..73f2ad5 100644 --- a/web-react/src/views/main/_layout/content/index.jsx +++ b/web-react/src/views/main/_layout/content/index.jsx @@ -113,13 +113,15 @@ export default class index extends Component { render() { this.panes = [] + const { actived } = this.state + return (
this.onChange(activeKey)} onEdit={(targetKey, action) => this.onClose(targetKey, action)} > @@ -183,7 +185,7 @@ export default class index extends Component {
this.panes.push(p)} />
From 934fc76a10bb9652ce6052f7ea4a0b574cefa933 Mon Sep 17 00:00:00 2001 From: ky_yusj <2655568377@qq.com> Date: Fri, 25 Jun 2021 17:36:15 +0800 Subject: [PATCH 06/23] =?UTF-8?q?update=20=20=E4=BA=BA=E5=91=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86/=E7=94=A8=E6=88=B7=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/business/house/member/data.jsx | 115 ++++++++++ .../src/pages/business/house/member/index.jsx | 16 ++ web-react/src/pages/system/role/data.jsx | 57 +++-- web-react/src/pages/system/user/data.jsx | 115 ++++++++++ web-react/src/pages/system/user/index.jsx | 206 +++++++++++------- web-react/src/pages/system/user/role.jsx | 99 +++++++++ 6 files changed, 504 insertions(+), 104 deletions(-) create mode 100644 web-react/src/pages/business/house/member/data.jsx create mode 100644 web-react/src/pages/system/user/data.jsx create mode 100644 web-react/src/pages/system/user/role.jsx diff --git a/web-react/src/pages/business/house/member/data.jsx b/web-react/src/pages/business/house/member/data.jsx new file mode 100644 index 0000000..7e6db71 --- /dev/null +++ b/web-react/src/pages/business/house/member/data.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class dataForm extends Component { + state = { + // 加载状态 + loading: true, + + options: { + orgData: [], + areaData: [], + orgCheckedKeys: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + const areaData = await this.loadAreaData() + const orgCheckedKeys = await this.loadMemberOwn(this.record.id) + this.setState({ + options: { + orgData, + areaData, + orgCheckedKeys, + }, + }) + this.form.current.setFieldsValue({ + id: this.record.id, + grantOrgIdList: orgCheckedKeys, + grantAreaCodeList: [], + }) + + 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 loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + return data + } + async loadMemberOwn(id) { + const { data } = await api.houseMemberOwnData({ id }) + return data + } + render() { + return ( +
+ }> +
+ + + + + + +
+
+ + ) + } +} diff --git a/web-react/src/pages/business/house/member/index.jsx b/web-react/src/pages/business/house/member/index.jsx index 3780bdc..b0d03f9 100644 --- a/web-react/src/pages/business/house/member/index.jsx +++ b/web-react/src/pages/business/house/member/index.jsx @@ -20,6 +20,7 @@ import { isEqual } from 'lodash' import getDictData from 'util/dic' import FormBody from './form' import Selector from './selector' +import DataForm from './data' // 配置页面所需接口函数 const apiAction = { @@ -31,6 +32,8 @@ const apiAction = { changeStatus: api.houseMemberChangeStatus, resetPwd: api.sysUserResetPwd, + + grantData: api.houseMemberGrantData, } // 用于弹窗标题 @@ -52,6 +55,7 @@ export default class index extends Component { // 编辑窗口实例 editForm = React.createRef() + dataForm = React.createRef() // 树选中节点 selectId = undefined @@ -200,6 +204,9 @@ export default class index extends Component { this.onResetPassword(record)}>重置密码 , + + this.onOpen(this.dataForm, record)}>授权额外数据 + , ]} > + this.list.current.onReloadData()} + > + + + +
}>
- this.onChange(value)} + > + {this.state.dataScopeType.map(item => { + return ( + + {item.value} + + ) + })} - { - this.state.isDefine && + {this.state.isDefine && ( <> - + - } + )}
diff --git a/web-react/src/pages/system/user/data.jsx b/web-react/src/pages/system/user/data.jsx new file mode 100644 index 0000000..88f6a0d --- /dev/null +++ b/web-react/src/pages/system/user/data.jsx @@ -0,0 +1,115 @@ +import React, { Component } from 'react' +import { Form, Spin, TreeSelect } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class data extends Component { + state = { + // 加载状态 + loading: true, + + options: { + orgData: [], + areaData: [], + orgCheckedKeys: [], + }, + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const orgData = await this.loadOrgData() + const areaData = await this.loadAreaData() + const orgCheckedKeys = await this.loadMemberOwn(this.record.id) + this.setState({ + options: { + orgData, + areaData, + orgCheckedKeys, + }, + }) + this.form.current.setFieldsValue({ + id: this.record.id, + grantOrgIdList: orgCheckedKeys, + grantAreaCodeList: [], + }) + + 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 loadOrgData() { + const { data } = await api.getOrgTree() + return data + } + + async loadAreaData() { + const { data } = await api.getAreaTree() + return data + } + async loadMemberOwn(id) { + const { data } = await api.sysUserOwnData({ id }) + return data + } + render() { + return ( +
+ }> +
+ + + + + + +
+
+ + ) + } +} diff --git a/web-react/src/pages/system/user/index.jsx b/web-react/src/pages/system/user/index.jsx index 24dd2c7..7418764 100644 --- a/web-react/src/pages/system/user/index.jsx +++ b/web-react/src/pages/system/user/index.jsx @@ -1,11 +1,26 @@ import React, { Component } from 'react' -import { Button, Card, Descriptions, Form, Input, List, message as Message, Popconfirm, Select, Switch } from 'antd' +import { + Button, + Card, + Descriptions, + Form, + Input, + List, + message as Message, + Popconfirm, + Select, + Switch, + Dropdown, + Menu, +} from 'antd' import { AntIcon, Auth, Container, Image, ModalForm, QueryList, QueryTreeLayout } from 'components' import { api } from 'common/api' import { toCamelCase } from 'util/format' import { isEqual } from 'lodash' import getDictData from 'util/dic' import FormBody from './form' +import RoleForm from './role' +import DataForm from './data' // 配置页面所需接口函数 const apiAction = { @@ -16,19 +31,21 @@ const apiAction = { delete: api.sysUserDelete, changeStatus: api.sysUserChangeStatus, - resetPwd: api.sysUserResetPwd + resetPwd: api.sysUserResetPwd, + + grantRole: api.sysUserGrantRole, + grantData: api.sysUserGrantData, } // 用于弹窗标题 const name = '用户' export default class index extends Component { - state = { codes: { sex: [], - commonStatus: [] - } + commonStatus: [], + }, } // 表格实例 @@ -39,6 +56,8 @@ export default class index extends Component { // 编辑窗口实例 editForm = React.createRef() + roleForm = React.createRef() + dataForm = React.createRef() // 树选中节点 selectId = undefined @@ -46,9 +65,9 @@ export default class index extends Component { * 阻止外部组件引发的渲染,提升性能 * 可自行添加渲染条件 * [必要] - * @param {*} props - * @param {*} state - * @returns + * @param {*} props + * @param {*} state + * @returns */ shouldComponentUpdate(props, state) { return !isEqual(this.state, state) @@ -61,28 +80,30 @@ export default class index extends Component { componentDidMount() { this.list.current.onLoading() getDictData('sex', 'common_status').then(res => { - this.setState({ - codes: res - }, () => { - this.list.current.onLoadData() - }) + this.setState( + { + codes: res, + }, + () => { + this.list.current.onLoadData() + } + ) }) } /** * 调用加载数据接口,可在调用前对query进行处理 * [异步,必要] - * @param {*} params - * @param {*} query - * @returns + * @param {*} params + * @param {*} query + * @returns */ loadData = async (params, query) => { - query = { ...query, sysEmpParam: { - orgId: this.selectId - } + orgId: this.selectId, + }, } const { data } = await apiAction.page({ @@ -105,7 +126,7 @@ export default class index extends Component { /** * 树节点选中事件 * [必要] - * @param {*} id + * @param {*} id */ onSelectTree(id) { this.selectId = id @@ -114,15 +135,15 @@ export default class index extends Component { /** * 绑定字典数据 - * @param {*} code - * @param {*} name - * @returns + * @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) + const c = codes.find(p => p.code == code) if (c) { return c.value } @@ -132,21 +153,21 @@ export default class index extends Component { /** * 打开新增/编辑弹窗 - * @param {*} modal - * @param {*} record + * @param {*} modal + * @param {*} record */ onOpen(modal, record) { modal.current.open({ orgId: this.selectId, - record + record, }) } /** * 对表格上的操作进行统一处理 * [异步] - * @param {*} action - * @param {*} successMessage + * @param {*} action + * @param {*} successMessage */ async onAction(action, successMessage) { this.list.current.onLoading() @@ -161,13 +182,10 @@ export default class index extends Component { /** * 删除 - * @param {*} record + * @param {*} record */ onDelete(record) { - this.onAction( - apiAction.delete(record), - '删除成功' - ) + this.onAction(apiAction.delete(record), '删除成功') } //#region 自定义方法 @@ -175,25 +193,51 @@ export default class index extends Component { return ( - this.onOpen(this.editForm, record)}>编辑 - , - - this.onDelete(record)} - > - 删除 - - , - - this.onResetPassword(record)}>重置密码 - - ] - } + actions={[ + + this.onOpen(this.editForm, record)}>编辑 + , + + this.onDelete(record)} + > + 删除 + + , + + this.onResetPassword(record)}>重置密码 + , + + + + + this.onOpen(this.roleForm, record)}> + 授权角色 + + + + + + this.onOpen(this.dataForm, record)}> + 授权额外数据 + + + + + } + > + + 授权 + + + + , + ]} > - {this.bindCodeValue(record.sex, 'sex')} + + {this.bindCodeValue(record.sex, 'sex')} + {record.phone || '未设置'} {record.email || '未设置'} @@ -228,21 +274,14 @@ export default class index extends Component {
) - } onSetUserStatus(record, checked) { - this.onAction( - apiAction.changeStatus({ id: record.id, status: +!checked }), - '设置成功' - ) + this.onAction(apiAction.changeStatus({ id: record.id, status: +!checked }), '设置成功') } onResetPassword(record) { - this.onAction( - apiAction.resetPwd(record), - '重置成功' - ) + this.onAction(apiAction.resetPwd(record), '重置成功') } //#endregion @@ -251,7 +290,7 @@ export default class index extends Component { this.onSelectTree(key)} + onSelect={key => this.onSelectTree(key)} > @@ -273,14 +312,13 @@ export default class index extends Component { placeholder="请选择状态" className="w-200" > - { - this.state.codes.commonStatus.map(item => { - return {item.value} - }) - } + {this.state.codes.commonStatus.map(item => { + return ( + + {item.value} + + ) + })} @@ -289,9 +327,11 @@ export default class index extends Component { + > + 新增{name} + } - renderItem={(record) => this.renderItem(record)} + renderItem={record => this.renderItem(record)} /> @@ -313,6 +353,24 @@ export default class index extends Component { > + + this.list.current.onReloadData()} + > + + + + this.list.current.onReloadData()} + > + + ) } diff --git a/web-react/src/pages/system/user/role.jsx b/web-react/src/pages/system/user/role.jsx new file mode 100644 index 0000000..beb472b --- /dev/null +++ b/web-react/src/pages/system/user/role.jsx @@ -0,0 +1,99 @@ +import React, { Component } from 'react' +import { Form, Spin, Select } from 'antd' +import { AntIcon } from 'components' +import { cloneDeep } from 'lodash' +import { api } from 'common/api' + +export default class role extends Component { + state = { + // 加载状态 + loading: true, + + options: { + roleData: [], + }, + roles: [], + } + + // 表单实例 + form = React.createRef() + + // 初始化数据 + record = {} + + /** + * mount后回调 + */ + componentDidMount() { + this.props.created && this.props.created(this) + } + async fillData(params) { + this.record = cloneDeep(params.record) + //#region 从后端转换成前段所需格式 + const roleData = await this.loadRoleData() + const roles = await this.loadRole(this.record.id) + this.setState({ + options: { + roleData, + }, + roles, + }) + this.form.current.setFieldsValue({ + id: this.record.id, + grantRoleIdList: roles, + }) + + 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 + } + } + async loadRoleData() { + const { data } = await api.getRolePage() + return data.items + } + async loadRole(id) { + const { data } = await api.sysUserOwnRole({ id }) + return data + } + render() { + return ( +
+ }> +
+ + + +
+
+ + ) + } +} From 4eace63902a96e9903a1083015aca5aeea935868 Mon Sep 17 00:00:00 2001 From: ky_yusj <2655568377@qq.com> Date: Fri, 25 Jun 2021 17:44:22 +0800 Subject: [PATCH 07/23] =?UTF-8?q?fix=20=20=20querytable=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=9D=A1=E4=BB=B6=E7=9A=84=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web-react/src/components/query-table/index.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web-react/src/components/query-table/index.jsx b/web-react/src/components/query-table/index.jsx index 01a3b19..d28e0fe 100644 --- a/web-react/src/components/query-table/index.jsx +++ b/web-react/src/components/query-table/index.jsx @@ -158,7 +158,7 @@ export default class QueryTable extends Component { onLoadData = async () => { this.onLoading() - if (isEqual(this.query, {})) { + if (isEqual(this.query, {}) && this.props.queryInitialValues) { this.query = this.props.queryInitialValues } 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 08/23] =?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 && ( +
+ + + {renderBreadcrumbItem.call(this)} + + + {this.props.children} diff --git a/web-react/src/store/reducer/layout.js b/web-react/src/store/reducer/layout.js index 7f2fa41..aeadbee 100644 --- a/web-react/src/store/reducer/layout.js +++ b/web-react/src/store/reducer/layout.js @@ -1,6 +1,21 @@ -const layout = (state = { - siderCollapsed: false -}, action) => { +import { SETTING_KEY } from "common/storage" +import { SIDER_BREAK_POINT } from "util/global" + +const defaultState = { + siderCollapsed: false, + allowSiderCollapsed: true +} + +const localStorageState = () => { + return JSON.parse(window.localStorage.getItem(SETTING_KEY)) +} + +const mergeState = { + ...defaultState, + ...localStorageState() +} + +const layout = (state = mergeState, action) => { switch (action.type) { // 打开窗口 case 'OPEN_WINDOW': @@ -13,8 +28,23 @@ const layout = (state = { return state // 侧边收起状态 case 'TOGGLE_COLLAPSED': - const _state = { ...state, siderCollapsed: action.siderCollapsed } - return _state + { + if (window.innerWidth <= SIDER_BREAK_POINT) { + return state + } + const _state = { ...state, siderCollapsed: action.siderCollapsed } + window.localStorage.setItem(SETTING_KEY, JSON.stringify(_state)) + return _state + } + case 'AUTO_TOGGLE_COLLAPSED': + { + const _state = { + ...state, + siderCollapsed: localStorageState().siderCollapsed || action.siderCollapsed, + allowSiderCollapsed: !action.siderCollapsed + } + return _state + } default: return state } diff --git a/web-react/src/util/global/index.js b/web-react/src/util/global/index.js index 2543e20..918cb47 100644 --- a/web-react/src/util/global/index.js +++ b/web-react/src/util/global/index.js @@ -36,4 +36,6 @@ export const RSA_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQU /** * 城市名称 */ -export const CITY = '黄石市' \ No newline at end of file +export const CITY = '黄石市' + +export const SIDER_BREAK_POINT = 1366 \ 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 c0e0b01..ccf9f9d 100644 --- a/web-react/src/views/main/_layout/header/index.jsx +++ b/web-react/src/views/main/_layout/header/index.jsx @@ -33,13 +33,20 @@ export default class index extends Component { } render() { + const { allowSiderCollapsed } = this.state + return (
- this.onCollapsed()}> - - + {allowSiderCollapsed && ( + this.onCollapsed()} + > + + + )}
diff --git a/web-react/src/views/main/index.jsx b/web-react/src/views/main/index.jsx index 665c59f..1e7e7eb 100644 --- a/web-react/src/views/main/index.jsx +++ b/web-react/src/views/main/index.jsx @@ -1,10 +1,9 @@ import React, { Component } from 'react' import { Spin, Layout } from 'antd' -import { LoadingOutlined } from '@ant-design/icons' import { api } from 'common/api' import { cloneDeep, groupBy, findIndex, last } from 'lodash' import store from 'store' -import { EMPTY_ID } from 'util/global' +import { EMPTY_ID, SIDER_BREAK_POINT } from 'util/global' import Header from './_layout/header' import Sider from './_layout/sider' @@ -83,6 +82,9 @@ export default class index extends Component { }) }) }) + + window.addEventListener('resize', this.onResizeSider) + this.onResizeSider() } /** @@ -231,6 +233,13 @@ export default class index extends Component { }) } + onResizeSider = () => { + dispatch({ + type: 'AUTO_TOGGLE_COLLAPSED', + siderCollapsed: window.innerWidth <= SIDER_BREAK_POINT, + }) + } + render() { const { loading, panes, actived } = this.state From f27e3956b97b0cfda18f587e59eb2f7dc71a871c 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: Mon, 28 Jun 2021 13:19:26 +0800 Subject: [PATCH 09/23] =?UTF-8?q?update=20=E6=93=8D=E4=BD=9C=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Ewide.Core/Filter/RequestActionFilter.cs | 27 ++- Api/Ewide.Core/Service/Auth/AuthService.cs | 32 +-- Api/Ewide.Core/Service/Log/SysOpLogService.cs | 13 +- Api/Ewide.Core/Service/Menu/SysMenuService.cs | 2 +- web-react/package.json | 3 +- .../src/components/query-table/index.jsx | 2 +- .../src/pages/system/log/oplog/index.jsx | 209 ++++++++++-------- web-react/src/pages/system/user/index.jsx | 15 +- web-react/src/store/reducer/layout.js | 2 +- .../src/views/main/_layout/sider/menu.jsx | 38 ++-- web-react/yarn.lock | 139 +++++++++++- 11 files changed, 334 insertions(+), 148 deletions(-) diff --git a/Api/Ewide.Core/Filter/RequestActionFilter.cs b/Api/Ewide.Core/Filter/RequestActionFilter.cs index fa6ce44..6c441ee 100644 --- a/Api/Ewide.Core/Filter/RequestActionFilter.cs +++ b/Api/Ewide.Core/Filter/RequestActionFilter.cs @@ -8,6 +8,8 @@ using System.Diagnostics; using System.Security.Claims; using System.Threading.Tasks; using UAParser; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Ewide.Core { @@ -34,12 +36,29 @@ namespace Ewide.Core var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor; var descAtt = Attribute.GetCustomAttribute(actionDescriptor.MethodInfo, typeof(DescriptionAttribute)) as DescriptionAttribute; + var message = "请求成功"; + if (isRequestSucceed) + { + var result = actionContext.Result; + var resultType = result.GetType(); + if (resultType.Name == "ContentResult") + { + var resultContent = ((Microsoft.AspNetCore.Mvc.ContentResult)actionContext.Result)?.Content; + var resultJson = JsonConvert.DeserializeObject(resultContent); + message = resultJson["message"]?.ToString(); + } + } + else + { + message = actionContext.Exception.Message; + } + var sysOpLog = new SysLogOp { Name = descAtt != null ? descAtt.Description : actionDescriptor.ActionName, OpType = 1, Success = isRequestSucceed, - //Message = isRequestSucceed ? "成功" : "失败", + Message = message, Ip = httpContext.GetRemoteIpAddressToIPv4(), Location = httpRequest.GetRequestUrlAddress(), Browser = clent.UA.Family + clent.UA.Major, @@ -48,13 +67,13 @@ namespace Ewide.Core ClassName = context.Controller.ToString(), MethodName = actionDescriptor.ActionName, ReqMethod = httpRequest.Method, - //Param = JsonSerializerUtility.Serialize(context.ActionArguments), - //Result = JsonSerializerUtility.Serialize(actionContext.Result), + Param = JsonConvert.SerializeObject(context.ActionArguments), + // Result = resultContent, ElapsedTime = sw.ElapsedMilliseconds, OpTime = DateTime.Now, Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) }; - await sysOpLog.InsertAsync(); + await sysOpLog.(); } } } diff --git a/Api/Ewide.Core/Service/Auth/AuthService.cs b/Api/Ewide.Core/Service/Auth/AuthService.cs index b9b3773..fba2662 100644 --- a/Api/Ewide.Core/Service/Auth/AuthService.cs +++ b/Api/Ewide.Core/Service/Auth/AuthService.cs @@ -101,6 +101,24 @@ namespace Ewide.Core.Service // 设置刷新Token令牌 _httpContextAccessor.HttpContext.Response.Headers["x-access-token"] = refreshToken; + // 增加登录日志 + var loginOutput = user.Adapt(); + var clent = Parser.GetDefault().Parse(App.GetService().HttpContext.Request.Headers["User-Agent"]); + loginOutput.LastLoginBrowser = clent.UA.Family + clent.UA.Major; + loginOutput.LastLoginOs = clent.OS.Family + clent.OS.Major; + await new SysLogVis + { + Name = "登录", + Success = true, + Message = "登录成功", + Ip = loginOutput.LastLoginIp, + Browser = loginOutput.LastLoginBrowser, + Os = loginOutput.LastLoginOs, + VisType = 1, + VisTime = loginOutput.LastLoginTime, + Account = loginOutput.Account + }.InsertAsync(); + return accessToken; } @@ -163,20 +181,6 @@ namespace Ewide.Core.Service loginOutput.Menus = await _sysMenuService.GetLoginMenusAntDesign(userId, defaultActiveAppCode); } - // 增加登录日志 - //await new SysLogVis - //{ - // Name = "登录", - // Success = true, - // Message = "登录成功", - // Ip = loginOutput.LastLoginIp, - // Browser = loginOutput.LastLoginBrowser, - // Os = loginOutput.LastLoginOs, - // VisType = 1, - // VisTime = loginOutput.LastLoginTime, - // Account = loginOutput.Account - //}.InsertAsync(); - return loginOutput; } diff --git a/Api/Ewide.Core/Service/Log/SysOpLogService.cs b/Api/Ewide.Core/Service/Log/SysOpLogService.cs index 7c23226..5a6e346 100644 --- a/Api/Ewide.Core/Service/Log/SysOpLogService.cs +++ b/Api/Ewide.Core/Service/Log/SysOpLogService.cs @@ -1,4 +1,5 @@ -using Ewide.Core.Extension; +using Dapper; +using Ewide.Core.Extension; using Furion.DatabaseAccessor; using Furion.DatabaseAccessor.Extensions; using Furion.DependencyInjection; @@ -20,10 +21,12 @@ namespace Ewide.Core.Service public class SysOpLogService : ISysOpLogService, IDynamicApiController, ITransient { private readonly IRepository _sysOpLogRep; // 操作日志表仓储 + private readonly IDapperRepository _dapperRepository; - public SysOpLogService(IRepository sysOpLogRep) + public SysOpLogService(IRepository sysOpLogRep, IDapperRepository dapperRepository) { _sysOpLogRep = sysOpLogRep; + _dapperRepository = dapperRepository; } /// @@ -54,11 +57,7 @@ namespace Ewide.Core.Service [HttpPost("/sysOpLog/delete")] public async Task ClearOpLog() { - var opLogs = await _sysOpLogRep.Entities.ToListAsync(); - opLogs.ForEach(u => - { - u.Delete(); - }); + await _dapperRepository.ExecuteAsync("DELETE FROM sys_log_op"); } } } diff --git a/Api/Ewide.Core/Service/Menu/SysMenuService.cs b/Api/Ewide.Core/Service/Menu/SysMenuService.cs index fa73ffa..2a79991 100644 --- a/Api/Ewide.Core/Service/Menu/SysMenuService.cs +++ b/Api/Ewide.Core/Service/Menu/SysMenuService.cs @@ -84,7 +84,7 @@ namespace Ewide.Core.Service .Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Application == appCode) .Where(u => u.Type != (int)MenuType.BTN) - //.Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT) + .Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT) .OrderBy(u => u.Sort).ToListAsync(); } else diff --git a/web-react/package.json b/web-react/package.json index 502591d..8de0070 100644 --- a/web-react/package.json +++ b/web-react/package.json @@ -20,6 +20,7 @@ "photoswipe": "^4.1.3", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-json-view": "^1.21.3", "react-monaco-editor": "^0.43.0", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", @@ -58,4 +59,4 @@ "last 1 safari version" ] } -} \ No newline at end of file +} diff --git a/web-react/src/components/query-table/index.jsx b/web-react/src/components/query-table/index.jsx index d28e0fe..750906b 100644 --- a/web-react/src/components/query-table/index.jsx +++ b/web-react/src/components/query-table/index.jsx @@ -298,7 +298,7 @@ export default class QueryTable extends Component { columns: (() => { const c = [] if (type !== 'tree' && rowNumber & !expandable & !expandedRowRender) { - c.push(rowNoColumn) + //c.push(rowNoColumn) } c.push(...(columns || [])) return c.filter(p => !p.hidden) diff --git a/web-react/src/pages/system/log/oplog/index.jsx b/web-react/src/pages/system/log/oplog/index.jsx index 52bf8d0..3a9b794 100644 --- a/web-react/src/pages/system/log/oplog/index.jsx +++ b/web-react/src/pages/system/log/oplog/index.jsx @@ -1,23 +1,42 @@ import React, { Component } from 'react' -import { Alert, Button, Card, Descriptions, Form, Popconfirm, Input, message as Message, Select, DatePicker } from 'antd' +import { + Alert, + Button, + Card, + Descriptions, + Form, + Popconfirm, + Input, + message as Message, + Select, + DatePicker, + Tag, +} from 'antd' import { Auth, Container, QueryTable } from 'components' import { api } from 'common/api' import { toCamelCase } from 'util/format' import { isEqual } from 'lodash' import getDictData from 'util/dic' import moment from 'moment' +import ReactJson from 'react-json-view' -const { RangePicker } = DatePicker; +const { RangePicker } = DatePicker const apiAction = { page: api.sysOpLogPage, - delete: api.sysOpLogDelete + delete: api.sysOpLogDelete, } + +const methodColor = { + POST: 'orange', + GET: 'green', +} + export default class index extends Component { state = { codes: { - opType: [] - } + opType: [], + }, } // 表格实例 table = React.createRef() @@ -26,37 +45,54 @@ export default class index extends Component { { title: '日志名称', dataIndex: 'name', - sorter: true, - }, - { - title: '操作类型', - dataIndex: 'opType', - render: text => (<>{this.bindCodeValue(text, 'op_type')}), - sorter: true, - }, - { - title: '是否成功', - dataIndex: 'success', - render: text => (<> {text ? '是' : '否'}), - sorter: true, - }, - { - title: 'ip', - dataIndex: 'ip', + width: 200, sorter: true, }, { title: '请求地址', dataIndex: 'url', + width: 300, + sorter: true, + render: (text, record) => ( + <> + + {record.reqMethod} + {' '} + {text} + + ), + }, + { + title: '是否成功', + dataIndex: 'success', + width: 100, + render: text => ( + <> + {text ? ( + + ) : ( + + )} + + ), + sorter: true, + }, + { + title: 'ip', + dataIndex: 'ip', + width: 120, sorter: true, }, { title: '操作时间', dataIndex: 'opTime', + width: 140, sorter: true, + defaultSortOrder: 'descend', }, { title: '操作人', + width: 140, dataIndex: 'account', sorter: true, }, @@ -66,9 +102,9 @@ export default class index extends Component { * 阻止外部组件引发的渲染,提升性能 * 可自行添加渲染条件 * [必要] - * @param {*} props - * @param {*} state - * @returns + * @param {*} props + * @param {*} state + * @returns */ shouldComponentUpdate(props, state) { return !isEqual(this.state, state) @@ -78,29 +114,20 @@ export default class index extends Component { * 加载字典数据,之后开始加载表格数据 * 如果必须要加载字典数据,可直接对表格设置autoLoad=true */ - componentDidMount() { - this.table.current.onLoading() - getDictData('op_type').then(res => { - this.setState({ - codes: res - }, () => { - this.table.current.onLoadData() - }) - }) - } + componentDidMount() {} /** * 调用加载数据接口,可在调用前对query进行处理 * [异步,必要] - * @param {*} params - * @param {*} query - * @returns + * @param {*} params + * @param {*} query + * @returns */ loadData = async (params, query) => { if (query.dates && query.dates.length) { - query.searchBeginTime = moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss'); - query.searchEndTime = moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss'); - delete query.dates; + query.searchBeginTime = moment(query.dates[0]).format('YYYY-MM-DD HH:mm:ss') + query.searchEndTime = moment(query.dates[1]).format('YYYY-MM-DD HH:mm:ss') + delete query.dates } const { data } = await apiAction.page({ ...params, @@ -109,16 +136,16 @@ export default class index extends Component { return data } /** - * 绑定字典数据 - * @param {*} code - * @param {*} name - * @returns - */ + * 绑定字典数据 + * @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) + const c = codes.find(p => +p.code === code) if (c) { return c.value } @@ -129,8 +156,8 @@ export default class index extends Component { /** * 对表格上的操作进行统一处理 * [异步] - * @param {*} action - * @param {*} successMessage + * @param {*} action + * @param {*} successMessage */ async onAction(action, successMessage) { this.table.current.onLoading() @@ -144,27 +171,27 @@ export default class index extends Component { } onOpLogClear() { - this.onAction( - apiAction.delete(), - '清空成功' - ) + this.onAction(apiAction.delete(), '清空成功') } render() { return (
- -
后端bug:任何操作的操作类型都是增加
-
没有记录请求参数.返回结果等信息
- - } /> + +
后端bug:任何操作的操作类型都是增加
+
没有记录请求参数.返回结果等信息
+ + } + />
- - - - + > } @@ -222,8 +231,14 @@ export default class index extends Component { } expandable={{ - expandedRowRender: record => - + expandedRowRender: record => ( + {record.methodName} @@ -240,20 +255,26 @@ export default class index extends Component { {record.className} - {record.result} + - {record.param} + {record.message} + ), }} - />
) } -} \ No newline at end of file +} diff --git a/web-react/src/pages/system/user/index.jsx b/web-react/src/pages/system/user/index.jsx index 7418764..f40c684 100644 --- a/web-react/src/pages/system/user/index.jsx +++ b/web-react/src/pages/system/user/index.jsx @@ -21,6 +21,7 @@ import getDictData from 'util/dic' import FormBody from './form' import RoleForm from './role' import DataForm from './data' +import auth from 'components/authorized/handler' // 配置页面所需接口函数 const apiAction = { @@ -209,25 +210,25 @@ export default class index extends Component { this.onResetPassword(record)}>重置密码 , - + - - + {auth('sysUser:grantRole') && ( + this.onOpen(this.roleForm, record)}> 授权角色 - - - + )} + {auth('sysUser:grantData') && ( + this.onOpen(this.dataForm, record)}> 授权额外数据 - + )} } > diff --git a/web-react/src/store/reducer/layout.js b/web-react/src/store/reducer/layout.js index aeadbee..1441e84 100644 --- a/web-react/src/store/reducer/layout.js +++ b/web-react/src/store/reducer/layout.js @@ -7,7 +7,7 @@ const defaultState = { } const localStorageState = () => { - return JSON.parse(window.localStorage.getItem(SETTING_KEY)) + return JSON.parse(window.localStorage.getItem(SETTING_KEY)) || {} } const mergeState = { diff --git a/web-react/src/views/main/_layout/sider/menu.jsx b/web-react/src/views/main/_layout/sider/menu.jsx index 4d91304..f20f406 100644 --- a/web-react/src/views/main/_layout/sider/menu.jsx +++ b/web-react/src/views/main/_layout/sider/menu.jsx @@ -6,9 +6,8 @@ import store from 'store' const { getState, subscribe } = store export default class index extends Component { - state = { - ...getState('nav') + ...getState('nav'), } constructor(props) { @@ -23,21 +22,25 @@ export default class index extends Component { this.unsubscribe() } - renderMenu = (menu) => { - return menu.map((p) => { + renderMenu = menu => { + return menu.map(p => { return p.children ? this.renderSubMenu(p) : this.renderMenuItem(p) }) } - renderSubMenu = (menu) => { + renderSubMenu = menu => { return ( - }> + } + > {this.renderMenu(menu.children)} ) } - renderMenuItem = (menu) => { + renderMenuItem = menu => { return ( this.onOpenContentWindow(menu)}> {menu.meta.icon && } @@ -46,12 +49,12 @@ export default class index extends Component { ) } - onOpenContentWindow = (menu) => { + onOpenContentWindow = menu => { window.openContentWindow({ key: menu.id, title: menu.meta.title, icon: menu.meta.icon, - path: menu.component + path: menu.component, }) } @@ -60,12 +63,11 @@ export default class index extends Component { } render() { - const props = { mode: 'inline', selectable: false, style: this.props.menuStyle, - theme: 'light' + theme: 'light', } const on = { @@ -74,16 +76,20 @@ export default class index extends Component { return ( <> - { - this.state.nav.map((item, i) => { + {this.state.nav.map((item, i) => { + if (item.menu.length) { return (
{item.app.name}
- {this.renderMenu(item.menu)} + + {this.renderMenu(item.menu)} +
) - }) - } + } else { + return false + } + })} ) } diff --git a/web-react/yarn.lock b/web-react/yarn.lock index c0cf4a2..cad1a03 100644 --- a/web-react/yarn.lock +++ b/web-react/yarn.lock @@ -2554,7 +2554,7 @@ arrify@^2.0.1: resolved "https://registry.nlark.com/arrify/download/arrify-2.0.1.tgz?cache=0&sync_timestamp=1619599497996&other_urls=https%3A%2F%2Fregistry.nlark.com%2Farrify%2Fdownload%2Farrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha1-yWVekzHgq81YjSp8rX6ZVvZnAfo= -asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.nlark.com/asap/download/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -2852,6 +2852,11 @@ balanced-match@^1.0.0: resolved "https://registry.npm.taobao.org/balanced-match/download/balanced-match-1.0.2.tgz?cache=0&sync_timestamp=1617714233441&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbalanced-match%2Fdownload%2Fbalanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4= +base16@^1.0.0: + version "1.0.0" + resolved "https://registry.npm.taobao.org/base16/download/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70" + integrity sha1-4pf2DX7BAUp6lxo568ipjAtoHnA= + base64-js@^1.0.2: version "1.5.1" resolved "https://registry.nlark.com/base64-js/download/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -3752,6 +3757,13 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +cross-fetch@^3.0.4: + version "3.1.4" + resolved "https://registry.npm.taobao.org/cross-fetch/download/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha1-lyPzo6JHv4uJA586OAqSROj6Lzk= + dependencies: + node-fetch "2.6.1" + cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.npm.taobao.org/cross-spawn/download/cross-spawn-7.0.3.tgz?cache=0&sync_timestamp=1606748073153&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcross-spawn%2Fdownload%2Fcross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -5086,6 +5098,31 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbemitter@^3.0.0: + version "3.0.0" + resolved "https://registry.npm.taobao.org/fbemitter/download/fbemitter-3.0.0.tgz#00b2a1af5411254aab416cd75f9e6289bee4bff3" + integrity sha1-ALKhr1QRJUqrQWzXX55iib7kv/M= + dependencies: + fbjs "^3.0.0" + +fbjs-css-vars@^1.0.0: + version "1.0.2" + resolved "https://registry.npm.taobao.org/fbjs-css-vars/download/fbjs-css-vars-1.0.2.tgz#216551136ae02fe255932c3ec8775f18e2c078b8" + integrity sha1-IWVRE2rgL+JVkyw+yHdfGOLAeLg= + +fbjs@^3.0.0: + version "3.0.0" + resolved "https://registry.npm.taobao.org/fbjs/download/fbjs-3.0.0.tgz?cache=0&sync_timestamp=1602048886093&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffbjs%2Fdownload%2Ffbjs-3.0.0.tgz#0907067fb3f57a78f45d95f1eacffcacd623c165" + integrity sha1-CQcGf7P1enj0XZXx6s/8rNYjwWU= + dependencies: + cross-fetch "^3.0.4" + fbjs-css-vars "^1.0.0" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + figgy-pudding@^3.5.1: version "3.5.2" resolved "https://registry.nlark.com/figgy-pudding/download/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" @@ -5212,6 +5249,14 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" +flux@^4.0.1: + version "4.0.1" + resolved "https://registry.npm.taobao.org/flux/download/flux-4.0.1.tgz#7843502b02841d4aaa534af0b373034a1f75ee5c" + integrity sha1-eENQKwKEHUqqU0rws3MDSh917lw= + dependencies: + fbemitter "^3.0.0" + fbjs "^3.0.0" + follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.14.1" resolved "https://registry.nlark.com/follow-redirects/download/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" @@ -7137,11 +7182,21 @@ lodash.clonedeep@^4.5.0: resolved "https://registry.nlark.com/lodash.clonedeep/download/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= +lodash.curry@^4.0.1: + version "4.1.1" + resolved "https://registry.npm.taobao.org/lodash.curry/download/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170" + integrity sha1-JI42By7ekGUB11lmIAqG2riyMXA= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npm.taobao.org/lodash.debounce/download/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.flow@^3.3.0: + version "3.5.0" + resolved "https://registry.npm.taobao.org/lodash.flow/download/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a" + integrity sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o= + lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.nlark.com/lodash.memoize/download/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7639,6 +7694,11 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-fetch@2.6.1: + version "2.6.1" + resolved "https://registry.nlark.com/node-fetch/download/node-fetch-2.6.1.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fnode-fetch%2Fdownload%2Fnode-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha1-BFvTI2Mfdu0uK1VXM5RBa2OaAFI= + node-forge@^0.10.0: version "0.10.0" resolved "https://registry.npm.taobao.org/node-forge/download/node-forge-0.10.0.tgz?cache=0&sync_timestamp=1599010726129&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fnode-forge%2Fdownload%2Fnode-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" @@ -9040,6 +9100,13 @@ promise-inflight@^1.0.1: resolved "https://registry.nlark.com/promise-inflight/download/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.npm.taobao.org/promise/download/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078= + dependencies: + asap "~2.0.3" + promise@^8.1.0: version "8.1.0" resolved "https://registry.npm.taobao.org/promise/download/promise-8.1.0.tgz#697c25c3dfe7435dd79fcd58c38a135888eaf05e" @@ -9142,6 +9209,11 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.nlark.com/punycode/download/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha1-tYsBCsQMIsVldhbI0sLALHv0eew= +pure-color@^1.2.0: + version "1.3.0" + resolved "https://registry.npm.taobao.org/pure-color/download/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e" + integrity sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4= + q@^1.1.2: version "1.5.1" resolved "https://registry.nlark.com/q/download/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -9576,6 +9648,16 @@ react-app-polyfill@^2.0.0: regenerator-runtime "^0.13.7" whatwg-fetch "^3.4.1" +react-base16-styling@^0.6.0: + version "0.6.0" + resolved "https://registry.npm.taobao.org/react-base16-styling/download/react-base16-styling-0.6.0.tgz#ef2156d66cf4139695c8a167886cb69ea660792c" + integrity sha1-7yFW1mz0E5aVyKFniGy2nqZgeSw= + dependencies: + base16 "^1.0.0" + lodash.curry "^4.0.1" + lodash.flow "^3.3.0" + pure-color "^1.2.0" + react-dev-utils@^11.0.3: version "11.0.4" resolved "https://registry.npm.taobao.org/react-dev-utils/download/react-dev-utils-11.0.4.tgz?cache=0&sync_timestamp=1615231838520&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Freact-dev-utils%2Fdownload%2Freact-dev-utils-11.0.4.tgz#a7ccb60257a1ca2e0efe7a83e38e6700d17aa37a" @@ -9630,6 +9712,21 @@ react-is@^17.0.1: resolved "https://registry.nlark.com/react-is/download/react-is-17.0.2.tgz?cache=0&sync_timestamp=1623273254569&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-is%2Fdownload%2Freact-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha1-5pHUqOnHiTZWVVOas3J2Kw77VPA= +react-json-view@^1.21.3: + version "1.21.3" + resolved "https://registry.npm.taobao.org/react-json-view/download/react-json-view-1.21.3.tgz#f184209ee8f1bf374fb0c41b0813cff54549c475" + integrity sha1-8YQgnujxvzdPsMQbCBPP9UVJxHU= + dependencies: + flux "^4.0.1" + react-base16-styling "^0.6.0" + react-lifecycles-compat "^3.0.4" + react-textarea-autosize "^8.3.2" + +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.npm.taobao.org/react-lifecycles-compat/download/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha1-TxonOv38jzSIqMUWv9p4+HI1I2I= + react-monaco-editor@^0.43.0: version "0.43.0" resolved "https://registry.npm.taobao.org/react-monaco-editor/download/react-monaco-editor-0.43.0.tgz#495578470db7b27ab306af813b31f206a6bf9d1c" @@ -9738,6 +9835,15 @@ react-scripts@4.0.3: optionalDependencies: fsevents "^2.1.3" +react-textarea-autosize@^8.3.2: + version "8.3.3" + resolved "https://registry.nlark.com/react-textarea-autosize/download/react-textarea-autosize-8.3.3.tgz?cache=0&sync_timestamp=1622628433420&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact-textarea-autosize%2Fdownload%2Freact-textarea-autosize-8.3.3.tgz#f70913945369da453fd554c168f6baacd1fa04d8" + integrity sha1-9wkTlFNp2kU/1VTBaPa6rNH6BNg= + dependencies: + "@babel/runtime" "^7.10.2" + use-composed-ref "^1.0.0" + use-latest "^1.0.0" + react@^17.0.2: version "17.0.2" resolved "https://registry.nlark.com/react/download/react-17.0.2.tgz?cache=0&sync_timestamp=1623272232595&other_urls=https%3A%2F%2Fregistry.nlark.com%2Freact%2Fdownload%2Freact-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -10387,7 +10493,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.nlark.com/setimmediate/download/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -11220,6 +11326,11 @@ tryer@^1.0.1: resolved "https://registry.npm.taobao.org/tryer/download/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" integrity sha1-8shUBoALmw90yfdGW4HqrSQSUvg= +ts-essentials@^2.0.3: + version "2.0.12" + resolved "https://registry.nlark.com/ts-essentials/download/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" + integrity sha1-yTA/PXT3X6dSjD1JuA4ImrCdh0U= + ts-pnp@1.2.0, ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.npm.taobao.org/ts-pnp/download/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -11336,6 +11447,11 @@ typedarray@^0.0.6: resolved "https://registry.nlark.com/typedarray/download/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +ua-parser-js@^0.7.18: + version "0.7.28" + resolved "https://registry.nlark.com/ua-parser-js/download/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" + integrity sha1-i6BOZT81ziECOcZGYWhb+RId7DE= + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.npm.taobao.org/unbox-primitive/download/unbox-primitive-1.0.1.tgz?cache=0&sync_timestamp=1616706302651&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funbox-primitive%2Fdownload%2Funbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -11480,6 +11596,25 @@ url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +use-composed-ref@^1.0.0: + version "1.1.0" + resolved "https://registry.npm.taobao.org/use-composed-ref/download/use-composed-ref-1.1.0.tgz#9220e4e94a97b7b02d7d27eaeab0b37034438bbc" + integrity sha1-kiDk6UqXt7AtfSfq6rCzcDRDi7w= + dependencies: + ts-essentials "^2.0.3" + +use-isomorphic-layout-effect@^1.0.0: + version "1.1.1" + resolved "https://registry.npm.taobao.org/use-isomorphic-layout-effect/download/use-isomorphic-layout-effect-1.1.1.tgz#7bb6589170cd2987a152042f9084f9effb75c225" + integrity sha1-e7ZYkXDNKYehUgQvkIT57/t1wiU= + +use-latest@^1.0.0: + version "1.2.0" + resolved "https://registry.npm.taobao.org/use-latest/download/use-latest-1.2.0.tgz#a44f6572b8288e0972ec411bdd0840ada366f232" + integrity sha1-pE9lcrgojgly7EEb3QhAraNm8jI= + dependencies: + use-isomorphic-layout-effect "^1.0.0" + use@^3.1.0: version "3.1.1" resolved "https://registry.nlark.com/use/download/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" From 6d4d69eeb6d81ab1da821ae907354964f478386f 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: Mon, 28 Jun 2021 13:21:26 +0800 Subject: [PATCH 10/23] =?UTF-8?q?update=20=E5=90=8C=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Ewide.Core/Filter/RequestActionFilter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Api/Ewide.Core/Filter/RequestActionFilter.cs b/Api/Ewide.Core/Filter/RequestActionFilter.cs index 6c441ee..de89d27 100644 --- a/Api/Ewide.Core/Filter/RequestActionFilter.cs +++ b/Api/Ewide.Core/Filter/RequestActionFilter.cs @@ -73,7 +73,7 @@ namespace Ewide.Core OpTime = DateTime.Now, Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) }; - await sysOpLog.(); + await sysOpLog.InsertAsync(); } } } From f1ae3d5b2a9c3aac1825ea8416ef1039f3ac2fa9 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: Mon, 28 Jun 2021 13:47:59 +0800 Subject: [PATCH 11/23] =?UTF-8?q?fix=20=E8=8F=9C=E5=8D=95=E6=8E=88?= =?UTF-8?q?=E6=9D=83=E9=80=89=E6=8B=A9=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/authority-view/index.jsx | 99 ++++++++++--------- 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/web-react/src/components/authority-view/index.jsx b/web-react/src/components/authority-view/index.jsx index adabbe9..e6c220f 100644 --- a/web-react/src/components/authority-view/index.jsx +++ b/web-react/src/components/authority-view/index.jsx @@ -17,11 +17,11 @@ function getVisible() { const caseChildren = checked.filter(item => item.visibleParent || item.type != 2) const visibleParents = [] // 递归寻找父级 - const findVisibleParents = (children) => { + const findVisibleParents = children => { const parents = [] children.forEach(item => { if (item.parentId) { - const parent = this.list.find(item => item.id === item.parentId) + const parent = this.list.find(p => p.id === item.parentId) if (parent) { parents.push(parent) visibleParents.push(parent) @@ -50,7 +50,9 @@ function getVisible() { function renderDescriptions(data) { return data.map(item => { - return item.children && item.children.length ? renderItem.call(this, item) : renderCheckbox.call(this, item) + return item.children && item.children.length + ? renderItem.call(this, item) + : renderCheckbox.call(this, item) }) } @@ -63,8 +65,10 @@ function renderItem(data) { value={data.id} checked={data.checked} indeterminate={data.indeterminate} - onChange={(e) => this.onChange(e, data)} - >{data.title} + onChange={e => this.onChange(e, data)} + > + {data.title} + } > {renderDescriptions.call(this, data.children)} @@ -76,26 +80,22 @@ function renderItem(data) { function renderCheckbox(data) { return (
- this.onChange(e, data)} - >{data.title} - { - data.visibleParent && data.type == 2 && + this.onChange(e, data)}> + {data.title} + + {data.visibleParent && data.type == 2 && ( - } + )}
) } export default class AuthorityView extends Component { - state = { loading: false, - dataSource: [] + dataSource: [], } list = [] @@ -104,7 +104,8 @@ export default class AuthorityView extends Component { 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 () => {} } /** @@ -126,7 +127,10 @@ export default class AuthorityView extends Component { if (this.props.defaultSelectedKeys) { this.list.map(item => { - if (this.props.defaultSelectedKeys.includes(item.id) && (!item.children || !item.children.length)) { + if ( + this.props.defaultSelectedKeys.includes(item.id) && + (!item.children || !item.children.length) + ) { this.onSelect(true, item) } }) @@ -134,8 +138,10 @@ export default class AuthorityView extends Component { this.setState({ dataSource: res, - loading: false + loading: false, }) + + this.onChange() } onReloadData = () => { @@ -143,16 +149,18 @@ export default class AuthorityView extends Component { } onChange = (e, item) => { - this.onSelect(e.target.checked, item) + if (e && item) { + this.onSelect(e.target.checked, item) + } const visible = getVisible.call(this) if (this.props.onSelect) { this.props.onSelect( // 返回所有选中 - this.list.filter(item => item.checked).map(item => item.id), + this.list.filter(p => p.checked).map(p => p.id), // 返回所有选中和半选 - this.list.filter(item => item.checked || item.indeterminate).map(item => item.id), + this.list.filter(p => p.checked || p.indeterminate).map(p => p.id), // 返回所有选中和半选,但是不返回没有子级选中visibleParent的半选 visible ) @@ -170,7 +178,7 @@ export default class AuthorityView extends Component { } this.setState({ - dataSource: this.list.filter(item => item.parentId === EMPTY_ID) + dataSource: this.list.filter(p => p.parentId === EMPTY_ID), }) } @@ -210,31 +218,30 @@ export default class AuthorityView extends Component { return (
}> - { - !this.state.loading ? - - { - this.state.dataSource.map(item => { - return ( - this.onChange(e, item)} - >{item.title} - } + {!this.state.loading ? ( + + {this.state.dataSource.map(item => { + return ( + this.onChange(e, item)} > - {renderDescriptions.call(this, item.children)} - - ) - }) - } - - : - - } + {item.title} + + } + > + {renderDescriptions.call(this, item.children)} + + ) + })} + + ) : ( + + )}
) From 6dceac060d7ed7a6e7769d019abba0224cf50fb3 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: Mon, 28 Jun 2021 17:23:47 +0800 Subject: [PATCH 12/23] =?UTF-8?q?update=20=E5=BC=BA=E5=8C=96=E8=8F=9C?= =?UTF-8?q?=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/Ewide.Core/Enum/MenuType.cs | 6 +- Api/Ewide.Core/Ewide.Core.xml | 16 +- Api/Ewide.Core/Filter/RequestActionFilter.cs | 2 +- .../Service/Menu/Dto/AntDesignTreeNode.cs | 7 +- Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs | 12 +- Api/Ewide.Core/Service/Menu/SysMenuService.cs | 66 ++-- web-react/src/assets/style/main.less | 8 + web-react/src/components/modal-form/index.jsx | 103 +++--- web-react/src/pages/system/menu/form.jsx | 316 ++++++++++-------- web-react/src/pages/system/menu/index.jsx | 72 +++- .../src/views/main/_layout/content/index.jsx | 60 +++- .../src/views/main/_layout/header/search.jsx | 16 +- .../src/views/main/_layout/sider/menu.jsx | 22 +- web-react/src/views/main/index.jsx | 28 +- 14 files changed, 491 insertions(+), 243 deletions(-) diff --git a/Api/Ewide.Core/Enum/MenuType.cs b/Api/Ewide.Core/Enum/MenuType.cs index 80363cd..ddc74ac 100644 --- a/Api/Ewide.Core/Enum/MenuType.cs +++ b/Api/Ewide.Core/Enum/MenuType.cs @@ -20,9 +20,9 @@ namespace Ewide.Core MENU = 1, /// - /// 按钮 + /// 功能 /// - [Description("按钮")] - BTN = 2 + [Description("功能")] + FUNCTION = 2 } } diff --git a/Api/Ewide.Core/Ewide.Core.xml b/Api/Ewide.Core/Ewide.Core.xml index 51a582c..7e5c095 100644 --- a/Api/Ewide.Core/Ewide.Core.xml +++ b/Api/Ewide.Core/Ewide.Core.xml @@ -2417,9 +2417,9 @@ 菜单
- + - 按钮 + 功能 @@ -4933,7 +4933,7 @@ 路由元信息(路由附带扩展信息) - + 路径 @@ -4943,6 +4943,11 @@ 控制路由和子路由是否显示在 sidebar + + + 打开方式 + + 路由元信息内部类 @@ -5068,6 +5073,11 @@ 菜单类型(字典 0目录 1菜单 2按钮) + + + 打开方式(字典 0无 1组件 2内链 3外链) + + 菜单Id diff --git a/Api/Ewide.Core/Filter/RequestActionFilter.cs b/Api/Ewide.Core/Filter/RequestActionFilter.cs index de89d27..6a37036 100644 --- a/Api/Ewide.Core/Filter/RequestActionFilter.cs +++ b/Api/Ewide.Core/Filter/RequestActionFilter.cs @@ -73,7 +73,7 @@ namespace Ewide.Core OpTime = DateTime.Now, Account = httpContext.User?.FindFirstValue(ClaimConst.CLAINM_ACCOUNT) }; - await sysOpLog.InsertAsync(); + await sysOpLog.InsertNowAsync(); } } } diff --git a/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs b/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs index adec4a9..8739f9e 100644 --- a/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs +++ b/Api/Ewide.Core/Service/Menu/Dto/AntDesignTreeNode.cs @@ -38,12 +38,17 @@ /// /// 路径 /// - public string Path { get; set; } + public string Link { get; set; } /// /// 控制路由和子路由是否显示在 sidebar /// public bool Hidden { get; set; } + + /// + /// 打开方式 + /// + public int OpenType { get; set; } } /// diff --git a/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs b/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs index 4c86f61..eae1c9d 100644 --- a/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs +++ b/Api/Ewide.Core/Service/Menu/Dto/MenuInput.cs @@ -25,7 +25,7 @@ namespace Ewide.Core.Service /// /// 菜单类型(字典 0目录 1菜单 2按钮) /// - public virtual string Type { get; set; } + public virtual int Type { get; set; } /// /// 图标 @@ -55,7 +55,7 @@ namespace Ewide.Core.Service /// /// 打开方式(字典 0无 1组件 2内链 3外链) /// - public virtual string OpenType { get; set; } + public virtual int OpenType { get; set; } /// /// 是否可见(Y-是,N-否) @@ -99,7 +99,13 @@ namespace Ewide.Core.Service /// 菜单类型(字典 0目录 1菜单 2按钮) /// [Required(ErrorMessage = "菜单类型不能为空")] - public override string Type { get; set; } + public override int Type { get; set; } + + /// + /// 打开方式(字典 0无 1组件 2内链 3外链) + /// + [Required(ErrorMessage = "打开方式不能为空")] + public override int OpenType { get; set; } } public class DeleteMenuInput diff --git a/Api/Ewide.Core/Service/Menu/SysMenuService.cs b/Api/Ewide.Core/Service/Menu/SysMenuService.cs index 2a79991..b9d3572 100644 --- a/Api/Ewide.Core/Service/Menu/SysMenuService.cs +++ b/Api/Ewide.Core/Service/Menu/SysMenuService.cs @@ -53,7 +53,7 @@ namespace Ewide.Core.Service var roleIdList = await _sysUserRoleService.GetUserRoleIdList(userId); var menuIdList = await _sysRoleMenuService.GetRoleMenuIdList(roleIdList); permissions = await _sysMenuRep.DetachedEntities.Where(u => menuIdList.Contains(u.Id)) - .Where(u => u.Type == (int)MenuType.BTN) + .Where(u => u.Type == (int)MenuType.FUNCTION) .Where(u => u.Status == (int)CommonStatus.ENABLE) .Select(u => u.Permission).ToListAsync(); #if DEBUG @@ -83,7 +83,7 @@ namespace Ewide.Core.Service sysMenuList = await _sysMenuRep.DetachedEntities .Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Application == appCode) - .Where(u => u.Type != (int)MenuType.BTN) + .Where(u => u.Type != (int)MenuType.FUNCTION) .Where(u => u.Weight != (int)MenuWeight.DEFAULT_WEIGHT) .OrderBy(u => u.Sort).ToListAsync(); } @@ -96,7 +96,7 @@ namespace Ewide.Core.Service .Where(u => menuIdList.Contains(u.Id)) .Where(u => u.Status == (int)CommonStatus.ENABLE) .Where(u => u.Application == appCode) - .Where(u => u.Type != (int)MenuType.BTN) + .Where(u => u.Type != (int)MenuType.FUNCTION) .OrderBy(u => u.Sort).ToListAsync(); } // 转换成登录菜单 @@ -106,8 +106,9 @@ namespace Ewide.Core.Service Pid = u.Pid, Name = u.Code, Component = u.Component, - Redirect = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Redirect, - Path = u.OpenType == (int)MenuOpenType.OUTER ? u.Link : u.Router, + Redirect = u.Redirect, + Link = u.Link, + OpenType = u.OpenType, Meta = new Meta { Title = u.Name, @@ -185,7 +186,7 @@ namespace Ewide.Core.Service /// 增加和编辑时检查参数 /// /// - private static void CheckMenuParam(MenuInput input) + private async Task CheckMenuParam(MenuInput input) { var type = input.Type; var router = input.Router; @@ -195,17 +196,17 @@ namespace Ewide.Core.Service if (type.Equals((int)MenuType.DIR)) { - if (string.IsNullOrEmpty(router)) - throw Oops.Oh(ErrorCode.D4001); + //if (string.IsNullOrEmpty(router)) + // throw Oops.Oh(ErrorCode.D4001); } else if (type.Equals((int)MenuType.MENU)) { - if (string.IsNullOrEmpty(router)) - throw Oops.Oh(ErrorCode.D4001); - if (string.IsNullOrEmpty(openType)) - throw Oops.Oh(ErrorCode.D4002); + //if (string.IsNullOrEmpty(router)) + // throw Oops.Oh(ErrorCode.D4001); + //if (string.IsNullOrEmpty(openType)) + // throw Oops.Oh(ErrorCode.D4002); } - else if (type.Equals((int)MenuType.BTN)) + else if (type.Equals((int)MenuType.FUNCTION)) { if (string.IsNullOrEmpty(permission)) throw Oops.Oh(ErrorCode.D4003); @@ -217,10 +218,37 @@ namespace Ewide.Core.Service //if (!urlSet.Contains(permission.Replace(":","/"))) // throw Oops.Oh(ErrorCode.meu1005); } - //按钮可以设置绑定菜单 - if(!isVisibleParent && type.Equals((int)MenuType.BTN)) + + // 检查上级菜单的类型是否正确 + var pid = input.Pid; + var flag = true; + var empty = System.Guid.Empty.ToString(); + switch(type) { - throw Oops.Oh(ErrorCode.D4004); + // 目录必须在顶级下 + case (int)MenuType.DIR: + flag = pid.Equals(empty); + break; + // 菜单必须在顶级或目录下 + case (int)MenuType.MENU: + if (!pid.Equals(empty)) + { + var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid); + flag = parent.Type.Equals((int)MenuType.DIR); + } + break; + // 功能必须在菜单下 + case (int)MenuType.FUNCTION: + { + var parent = await _sysMenuRep.DetachedEntities.FirstOrDefaultAsync(p => p.Id == pid); + flag = parent == null ? false : parent.Type.Equals((int)MenuType.MENU); + } + break; + } + + if (!flag) + { + throw Oops.Oh("父级菜单类型错误"); } } @@ -240,7 +268,7 @@ namespace Ewide.Core.Service } // 校验参数 - CheckMenuParam(input); + await CheckMenuParam(input); var menu = input.Adapt(); menu.Pids = await CreateNewPids(input.Pid); @@ -296,7 +324,7 @@ namespace Ewide.Core.Service } // 校验参数 - CheckMenuParam(input); + await CheckMenuParam(input); // 如果是编辑,父id不能为自己的子节点 var childIdList = await _sysMenuRep.DetachedEntities.Where(u => u.Pids.Contains(input.Id.ToString())) .Select(u => u.Id).ToListAsync(); @@ -360,7 +388,7 @@ namespace Ewide.Core.Service // 更新当前菜单 oldMenu = input.Adapt(); oldMenu.Pids = newPids; - await oldMenu.UpdateAsync(ignoreNullValues: true); + await oldMenu.UpdateExcludeAsync(new[] { nameof(SysMenu.Type) }, ignoreNullValues: true); // 清除缓存 await _sysCacheService.DelByPatternAsync(CommonConst.CACHE_KEY_MENU); diff --git a/web-react/src/assets/style/main.less b/web-react/src/assets/style/main.less index 7cf67c9..3abb0d5 100644 --- a/web-react/src/assets/style/main.less +++ b/web-react/src/assets/style/main.less @@ -300,6 +300,14 @@ opacity: 0; } + >iframe { + display: block; + + width: 100%; + height: 100%; + + border: 0; + } } } } diff --git a/web-react/src/components/modal-form/index.jsx b/web-react/src/components/modal-form/index.jsx index 5b47046..5b86f51 100644 --- a/web-react/src/components/modal-form/index.jsx +++ b/web-react/src/components/modal-form/index.jsx @@ -3,56 +3,62 @@ import { Button, Drawer, message as Message, Modal } from 'antd' import { cloneDeep, isEqual } from 'lodash' /** - * 渲染对话框 - * @param {*} props - * @param {*} on - * @param {*} childWithProps - * @returns - */ + * 渲染对话框 + * @param {*} props + * @param {*} on + * @param {*} childWithProps + * @returns + */ function renderModal(props, on, childWithProps) { - on = { ...on, - onCancel: () => this.onClose() + onCancel: () => this.onClose(), } - return - {childWithProps} - - + return ( + + {childWithProps} + + ) } /** * 渲染抽屉 - * @param {*} props - * @param {*} on - * @param {*} childWithProps - * @returns + * @param {*} props + * @param {*} on + * @param {*} childWithProps + * @returns */ function renderDrawer(props, on, childWithProps) { on = { ...on, - onClose: () => this.onClose() + onClose: () => this.onClose(), } - return -
- {childWithProps} -
-
- - -
-
+ // 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