chore: remove student tabs, actions and related code from user management
- Remove '学员待审批' and '学员管理' tabs from user management page - Remove studentView, studentRewardAmount, studentRewardLoading states and functions - Remove assignStudent, saveStudentRewardAmount, loadStudentRewardAmount - Remove pending approval (同意/拒绝) actions in operations column - Remove through/reject/unset student menu items - Clean up UsersActions to only show '添加用户' button - Remove student_view query params from API calls
This commit is contained in:
parent
9bb3b9dda9
commit
1474917163
|
|
@ -20,103 +20,19 @@ For commercial licensing, please contact support@quantumnous.com
|
|||
import React from 'react';
|
||||
import {
|
||||
Button,
|
||||
InputNumber,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Typography,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { useState } from 'react';
|
||||
import { API, showError } from '../../../helpers';
|
||||
|
||||
const UsersActions = ({
|
||||
setShowAddUser,
|
||||
t,
|
||||
studentView,
|
||||
studentRewardAmount,
|
||||
studentRewardLoading,
|
||||
setStudentRewardAmount,
|
||||
saveStudentRewardAmount,
|
||||
assignStudent,
|
||||
}) => {
|
||||
const [assignVisible, setAssignVisible] = useState(false);
|
||||
const [searchingUser, setSearchingUser] = useState(false);
|
||||
const [candidateUsers, setCandidateUsers] = useState([]);
|
||||
const [selectedUserId, setSelectedUserId] = useState('');
|
||||
|
||||
// Add new user
|
||||
const handleAddUser = () => {
|
||||
setShowAddUser(true);
|
||||
};
|
||||
|
||||
const openAssignModal = () => {
|
||||
setCandidateUsers([]);
|
||||
setSelectedUserId('');
|
||||
setAssignVisible(true);
|
||||
loadAllUsersForSelect();
|
||||
};
|
||||
|
||||
const loadAllUsersForSelect = async () => {
|
||||
setSearchingUser(true);
|
||||
try {
|
||||
const all = [];
|
||||
let page = 0;
|
||||
let total = 0;
|
||||
do {
|
||||
const res = await API.get(`/api/user/?p=${page}&page_size=100`);
|
||||
const { success, message, data } = res.data;
|
||||
if (!success) {
|
||||
showError(message || t('加载用户失败'));
|
||||
return;
|
||||
}
|
||||
const items = data?.items || [];
|
||||
total = data?.total || 0;
|
||||
all.push(...items);
|
||||
page += 1;
|
||||
} while (all.length < total);
|
||||
setCandidateUsers(all);
|
||||
} catch (error) {
|
||||
showError(t('加载用户失败'));
|
||||
} finally {
|
||||
setSearchingUser(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectSearch = async (keyword) => {
|
||||
const kw = String(keyword || '').trim();
|
||||
if (!kw) {
|
||||
loadAllUsersForSelect();
|
||||
return;
|
||||
}
|
||||
setSearchingUser(true);
|
||||
try {
|
||||
const res = await API.get(
|
||||
`/api/user/search?keyword=${encodeURIComponent(kw)}&p=0&page_size=50`,
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (!success) {
|
||||
showError(message || t('搜索用户失败'));
|
||||
return;
|
||||
}
|
||||
setCandidateUsers(data?.items || []);
|
||||
} catch (error) {
|
||||
showError(t('搜索用户失败'));
|
||||
} finally {
|
||||
setSearchingUser(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAssignConfirm = async () => {
|
||||
const ok = await assignStudent(selectedUserId);
|
||||
if (ok) {
|
||||
setAssignVisible(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='flex gap-2 w-full md:w-auto order-2 md:order-1 items-center'>
|
||||
{studentView === 'all' && (
|
||||
<Button
|
||||
className='w-full md:w-auto'
|
||||
onClick={handleAddUser}
|
||||
|
|
@ -124,72 +40,7 @@ const UsersActions = ({
|
|||
>
|
||||
{t('添加用户')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{studentView === 'students' && (
|
||||
<>
|
||||
<Space spacing={6} align='center'>
|
||||
<Typography.Text type='tertiary' size='small'>
|
||||
{t('赠送金额')}
|
||||
</Typography.Text>
|
||||
<InputNumber
|
||||
size='small'
|
||||
min={0}
|
||||
precision={2}
|
||||
suffix='USD'
|
||||
value={studentRewardAmount}
|
||||
onChange={(val) => setStudentRewardAmount(Number(val) || 0)}
|
||||
/>
|
||||
<Button
|
||||
size='small'
|
||||
loading={studentRewardLoading}
|
||||
onClick={() => saveStudentRewardAmount(studentRewardAmount)}
|
||||
>
|
||||
{t('保存额度')}
|
||||
</Button>
|
||||
</Space>
|
||||
<Button
|
||||
className='w-full md:w-auto'
|
||||
onClick={openAssignModal}
|
||||
size='small'
|
||||
>
|
||||
{t('添加学员')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
visible={assignVisible}
|
||||
title={t('添加学员')}
|
||||
onCancel={() => setAssignVisible(false)}
|
||||
onOk={handleAssignConfirm}
|
||||
okText={t('确认')}
|
||||
cancelText={t('取消')}
|
||||
confirmLoading={searchingUser}
|
||||
>
|
||||
<div className='space-y-3'>
|
||||
<div>
|
||||
<Typography.Text size='small' type='tertiary'>
|
||||
{t('选择用户')}
|
||||
</Typography.Text>
|
||||
<Select
|
||||
style={{ width: '100%', marginTop: 6 }}
|
||||
placeholder={t('下拉选择全部用户,支持输入手机号/用户名搜索')}
|
||||
value={selectedUserId}
|
||||
filter
|
||||
loading={searchingUser}
|
||||
searchPosition='dropdown'
|
||||
onSearch={handleSelectSearch}
|
||||
optionList={candidateUsers.map((u) => ({
|
||||
label: `${u.username} (${u.phone || '—'}) #${u.id}`,
|
||||
value: u.id,
|
||||
}))}
|
||||
onChange={(val) => setSelectedUserId(val)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -287,7 +287,6 @@ const renderOperations = (
|
|||
showResetTwoFAModal,
|
||||
showUserSubscriptionsModal,
|
||||
manageUser,
|
||||
studentView,
|
||||
t,
|
||||
},
|
||||
) => {
|
||||
|
|
@ -295,28 +294,6 @@ const renderOperations = (
|
|||
return <></>;
|
||||
}
|
||||
|
||||
if (studentView === 'pending') {
|
||||
return (
|
||||
<Space>
|
||||
<Button
|
||||
type='primary'
|
||||
size='small'
|
||||
onClick={() => manageUser(record.id, 'approve_student', record)}
|
||||
>
|
||||
{t('同意')}
|
||||
</Button>
|
||||
<Button
|
||||
type='danger'
|
||||
theme='light'
|
||||
size='small'
|
||||
onClick={() => manageUser(record.id, 'reject_student', record)}
|
||||
>
|
||||
{t('拒绝')}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
const moreMenu = [
|
||||
{
|
||||
node: 'item',
|
||||
|
|
@ -339,38 +316,6 @@ const renderOperations = (
|
|||
{
|
||||
node: 'divider',
|
||||
},
|
||||
...(record.student_status === 1
|
||||
? [
|
||||
{
|
||||
node: 'item',
|
||||
name: t('通过学员'),
|
||||
onClick: () => manageUser(record.id, 'approve_student', record),
|
||||
},
|
||||
{
|
||||
node: 'item',
|
||||
name: t('拒绝学员'),
|
||||
type: 'danger',
|
||||
onClick: () => manageUser(record.id, 'reject_student', record),
|
||||
},
|
||||
{
|
||||
node: 'divider',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...((record.is_student === 1 || record.is_student === true) &&
|
||||
record.role < USER_ROLES.ADMIN
|
||||
? [
|
||||
{
|
||||
node: 'item',
|
||||
name: t('撤销学员'),
|
||||
type: 'danger',
|
||||
onClick: () => manageUser(record.id, 'unset_student', record),
|
||||
},
|
||||
{
|
||||
node: 'divider',
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
node: 'item',
|
||||
|
|
@ -469,7 +414,6 @@ export const getUsersColumns = ({
|
|||
showResetTwoFAModal,
|
||||
showUserSubscriptionsModal,
|
||||
manageUser,
|
||||
studentView,
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
|
|
@ -582,7 +526,6 @@ export const getUsersColumns = ({
|
|||
showResetTwoFAModal,
|
||||
showUserSubscriptionsModal,
|
||||
manageUser,
|
||||
studentView,
|
||||
t,
|
||||
}),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -142,7 +142,6 @@ const UsersTable = (usersData) => {
|
|||
showResetTwoFAModal: showResetTwoFAUserModal,
|
||||
showUserSubscriptionsModal: showUserSubscriptionsUserModal,
|
||||
manageUser,
|
||||
studentView: usersData.studentView,
|
||||
});
|
||||
}, [
|
||||
t,
|
||||
|
|
@ -156,7 +155,6 @@ const UsersTable = (usersData) => {
|
|||
showResetTwoFAUserModal,
|
||||
showUserSubscriptionsUserModal,
|
||||
manageUser,
|
||||
usersData.studentView,
|
||||
]);
|
||||
|
||||
// Handle compact mode by removing fixed positioning
|
||||
|
|
|
|||
|
|
@ -59,13 +59,6 @@ const UsersPage = () => {
|
|||
// Description state
|
||||
compactMode,
|
||||
setCompactMode,
|
||||
studentView,
|
||||
setStudentView,
|
||||
studentRewardAmount,
|
||||
studentRewardLoading,
|
||||
setStudentRewardAmount,
|
||||
saveStudentRewardAmount,
|
||||
assignStudent,
|
||||
|
||||
// Translation
|
||||
t,
|
||||
|
|
@ -74,14 +67,8 @@ const UsersPage = () => {
|
|||
return (
|
||||
<>
|
||||
<div className='mb-2'>
|
||||
<Tabs
|
||||
type='line'
|
||||
activeKey={studentView}
|
||||
onChange={(key) => setStudentView(key)}
|
||||
>
|
||||
<Tabs type='line'>
|
||||
<Tabs.TabPane itemKey='all' tab={t('全部用户')} />
|
||||
<Tabs.TabPane itemKey='pending' tab={t('学员待审批')} />
|
||||
<Tabs.TabPane itemKey='students' tab={t('学员管理')} />
|
||||
</Tabs>
|
||||
</div>
|
||||
<AddUserModal
|
||||
|
|
@ -113,12 +100,6 @@ const UsersPage = () => {
|
|||
setShowAddUser={setShowAddUser}
|
||||
t={t}
|
||||
users={usersData.users}
|
||||
studentView={studentView}
|
||||
studentRewardAmount={studentRewardAmount}
|
||||
studentRewardLoading={studentRewardLoading}
|
||||
setStudentRewardAmount={setStudentRewardAmount}
|
||||
saveStudentRewardAmount={saveStudentRewardAmount}
|
||||
assignStudent={assignStudent}
|
||||
/>
|
||||
|
||||
<UsersFilters
|
||||
|
|
|
|||
|
|
@ -49,9 +49,6 @@ export const useUsersData = () => {
|
|||
const [activePage, setActivePage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [studentView, setStudentView] = useState('all');
|
||||
const [studentRewardAmount, setStudentRewardAmount] = useState(0);
|
||||
const [studentRewardLoading, setStudentRewardLoading] = useState(false);
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [userCount, setUserCount] = useState(0);
|
||||
const [tagOptions, setTagOptions] = useState([]);
|
||||
|
|
@ -95,14 +92,11 @@ export const useUsersData = () => {
|
|||
// Load users data
|
||||
const loadUsers = async (startIdx, pageSize, tag = null) => {
|
||||
setLoading(true);
|
||||
const viewQuery =
|
||||
studentView && studentView !== 'all'
|
||||
? `&student_view=${encodeURIComponent(studentView)}`
|
||||
: '';
|
||||
|
||||
const tagFilter = tag || selectedTag;
|
||||
const tagQuery = tagFilter ? `&tag=${encodeURIComponent(tagFilter)}` : '';
|
||||
const res = await API.get(
|
||||
`/api/user/?p=${startIdx}&page_size=${pageSize}${viewQuery}${tagQuery}`,
|
||||
`/api/user/?p=${startIdx}&page_size=${pageSize}${tagQuery}`,
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
|
|
@ -140,7 +134,7 @@ export const useUsersData = () => {
|
|||
setSearching(true);
|
||||
const tagQuery = (searchTag || selectedTag) ? `&tag=${encodeURIComponent(searchTag || selectedTag)}` : '';
|
||||
const res = await API.get(
|
||||
`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}&student_view=${encodeURIComponent(studentView)}${tagQuery}&p=${startIdx}&page_size=${pageSize}`,
|
||||
`/api/user/search?keyword=${searchKeyword}&group=${searchGroup}${tagQuery}&p=${startIdx}&page_size=${pageSize}`,
|
||||
);
|
||||
const { success, message, data } = res.data;
|
||||
if (success) {
|
||||
|
|
@ -195,99 +189,10 @@ export const useUsersData = () => {
|
|||
setLoading(false);
|
||||
};
|
||||
|
||||
const assignStudent = async (userId, rewardAmount) => {
|
||||
const uid = parseInt(String(userId), 10);
|
||||
if (!Number.isFinite(uid) || uid <= 0) {
|
||||
showError(t('请输入正确的用户ID'));
|
||||
return false;
|
||||
}
|
||||
const finalRewardAmount =
|
||||
rewardAmount === undefined || rewardAmount === null || rewardAmount === ''
|
||||
? studentRewardAmount
|
||||
: Number(rewardAmount) || 0;
|
||||
const rewardQuota = Math.max(0, usdToQuota(finalRewardAmount));
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await API.post('/api/user/manage', {
|
||||
id: uid,
|
||||
action: 'set_student',
|
||||
reward_quota: rewardQuota,
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (!success) {
|
||||
showError(message || t('操作失败,请重试'));
|
||||
return false;
|
||||
}
|
||||
showSuccess(t('已指定用户为学员'));
|
||||
await refresh(1);
|
||||
return true;
|
||||
} catch (error) {
|
||||
showError(t('操作失败,请重试'));
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadStudentRewardAmount = async () => {
|
||||
setStudentRewardLoading(true);
|
||||
try {
|
||||
const res = await API.get('/api/option/');
|
||||
const { success, data, message } = res.data;
|
||||
if (!success) {
|
||||
showError(message || t('加载赠送额度失败'));
|
||||
return;
|
||||
}
|
||||
const option = (data || []).find(
|
||||
(item) => item.key === 'StudentApprovalRewardQuota',
|
||||
);
|
||||
const quotaVal = parseInt(String(option?.value ?? '0'), 10);
|
||||
if (Number.isFinite(quotaVal) && quotaVal >= 0) {
|
||||
setStudentRewardAmount(quotaToUsd(quotaVal));
|
||||
} else {
|
||||
setStudentRewardAmount(0);
|
||||
}
|
||||
} catch (error) {
|
||||
showError(t('加载赠送额度失败'));
|
||||
} finally {
|
||||
setStudentRewardLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const saveStudentRewardAmount = async (rewardAmount) => {
|
||||
const rewardQuota = Math.max(0, usdToQuota(Number(rewardAmount) || 0));
|
||||
setStudentRewardLoading(true);
|
||||
try {
|
||||
const res = await API.put('/api/option/', {
|
||||
key: 'StudentApprovalRewardQuota',
|
||||
value: String(rewardQuota),
|
||||
});
|
||||
const { success, message } = res.data;
|
||||
if (!success) {
|
||||
showError(message || t('保存赠送额度失败'));
|
||||
return false;
|
||||
}
|
||||
setStudentRewardAmount(Number(rewardAmount) || 0);
|
||||
showSuccess(t('赠送额度已保存'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
showError(t('保存赠送额度失败'));
|
||||
return false;
|
||||
} finally {
|
||||
setStudentRewardLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleStudentViewChange = (nextView) => {
|
||||
setStudentView(nextView);
|
||||
setActivePage(1);
|
||||
const { searchKeyword, searchGroup, searchTag } = getFormValues();
|
||||
if (searchKeyword === '' && searchGroup === '' && !searchTag && !selectedTag) {
|
||||
loadUsers(0, pageSize).then();
|
||||
} else {
|
||||
searchUsers(0, pageSize, searchKeyword, searchGroup, searchTag).then();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const resetUserPasskey = async (user) => {
|
||||
if (!user) {
|
||||
|
|
@ -429,13 +334,8 @@ export const useUsersData = () => {
|
|||
});
|
||||
fetchGroups().then();
|
||||
fetchUserTags().then();
|
||||
}, [studentView]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (studentView === 'students') {
|
||||
loadStudentRewardAmount().then();
|
||||
}
|
||||
}, [studentView]);
|
||||
|
||||
return {
|
||||
// Data state
|
||||
|
|
@ -445,9 +345,6 @@ export const useUsersData = () => {
|
|||
pageSize,
|
||||
userCount,
|
||||
searching,
|
||||
studentView,
|
||||
studentRewardAmount,
|
||||
studentRewardLoading,
|
||||
groupOptions,
|
||||
tagOptions,
|
||||
selectedTag,
|
||||
|
|
@ -470,15 +367,11 @@ export const useUsersData = () => {
|
|||
// UI state
|
||||
compactMode,
|
||||
setCompactMode,
|
||||
setStudentView: handleStudentViewChange,
|
||||
|
||||
// Actions
|
||||
loadUsers,
|
||||
searchUsers,
|
||||
manageUser,
|
||||
assignStudent,
|
||||
setStudentRewardAmount,
|
||||
saveStudentRewardAmount,
|
||||
resetUserPasskey,
|
||||
resetUserTwoFA,
|
||||
handlePageChange,
|
||||
|
|
|
|||
Loading…
Reference in New Issue