feat: add space member

This commit is contained in:
seaheart
2023-06-01 23:24:29 +08:00
parent b553c82083
commit 57237c254a
12 changed files with 408 additions and 28 deletions
+4
View File
@@ -0,0 +1,4 @@
export const FORM_LAYOUT = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};
+13
View File
@@ -352,6 +352,18 @@ let LangEn = {
totalSizeLimitTip: 'Please enter total space limit',
deleteHint:
'This operation will permanently delete the space. Please delete all files and members of the space first. Do you want to continue?',
memberManage: 'Space member Manage',
member: 'member',
memberTip: 'Please enter member',
memberRole: 'Member role',
memberRoleTip: 'Please select member role',
bindMember: 'Bind space member',
unBindMember: 'Unbind space member',
unBindMemberHint:
'This operation will unbind the member from the space. Do you want to continue?',
memberRoleReadonly: 'Readonly',
memberRoleReadWrite: 'Read and write',
memberRoleAdmin: 'Admin',
},
model: {
usernameRule: 'only lowercase letter and number and _ is permitted.',
@@ -401,6 +413,7 @@ let LangEn = {
all: 'All',
refresh: 'refresh',
inputRequired: 'Input required',
selectRequired: 'Select required',
};
export default LangEn;
+12
View File
@@ -345,6 +345,17 @@ let LangZh = {
totalSizeLimitTip: '请输入总大小限制',
deleteHint:
'此操作将永久删除该空间, 请先删除空间所有文件和空间成员, 是否继续?',
memberManage: '空间成员管理',
member: '空间成员',
memberTip: '请搜索选择用户',
memberRole: '成员角色',
memberRoleTip: '请选择成员角色',
bindMember: '绑定空间成员',
unBindMember: '解绑空间成员',
unBindMemberHint: '此操作将解绑该空间成员, 是否继续?',
memberRoleReadonly: '只读',
memberRoleReadWrite: '可读写',
memberRoleAdmin: '管理员',
},
model: {
usernameRule: '用户名只能包含字母,数字和"_"',
@@ -395,6 +406,7 @@ let LangZh = {
all: '所有',
refresh: '刷新',
inputRequired: '该项必填',
selectRequired: '该项必选',
};
export default LangZh;
+2 -2
View File
@@ -1,7 +1,7 @@
import BaseEntity from '../base/BaseEntity';
import User from '../user/User';
export interface FormValues {
export interface SpaceFormValues {
name: string;
sizeLimit: number;
totalSizeLimit: number;
@@ -25,7 +25,7 @@ export default class Space extends BaseEntity {
this.assignEntity('user', User);
}
getForm(): FormValues {
getForm(): SpaceFormValues {
return {
name: this.name!,
sizeLimit: this.sizeLimit,
+26 -1
View File
@@ -1,8 +1,17 @@
import BaseEntity from '../../base/BaseEntity';
import User from '../../user/User';
import { SpaceMemberRole } from './SpaceMemberRole';
import Filter from '../../base/filter/Filter';
import SortFilter from '../../base/filter/SortFilter';
import InputFilter from '../../base/filter/InputFilter';
export default class Space extends BaseEntity {
export interface SpaceMemberFormValues {
spaceUuid: string;
userUuid: string;
role: SpaceMemberRole;
uuid: string | null;
}
export default class SpaceMember extends BaseEntity {
spaceUuid: string | null = null;
userUuid: string | null = null;
role: SpaceMemberRole | null = null;
@@ -16,4 +25,20 @@ export default class Space extends BaseEntity {
super.assign(obj);
this.assignEntity('user', User);
}
getFilters(): Filter[] {
return [
...super.getFilters(),
new InputFilter('空间ID', 'spaceUuid', '按空间ID搜索', false),
];
}
getForm(): SpaceMemberFormValues {
return {
spaceUuid: this.spaceUuid!,
userUuid: this.userUuid!,
role: this.role!,
uuid: this.uuid ? this.uuid : null,
};
}
}
@@ -1,5 +1,6 @@
import SelectionOption from '../../base/option/SelectionOption';
import Lang from '../../global/Lang';
import ColorSelectionOption from '../../base/option/ColorSelectionOption';
import Color from '../../base/option/Color';
enum SpaceMemberRole {
READ_ONLY = 'READ_ONLY',
@@ -11,22 +12,25 @@ const SpaceMemberRoles: SpaceMemberRole[] = Object.keys(SpaceMemberRole).map(
(k) => k as SpaceMemberRole
);
const SpaceMemberRoleMap: Record<SpaceMemberRole, SelectionOption> = {
const SpaceMemberRoleMap: Record<SpaceMemberRole, ColorSelectionOption> = {
[SpaceMemberRole.READ_ONLY]: {
name: Lang.t('spaceMember.readOnly'),
name: Lang.t('space.memberRoleReadonly'),
value: SpaceMemberRole.READ_ONLY,
color: Color.SUCCESS,
},
[SpaceMemberRole.READ_WRITE]: {
name: Lang.t('spaceMember.readWrite'),
name: Lang.t('space.memberRoleReadWrite'),
value: SpaceMemberRole.READ_WRITE,
color: Color.PRIMARY,
},
[SpaceMemberRole.ADMIN]: {
name: Lang.t('spaceMember.admin'),
name: Lang.t('space.memberRoleAdmin'),
value: SpaceMemberRole.ADMIN,
color: Color.DANGER,
},
};
const SpaceMemberRoleList: SelectionOption[] = [];
const SpaceMemberRoleList: ColorSelectionOption[] = [];
SpaceMemberRoles.forEach((type: SpaceMemberRole, index: number) => {
SpaceMemberRoleList.push(SpaceMemberRoleMap[type]);
});
+7 -1
View File
@@ -45,6 +45,7 @@ import TopLayout from './layout/TopLayout';
import ContentLayout from './layout/ContentLayout';
import SpaceList from './space/List';
import SpaceMemberList from './space/member/List';
interface IProps extends RouteComponentProps<{}> {}
@@ -175,7 +176,12 @@ class RawFrame extends TankComponent<IProps, IState> {
<Route path="/share/detail/:uuid" component={ShareDetail} />
<Route path="/bin/list" component={BinList} />
<Route path="/space" component={SpaceList} />
<Route path="/space" exact component={SpaceList} />
<Route
path="/space/:spaceUuid/member"
exact
component={SpaceMemberList}
/>
</div>
) : (
<div className="pages-content">
+10 -8
View File
@@ -2,7 +2,7 @@ import React from 'react';
import TankComponent from '../../common/component/TankComponent';
import { RouteComponentProps } from 'react-router-dom';
import Pager from '../../common/model/base/Pager';
import Space, { FormValues } from '../../common/model/space/Space';
import Space, { SpaceFormValues } from '../../common/model/space/Space';
import {
Button,
Card,
@@ -36,7 +36,7 @@ export default class List extends TankComponent<IProps, IState> {
modalState: {
visible: boolean;
mode: 'create' | 'edit';
initialValues?: FormValues;
initialValues?: SpaceFormValues;
} = {
visible: false,
mode: 'create',
@@ -74,7 +74,7 @@ export default class List extends TankComponent<IProps, IState> {
this.updateUI();
}
handleConfirmModalForm(values: FormValues) {
handleConfirmModalForm(values: SpaceFormValues) {
const space = new Space();
space.assign(values);
space.httpSave(
@@ -83,11 +83,11 @@ export default class List extends TankComponent<IProps, IState> {
this.pager.httpList();
},
null,
() => this.handleCancelModalForm()
() => this.handleHideModalForm()
);
}
handleCancelModalForm() {
handleHideModalForm() {
this.modalState = {
...this.modalState,
visible: false,
@@ -109,7 +109,9 @@ export default class List extends TankComponent<IProps, IState> {
},
});
}
handleSpaceMember() {}
handleSpaceMember(space: Space) {
this.props.history.push(`/space/${space.uuid}/member`);
}
render() {
const { pager, modalState } = this;
@@ -139,7 +141,7 @@ export default class List extends TankComponent<IProps, IState> {
<AntdSpace className="space-item-icons">
<TeamOutlined
className="btn-action btn-member"
onClick={() => this.handleSpaceMember()}
onClick={() => this.handleSpaceMember(space)}
/>
<EditOutlined
className="btn-action btn-edit"
@@ -191,7 +193,7 @@ export default class List extends TankComponent<IProps, IState> {
mode={modalState.mode}
initialValues={modalState.initialValues}
onOk={this.handleConfirmModalForm.bind(this)}
onCancel={() => this.handleCancelModalForm()}
onCancel={() => this.handleHideModalForm()}
/>
)}
</div>
View File
+225
View File
@@ -0,0 +1,225 @@
import TankComponent from '../../../common/component/TankComponent';
import { Link, RouteComponentProps } from 'react-router-dom';
import Pager from '../../../common/model/base/Pager';
import SpaceMember, {
SpaceMemberFormValues,
} from '../../../common/model/space/member/SpaceMember';
import Lang from '../../../common/model/global/Lang';
import { Button, Modal, Table, Tag, Tooltip, Typography } from 'antd';
import TankTitle from '../../widget/TankTitle';
import React from 'react';
import TableEmpty from '../../widget/TableEmpty';
import SortDirection from '../../../common/model/base/SortDirection';
import { ColumnsType } from 'antd/es/table';
import User from '../../../common/model/user/User';
import Moon from '../../../common/model/global/Moon';
import {
SpaceMemberRole,
SpaceMemberRoleMap,
} from '../../../common/model/space/member/SpaceMemberRole';
import DateUtil from '../../../common/util/DateUtil';
import {
DeleteOutlined,
EditOutlined,
ExclamationCircleFilled,
} from '@ant-design/icons';
import Color from '../../../common/model/base/option/Color';
import MessageBoxUtil from '../../../common/util/MessageBoxUtil';
import ModalForm from './widget/ModalForm';
import FilterPanel from '../../widget/filter/FilterPanel';
interface IProps
extends RouteComponentProps<{
spaceUuid: string;
}> {}
interface IState {}
export default class SpaceMemberList extends TankComponent<IProps, IState> {
spaceUuid = this.props.match.params.spaceUuid;
pager = new Pager<SpaceMember>(this, SpaceMember, 10);
currentUser: User = Moon.getSingleton().user;
modalState: {
visible: boolean;
mode: 'create' | 'edit';
initialValues?: SpaceMemberFormValues;
} = {
visible: false,
mode: 'create',
};
componentDidMount() {
this.pager.enableHistory();
this.pager.setFilterValue('spaceUuid', this.spaceUuid);
this.refresh();
}
handleCreate() {
this.modalState = {
visible: true,
mode: 'create',
};
this.updateUI();
}
refresh() {
//如果没有任何的排序,默认使用时间倒序
if (!this.pager.getCurrentSortFilter()) {
this.pager.setFilterValue('orderCreateTime', SortDirection.DESC);
}
this.pager.httpList();
}
handleEdit(spaceMember: SpaceMember) {
this.modalState = {
visible: true,
mode: 'edit',
initialValues: spaceMember.getForm(),
};
this.updateUI();
}
handleDelete(spaceMember: SpaceMember) {
Modal.confirm({
title: Lang.t('space.unBindMemberHint'),
icon: <ExclamationCircleFilled twoToneColor={Color.WARNING} />,
cancelText: Lang.t('cancel'),
okText: Lang.t('confirm'),
onOk: () => {
spaceMember.httpDel(() => {
MessageBoxUtil.success(Lang.t('operationSuccess'));
this.refresh();
});
},
});
}
handleConfirmModalForm(values: SpaceMemberFormValues) {
const spaceMember = new SpaceMember();
spaceMember.spaceUuid = this.spaceUuid;
spaceMember.assign(values);
spaceMember.httpSave(
() => {
MessageBoxUtil.success(Lang.t('operationSuccess'));
this.refresh();
},
null,
() => this.handleHideModalForm()
);
}
handleHideModalForm() {
this.modalState = {
...this.modalState,
visible: false,
};
this.updateUI();
}
render() {
const { pager, modalState } = this;
const columns: ColumnsType<SpaceMember> = [
{
title: Lang.t('user.avatar'),
dataIndex: 'user',
responsive: ['md', 'lg', 'xl', 'xxl'],
render: (user: User | null) => (
<Link to={`/user/detail/${user?.uuid}`}>
<img
alt="avatar"
className="avatar-small cursor"
src={user?.getAvatarUrl()}
/>
</Link>
),
},
{
title: Lang.t('user.username'),
dataIndex: 'user',
render: (user: User | null) => {
return (
<div>
<div>
<Link to={`/user/detail/${user?.uuid}`}>{user?.username}</Link>
</div>
{this.currentUser.uuid === user?.uuid && (
<div className="text-danger">(It's you)</div>
)}
</div>
);
},
},
{
title: Lang.t('space.memberRole'),
dataIndex: 'role',
render: (role: SpaceMemberRole) => (
<Tag color={SpaceMemberRoleMap[role]?.color}>
{SpaceMemberRoleMap[role]?.name}
</Tag>
),
},
{
title: Lang.t('createTime'),
dataIndex: 'createTime',
responsive: ['md', 'lg', 'xl', 'xxl'],
sorter: true,
sortOrder: pager.getDefaultSortOrder('createTime'),
sortDirections: [SortDirection.DESCEND, SortDirection.ASCEND],
render: (text: Date) => DateUtil.simpleDateTime(text),
},
{
title: Lang.t('operation'),
render: (_, record) => (
<>
<Tooltip title={Lang.t('edit')}>
<Typography.Link>
<EditOutlined
className="btn-action"
onClick={() => this.handleEdit(record)}
/>
</Typography.Link>
</Tooltip>
<Tooltip title={Lang.t('space.unBindMember')}>
<DeleteOutlined
className="btn-action"
style={{ color: Color.DANGER }}
onClick={() => this.handleDelete(record)}
/>
</Tooltip>
</>
),
},
];
return (
<div className="page-space-member-list">
<TankTitle name={Lang.t('space.memberManage')}>
<Button type="primary" onClick={() => this.handleCreate()}>
{Lang.t('space.bindMember')}
</Button>
</TankTitle>
<Table
rowKey="uuid"
loading={pager.loading}
dataSource={pager.data}
columns={columns}
pagination={pager.getPagination()}
onChange={pager.tableOnChange.bind(pager)}
locale={{
emptyText: (
<TableEmpty pager={pager} onRefresh={this.refresh.bind(this)} />
),
}}
/>
{modalState.visible && (
<ModalForm
mode={modalState.mode}
initialValues={modalState.initialValues}
onOk={this.handleConfirmModalForm.bind(this)}
onCancel={() => this.handleHideModalForm()}
/>
)}
</div>
);
}
}
@@ -0,0 +1,93 @@
import Lang from '../../../../common/model/global/Lang';
import { Form, Input, Modal, Select } from 'antd';
import React, { useEffect, useRef, useState } from 'react';
import { FORM_LAYOUT } from '../../../../common/const/Form';
import { SpaceMemberFormValues } from '../../../../common/model/space/member/SpaceMember';
import { SpaceMemberRoleList } from '../../../../common/model/space/member/SpaceMemberRole';
import Pager from '../../../../common/model/base/Pager';
import User from '../../../../common/model/user/User';
interface Props {
mode: 'create' | 'edit';
initialValues?: SpaceMemberFormValues;
onOk: (values: SpaceMemberFormValues) => void;
onCancel: () => void;
}
const ModalForm = ({ mode, initialValues, onOk, onCancel }: Props) => {
const isCreate = mode === 'create';
const userPager = useRef(new Pager<User>(null, User, 100));
const [form] = Form.useForm<SpaceMemberFormValues>();
const [userList, setUserList] = useState<User[]>([]); // 用户列表
useEffect(() => {
refresh();
}, []);
const refresh = () => {
userPager.current.httpList(() => {
setUserList(userPager.current.data);
});
};
const handleSearchUser = (value?: string) => {
userPager.current.setFilterValue('username', value);
refresh();
};
return (
<Modal
visible
title={isCreate ? Lang.t('create') : Lang.t('edit')}
onOk={form.submit}
onCancel={onCancel}
okText={Lang.t('confirm')}
cancelText={Lang.t('cancel')}
>
<Form
{...FORM_LAYOUT}
form={form}
onFinish={onOk}
initialValues={initialValues}
>
{/*编辑时暂存uuid*/}
<Form.Item name="uuid" hidden>
<Input />
</Form.Item>
<Form.Item
name="userUuid"
label={Lang.t('space.member')}
rules={[{ required: true, message: Lang.t('selectRequired') }]}
>
<Select
options={userList.map((item) => ({
label: item.username,
value: item.uuid,
}))}
placeholder={Lang.t('space.memberTip')}
disabled={!isCreate}
showSearch
allowClear
filterOption={false}
onSearch={handleSearchUser}
/>
</Form.Item>
<Form.Item
name="role"
label={Lang.t('space.memberRole')}
rules={[{ required: true, message: Lang.t('selectRequired') }]}
>
<Select
allowClear
placeholder={Lang.t('space.memberRoleTip')}
options={SpaceMemberRoleList.map((item) => ({
label: item.name,
value: item.value,
}))}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default ModalForm;
+6 -10
View File
@@ -2,22 +2,18 @@ import React from 'react';
import { Form, Input, Modal } from 'antd';
import Lang from '../../../common/model/global/Lang';
import InputSize from '../../widget/form/InputSize';
import { FormValues } from '../../../common/model/space/Space';
import { FORM_LAYOUT } from '../../../common/const/Form';
import { SpaceFormValues } from '../../../common/model/space/Space';
interface Props {
mode: 'create' | 'edit';
initialValues?: FormValues;
onOk: (values: FormValues) => void;
initialValues?: SpaceFormValues;
onOk: (values: SpaceFormValues) => void;
onCancel: () => void;
}
const ModalForm = ({ mode, initialValues, onOk, onCancel }: Props) => {
const isCreate = mode === 'create';
const [form] = Form.useForm<FormValues>();
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};
const [form] = Form.useForm<SpaceFormValues>();
return (
<Modal
@@ -29,7 +25,7 @@ const ModalForm = ({ mode, initialValues, onOk, onCancel }: Props) => {
cancelText={Lang.t('cancel')}
>
<Form
{...layout}
{...FORM_LAYOUT}
form={form}
onFinish={onOk}
initialValues={initialValues}