团队管理设置
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
||||
Button, Card, Checkbox, Form, Input, InputNumber, message, Modal, Popconfirm, Progress, Select, Space, Switch, Table, Tabs, Tag, Typography,
|
||||
} from 'antd';
|
||||
import {
|
||||
UserOutlined, WalletOutlined, SearchOutlined, StopOutlined, CheckCircleOutlined, PlusOutlined, MenuOutlined, LockOutlined, SettingOutlined, SaveOutlined, DatabaseOutlined,
|
||||
UserOutlined, WalletOutlined, SearchOutlined, StopOutlined, CheckCircleOutlined, PlusOutlined, MenuOutlined, LockOutlined, SettingOutlined, SaveOutlined, DatabaseOutlined, TeamOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
adjustCredits,
|
||||
@@ -11,18 +11,22 @@ import {
|
||||
deleteUserResourceCapacity,
|
||||
getAdminUsers,
|
||||
getMenuConfigs,
|
||||
getTeamOptions,
|
||||
getSystemConfigs,
|
||||
getUserResourceCapacity,
|
||||
resetUserPassword,
|
||||
saveUserResourceCapacity,
|
||||
toggleUserStatus,
|
||||
updateFrontendUserKind,
|
||||
updateUserTeam,
|
||||
updateSystemConfig,
|
||||
updateUserMenus,
|
||||
} from '../api';
|
||||
import type { AdminUser, AdminUserResourceCapacityOut, ResourceCapacityUnit, ResourceCapacityUsage, SystemConfig } from '../types';
|
||||
import type { AdminTeamOption, AdminUser, AdminUserResourceCapacityOut, ResourceCapacityUnit, ResourceCapacityUsage, SystemConfig } from '../types';
|
||||
import { formatDate } from '../utils/formatDate';
|
||||
|
||||
const TEAM_UNASSIGNED_VALUE = '__none__';
|
||||
|
||||
const capacityUnitOptions: { value: ResourceCapacityUnit; label: string }[] = [
|
||||
{ value: 'MB', label: 'MB(1024 × 1024 字节)' },
|
||||
{ value: 'GB', label: 'GB(1024 × 1024 × 1024 字节)' },
|
||||
@@ -56,6 +60,8 @@ const AdminUsers: React.FC = () => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [activeTab, setActiveTab] = useState<string>('frontend');
|
||||
const [frontendKindFilter, setFrontendKindFilter] = useState<string>('');
|
||||
const [teamFilter, setTeamFilter] = useState<string>('');
|
||||
const [teamOptions, setTeamOptions] = useState<AdminTeamOption[]>([]);
|
||||
const [creditModal, setCreditModal] = useState<{ open: boolean; user: AdminUser | null }>({ open: false, user: null });
|
||||
const [createModal, setCreateModal] = useState(false);
|
||||
const [createType, setCreateType] = useState<string>('frontend');
|
||||
@@ -64,12 +70,15 @@ const AdminUsers: React.FC = () => {
|
||||
const [checkedMenus, setCheckedMenus] = useState<string[]>([]);
|
||||
const [resetPwdModal, setResetPwdModal] = useState<{ open: boolean; user: AdminUser | null }>({ open: false, user: null });
|
||||
const [capacityModal, setCapacityModal] = useState<{ open: boolean; user: AdminUser | null; detail: AdminUserResourceCapacityOut | null }>({ open: false, user: null, detail: null });
|
||||
const [teamModal, setTeamModal] = useState<{ open: boolean; user: AdminUser | null }>({ open: false, user: null });
|
||||
const [capacityLoading, setCapacityLoading] = useState(false);
|
||||
const [capacitySaving, setCapacitySaving] = useState(false);
|
||||
const [teamSaving, setTeamSaving] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [createForm] = Form.useForm();
|
||||
const [resetPwdForm] = Form.useForm();
|
||||
const [capacityForm] = Form.useForm();
|
||||
const [teamForm] = Form.useForm();
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
@@ -82,14 +91,25 @@ const AdminUsers: React.FC = () => {
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await getAdminUsers(page, pageSize, search || undefined, activeTab, activeTab === 'frontend' ? (frontendKindFilter || undefined) : undefined);
|
||||
const data = await getAdminUsers(
|
||||
page,
|
||||
pageSize,
|
||||
search || undefined,
|
||||
activeTab,
|
||||
activeTab === 'frontend' ? (frontendKindFilter || undefined) : undefined,
|
||||
activeTab === 'frontend' ? (teamFilter || undefined) : undefined,
|
||||
);
|
||||
setUsers(data.items || []);
|
||||
setTotal(data.total || 0);
|
||||
} catch { /* auth error handled by client */ }
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => { load(); }, [page, pageSize, search, activeTab, frontendKindFilter]);
|
||||
useEffect(() => { load(); }, [page, pageSize, search, activeTab, frontendKindFilter, teamFilter]);
|
||||
|
||||
useEffect(() => {
|
||||
getTeamOptions(true).then(setTeamOptions).catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadCreditConfigs = async () => {
|
||||
@@ -246,6 +266,30 @@ const AdminUsers: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const openTeamModal = (user: AdminUser) => {
|
||||
teamForm.setFieldsValue({ teamId: user.teamId || '' });
|
||||
setTeamModal({ open: true, user });
|
||||
};
|
||||
|
||||
const handleSaveTeam = async () => {
|
||||
const { user } = teamModal;
|
||||
if (!user) return;
|
||||
try {
|
||||
const values = await teamForm.validateFields();
|
||||
setTeamSaving(true);
|
||||
await updateUserTeam(user.id, values.teamId || null);
|
||||
message.success('用户团队已更新');
|
||||
setTeamModal({ open: false, user: null });
|
||||
teamForm.resetFields();
|
||||
load();
|
||||
} catch (e: any) {
|
||||
if (e?.errorFields) return;
|
||||
message.error(e?.message || '保存团队失败');
|
||||
} finally {
|
||||
setTeamSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const menuGroups = allMenus.filter((m: any) => (m.menu_type ?? m.menuType) === 'group');
|
||||
const menuPages = allMenus.filter((m: any) => (m.menu_type ?? m.menuType) !== 'group');
|
||||
const childMap: Record<string, any[]> = {};
|
||||
@@ -332,6 +376,10 @@ const AdminUsers: React.FC = () => {
|
||||
title: '前台归类', dataIndex: 'frontendUserKind', width: 110,
|
||||
render: (v: string) => <Tag color={v === 'internal' ? 'geekblue' : 'default'}>{v === 'internal' ? '内部用户' : '外部用户'}</Tag>,
|
||||
}] : []),
|
||||
...(!isAdminTab ? [{
|
||||
title: '团队', dataIndex: 'teamName', width: 140,
|
||||
render: (v: string | null | undefined) => v ? <Tag color="blue">{v}</Tag> : <Typography.Text type="secondary">未分配</Typography.Text>,
|
||||
}] : []),
|
||||
...(!isAdminTab ? [{
|
||||
title: '资源容量', dataIndex: 'resourceCapacity', width: 230,
|
||||
render: (capacity: ResourceCapacityUsage | null | undefined) => {
|
||||
@@ -377,7 +425,7 @@ const AdminUsers: React.FC = () => {
|
||||
render: (v: string) => <Typography.Text type="secondary" style={{ fontSize: 12 }}>{formatDate(v)}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '操作', key: 'action', width: 390, fixed: 'right' as const,
|
||||
title: '操作', key: 'action', width: 460, fixed: 'right' as const,
|
||||
render: (_: any, r: AdminUser) => (
|
||||
<Space size={4} wrap>
|
||||
{!isAdminTab && (
|
||||
@@ -392,6 +440,12 @@ const AdminUsers: React.FC = () => {
|
||||
容量设置
|
||||
</Button>
|
||||
)}
|
||||
{!isAdminTab && (
|
||||
<Button type="link" size="small" icon={<TeamOutlined />}
|
||||
onClick={() => openTeamModal(r)}>
|
||||
团队设置
|
||||
</Button>
|
||||
)}
|
||||
{!isAdminTab && r.frontendUserKind !== 'internal' && (
|
||||
<Button type="link" size="small" onClick={() => handleUpdateFrontendKind(r, 'internal')}>设为内部</Button>
|
||||
)}
|
||||
@@ -500,6 +554,18 @@ const AdminUsers: React.FC = () => {
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
{!isAdminTab && (
|
||||
<Select
|
||||
value={teamFilter}
|
||||
onChange={(v) => { setPage(1); setTeamFilter(v); }}
|
||||
style={{ width: 170 }}
|
||||
options={[
|
||||
{ value: '', label: '全部团队' },
|
||||
{ value: TEAM_UNASSIGNED_VALUE, label: '未分配团队' },
|
||||
...teamOptions.map(t => ({ value: t.id, label: t.status === 'disabled' ? `${t.name}(禁用)` : t.name })),
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<Button type="primary" onClick={handleSearch} style={{ borderRadius: 8 }}>搜索</Button>
|
||||
</div>
|
||||
<Button type="primary" icon={<PlusOutlined />}
|
||||
@@ -511,7 +577,7 @@ const AdminUsers: React.FC = () => {
|
||||
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={(key) => { setActiveTab(key); setFrontendKindFilter(''); setPage(1); }}
|
||||
onChange={(key) => { setActiveTab(key); setFrontendKindFilter(''); setTeamFilter(''); setPage(1); }}
|
||||
items={[
|
||||
{ key: 'frontend', label: '前台用户' },
|
||||
{ key: 'admin', label: '后台用户' },
|
||||
@@ -565,6 +631,31 @@ const AdminUsers: React.FC = () => {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={<Space><TeamOutlined />团队设置 - {teamModal.user?.username}</Space>}
|
||||
open={teamModal.open}
|
||||
confirmLoading={teamSaving}
|
||||
onOk={handleSaveTeam}
|
||||
onCancel={() => { setTeamModal({ open: false, user: null }); teamForm.resetFields(); }}
|
||||
okText="保存" cancelText="取消" width={460}
|
||||
>
|
||||
<Form form={teamForm} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item name="teamId" label="所属团队" extra="团队归属独立于前台内部/外部归类,不会改变用户内外部设置。">
|
||||
<Select
|
||||
size="large"
|
||||
allowClear
|
||||
placeholder="未分配团队"
|
||||
options={[
|
||||
{ value: '', label: '未分配团队' },
|
||||
...teamOptions
|
||||
.filter(t => t.status === 'active' || t.id === teamModal.user?.teamId)
|
||||
.map(t => ({ value: t.id, label: t.status === 'disabled' ? `${t.name}(禁用)` : t.name })),
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal
|
||||
title={<Space><DatabaseOutlined />容量设置 - {capacityModal.user?.username}</Space>}
|
||||
open={capacityModal.open}
|
||||
|
||||
Reference in New Issue
Block a user