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 {
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}
size='small'
>
{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>
</>
)}
<Button
className='w-full md:w-auto'
onClick={handleAddUser}
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>
</>
);
};

View File

@ -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,
}),
},

View File

@ -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

View File

@ -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

View File

@ -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,