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:
xiezhouwei 2026-06-11 17:04:15 +08:00
parent 9bb3b9dda9
commit 1474917163
5 changed files with 17 additions and 351 deletions

View File

@ -20,176 +20,27 @@ For commercial licensing, please contact support@quantumnous.com
import React from 'react'; import React from 'react';
import { import {
Button, Button,
InputNumber,
Modal,
Select,
Space,
Typography,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { useState } from 'react';
import { API, showError } from '../../../helpers';
const UsersActions = ({ const UsersActions = ({
setShowAddUser, setShowAddUser,
t, 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 = () => { const handleAddUser = () => {
setShowAddUser(true); 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 ( return (
<> <>
<div className='flex gap-2 w-full md:w-auto order-2 md:order-1 items-center'> <div className='flex gap-2 w-full md:w-auto order-2 md:order-1 items-center'>
{studentView === 'all' && ( <Button
<Button className='w-full md:w-auto'
className='w-full md:w-auto' onClick={handleAddUser}
onClick={handleAddUser} size='small'
size='small' >
> {t('添加用户')}
{t('添加用户')} </Button>
</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> </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>
</> </>
); );
}; };

View File

@ -287,7 +287,6 @@ const renderOperations = (
showResetTwoFAModal, showResetTwoFAModal,
showUserSubscriptionsModal, showUserSubscriptionsModal,
manageUser, manageUser,
studentView,
t, t,
}, },
) => { ) => {
@ -295,28 +294,6 @@ const renderOperations = (
return <></>; 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 = [ const moreMenu = [
{ {
node: 'item', node: 'item',
@ -339,38 +316,6 @@ const renderOperations = (
{ {
node: 'divider', 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', node: 'item',
@ -469,7 +414,6 @@ export const getUsersColumns = ({
showResetTwoFAModal, showResetTwoFAModal,
showUserSubscriptionsModal, showUserSubscriptionsModal,
manageUser, manageUser,
studentView,
}) => { }) => {
return [ return [
{ {
@ -582,7 +526,6 @@ export const getUsersColumns = ({
showResetTwoFAModal, showResetTwoFAModal,
showUserSubscriptionsModal, showUserSubscriptionsModal,
manageUser, manageUser,
studentView,
t, t,
}), }),
}, },

View File

@ -142,7 +142,6 @@ const UsersTable = (usersData) => {
showResetTwoFAModal: showResetTwoFAUserModal, showResetTwoFAModal: showResetTwoFAUserModal,
showUserSubscriptionsModal: showUserSubscriptionsUserModal, showUserSubscriptionsModal: showUserSubscriptionsUserModal,
manageUser, manageUser,
studentView: usersData.studentView,
}); });
}, [ }, [
t, t,
@ -156,7 +155,6 @@ const UsersTable = (usersData) => {
showResetTwoFAUserModal, showResetTwoFAUserModal,
showUserSubscriptionsUserModal, showUserSubscriptionsUserModal,
manageUser, manageUser,
usersData.studentView,
]); ]);
// Handle compact mode by removing fixed positioning // Handle compact mode by removing fixed positioning

View File

@ -59,13 +59,6 @@ const UsersPage = () => {
// Description state // Description state
compactMode, compactMode,
setCompactMode, setCompactMode,
studentView,
setStudentView,
studentRewardAmount,
studentRewardLoading,
setStudentRewardAmount,
saveStudentRewardAmount,
assignStudent,
// Translation // Translation
t, t,
@ -74,14 +67,8 @@ const UsersPage = () => {
return ( return (
<> <>
<div className='mb-2'> <div className='mb-2'>
<Tabs <Tabs type='line'>
type='line'
activeKey={studentView}
onChange={(key) => setStudentView(key)}
>
<Tabs.TabPane itemKey='all' tab={t('全部用户')} /> <Tabs.TabPane itemKey='all' tab={t('全部用户')} />
<Tabs.TabPane itemKey='pending' tab={t('学员待审批')} />
<Tabs.TabPane itemKey='students' tab={t('学员管理')} />
</Tabs> </Tabs>
</div> </div>
<AddUserModal <AddUserModal
@ -113,12 +100,6 @@ const UsersPage = () => {
setShowAddUser={setShowAddUser} setShowAddUser={setShowAddUser}
t={t} t={t}
users={usersData.users} users={usersData.users}
studentView={studentView}
studentRewardAmount={studentRewardAmount}
studentRewardLoading={studentRewardLoading}
setStudentRewardAmount={setStudentRewardAmount}
saveStudentRewardAmount={saveStudentRewardAmount}
assignStudent={assignStudent}
/> />
<UsersFilters <UsersFilters

View File

@ -49,9 +49,6 @@ export const useUsersData = () => {
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [searching, setSearching] = useState(false); 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 [groupOptions, setGroupOptions] = useState([]);
const [userCount, setUserCount] = useState(0); const [userCount, setUserCount] = useState(0);
const [tagOptions, setTagOptions] = useState([]); const [tagOptions, setTagOptions] = useState([]);
@ -95,14 +92,11 @@ export const useUsersData = () => {
// Load users data // Load users data
const loadUsers = async (startIdx, pageSize, tag = null) => { const loadUsers = async (startIdx, pageSize, tag = null) => {
setLoading(true); setLoading(true);
const viewQuery =
studentView && studentView !== 'all'
? `&student_view=${encodeURIComponent(studentView)}`
: '';
const tagFilter = tag || selectedTag; const tagFilter = tag || selectedTag;
const tagQuery = tagFilter ? `&tag=${encodeURIComponent(tagFilter)}` : ''; const tagQuery = tagFilter ? `&tag=${encodeURIComponent(tagFilter)}` : '';
const res = await API.get( 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; const { success, message, data } = res.data;
if (success) { if (success) {
@ -140,7 +134,7 @@ export const useUsersData = () => {
setSearching(true); setSearching(true);
const tagQuery = (searchTag || selectedTag) ? `&tag=${encodeURIComponent(searchTag || selectedTag)}` : ''; const tagQuery = (searchTag || selectedTag) ? `&tag=${encodeURIComponent(searchTag || selectedTag)}` : '';
const res = await API.get( 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; const { success, message, data } = res.data;
if (success) { if (success) {
@ -195,99 +189,10 @@ export const useUsersData = () => {
setLoading(false); 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) => { const resetUserPasskey = async (user) => {
if (!user) { if (!user) {
@ -429,13 +334,8 @@ export const useUsersData = () => {
}); });
fetchGroups().then(); fetchGroups().then();
fetchUserTags().then(); fetchUserTags().then();
}, [studentView]); }, []);
useEffect(() => {
if (studentView === 'students') {
loadStudentRewardAmount().then();
}
}, [studentView]);
return { return {
// Data state // Data state
@ -445,9 +345,6 @@ export const useUsersData = () => {
pageSize, pageSize,
userCount, userCount,
searching, searching,
studentView,
studentRewardAmount,
studentRewardLoading,
groupOptions, groupOptions,
tagOptions, tagOptions,
selectedTag, selectedTag,
@ -470,15 +367,11 @@ export const useUsersData = () => {
// UI state // UI state
compactMode, compactMode,
setCompactMode, setCompactMode,
setStudentView: handleStudentViewChange,
// Actions // Actions
loadUsers, loadUsers,
searchUsers, searchUsers,
manageUser, manageUser,
assignStudent,
setStudentRewardAmount,
saveStudentRewardAmount,
resetUserPasskey, resetUserPasskey,
resetUserTwoFA, resetUserTwoFA,
handlePageChange, handlePageChange,