diff --git a/web/src/App.js b/web/src/App.js index 874aa0ca..1ffeba72 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -8,7 +8,6 @@ import LoginForm from './components/LoginForm'; import NotFound from './pages/NotFound'; import Setting from './pages/Setting'; import EditUser from './pages/User/EditUser'; -import AddUser from './pages/User/AddUser'; import { API, getLogo, getSystemName, showError, showNotice } from './helpers'; import PasswordResetForm from './components/PasswordResetForm'; import GitHubOAuth from './components/GitHubOAuth'; @@ -17,10 +16,8 @@ import { UserContext } from './context/User'; import { StatusContext } from './context/Status'; import Channel from './pages/Channel'; import Token from './pages/Token'; -import EditToken from './pages/Token/EditToken'; import EditChannel from './pages/Channel/EditChannel'; import Redemption from './pages/Redemption'; -import EditRedemption from './pages/Redemption/EditRedemption'; import TopUp from './pages/TopUp'; import Log from './pages/Log'; import Chat from './pages/Chat'; @@ -131,22 +128,6 @@ function App() { } /> - }> - - - } - /> - }> - - - } - /> } /> - }> - - - } - /> - }> - - - } - /> } /> - }> - - - } - /> 普通用户; - case 10: - return ; - case 100: - return ; - default: - return ; - } + switch (role) { + case 1: + return 普通用户; + case 10: + return 管理员; + case 100: + return 超级管理员; + default: + return 未知身份; + } } const UsersTable = () => { - const [users, setUsers] = useState([]); - const [loading, setLoading] = useState(true); - const [activePage, setActivePage] = useState(1); - const [searchKeyword, setSearchKeyword] = useState(''); - const [searching, setSearching] = useState(false); - - const loadUsers = async (startIdx) => { - const res = await API.get(`/api/user/?p=${startIdx}`); - const { success, message, data } = res.data; - if (success) { - if (startIdx === 0) { - setUsers(data); - } else { - let newUsers = users; - newUsers.push(...data); - setUsers(newUsers); - } - } else { - showError(message); - } - setLoading(false); - }; - - const onPaginationChange = (e, { activePage }) => { - (async () => { - if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) { - // In this case we have to load more data and then append them. - await loadUsers(activePage - 1); - } - setActivePage(activePage); - })(); - }; - - useEffect(() => { - loadUsers(0) - .then() - .catch((reason) => { - showError(reason); - }); - }, []); - - const manageUser = (username, action, idx) => { - (async () => { - const res = await API.post('/api/user/manage', { - username, - action - }); - const { success, message } = res.data; - if (success) { - showSuccess('操作成功完成!'); - let user = res.data.data; - let newUsers = [...users]; - let realIdx = (activePage - 1) * ITEMS_PER_PAGE + idx; - if (action === 'delete') { - newUsers[realIdx].deleted = true; - } else { - newUsers[realIdx].status = user.status; - newUsers[realIdx].role = user.role; + const columns = [{ + title: 'ID', dataIndex: 'id', + }, { + title: '用户名', dataIndex: 'username', + }, { + title: '分组', dataIndex: 'group', render: (text, record, index) => { + return (
+ {renderGroup(text)} +
); + }, + }, { + title: '统计信息', dataIndex: 'info', render: (text, record, index) => { + return (
+ + + {renderQuota(record.quota)} + + + {renderQuota(record.used_quota)} + + + {renderNumber(record.request_count)} + + +
); } - setUsers(newUsers); - } else { - showError(message); - } - })(); - }; + }, { + title: '邀请信息', dataIndex: 'invite', render: (text, record, index) => { + return (
+ + + {renderNumber(record.aff_count)} + + + {renderQuota(record.aff_history_quota)} + + + {record.inviter_id === 0 ? : + {record.inviter_id}} + + +
); + } + }, { + title: '角色', dataIndex: 'role', render: (text, record, index) => { + return (
+ {renderRole(text)} +
); + }, + }, { + title: '状态', dataIndex: 'status', render: (text, record, index) => { + return (
+ {renderStatus(text)} +
); + }, + }, { + title: '', dataIndex: 'operate', render: (text, record, index) => (
+ { + manageUser(record.username, 'promote', record) + }} + > + + + { + manageUser(record.username, 'demote', record) + }} + > + + + { + manageUser(record.username, 'delete', record).then(() => { + removeRecord(record.id); + }) + }} + > + + + {record.status === 1 ? + : + } + +
), + },]; - const renderStatus = (status) => { - switch (status) { - case 1: - return ; - case 2: - return ( - - ); - default: - return ( - - ); - } - }; - - const searchUsers = async () => { - if (searchKeyword === '') { - // if keyword is blank, load files instead. - await loadUsers(0); - setActivePage(1); - return; - } - setSearching(true); - const res = await API.get(`/api/user/search?keyword=${searchKeyword}`); - const { success, message, data } = res.data; - if (success) { - setUsers(data); - setActivePage(1); - } else { - showError(message); - } - setSearching(false); - }; - - const handleKeywordChange = async (e, { value }) => { - setSearchKeyword(value.trim()); - }; - - const sortUser = (key) => { - if (users.length === 0) return; - setLoading(true); - let sortedUsers = [...users]; - sortedUsers.sort((a, b) => { - return ('' + a[key]).localeCompare(b[key]); + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [activePage, setActivePage] = useState(1); + const [searchKeyword, setSearchKeyword] = useState(''); + const [searching, setSearching] = useState(false); + const [userCount, setUserCount] = useState(ITEMS_PER_PAGE); + const [showAddUser, setShowAddUser] = useState(false); + const [showEditUser, setShowEditUser] = useState(false); + const [editingUser, setEditingUser] = useState({ + id: undefined, }); - if (sortedUsers[0].id === users[0].id) { - sortedUsers.reverse(); + + const setCount = (data) => { + if (data.length >= (activePage) * ITEMS_PER_PAGE) { + setUserCount(data.length + 1); + } else { + setUserCount(data.length); + } } - setUsers(sortedUsers); - setLoading(false); - }; - return ( - <> -
- - + const removeRecord = key => { + console.log(key); + let newDataSource = [...users]; + if (key != null) { + let idx = newDataSource.findIndex(data => data.id === key); - - - - { - sortUser('id'); - }} - > - ID - - { - sortUser('username'); - }} - > - 用户名 - - { - sortUser('group'); - }} - > - 分组 - - { - sortUser('quota'); - }} - > - 统计信息 - - { - sortUser('role'); - }} - > - 用户角色 - - { - sortUser('status'); - }} - > - 状态 - - 操作 - - + if (idx > -1) { + newDataSource.splice(idx, 1); + setUsers(newDataSource); + } + } + }; - - {users - .slice( - (activePage - 1) * ITEMS_PER_PAGE, - activePage * ITEMS_PER_PAGE - ) - .map((user, idx) => { - if (user.deleted) return <>; - return ( - - {user.id} - - {renderText(user.username, 15)}} - hoverable - /> - - {renderGroup(user.group)} - {/**/} - {/* {user.email ? {renderText(user.email, 24)}} /> : '无'}*/} - {/**/} - - {renderQuota(user.quota)}} /> - {renderQuota(user.used_quota)}} /> - {renderNumber(user.request_count)}} /> - - {renderRole(user.role)} - {renderStatus(user.status)} - -
- - - - 删除 - - } - on='click' - flowing - hoverable - > - - - - -
-
-
- ); - })} -
+ const loadUsers = async (startIdx) => { + const res = await API.get(`/api/user/?p=${startIdx}`); + const {success, message, data} = res.data; + if (success) { + if (startIdx === 0) { + setUsers(data); + setCount(data); + } else { + let newUsers = users; + newUsers.push(...data); + setUsers(newUsers); + setCount(newUsers); + } + } else { + showError(message); + } + setLoading(false); + }; - - - - - { + (async () => { + if (activePage === Math.ceil(users.length / ITEMS_PER_PAGE) + 1) { + // In this case we have to load more data and then append them. + await loadUsers(activePage - 1); + } + setActivePage(activePage); + })(); + }; + + useEffect(() => { + loadUsers(0) + .then() + .catch((reason) => { + showError(reason); + }); + }, []); + + const manageUser = async (username, action, record) => { + const res = await API.post('/api/user/manage', { + username, action + }); + const {success, message} = res.data; + if (success) { + showSuccess('操作成功完成!'); + let user = res.data.data; + let newUsers = [...users]; + if (action === 'delete') { + + } else { + record.status = user.status; + record.role = user.role; + } + setUsers(newUsers); + } else { + showError(message); + } + }; + + const renderStatus = (status) => { + switch (status) { + case 1: + return 已激活; + case 2: + return ( + 已封禁 + ); + default: + return ( + 未知状态 + ); + } + }; + + const searchUsers = async () => { + if (searchKeyword === '') { + // if keyword is blank, load files instead. + await loadUsers(0); + setActivePage(1); + return; + } + setSearching(true); + const res = await API.get(`/api/user/search?keyword=${searchKeyword}`); + const {success, message, data} = res.data; + if (success) { + setUsers(data); + setActivePage(1); + } else { + showError(message); + } + setSearching(false); + }; + + const handleKeywordChange = async (value) => { + setSearchKeyword(value.trim()); + }; + + const sortUser = (key) => { + if (users.length === 0) return; + setLoading(true); + let sortedUsers = [...users]; + sortedUsers.sort((a, b) => { + return ('' + a[key]).localeCompare(b[key]); + }); + if (sortedUsers[0].id === users[0].id) { + sortedUsers.reverse(); + } + setUsers(sortedUsers); + setLoading(false); + }; + + const pageData = users.slice((activePage - 1) * ITEMS_PER_PAGE, activePage * ITEMS_PER_PAGE); + + const closeAddUser = () => { + setShowAddUser(false); + } + + const closeEditUser = () => { + setShowEditUser(false); + setEditingUser({ + id: undefined, + }); + } + + const refresh = async () => { + await loadUsers(activePage - 1); + }; + + return ( + <> + + +
+ handleKeywordChange(value)} + /> + + +
+
- - ); + }>添加用户 + + ); }; export default UsersTable; diff --git a/web/src/helpers/render.js b/web/src/helpers/render.js index 2a7fd4a3..84cba0c9 100644 --- a/web/src/helpers/render.js +++ b/web/src/helpers/render.js @@ -1,4 +1,5 @@ import { Label } from 'semantic-ui-react'; +import {Tag} from "@douyinfe/semi-ui"; export function renderText(text, limit) { if (text.length > limit) { @@ -9,18 +10,22 @@ export function renderText(text, limit) { export function renderGroup(group) { if (group === '') { - return ; + return default; } let groups = group.split(','); groups.sort(); return <> {groups.map((group) => { if (group === 'vip' || group === 'pro') { - return ; + return {group}; } else if (group === 'svip' || group === 'premium') { - return ; + return {group}; + } + if (group === 'default') { + return {group}; + } else { + return {group}; } - return ; })} ; } diff --git a/web/src/pages/Token/EditToken.js b/web/src/pages/Token/EditToken.js index 7b33d68c..dc21e93d 100644 --- a/web/src/pages/Token/EditToken.js +++ b/web/src/pages/Token/EditToken.js @@ -1,5 +1,4 @@ import React, {useEffect, useRef, useState} from 'react'; -// import {Button, Form, Header, Message, Segment} from 'semantic-ui-react'; import {useParams, useNavigate} from 'react-router-dom'; import {API, isMobile, showError, showSuccess, timestamp2string} from '../../helpers'; import {renderQuota, renderQuotaWithPrompt} from '../../helpers/render'; @@ -247,7 +246,7 @@ const EditToken = (props) => { disabled={unlimited_quota} />
- {`新建数量`} + 新建数量
{!isEdit && ( { - const originInputs = { - username: '', - display_name: '', - password: '', - }; - const [inputs, setInputs] = useState(originInputs); - const { username, display_name, password } = inputs; +const AddUser = (props) => { + const originInputs = { + username: '', + display_name: '', + password: '', + }; + const [inputs, setInputs] = useState(originInputs); + const [loading, setLoading] = useState(false); + const {username, display_name, password} = inputs; - const handleInputChange = (e, { name, value }) => { - setInputs((inputs) => ({ ...inputs, [name]: value })); - }; + const handleInputChange = (name, value) => { + setInputs((inputs) => ({...inputs, [name]: value})); + }; - const submit = async () => { - if (inputs.username === '' || inputs.password === '') return; - const res = await API.post(`/api/user/`, inputs); - const { success, message } = res.data; - if (success) { - showSuccess('用户账户创建成功!'); - setInputs(originInputs); - } else { - showError(message); + const submit = async () => { + setLoading(true); + if (inputs.username === '' || inputs.password === '') return; + const res = await API.post(`/api/user/`, inputs); + const {success, message} = res.data; + if (success) { + showSuccess('用户账户创建成功!'); + setInputs(originInputs); + props.refresh(); + props.handleClose(); + } else { + showError(message); + } + setLoading(false); + }; + + const handleCancel = () => { + props.handleClose(); } - }; - return ( - <> - -
创建新用户账户
-
- - - - - - - - - - -
-
- - ); + return ( + <> + {'添加用户'}} + headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + visible={props.visible} + footer={ +
+ + + + +
+ } + closeIcon={null} + onCancel={() => handleCancel()} + width={isMobile() ? '100%' : 600} + > + + handleInputChange('username', value)} + value={username} + autoComplete="off" + /> + handleInputChange('display_name', value)} + value={display_name} + /> + handleInputChange('password', value)} + value={password} + autoComplete="off" + /> + +
+ + ); }; export default AddUser; diff --git a/web/src/pages/User/EditUser.js b/web/src/pages/User/EditUser.js index d99d83bc..705f7a20 100644 --- a/web/src/pages/User/EditUser.js +++ b/web/src/pages/User/EditUser.js @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react'; -import { Button, Form, Header, Segment } from 'semantic-ui-react'; import { useParams, useNavigate } from 'react-router-dom'; -import { API, showError, showSuccess } from '../../helpers'; +import {API, isMobile, showError, showSuccess} from '../../helpers'; import { renderQuota, renderQuotaWithPrompt } from '../../helpers/render'; +import Title from "@douyinfe/semi-ui/lib/es/typography/title"; +import {SideSheet, Space, Button, Spin, Input, Typography, Select, Divider} from "@douyinfe/semi-ui"; -const EditUser = () => { - const params = useParams(); - const userId = params.id; +const EditUser = (props) => { + const userId = props.editingUser.id; const [loading, setLoading] = useState(true); const [inputs, setInputs] = useState({ username: '', @@ -21,15 +21,14 @@ const EditUser = () => { const [groupOptions, setGroupOptions] = useState([]); const { username, display_name, password, github_id, wechat_id, email, quota, group } = inputs; - const handleInputChange = (e, { name, value }) => { + const handleInputChange = (name, value) => { setInputs((inputs) => ({ ...inputs, [name]: value })); }; const fetchGroups = async () => { try { let res = await API.get(`/api/group/`); setGroupOptions(res.data.data.map((group) => ({ - key: group, - text: group, + label: group, value: group, }))); } catch (error) { @@ -38,9 +37,10 @@ const EditUser = () => { }; const navigate = useNavigate(); const handleCancel = () => { - navigate("/setting"); + props.handleClose(); } const loadUser = async () => { + setLoading(true); let res = undefined; if (userId) { res = await API.get(`/api/user/${userId}`); @@ -56,14 +56,16 @@ const EditUser = () => { } setLoading(false); }; + useEffect(() => { loadUser().then(); if (userId) { fetchGroups().then(); } - }, []); + }, [props.editingUser.id]); const submit = async () => { + setLoading(true); let res = undefined; if (userId) { let data = { ...inputs, id: parseInt(userId) }; @@ -77,112 +79,134 @@ const EditUser = () => { const { success, message } = res.data; if (success) { showSuccess('用户信息更新成功!'); + props.refresh(); + props.handleClose(); } else { showError(message); } + setLoading(false); }; return ( <> - -
更新用户信息
-
- - - - - - - - - + {'编辑用户'}} + headerStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + bodyStyle={{borderBottom: '1px solid var(--semi-color-border)'}} + visible={props.visible} + footer={ +
+ + + + +
+ } + closeIcon={null} + onCancel={() => handleCancel()} + width={isMobile() ? '100%' : 600} + > + +
+ 用户名 +
+ handleInputChange('username', value)} + value={username} + autoComplete='new-password' + /> +
+ 密码 +
+ handleInputChange('password', value)} + value={password} + autoComplete='new-password' + /> +
+ 显示名称 +
+ handleInputChange('display_name', value)} + value={display_name} + autoComplete='new-password' + /> { userId && <> - - - - - - +
+ 分组 +
+ handleInputChange('quota', value)} + value={quota} + type={'number'} + autoComplete='new-password' + /> } - - - - - - - - - - - - -
+ 以下信息不可修改 +
+ 已绑定的 GitHub 账户 +
+ +
+ 已绑定的微信账户 +
+ +
+ 已绑定的邮箱账户 +
+ + + + ); }; diff --git a/web/src/pages/User/index.js b/web/src/pages/User/index.js index 29f7437a..5d0585c6 100644 --- a/web/src/pages/User/index.js +++ b/web/src/pages/User/index.js @@ -1,13 +1,18 @@ import React from 'react'; import { Segment, Header } from 'semantic-ui-react'; import UsersTable from '../../components/UsersTable'; +import {Layout} from "@douyinfe/semi-ui"; const User = () => ( <> - -
管理用户
- -
+ + +

管理用户

+
+ + + +
);