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 PropTypes from 'prop-types';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, loginUrl } from '../../utils/constants';
|
import { gettext, loginUrl } from '../../utils/constants';
|
||||||
|
import Account from '../../components/common/account';
|
||||||
|
|
||||||
|
|
||||||
const tablePropTypes = {
|
const tablePropTypes = {
|
||||||
@@ -116,20 +117,30 @@ class FileScanRecords extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-center">
|
<React.Fragment>
|
||||||
<div className="cur-view-container" id="content-scan-records">
|
<div className="main-panel-north border-left-show">
|
||||||
<div className="cur-view-path">
|
<div className="cur-view-toolbar">
|
||||||
<h3 className="sf-heading">{gettext('Content Scan Records')}</h3>
|
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="common-toolbar">
|
||||||
<Table
|
<Account isAdminPanel={true} />
|
||||||
loading={this.state.loading}
|
|
||||||
errorMsg={this.state.errorMsg}
|
|
||||||
records={this.state.records}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</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 React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import Account from '../../components/common/account';
|
|
||||||
|
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
children: PropTypes.array.isRequired,
|
children: PropTypes.array.isRequired,
|
||||||
@@ -12,14 +10,6 @@ class MainPanel extends Component {
|
|||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="main-panel o-hidden">
|
<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}
|
{this.props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@@ -172,7 +172,7 @@ class SidePanel extends React.Component {
|
|||||||
{isDefaultAdmin && enableWorkWeixinDepartments &&
|
{isDefaultAdmin && enableWorkWeixinDepartments &&
|
||||||
<li className="nav-item">
|
<li className="nav-item">
|
||||||
<Link className='nav-link ellipsis' to={siteRoot + 'sys/work-weixin/departments/'}>
|
<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>
|
<span className="nav-text">{'企业微信集成'}</span>
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
|
@@ -1,225 +1,12 @@
|
|||||||
import React, {Component} from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Button } from 'reactstrap';
|
||||||
import {seafileAPI} from '../../utils/seafile-api';
|
import _ from 'lodash';
|
||||||
import {gettext, siteRoot} from '../../utils/constants';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
|
import { gettext, siteRoot } from '../../utils/constants';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Loading from '../../components/loading';
|
import Account from '../../components/common/account';
|
||||||
import {Button, Table} from 'reactstrap';
|
import { WorkWeixinDepartmentMembersList, WorkWeixinDepartmentsTreePanel } from './work-weixin';
|
||||||
|
import '../../css/work-weixin-departments.css';
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
|
||||||
class WorkWeixinDepartments extends Component {
|
class WorkWeixinDepartments extends Component {
|
||||||
|
|
||||||
@@ -255,8 +42,7 @@ class WorkWeixinDepartments extends Component {
|
|||||||
let rootIds = parentIds.concat(intersection).filter((v) => {
|
let rootIds = parentIds.concat(intersection).filter((v) => {
|
||||||
return parentIds.indexOf(v) === -1 || intersection.indexOf(v) === -1;
|
return parentIds.indexOf(v) === -1 || intersection.indexOf(v) === -1;
|
||||||
});
|
});
|
||||||
|
let cloneData = _.cloneDeep(list);
|
||||||
let cloneData = JSON.parse(JSON.stringify(list));
|
|
||||||
return cloneData.filter(father => {
|
return cloneData.filter(father => {
|
||||||
let branchArr = cloneData.filter(child => father.id === child.parentid);
|
let branchArr = cloneData.filter(child => father.id === child.parentid);
|
||||||
branchArr.length > 0 ? father.children = branchArr : '';
|
branchArr.length > 0 ? father.children = branchArr : '';
|
||||||
@@ -272,16 +58,12 @@ class WorkWeixinDepartments extends Component {
|
|||||||
departmentsTree: departmentsTree,
|
departmentsTree: departmentsTree,
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
this.handleError(error);
|
||||||
this.setState({
|
this.setState({
|
||||||
isTreeLoading: false,
|
isTreeLoading: false,
|
||||||
isMembersListLoading: false,
|
isMembersListLoading: false,
|
||||||
});
|
});
|
||||||
if (error.response) {
|
if (error.response && error.response.status === 403) {
|
||||||
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) {
|
|
||||||
window.location = siteRoot + 'sys/useradmin/';
|
window.location = siteRoot + 'sys/useradmin/';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -291,7 +73,7 @@ class WorkWeixinDepartments extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
isMembersListLoading: true,
|
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;
|
let membersTempObj = this.state.membersTempObj;
|
||||||
membersTempObj[department_id] = res.data.userlist;
|
membersTempObj[department_id] = res.data.userlist;
|
||||||
let canCheckUserIds = this.getCanCheckUserIds(res.data.userlist);
|
let canCheckUserIds = this.getCanCheckUserIds(res.data.userlist);
|
||||||
@@ -303,23 +85,16 @@ class WorkWeixinDepartments extends Component {
|
|||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({isMembersListLoading: false});
|
this.setState({isMembersListLoading: false});
|
||||||
if (error.response) {
|
this.handleError(error);
|
||||||
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});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
getCanCheckUserIds = (membersList) => {
|
getCanCheckUserIds = (membersList) => {
|
||||||
let canCheckUserIds = [];
|
let userIds = [];
|
||||||
for (let i = 0; i < membersList.length; i++) {
|
membersList.forEach((member) => {
|
||||||
let user = membersList[i];
|
if (!member.email) userIds.push(member.userid);
|
||||||
if (!user.email) {
|
});
|
||||||
canCheckUserIds.push(user.userid);
|
return userIds;
|
||||||
}
|
|
||||||
}
|
|
||||||
return canCheckUserIds;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
onChangeDepartment = (department_id) => {
|
onChangeDepartment = (department_id) => {
|
||||||
@@ -345,21 +120,15 @@ class WorkWeixinDepartments extends Component {
|
|||||||
if (user.userid in newUsersTempObj) {
|
if (user.userid in newUsersTempObj) {
|
||||||
delete newUsersTempObj[user.userid];
|
delete newUsersTempObj[user.userid];
|
||||||
if (this.state.isCheckedAll) {
|
if (this.state.isCheckedAll) {
|
||||||
this.setState({
|
this.setState({ isCheckedAll: false });
|
||||||
isCheckedAll: false,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newUsersTempObj[user.userid] = user;
|
newUsersTempObj[user.userid] = user;
|
||||||
if (Object.keys(newUsersTempObj).length === this.state.canCheckUserIds.length) {
|
if (Object.keys(newUsersTempObj).length === this.state.canCheckUserIds.length) {
|
||||||
this.setState({
|
this.setState({ isCheckedAll: true });
|
||||||
isCheckedAll: true,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({ newUsersTempObj: newUsersTempObj });
|
||||||
newUsersTempObj: newUsersTempObj,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -372,124 +141,139 @@ class WorkWeixinDepartments extends Component {
|
|||||||
let newUsersTempList = this.state.membersList.filter(user => {
|
let newUsersTempList = this.state.membersList.filter(user => {
|
||||||
return this.state.canCheckUserIds.indexOf(user.userid) !== -1;
|
return this.state.canCheckUserIds.indexOf(user.userid) !== -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let i = 0; i < newUsersTempList.length; i++) {
|
for (let i = 0; i < newUsersTempList.length; i++) {
|
||||||
newUsersTempObj[newUsersTempList[i].userid] = newUsersTempList[i];
|
newUsersTempObj[newUsersTempList[i].userid] = newUsersTempList[i];
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({ newUsersTempObj: newUsersTempObj });
|
||||||
newUsersTempObj: newUsersTempObj,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
this.setState({ newUsersTempObj: {} });
|
||||||
newUsersTempObj: {},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onSubmit = () => {
|
onSubmit = () => {
|
||||||
|
const { newUsersTempObj } = this.state;
|
||||||
|
if (JSON.stringify(newUsersTempObj) === '{}') return;
|
||||||
let userList = [];
|
let userList = [];
|
||||||
for (let i in this.state.newUsersTempObj) {
|
for (let i in newUsersTempObj) {
|
||||||
userList.push(this.state.newUsersTempObj[i]);
|
userList.push(newUsersTempObj[i]);
|
||||||
}
|
}
|
||||||
if (!userList.length) {
|
if (userList.length === 0) {
|
||||||
toaster.danger('未选择成员', {duration: 3});
|
toaster.danger('未选择成员', {duration: 3});
|
||||||
} else {
|
return;
|
||||||
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});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
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() {
|
componentDidMount() {
|
||||||
this.getWorkWeixinDepartmentsList();
|
this.getWorkWeixinDepartmentsList();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderNav() {
|
||||||
|
const btnClass = 'btn btn-secondary operation-item ';
|
||||||
return (
|
return (
|
||||||
<div className="main-panel-center">
|
<div className="main-panel-north border-left-show">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-toolbar">
|
||||||
<div className="cur-view-path">
|
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||||
<h3 className="sf-heading">{'企业微信集成'}</h3>
|
<Button className={btnClass + "my-1 d-md-none"} onClick={this.onSubmit}>{'导入用户'}</Button>
|
||||||
{JSON.stringify(this.state.newUsersTempObj) !== '{}' &&
|
<Button className={btnClass + "hidden-md-up"} onClick={this.onSubmit}>{'导入用户'}</Button>
|
||||||
<Button className="btn btn-secondary operation-item" onClick={this.onSubmit}>{'导入用户'}</Button>
|
</div>
|
||||||
}
|
<div className="common-toolbar">
|
||||||
</div>
|
<Account isAdminPanel={true}/>
|
||||||
<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>
|
</div>
|
||||||
</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;
|
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