diff --git a/frontend/src/css/work-weixin-departments.css b/frontend/src/css/work-weixin-departments.css new file mode 100644 index 0000000000..48926b5dbc --- /dev/null +++ b/frontend/src/css/work-weixin-departments.css @@ -0,0 +1,60 @@ +.dir-content-main { + width: 75%; + overflow-y: hidden; + padding-right: 1rem; +} +.dir-content-main:hover { + overflow-y: auto; +} +.dir-content-main table td { + line-height: 2rem; +} +.dir-content-main .empty-tip { + box-shadow: none; +} +.dir-content-nav { + width: 24%; + overflow: hidden; +} +.dir-content-nav:hover { + overflow: auto; +} +.dir-content-resize { + width: 1%; + border-left: 1px solid #eee; +} +.department-children { + padding-left: 1rem; + position: relative; +} +.tree-node-inner { + position: relative; +} +.tree-node-inner i { + position: absolute; + top: 20%; + left: 0.3rem; +} +.tree-node-inner:hover { + background-color: #FFEFB2; + border-radius: 0.25rem; + cursor: pointer; +} +.tree-node-hight-light { + color: #fff; + border-radius: 4px; + background-color: #feac74 !important; +} +.tree-node-text { + padding-left: 1.3rem; + width: calc(100% - 1.3rem); + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + line-height: 24px; +} +.tree-view { + padding: 12px 12px 12px 0; + flex: 1 1; +} \ No newline at end of file diff --git a/frontend/src/pages/sys-admin/file-scan-records.js b/frontend/src/pages/sys-admin/file-scan-records.js index 744697739a..9c9d947aef 100644 --- a/frontend/src/pages/sys-admin/file-scan-records.js +++ b/frontend/src/pages/sys-admin/file-scan-records.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { seafileAPI } from '../../utils/seafile-api'; import { gettext, loginUrl } from '../../utils/constants'; +import Account from '../../components/common/account'; const tablePropTypes = { @@ -116,20 +117,30 @@ class FileScanRecords extends Component { render() { return ( -
-
-
-

{gettext('Content Scan Records')}

+ +
+
+
-
- +
+
- +
+
+
+

{gettext('Content Scan Records')}

+
+
+
+ + + + ); } } diff --git a/frontend/src/pages/sys-admin/main-panel.js b/frontend/src/pages/sys-admin/main-panel.js index a83b54728b..2c579ea438 100644 --- a/frontend/src/pages/sys-admin/main-panel.js +++ b/frontend/src/pages/sys-admin/main-panel.js @@ -1,7 +1,5 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import Account from '../../components/common/account'; - const propTypes = { children: PropTypes.array.isRequired, @@ -12,14 +10,6 @@ class MainPanel extends Component { render() { return (
-
-
- -
-
- -
-
{this.props.children}
); diff --git a/frontend/src/pages/sys-admin/side-panel.js b/frontend/src/pages/sys-admin/side-panel.js index 4cc0746c31..f3ac2e2065 100644 --- a/frontend/src/pages/sys-admin/side-panel.js +++ b/frontend/src/pages/sys-admin/side-panel.js @@ -172,7 +172,7 @@ class SidePanel extends React.Component { {isDefaultAdmin && enableWorkWeixinDepartments &&
  • - + {'企业微信集成'}
  • diff --git a/frontend/src/pages/sys-admin/work-weixin-departments.js b/frontend/src/pages/sys-admin/work-weixin-departments.js index 6fa3d4678f..b313a96108 100644 --- a/frontend/src/pages/sys-admin/work-weixin-departments.js +++ b/frontend/src/pages/sys-admin/work-weixin-departments.js @@ -1,225 +1,12 @@ -import React, {Component} from 'react'; -import PropTypes from 'prop-types'; -import {seafileAPI} from '../../utils/seafile-api'; -import {gettext, siteRoot} from '../../utils/constants'; +import React, { Component, Fragment } from 'react'; +import { Button } from 'reactstrap'; +import _ from 'lodash'; +import { seafileAPI } from '../../utils/seafile-api'; +import { gettext, siteRoot } from '../../utils/constants'; import toaster from '../../components/toast'; -import Loading from '../../components/loading'; -import {Button, Table} from 'reactstrap'; - - -class WorkWeixinDepartmentMembersList extends Component { - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const membersList = this.props.membersList.map((member, index) => { - let avatar = member.avatar; - if (member.avatar.length > 0) { - // get smaller avatar - avatar = member.avatar.substring(0, member.avatar.length - 1) + '100'; - } else { - avatar = siteRoot + 'media/avatars/default.png'; - } - const userCheckBox = member.email ? '' : - this.props.onUserChecked(member)} - >; - return ( - - - - - - - - - ); - }); - - const allUsersCheckBox = !this.props.canCheckUserIds.length ? '' : - this.props.onAllUsersChecked()} - >; - - return ( -
    -
    - {this.props.isMembersListLoading && } - - {!this.props.isMembersListLoading && -
    {userCheckBox}{member.name}{member.mobile}{member.contact_email}{member.email ? : ''}
    - - - - - - - - - - - - {membersList} - -
    {allUsersCheckBox}{'名称'}{'手机号'}{'邮箱'}{'已添加'}
    - } - {!this.props.isMembersListLoading && this.props.membersList.length === 0 && -
    -

    {'无成员'}

    -
    - } -
    -
    - ); - } -} - -const WorkWeixinDepartmentMembersListPropTypes = { - isMembersListLoading: PropTypes.bool.isRequired, - membersList: PropTypes.array.isRequired, - newUsersTempObj: PropTypes.object.isRequired, - checkedDepartmentId: PropTypes.number.isRequired, - onUserChecked: PropTypes.func.isRequired, - onAllUsersChecked: PropTypes.func.isRequired, - isCheckedAll: PropTypes.bool.isRequired, - canCheckUserIds: PropTypes.array.isRequired, -}; - -WorkWeixinDepartmentMembersList.propTypes = WorkWeixinDepartmentMembersListPropTypes; - - -class WorkWeixinDepartmentsTreeNode extends Component { - constructor(props) { - super(props); - this.state = { - isChildrenShow: false, - }; - } - - toggleChildren = () => { - this.setState({ - isChildrenShow: !this.state.isChildrenShow, - }); - }; - - renderTreeNodes = (departmentsTree) => { - if (departmentsTree.length > 0) { - return departmentsTree.map((department) => { - return ( - - ); - }); - } - }; - - componentDidMount() { - if (this.props.index === 0) { - this.toggleChildren(); - this.props.onChangeDepartment(this.props.department.id); - } - } - - render() { - let toggleIconClass = ''; - if (this.props.department.children) { - if (this.state.isChildrenShow) { - toggleIconClass = 'folder-toggle-icon fa fa-caret-down'; - } else { - toggleIconClass = 'folder-toggle-icon fa fa-caret-right'; - } - } - let departmentStyle = this.props.checkedDepartmentId === this.props.department.id ? {color: 'blue'} : {}; - - return ( -
    - {this.props.isChildrenShow && -
    - this.toggleChildren()}>{' '} - this.props.onChangeDepartment(this.props.department.id)} - >{this.props.department.name} -
    - } - {this.state.isChildrenShow && -
    - {this.props.department.children && this.renderTreeNodes(this.props.department.children)} -
    - } -
    - ); - } -} - -const WorkWeixinDepartmentsTreeNodePropTypes = { - index: PropTypes.number, - department: PropTypes.object.isRequired, - isChildrenShow: PropTypes.bool.isRequired, - onChangeDepartment: PropTypes.func.isRequired, - checkedDepartmentId: PropTypes.number.isRequired, -}; - -WorkWeixinDepartmentsTreeNode.propTypes = WorkWeixinDepartmentsTreeNodePropTypes; - - -class WorkWeixinDepartmentsTreePanel extends Component { - constructor(props) { - super(props); - this.state = {}; - } - - render() { - const departmentsTree = this.props.departmentsTree.map((department, index) => { - return ( - - ); - }); - - return ( -
    -
    - {this.props.isTreeLoading && } - {!this.props.isTreeLoading && -
    - {this.props.departmentsTree.length > 0 && departmentsTree} -
    - } -
    -
    - ); - } -} - -const WorkWeixinDepartmentsTreePanelPropTypes = { - isTreeLoading: PropTypes.bool.isRequired, - departmentsTree: PropTypes.array.isRequired, - onChangeDepartment: PropTypes.func.isRequired, - checkedDepartmentId: PropTypes.number.isRequired, -}; - -WorkWeixinDepartmentsTreePanel.propTypes = WorkWeixinDepartmentsTreePanelPropTypes; - +import Account from '../../components/common/account'; +import { WorkWeixinDepartmentMembersList, WorkWeixinDepartmentsTreePanel } from './work-weixin'; +import '../../css/work-weixin-departments.css'; class WorkWeixinDepartments extends Component { @@ -255,8 +42,7 @@ class WorkWeixinDepartments extends Component { let rootIds = parentIds.concat(intersection).filter((v) => { return parentIds.indexOf(v) === -1 || intersection.indexOf(v) === -1; }); - - let cloneData = JSON.parse(JSON.stringify(list)); + let cloneData = _.cloneDeep(list); return cloneData.filter(father => { let branchArr = cloneData.filter(child => father.id === child.parentid); branchArr.length > 0 ? father.children = branchArr : ''; @@ -272,16 +58,12 @@ class WorkWeixinDepartments extends Component { departmentsTree: departmentsTree, }); }).catch((error) => { + this.handleError(error); this.setState({ isTreeLoading: false, isMembersListLoading: false, }); - if (error.response) { - toaster.danger(error.response.data.error_msg || error.response.data.detail || gettext('Error'), {duration: 3}); - } else { - toaster.danger(gettext('Please check the network.'), {duration: 3}); - } - if (error.response.status === 403) { + if (error.response && error.response.status === 403) { window.location = siteRoot + 'sys/useradmin/'; } }); @@ -291,7 +73,7 @@ class WorkWeixinDepartments extends Component { this.setState({ isMembersListLoading: true, }); - seafileAPI.adminListWorkWeixinDepartmentMembers(department_id.toString(), {fetch_child: true}).then((res) => { + seafileAPI.adminListWorkWeixinDepartmentMembers(department_id, {fetch_child: true}).then((res) => { let membersTempObj = this.state.membersTempObj; membersTempObj[department_id] = res.data.userlist; let canCheckUserIds = this.getCanCheckUserIds(res.data.userlist); @@ -303,23 +85,16 @@ class WorkWeixinDepartments extends Component { }); }).catch((error) => { this.setState({isMembersListLoading: false}); - if (error.response) { - toaster.danger(error.response.data.error_msg || error.response.data.detail || gettext('Error'), {duration: 3}); - } else { - toaster.danger(gettext('Please check the network.'), {duration: 3}); - } + this.handleError(error); }); }; getCanCheckUserIds = (membersList) => { - let canCheckUserIds = []; - for (let i = 0; i < membersList.length; i++) { - let user = membersList[i]; - if (!user.email) { - canCheckUserIds.push(user.userid); - } - } - return canCheckUserIds; + let userIds = []; + membersList.forEach((member) => { + if (!member.email) userIds.push(member.userid); + }); + return userIds; }; onChangeDepartment = (department_id) => { @@ -345,21 +120,15 @@ class WorkWeixinDepartments extends Component { if (user.userid in newUsersTempObj) { delete newUsersTempObj[user.userid]; if (this.state.isCheckedAll) { - this.setState({ - isCheckedAll: false, - }); + this.setState({ isCheckedAll: false }); } } else { newUsersTempObj[user.userid] = user; if (Object.keys(newUsersTempObj).length === this.state.canCheckUserIds.length) { - this.setState({ - isCheckedAll: true, - }); + this.setState({ isCheckedAll: true }); } } - this.setState({ - newUsersTempObj: newUsersTempObj, - }); + this.setState({ newUsersTempObj: newUsersTempObj }); } }; @@ -372,124 +141,139 @@ class WorkWeixinDepartments extends Component { let newUsersTempList = this.state.membersList.filter(user => { return this.state.canCheckUserIds.indexOf(user.userid) !== -1; }); - for (let i = 0; i < newUsersTempList.length; i++) { newUsersTempObj[newUsersTempList[i].userid] = newUsersTempList[i]; } - this.setState({ - newUsersTempObj: newUsersTempObj, - }); + this.setState({ newUsersTempObj: newUsersTempObj }); } else { - this.setState({ - newUsersTempObj: {}, - }); + this.setState({ newUsersTempObj: {} }); } }); }; onSubmit = () => { + const { newUsersTempObj } = this.state; + if (JSON.stringify(newUsersTempObj) === '{}') return; let userList = []; - for (let i in this.state.newUsersTempObj) { - userList.push(this.state.newUsersTempObj[i]); + for (let i in newUsersTempObj) { + userList.push(newUsersTempObj[i]); } - if (!userList.length) { + if (userList.length === 0) { toaster.danger('未选择成员', {duration: 3}); - } else { - seafileAPI.adminAddWorkWeixinUsersBatch(userList).then((res) => { - this.setState({ - newUsersTempObj: {}, - isCheckedAll: false, - }); - if (res.data.success) { - let membersTempObj = this.state.membersTempObj; - let membersList = this.state.membersList; - let canCheckUserIds = this.state.canCheckUserIds; - for (let i = 0; i < res.data.success.length; i++) { - let userid = res.data.success[i].userid; - let name = res.data.success[i].name; - let email = res.data.success[i].email; - toaster.success(name + ' 成功导入', {duration: 1}); - // refresh all temp - if (canCheckUserIds.indexOf(userid) !== -1) { - canCheckUserIds.splice(canCheckUserIds.indexOf(userid), 1); - } - for (let j = 0; j < membersList.length; j++) { - if (membersList[j].userid === userid) { - membersList[j].email = email; - break; - } - } - for (let departmentId in membersTempObj) { - for (let k = 0; k < membersTempObj[departmentId].length; k++) { - if (membersTempObj[departmentId][k].userid === userid) { - membersTempObj[departmentId][k].email = email; - break; - } - } - } - } - this.setState({ - membersTempObj: membersTempObj, - membersList: membersList, - canCheckUserIds: canCheckUserIds, - }); - } - if (res.data.failed) { - for (let i = 0; i < res.data.failed.length; i++) { - let name = res.data.failed[i].name; - let errorMsg = res.data.failed[i].error_msg; - toaster.danger(name + ' ' + errorMsg, {duration: 3}); - } - } - }).catch((error) => { - if (error.response) { - toaster.danger(error.response.data.error_msg || error.response.data.detail || gettext('Error'), {duration: 3}); - } else { - toaster.danger(gettext('Please check the network.'), {duration: 3}); - } - }); + return; } + seafileAPI.adminAddWorkWeixinUsersBatch(userList).then((res) => { + this.setState({ + newUsersTempObj: {}, + isCheckedAll: false, + }); + if (res.data.success) { + this.handleSubmitSuccess(res.data.success); + } + if (res.data.failed) { + const fails= res.data.failed; + for (let i = 0; i < fails.length; i++) { + toaster.danger(fails[i].name + ' ' + fails[i].error_msg, {duration: 3}); + } + } + }).catch((error) => { + this.handleError(error); + }); }; + handleSubmitSuccess = (success) => { + let { membersTempObj, membersList, canCheckUserIds } = this.state; + for (let i = 0; i < success.length; i++) { + let { userid, name, email } = success[i]; + toaster.success(name + ' 成功导入', {duration: 1}); + // refresh all temp + if (canCheckUserIds.indexOf(userid) !== -1) { + canCheckUserIds.splice(canCheckUserIds.indexOf(userid), 1); + } + for (let j = 0; j < membersList.length; j++) { + if (membersList[j].userid === userid) { + membersList[j].email = email; + break; + } + } + for (let departmentId in membersTempObj) { + for (let k = 0; k < membersTempObj[departmentId].length; k++) { + if (membersTempObj[departmentId][k].userid === userid) { + membersTempObj[departmentId][k].email = email; + break; + } + } + } + } + this.setState({ + membersTempObj: membersTempObj, + membersList: membersList, + canCheckUserIds: canCheckUserIds, + }); + } + + handleError = (e) => { + if (e.response) { + toaster.danger(e.response.data.error_msg || e.response.data.detail || gettext('Error'), {duration: 3}); + } else { + toaster.danger(gettext('Please check the network.'), {duration: 3}); + } + } + componentDidMount() { this.getWorkWeixinDepartmentsList(); } - render() { + renderNav() { + const btnClass = 'btn btn-secondary operation-item '; return ( -
    -
    -
    -

    {'企业微信集成'}

    - {JSON.stringify(this.state.newUsersTempObj) !== '{}' && - - } -
    -
    - -
    - -
    +
    +
    + + + +
    +
    +
    ); } + + render() { + return ( + + {this.renderNav()} +
    +
    +
    +

    {'企业微信集成'}

    +
    +
    + +
    + +
    +
    +
    +
    + ); + } } export default WorkWeixinDepartments; - diff --git a/frontend/src/pages/sys-admin/work-weixin/index.js b/frontend/src/pages/sys-admin/work-weixin/index.js new file mode 100644 index 0000000000..e7fbbddf23 --- /dev/null +++ b/frontend/src/pages/sys-admin/work-weixin/index.js @@ -0,0 +1,5 @@ +import WorkWeixinDepartmentMembersList from './work-weixin-department-members-list'; +import WorkWeixinDepartmentsTreePanel from './work-weixin-departments-tree-panel'; +import WorkWeixinDepartmentsTreeNode from './work-weixin-departments-tree-node'; + +export { WorkWeixinDepartmentMembersList, WorkWeixinDepartmentsTreePanel, WorkWeixinDepartmentsTreeNode }; \ No newline at end of file diff --git a/frontend/src/pages/sys-admin/work-weixin/work-weixin-department-members-list.js b/frontend/src/pages/sys-admin/work-weixin/work-weixin-department-members-list.js new file mode 100644 index 0000000000..8238892ffb --- /dev/null +++ b/frontend/src/pages/sys-admin/work-weixin/work-weixin-department-members-list.js @@ -0,0 +1,84 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { Table } from 'reactstrap'; +import { siteRoot } from '../../../utils/constants'; +import Loading from '../../../components/loading'; + +const WorkWeixinDepartmentMembersListPropTypes = { + isMembersListLoading: PropTypes.bool.isRequired, + membersList: PropTypes.array.isRequired, + newUsersTempObj: PropTypes.object.isRequired, + checkedDepartmentId: PropTypes.number.isRequired, + onUserChecked: PropTypes.func.isRequired, + onAllUsersChecked: PropTypes.func.isRequired, + isCheckedAll: PropTypes.bool.isRequired, + canCheckUserIds: PropTypes.array.isRequired, +}; + +class WorkWeixinDepartmentMembersList extends Component { + + constructor(props) { + super(props); + } + + render() { + const { newUsersTempObj, checkedDepartmentId, isMembersListLoading, canCheckUserIds } = this.props; + const membersList = this.props.membersList.map((member, index) => { + let avatar = member.avatar; + if (member.avatar.length > 0) { + avatar = member.avatar.substring(0, member.avatar.length - 1) + '100';// get smaller avatar + } else { + avatar = siteRoot + 'media/avatars/default.png'; + } + return ( + + + {!member.email && + this.props.onUserChecked(member)} + checked={(member.userid in newUsersTempObj) ? 'checked' : ''}>} + + + {member.name} + {member.mobile} + {member.contact_email} + {member.email && } + + ); + }); + + return ( +
    + {isMembersListLoading && } + {!isMembersListLoading && this.props.membersList.length > 0 && + + + + + + + + + + + + {membersList} +
    + {canCheckUserIds.length > 0 && + this.props.onAllUsersChecked()}>} + {'名称'}{'手机号'}{'邮箱'}{'已添加'}
    + } + {!isMembersListLoading && this.props.membersList.length === 0 && +
    + +

    {'成员列表为空'}

    +
    + } +
    + ); + } +} + +WorkWeixinDepartmentMembersList.propTypes = WorkWeixinDepartmentMembersListPropTypes; + +export default WorkWeixinDepartmentMembersList; diff --git a/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-node.js b/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-node.js new file mode 100644 index 0000000000..a8495f415c --- /dev/null +++ b/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-node.js @@ -0,0 +1,81 @@ +import React, { Component, Fragment } from 'react'; +import PropTypes from 'prop-types'; +import classNames from 'classnames'; + +const WorkWeixinDepartmentsTreeNodePropTypes = { + index: PropTypes.number, + department: PropTypes.object.isRequired, + isChildrenShow: PropTypes.bool.isRequired, + onChangeDepartment: PropTypes.func.isRequired, + checkedDepartmentId: PropTypes.number.isRequired, +}; + +class WorkWeixinDepartmentsTreeNode extends Component { + + constructor(props) { + super(props); + this.state = { + isChildrenShow: false, + }; + } + + toggleChildren = () => { + this.setState({ + isChildrenShow: !this.state.isChildrenShow, + }); + }; + + componentDidMount() { + if (this.props.index === 0) { + this.toggleChildren(); + this.props.onChangeDepartment(this.props.department.id); + } + } + + renderTreeNodes = (departmentsTree) => { + if (departmentsTree.length > 0) { + return departmentsTree.map((department) => { + return ( + + ); + }); + } + }; + + render() { + const { isChildrenShow, department, checkedDepartmentId } = this.props; + let toggleClass = classNames({ + 'folder-toggle-icon fa fa-caret-down': department.children && this.state.isChildrenShow, + 'folder-toggle-icon fa fa-caret-right': department.children && !this.state.isChildrenShow, + }) + let nodeInnerClass = classNames({ + 'tree-node-inner': true, + 'tree-node-hight-light': checkedDepartmentId === department.id + }); + return ( + + {isChildrenShow && +
    + this.toggleChildren()}>{' '} + this.props.onChangeDepartment(department.id)}>{department.name} +
    + } + {this.state.isChildrenShow && +
    + {department.children && this.renderTreeNodes(department.children)} +
    + } +
    + ); + } +} + +WorkWeixinDepartmentsTreeNode.propTypes = WorkWeixinDepartmentsTreeNodePropTypes; + +export default WorkWeixinDepartmentsTreeNode; diff --git a/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-panel.js b/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-panel.js new file mode 100644 index 0000000000..080d6e39a5 --- /dev/null +++ b/frontend/src/pages/sys-admin/work-weixin/work-weixin-departments-tree-panel.js @@ -0,0 +1,49 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import Loading from '../../../components/loading'; +import WorkWeixinDepartmentsTreeNode from './work-weixin-departments-tree-node'; + +const WorkWeixinDepartmentsTreePanelPropTypes = { + isTreeLoading: PropTypes.bool.isRequired, + departmentsTree: PropTypes.array.isRequired, + onChangeDepartment: PropTypes.func.isRequired, + checkedDepartmentId: PropTypes.number.isRequired, +}; + +class WorkWeixinDepartmentsTreePanel extends Component { + + constructor(props) { + super(props); + } + + render() { + const { departmentsTree } = this.props; + return ( +
    +
    + {this.props.isTreeLoading ? + : +
    + {departmentsTree.length > 0 && departmentsTree.map((department, index) => { + return ( + + ); + })} +
    + } +
    +
    + ); + } +} + +WorkWeixinDepartmentsTreePanel.propTypes = WorkWeixinDepartmentsTreePanelPropTypes; + +export default WorkWeixinDepartmentsTreePanel; diff --git a/media/img/member-list-empty.png b/media/img/member-list-empty.png new file mode 100644 index 0000000000..8731b889f9 Binary files /dev/null and b/media/img/member-list-empty.png differ