mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-11 20:01:10 +00:00
change button and style
This commit is contained in:
57
frontend/src/css/work-weixin-departments.css
Normal file
57
frontend/src/css/work-weixin-departments.css
Normal file
@@ -0,0 +1,57 @@
|
||||
.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-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;
|
||||
}
|
@@ -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 (
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container" id="content-scan-records">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{gettext('Content Scan Records')}</h3>
|
||||
<React.Fragment>
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Table
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
records={this.state.records}
|
||||
/>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={true} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container" id="content-scan-records">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{gettext('Content Scan Records')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Table
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
records={this.state.records}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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 (
|
||||
<div className="main-panel o-hidden">
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={true} />
|
||||
</div>
|
||||
</div>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
|
@@ -172,7 +172,7 @@ class SidePanel extends React.Component {
|
||||
{isDefaultAdmin && enableWorkWeixinDepartments &&
|
||||
<li className="nav-item">
|
||||
<Link className='nav-link ellipsis' to={siteRoot + 'sys/work-weixin/departments/'}>
|
||||
<span className="sf2-icon-organization" aria-hidden="true"></span>
|
||||
<span className="sf2-icon-msgs" aria-hidden="true"></span>
|
||||
<span className="nav-text">{'企业微信集成'}</span>
|
||||
</Link>
|
||||
</li>
|
||||
|
@@ -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 ? '' :
|
||||
<input
|
||||
type="checkbox"
|
||||
className="vam"
|
||||
checked={(member.userid in this.props.newUsersTempObj) ? 'checked' : ''}
|
||||
onChange={() => this.props.onUserChecked(member)}
|
||||
></input>;
|
||||
return (
|
||||
<tr key={this.props.checkedDepartmentId.toString() + member.userid}>
|
||||
<td>{userCheckBox}</td>
|
||||
<td><img className="avatar" src={avatar}></img></td>
|
||||
<td>{member.name}</td>
|
||||
<td>{member.mobile}</td>
|
||||
<td>{member.contact_email}</td>
|
||||
<td>{member.email ? <i className="sf2-icon-tick"></i> : ''}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
const allUsersCheckBox = !this.props.canCheckUserIds.length ? '' :
|
||||
<input
|
||||
type="checkbox"
|
||||
className="vam"
|
||||
checked={this.props.isCheckedAll}
|
||||
onChange={() => this.props.onAllUsersChecked()}
|
||||
></input>;
|
||||
|
||||
return (
|
||||
<div className="dir-content-main" style={{width: '75%'}}>
|
||||
<div className="table-container ">
|
||||
{this.props.isMembersListLoading && <Loading/>}
|
||||
|
||||
{!this.props.isMembersListLoading &&
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="36px">{allUsersCheckBox}</th>
|
||||
<th width="56px"></th>
|
||||
<th width="">{'名称'}</th>
|
||||
<th width="">{'手机号'}</th>
|
||||
<th width="">{'邮箱'}</th>
|
||||
<th width="66px">{'已添加'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{membersList}
|
||||
</tbody>
|
||||
</Table>
|
||||
}
|
||||
{!this.props.isMembersListLoading && this.props.membersList.length === 0 &&
|
||||
<div className="message empty-tip">
|
||||
<h2>{'无成员'}</h2>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<WorkWeixinDepartmentsTreeNode
|
||||
key={department.id}
|
||||
department={department}
|
||||
isChildrenShow={this.state.isChildrenShow}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
checkedDepartmentId={this.props.checkedDepartmentId}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<div>
|
||||
{this.props.isChildrenShow &&
|
||||
<div>
|
||||
<i className={toggleIconClass} onClick={() => this.toggleChildren()}></i>{' '}
|
||||
<i
|
||||
style={departmentStyle}
|
||||
onClick={() => this.props.onChangeDepartment(this.props.department.id)}
|
||||
>{this.props.department.name}</i>
|
||||
</div>
|
||||
}
|
||||
{this.state.isChildrenShow &&
|
||||
<div style={{left: '30px', position: 'relative'}}>
|
||||
{this.props.department.children && this.renderTreeNodes(this.props.department.children)}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<WorkWeixinDepartmentsTreeNode
|
||||
key={department.id}
|
||||
index={index}
|
||||
department={department}
|
||||
isChildrenShow={true}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
checkedDepartmentId={this.props.checkedDepartmentId}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="dir-content-nav" style={{width: '25%'}}>
|
||||
<div className="tree-view tree">
|
||||
{this.props.isTreeLoading && <Loading/>}
|
||||
{!this.props.isTreeLoading &&
|
||||
<div className="tree-node">
|
||||
{this.props.departmentsTree.length > 0 && departmentsTree}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{'企业微信集成'}</h3>
|
||||
{JSON.stringify(this.state.newUsersTempObj) !== '{}' &&
|
||||
<Button className="btn btn-secondary operation-item" onClick={this.onSubmit}>{'导入用户'}</Button>
|
||||
}
|
||||
</div>
|
||||
<div className="cur-view-content" style={{display: 'flex', flexDirection: 'row'}}>
|
||||
<WorkWeixinDepartmentsTreePanel
|
||||
departmentsTree={this.state.departmentsTree}
|
||||
isTreeLoading={this.state.isTreeLoading}
|
||||
onChangeDepartment={this.onChangeDepartment}
|
||||
checkedDepartmentId={this.state.checkedDepartmentId}
|
||||
/>
|
||||
<div className="dir-content-resize"></div>
|
||||
<WorkWeixinDepartmentMembersList
|
||||
isMembersListLoading={this.state.isMembersListLoading}
|
||||
membersList={this.state.membersList}
|
||||
checkedDepartmentId={this.state.checkedDepartmentId}
|
||||
newUsersTempObj={this.state.newUsersTempObj}
|
||||
onUserChecked={this.onUserChecked}
|
||||
onAllUsersChecked={this.onAllUsersChecked}
|
||||
isCheckedAll={this.state.isCheckedAll}
|
||||
canCheckUserIds={this.state.canCheckUserIds}
|
||||
/>
|
||||
</div>
|
||||
<div className="main-panel-north border-left-show">
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<Button className={btnClass + "my-1 d-md-none"} onClick={this.onSubmit}>{'导入用户'}</Button>
|
||||
<Button className={btnClass + "hidden-md-up"} onClick={this.onSubmit}>{'导入用户'}</Button>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={true}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
{this.renderNav()}
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading">{'企业微信集成'}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content d-flex flex-row">
|
||||
<WorkWeixinDepartmentsTreePanel
|
||||
departmentsTree={this.state.departmentsTree}
|
||||
isTreeLoading={this.state.isTreeLoading}
|
||||
onChangeDepartment={this.onChangeDepartment}
|
||||
checkedDepartmentId={this.state.checkedDepartmentId}
|
||||
/>
|
||||
<div className="dir-content-resize"></div>
|
||||
<WorkWeixinDepartmentMembersList
|
||||
isMembersListLoading={this.state.isMembersListLoading}
|
||||
membersList={this.state.membersList}
|
||||
checkedDepartmentId={this.state.checkedDepartmentId}
|
||||
newUsersTempObj={this.state.newUsersTempObj}
|
||||
onUserChecked={this.onUserChecked}
|
||||
onAllUsersChecked={this.onAllUsersChecked}
|
||||
isCheckedAll={this.state.isCheckedAll}
|
||||
canCheckUserIds={this.state.canCheckUserIds}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default WorkWeixinDepartments;
|
||||
|
||||
|
5
frontend/src/pages/sys-admin/work-weixin/index.js
Normal file
5
frontend/src/pages/sys-admin/work-weixin/index.js
Normal file
@@ -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 };
|
@@ -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 (
|
||||
<tr key={checkedDepartmentId.toString() + member.userid}>
|
||||
<td>
|
||||
{!member.email &&
|
||||
<input type="checkbox" className="vam" onChange={() => this.props.onUserChecked(member)}
|
||||
checked={(member.userid in newUsersTempObj) ? 'checked' : ''}></input>}
|
||||
</td>
|
||||
<td><img className="avatar" src={avatar} alt=""></img></td>
|
||||
<td>{member.name}</td>
|
||||
<td>{member.mobile}</td>
|
||||
<td>{member.contact_email}</td>
|
||||
<td>{member.email && <i className="sf2-icon-tick"></i>}</td>
|
||||
</tr>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="dir-content-main">
|
||||
{isMembersListLoading && <Loading/>}
|
||||
{!isMembersListLoading && this.props.membersList.length > 0 &&
|
||||
<Table hover>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">
|
||||
{canCheckUserIds.length > 0 &&
|
||||
<input type="checkbox" className="vam" checked={this.props.isCheckedAll}
|
||||
onChange={() => this.props.onAllUsersChecked()}></input>}
|
||||
</th>
|
||||
<th width="10%"></th>
|
||||
<th width="20%">{'名称'}</th>
|
||||
<th width="20%">{'手机号'}</th>
|
||||
<th width="30%">{'邮箱'}</th>
|
||||
<th width="15%">{'已添加'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{membersList}</tbody>
|
||||
</Table>
|
||||
}
|
||||
{!isMembersListLoading && this.props.membersList.length === 0 &&
|
||||
<div className="message empty-tip text-center">
|
||||
<img src='/media/img/member-list-empty.png'/>
|
||||
<h4>{'成员列表为空'}</h4>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WorkWeixinDepartmentMembersList.propTypes = WorkWeixinDepartmentMembersListPropTypes;
|
||||
|
||||
export default WorkWeixinDepartmentMembersList;
|
@@ -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 (
|
||||
<WorkWeixinDepartmentsTreeNode
|
||||
key={department.id}
|
||||
department={department}
|
||||
isChildrenShow={this.state.isChildrenShow}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
checkedDepartmentId={this.props.checkedDepartmentId}
|
||||
/>
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
{isChildrenShow &&
|
||||
<div className={nodeInnerClass}>
|
||||
<i className={toggleClass} onClick={() => this.toggleChildren()}></i>{' '}
|
||||
<span className="tree-node-text" onClick={() => this.props.onChangeDepartment(department.id)}>{department.name}</span>
|
||||
</div>
|
||||
}
|
||||
{this.state.isChildrenShow &&
|
||||
<div className="department-children">
|
||||
{department.children && this.renderTreeNodes(department.children)}
|
||||
</div>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WorkWeixinDepartmentsTreeNode.propTypes = WorkWeixinDepartmentsTreeNodePropTypes;
|
||||
|
||||
export default WorkWeixinDepartmentsTreeNode;
|
@@ -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 (
|
||||
<div className="dir-content-nav">
|
||||
<div className="tree-view tree">
|
||||
{this.props.isTreeLoading ?
|
||||
<Loading/> :
|
||||
<div className="tree-node">
|
||||
{departmentsTree.length > 0 && departmentsTree.map((department, index) => {
|
||||
return (
|
||||
<WorkWeixinDepartmentsTreeNode
|
||||
key={department.id}
|
||||
index={index}
|
||||
department={department}
|
||||
isChildrenShow={true}
|
||||
onChangeDepartment={this.props.onChangeDepartment}
|
||||
checkedDepartmentId={this.props.checkedDepartmentId}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
WorkWeixinDepartmentsTreePanel.propTypes = WorkWeixinDepartmentsTreePanelPropTypes;
|
||||
|
||||
export default WorkWeixinDepartmentsTreePanel;
|
BIN
media/img/member-list-empty.png
Normal file
BIN
media/img/member-list-empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
Reference in New Issue
Block a user