684 lines
25 KiB
JavaScript
684 lines
25 KiB
JavaScript
import React, {Component} from 'react';
|
||
|
||
import {
|
||
Badge,
|
||
Button,
|
||
Col,
|
||
Divider,
|
||
Dropdown,
|
||
Form,
|
||
Input,
|
||
Layout,
|
||
Menu,
|
||
Modal,
|
||
Row,
|
||
Space,
|
||
Table,
|
||
Tag,
|
||
Tooltip,
|
||
Typography,
|
||
} from "antd";
|
||
import qs from "qs";
|
||
import UserModal from "./UserModal";
|
||
import request from "../../common/request";
|
||
import {message} from "antd/es";
|
||
import {
|
||
DeleteOutlined,
|
||
DownOutlined,
|
||
ExclamationCircleOutlined,
|
||
InsuranceOutlined,
|
||
LockOutlined,
|
||
PlusOutlined,
|
||
SyncOutlined,
|
||
UndoOutlined
|
||
} from '@ant-design/icons';
|
||
import UserShareAsset from "./UserShareAsset";
|
||
import {hasPermission} from "../../service/permission";
|
||
import dayjs from "dayjs";
|
||
|
||
const confirm = Modal.confirm;
|
||
const {Search} = Input;
|
||
const {Title, Text} = Typography;
|
||
const {Content} = Layout;
|
||
|
||
class User extends Component {
|
||
|
||
inputRefOfNickname = React.createRef();
|
||
inputRefOfUsername = React.createRef();
|
||
inputRefOfMail = React.createRef();
|
||
changePasswordFormRef = React.createRef()
|
||
|
||
state = {
|
||
items: [],
|
||
total: 0,
|
||
queryParams: {
|
||
pageIndex: 1,
|
||
pageSize: 10
|
||
},
|
||
loading: false,
|
||
modalVisible: false,
|
||
modalTitle: '',
|
||
modalConfirmLoading: false,
|
||
model: null,
|
||
selectedRowKeys: [],
|
||
delBtnLoading: false,
|
||
assetVisible: false,
|
||
changePasswordVisible: false,
|
||
changePasswordConfirmLoading: false,
|
||
selectedRow: {}
|
||
};
|
||
|
||
componentDidMount() {
|
||
this.loadTableData();
|
||
}
|
||
|
||
async delete(id) {
|
||
let result = await request.delete('/users/' + id);
|
||
if (result.code === 1) {
|
||
message.success('操作成功', 3);
|
||
await this.loadTableData(this.state.queryParams);
|
||
} else {
|
||
message.error('删除失败 :( ' + result.message, 10);
|
||
}
|
||
}
|
||
|
||
async loadTableData(queryParams) {
|
||
this.setState({
|
||
loading: true
|
||
});
|
||
|
||
queryParams = queryParams || this.state.queryParams;
|
||
|
||
let paramsStr = qs.stringify(queryParams);
|
||
|
||
let data = {
|
||
items: [],
|
||
total: 0
|
||
};
|
||
|
||
try {
|
||
let result = await request.get('/users/paging?' + paramsStr);
|
||
if (result.code === 1) {
|
||
data = result.data;
|
||
} else {
|
||
message.error(result.message, 10);
|
||
}
|
||
} catch (e) {
|
||
|
||
} finally {
|
||
const items = data.items.map(item => {
|
||
return {'key': item['id'], ...item}
|
||
})
|
||
this.setState({
|
||
items: items,
|
||
total: data.total,
|
||
queryParams: queryParams,
|
||
loading: false
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
handleChangPage = (pageIndex, pageSize) => {
|
||
let queryParams = this.state.queryParams;
|
||
queryParams.pageIndex = pageIndex;
|
||
queryParams.pageSize = pageSize;
|
||
|
||
this.setState({
|
||
queryParams: queryParams
|
||
});
|
||
|
||
this.loadTableData(queryParams).then(r => {
|
||
})
|
||
};
|
||
|
||
showDeleteConfirm(id, content) {
|
||
let self = this;
|
||
confirm({
|
||
title: '您确定要删除此用户吗?',
|
||
content: content,
|
||
okText: '确定',
|
||
okType: 'danger',
|
||
cancelText: '取消',
|
||
onOk() {
|
||
self.delete(id);
|
||
}
|
||
});
|
||
};
|
||
|
||
showModal(title, user = {}) {
|
||
this.setState({
|
||
model: user,
|
||
modalVisible: true,
|
||
modalTitle: title
|
||
});
|
||
};
|
||
|
||
handleCancelModal = e => {
|
||
this.setState({
|
||
modalVisible: false,
|
||
modalTitle: ''
|
||
});
|
||
};
|
||
|
||
handleOk = async (formData) => {
|
||
// 弹窗 form 传来的数据
|
||
this.setState({
|
||
modalConfirmLoading: true
|
||
});
|
||
|
||
if (formData.id) {
|
||
// 向后台提交数据
|
||
const result = await request.put('/users/' + formData.id, formData);
|
||
if (result.code === 1) {
|
||
message.success('操作成功', 3);
|
||
|
||
this.setState({
|
||
modalVisible: false
|
||
});
|
||
await this.loadTableData(this.state.queryParams);
|
||
} else {
|
||
message.error('操作失败 :( ' + result.message, 10);
|
||
}
|
||
} else {
|
||
// 向后台提交数据
|
||
const result = await request.post('/users', formData);
|
||
if (result.code === 1) {
|
||
message.success('操作成功', 3);
|
||
|
||
this.setState({
|
||
modalVisible: false
|
||
});
|
||
await this.loadTableData(this.state.queryParams);
|
||
} else {
|
||
message.error('操作失败 :( ' + result.message, 10);
|
||
}
|
||
}
|
||
|
||
this.setState({
|
||
modalConfirmLoading: false
|
||
});
|
||
};
|
||
|
||
handleSearchByUsername = username => {
|
||
let query = {
|
||
...this.state.queryParams,
|
||
'pageIndex': 1,
|
||
'pageSize': this.state.queryParams.pageSize,
|
||
'username': username,
|
||
}
|
||
|
||
this.loadTableData(query);
|
||
};
|
||
|
||
handleSearchByNickname = nickname => {
|
||
let query = {
|
||
...this.state.queryParams,
|
||
'pageIndex': 1,
|
||
'pageSize': this.state.queryParams.pageSize,
|
||
'nickname': nickname,
|
||
}
|
||
|
||
this.loadTableData(query);
|
||
};
|
||
|
||
handleSearchByMail = mail => {
|
||
let query = {
|
||
...this.state.queryParams,
|
||
'pageIndex': 1,
|
||
'pageSize': this.state.queryParams.pageSize,
|
||
'mail': mail,
|
||
}
|
||
|
||
this.loadTableData(query);
|
||
};
|
||
|
||
batchDelete = async () => {
|
||
this.setState({
|
||
delBtnLoading: true
|
||
})
|
||
try {
|
||
let result = await request.delete('/users/' + this.state.selectedRowKeys.join(','));
|
||
if (result['code'] === 1) {
|
||
message.success('操作成功', 3);
|
||
this.setState({
|
||
selectedRowKeys: []
|
||
})
|
||
await this.loadTableData(this.state.queryParams);
|
||
} else {
|
||
message.error(result['message'], 10);
|
||
}
|
||
} finally {
|
||
this.setState({
|
||
delBtnLoading: false
|
||
})
|
||
}
|
||
}
|
||
|
||
handleAssetCancel = () => {
|
||
this.loadTableData()
|
||
this.setState({
|
||
assetVisible: false
|
||
})
|
||
}
|
||
|
||
handleChangePassword = async (values) => {
|
||
this.setState({
|
||
changePasswordConfirmLoading: true
|
||
})
|
||
|
||
let result = await request.post(`/users/${this.state.selectedRow['id']}/change-password?password=${values['password']}`);
|
||
if (result['code'] === 1) {
|
||
message.success('操作成功', 3);
|
||
} else {
|
||
message.error(result['message'], 10);
|
||
}
|
||
|
||
this.setState({
|
||
changePasswordConfirmLoading: false,
|
||
changePasswordVisible: false
|
||
})
|
||
}
|
||
|
||
handleTableChange = (pagination, filters, sorter) => {
|
||
let query = {
|
||
...this.state.queryParams,
|
||
'order': sorter.order,
|
||
'field': sorter.field
|
||
}
|
||
|
||
this.loadTableData(query);
|
||
}
|
||
|
||
render() {
|
||
|
||
const columns = [{
|
||
title: '序号',
|
||
dataIndex: 'id',
|
||
key: 'id',
|
||
render: (id, record, index) => {
|
||
return index + 1;
|
||
}
|
||
}, {
|
||
title: '登录账号',
|
||
dataIndex: 'username',
|
||
key: 'username',
|
||
sorter: true,
|
||
}, {
|
||
title: '用户昵称',
|
||
dataIndex: 'nickname',
|
||
key: 'nickname',
|
||
sorter: true,
|
||
}, {
|
||
title: '用户类型',
|
||
dataIndex: 'type',
|
||
key: 'type',
|
||
render: (text, record) => {
|
||
|
||
if (text === 'user') {
|
||
return (
|
||
<Tag>普通用户</Tag>
|
||
);
|
||
} else if (text === 'admin') {
|
||
return (
|
||
<Tag color="blue">管理用户</Tag>
|
||
);
|
||
} else {
|
||
return text;
|
||
}
|
||
}
|
||
}, {
|
||
title: '邮箱',
|
||
dataIndex: 'mail',
|
||
key: 'mail',
|
||
}, {
|
||
title: '二次认证',
|
||
dataIndex: 'totpSecret',
|
||
key: 'totpSecret',
|
||
render: (text, record) => {
|
||
|
||
if (text === '1') {
|
||
return <Tag icon={<InsuranceOutlined/>} color="success">开启</Tag>;
|
||
} else {
|
||
return <Tag icon={<ExclamationCircleOutlined/>} color="warning">关闭</Tag>;
|
||
}
|
||
}
|
||
}, {
|
||
title: '在线状态',
|
||
dataIndex: 'online',
|
||
key: 'online',
|
||
render: text => {
|
||
if (text) {
|
||
return (<Badge status="success" text="在线"/>);
|
||
} else {
|
||
return (<Badge status="default" text="离线"/>);
|
||
}
|
||
}
|
||
}, {
|
||
title: '授权资产',
|
||
dataIndex: 'sharerAssetCount',
|
||
key: 'sharerAssetCount',
|
||
render: (text, record, index) => {
|
||
return <Button type='link' onClick={async () => {
|
||
this.setState({
|
||
assetVisible: true,
|
||
sharer: record['id']
|
||
})
|
||
}}>{text}</Button>
|
||
}
|
||
}, {
|
||
title: '创建日期',
|
||
dataIndex: 'created',
|
||
key: 'created',
|
||
render: (text, record) => {
|
||
return (
|
||
<Tooltip title={text}>
|
||
{dayjs(text).fromNow()}
|
||
</Tooltip>
|
||
)
|
||
},
|
||
sorter: true,
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
render: (text, record) => {
|
||
|
||
const menu = (
|
||
<Menu>
|
||
<Menu.Item key="1">
|
||
<Button type="text" size='small'
|
||
onClick={() => {
|
||
this.setState({
|
||
changePasswordVisible: true,
|
||
selectedRow: record
|
||
})
|
||
}}>修改密码</Button>
|
||
</Menu.Item>
|
||
|
||
<Menu.Item key="2">
|
||
<Button type="text" size='small'
|
||
onClick={() => {
|
||
confirm({
|
||
title: '您确定要重置此用户的双因素认证吗?',
|
||
content: record['name'],
|
||
okText: '确定',
|
||
cancelText: '取消',
|
||
onOk: async () => {
|
||
let result = await request.post(`/users/${record['id']}/reset-totp`);
|
||
if (result['code'] === 1) {
|
||
message.success('操作成功', 3);
|
||
this.loadTableData();
|
||
} else {
|
||
message.error(result['message'], 10);
|
||
}
|
||
}
|
||
});
|
||
}}>重置双因素认证</Button>
|
||
</Menu.Item>
|
||
|
||
<Menu.Divider/>
|
||
<Menu.Item key="5">
|
||
<Button type="text" size='small' danger
|
||
disabled={!hasPermission(record['owner'])}
|
||
onClick={() => this.showDeleteConfirm(record.id, record.name)}>删除</Button>
|
||
</Menu.Item>
|
||
</Menu>
|
||
);
|
||
|
||
return (
|
||
<div>
|
||
<Button type="link" size='small'
|
||
onClick={async () => {
|
||
let result = await request.get(`/users/${record['id']}`);
|
||
if (result['code'] !== 1) {
|
||
message.error(result['message']);
|
||
return;
|
||
}
|
||
this.showModal('更新用户', result['data']);
|
||
}}>编辑</Button>
|
||
<Dropdown overlay={menu}>
|
||
<Button type="link" size='small'>
|
||
更多 <DownOutlined/>
|
||
</Button>
|
||
</Dropdown>
|
||
</div>
|
||
)
|
||
},
|
||
}
|
||
];
|
||
|
||
const selectedRowKeys = this.state.selectedRowKeys;
|
||
const rowSelection = {
|
||
selectedRowKeys: this.state.selectedRowKeys,
|
||
onChange: (selectedRowKeys, selectedRows) => {
|
||
this.setState({selectedRowKeys});
|
||
},
|
||
};
|
||
const hasSelected = selectedRowKeys.length > 0;
|
||
|
||
return (
|
||
<>
|
||
<Content className="site-layout-background page-content">
|
||
<div style={{marginBottom: 20}}>
|
||
<Row justify="space-around" align="middle" gutter={24}>
|
||
<Col span={8} key={1}>
|
||
<Title level={3}>用户列表</Title>
|
||
</Col>
|
||
<Col span={16} key={2} style={{textAlign: 'right'}}>
|
||
<Space>
|
||
|
||
<Search
|
||
ref={this.inputRefOfNickname}
|
||
placeholder="用户昵称"
|
||
allowClear
|
||
onSearch={this.handleSearchByNickname}
|
||
/>
|
||
|
||
<Search
|
||
ref={this.inputRefOfUsername}
|
||
placeholder="登录账号"
|
||
allowClear
|
||
onSearch={this.handleSearchByUsername}
|
||
/>
|
||
|
||
<Search
|
||
ref={this.inputRefOfMail}
|
||
placeholder="邮箱"
|
||
allowClear
|
||
onSearch={this.handleSearchByMail}
|
||
/>
|
||
|
||
<Tooltip title='重置查询'>
|
||
|
||
<Button icon={<UndoOutlined/>} onClick={() => {
|
||
this.inputRefOfUsername.current.setValue('');
|
||
this.inputRefOfNickname.current.setValue('');
|
||
this.inputRefOfMail.current.setValue('');
|
||
this.loadTableData({pageIndex: 1, pageSize: 10})
|
||
}}>
|
||
|
||
</Button>
|
||
</Tooltip>
|
||
|
||
<Divider type="vertical"/>
|
||
|
||
<Tooltip title="新增">
|
||
<Button type="dashed" icon={<PlusOutlined/>}
|
||
onClick={() => this.showModal('新增用户', {})}>
|
||
|
||
</Button>
|
||
</Tooltip>
|
||
|
||
<Tooltip title="刷新列表">
|
||
<Button icon={<SyncOutlined/>} onClick={() => {
|
||
this.loadTableData(this.state.queryParams)
|
||
}}>
|
||
|
||
</Button>
|
||
</Tooltip>
|
||
|
||
{/*<Tooltip title="批量启用">*/}
|
||
{/* <Button type="dashed" danger disabled={!hasSelected}*/}
|
||
{/* icon={<IssuesCloseOutlined/>}*/}
|
||
{/* loading={this.state.delBtnLoading}*/}
|
||
{/* onClick={() => {*/}
|
||
{/* constant content = <div>*/}
|
||
{/* 您确定要启用选中的<Text style={{color: '#1890FF'}}*/}
|
||
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
|
||
{/* </div>;*/}
|
||
{/* confirm({*/}
|
||
{/* icon: <ExclamationCircleOutlined/>,*/}
|
||
{/* content: content,*/}
|
||
{/* onOk: () => {*/}
|
||
|
||
{/* },*/}
|
||
{/* onCancel() {*/}
|
||
|
||
{/* },*/}
|
||
{/* });*/}
|
||
{/* }}>*/}
|
||
|
||
{/* </Button>*/}
|
||
{/*</Tooltip>*/}
|
||
|
||
{/*<Tooltip title="批量禁用">*/}
|
||
{/* <Button type="default" danger disabled={!hasSelected} icon={<StopOutlined/>}*/}
|
||
{/* loading={this.state.delBtnLoading}*/}
|
||
{/* onClick={() => {*/}
|
||
{/* constant content = <div>*/}
|
||
{/* 您确定要禁用选中的<Text style={{color: '#1890FF'}}*/}
|
||
{/* strong>{this.state.selectedRowKeys.length}</Text>条记录吗?*/}
|
||
{/* </div>;*/}
|
||
{/* confirm({*/}
|
||
{/* icon: <ExclamationCircleOutlined/>,*/}
|
||
{/* content: content,*/}
|
||
{/* onOk: () => {*/}
|
||
|
||
{/* },*/}
|
||
{/* onCancel() {*/}
|
||
|
||
{/* },*/}
|
||
{/* });*/}
|
||
{/* }}>*/}
|
||
|
||
{/* </Button>*/}
|
||
{/*</Tooltip>*/}
|
||
|
||
<Tooltip title="批量删除">
|
||
<Button type="primary" danger disabled={!hasSelected} icon={<DeleteOutlined/>}
|
||
loading={this.state.delBtnLoading}
|
||
onClick={() => {
|
||
const content = <div>
|
||
您确定要删除选中的<Text style={{color: '#1890FF'}}
|
||
strong>{this.state.selectedRowKeys.length}</Text>条记录吗?
|
||
</div>;
|
||
confirm({
|
||
icon: <ExclamationCircleOutlined/>,
|
||
content: content,
|
||
onOk: () => {
|
||
this.batchDelete()
|
||
},
|
||
onCancel() {
|
||
|
||
},
|
||
});
|
||
}}>
|
||
|
||
</Button>
|
||
</Tooltip>
|
||
|
||
</Space>
|
||
</Col>
|
||
</Row>
|
||
</div>
|
||
|
||
<Table rowSelection={rowSelection}
|
||
dataSource={this.state.items}
|
||
columns={columns}
|
||
position={'both'}
|
||
pagination={{
|
||
showSizeChanger: true,
|
||
current: this.state.queryParams.pageIndex,
|
||
pageSize: this.state.queryParams.pageSize,
|
||
onChange: this.handleChangPage,
|
||
onShowSizeChange: this.handleChangPage,
|
||
total: this.state.total,
|
||
showTotal: total => `总计 ${total} 条`
|
||
}}
|
||
loading={this.state.loading}
|
||
onChange={this.handleTableChange}
|
||
/>
|
||
|
||
{/* 为了屏蔽ant modal 关闭后数据仍然遗留的问题*/}
|
||
{
|
||
this.state.modalVisible ?
|
||
<UserModal
|
||
visible={this.state.modalVisible}
|
||
title={this.state.modalTitle}
|
||
handleOk={this.handleOk}
|
||
handleCancel={this.handleCancelModal}
|
||
confirmLoading={this.state.modalConfirmLoading}
|
||
model={this.state.model}
|
||
>
|
||
</UserModal> : undefined
|
||
}
|
||
|
||
<Modal
|
||
width={window.innerWidth * 0.8}
|
||
title='已授权资产'
|
||
visible={this.state.assetVisible}
|
||
maskClosable={false}
|
||
destroyOnClose={true}
|
||
onOk={() => {
|
||
|
||
}}
|
||
onCancel={this.handleAssetCancel}
|
||
okText='确定'
|
||
cancelText='取消'
|
||
footer={null}
|
||
>
|
||
<UserShareAsset
|
||
sharer={this.state.sharer}
|
||
/>
|
||
</Modal>
|
||
|
||
{
|
||
this.state.changePasswordVisible ?
|
||
<Modal title="修改密码" visible={this.state.changePasswordVisible}
|
||
confirmLoading={this.state.changePasswordConfirmLoading}
|
||
maskClosable={false}
|
||
onOk={() => {
|
||
this.changePasswordFormRef.current
|
||
.validateFields()
|
||
.then(values => {
|
||
this.changePasswordFormRef.current.resetFields();
|
||
this.handleChangePassword(values);
|
||
})
|
||
.catch(info => {
|
||
|
||
});
|
||
}}
|
||
onCancel={() => {
|
||
this.setState({
|
||
changePasswordVisible: false
|
||
})
|
||
}}>
|
||
|
||
<Form ref={this.changePasswordFormRef}>
|
||
|
||
<Form.Item name='password' rules={[{required: true, message: '请输入新密码'}]}>
|
||
<Input prefix={<LockOutlined/>} placeholder="请输入新密码"/>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal> : undefined
|
||
}
|
||
|
||
</Content>
|
||
</>
|
||
);
|
||
}
|
||
}
|
||
|
||
export default User;
|