1、后台/teams需要可以设置管理人,后台只给这个管理人分配积分,然后前台管理员自己分配积分

2、前台判断用户是管理人,左下角显示团队管理
3、团队管理可以看到团队的人员、分配人员积分、人员的积分情况的功能
4、团队管理还可以邀请用户,比如生成个链接,未注册需要注册绑定团队,已注册用户访问弹窗是否加入这个团队,然后都需要管理同意才可以加入团队
This commit is contained in:
2026-07-06 13:38:27 +08:00
parent 30db9fb425
commit 16b3d223c1
25 changed files with 1671 additions and 29 deletions
+13
View File
@@ -180,6 +180,19 @@ export async function deleteAdminTeam(id: string): Promise<void> {
await api.delete(`/admin/teams/${id}`);
}
export async function setTeamManager(teamId: string, userId: string | null): Promise<AdminTeam> {
return api.put(`/admin/teams/${teamId}/manager`, { user_id: userId });
}
export async function getTeamMembersForAdmin(teamId: string, page = 1, pageSize = 50): Promise<{ items: AdminUser[]; total: number }> {
const params = new URLSearchParams();
params.set('page', String(page));
params.set('page_size', String(pageSize));
params.set('user_type', 'frontend');
params.set('team_id', teamId);
return api.get(`/admin/users?${params.toString()}`);
}
export async function adjustCredits(userId: string, amount: number, description: string): Promise<void> {
await api.post(`/admin/users/${userId}/credits`, { amount, description });
}
+85 -3
View File
@@ -1,8 +1,8 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Button, Card, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography } from 'antd';
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, TeamOutlined } from '@ant-design/icons';
import { deleteAdminTeam, getAdminTeams, saveAdminTeam } from '../api';
import type { AdminTeam } from '../types';
import { DeleteOutlined, EditOutlined, PlusOutlined, ReloadOutlined, SearchOutlined, SettingOutlined, TeamOutlined, UserOutlined } from '@ant-design/icons';
import { deleteAdminTeam, getAdminTeams, getTeamMembersForAdmin, saveAdminTeam, setTeamManager } from '../api';
import type { AdminTeam, AdminUser } from '../types';
import { formatDate } from '../utils/formatDate';
const statusOptions = [
@@ -34,6 +34,12 @@ const AdminTeams: React.FC = () => {
const [modal, setModal] = useState<{ open: boolean; item: AdminTeam | null }>({ open: false, item: null });
const [form] = Form.useForm();
// 管理人弹窗状态
const [managerModal, setManagerModal] = useState<{ open: boolean; team: AdminTeam | null }>({ open: false, team: null });
const [members, setMembers] = useState<AdminUser[]>([]);
const [managerLoading, setManagerLoading] = useState(false);
const [selectedManagerId, setSelectedManagerId] = useState<string | null>(null);
const query = useMemo(() => ({
page,
pageSize,
@@ -107,6 +113,36 @@ const AdminTeams: React.FC = () => {
}
};
const openManagerModal = async (team: AdminTeam) => {
setManagerModal({ open: true, team });
setSelectedManagerId(team.managerId || null);
setManagerLoading(true);
try {
const res = await getTeamMembersForAdmin(team.id);
setMembers(res.items || []);
} catch (e: any) {
message.error(e?.message || '加载成员失败');
setMembers([]);
} finally {
setManagerLoading(false);
}
};
const handleSetManager = async () => {
if (!managerModal.team) return;
try {
setManagerLoading(true);
await setTeamManager(managerModal.team.id, selectedManagerId);
message.success(selectedManagerId ? '已设置管理人' : '已取消管理人');
setManagerModal({ open: false, team: null });
load();
} catch (e: any) {
message.error(e?.message || '设置失败');
} finally {
setManagerLoading(false);
}
};
const columns = [
{
title: '团队名称',
@@ -136,6 +172,16 @@ const AdminTeams: React.FC = () => {
width: 100,
render: (v: number) => <Typography.Text strong>{Number(v || 0).toLocaleString()}</Typography.Text>,
},
{
title: '管理人',
key: 'manager',
width: 140,
render: (_: any, r: AdminTeam) => (
<Typography.Text style={{ fontSize: 13 }}>
{r.managerName || <span style={{ color: '#94a3b8' }}></span>}
</Typography.Text>
),
},
{
title: '排序',
dataIndex: 'sortOrder',
@@ -161,6 +207,7 @@ const AdminTeams: React.FC = () => {
fixed: 'right' as const,
render: (_: any, r: AdminTeam) => (
<Space size={4}>
<Button type="link" size="small" icon={<SettingOutlined />} onClick={() => openManagerModal(r)}></Button>
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)}></Button>
<Popconfirm
title="确定删除该团队?"
@@ -239,6 +286,41 @@ const AdminTeams: React.FC = () => {
</Form.Item>
</Form>
</Modal>
{/* 设置管理人弹窗 */}
<Modal
title={<Space><UserOutlined /></Space>}
open={managerModal.open}
confirmLoading={managerLoading}
onOk={handleSetManager}
onCancel={() => { setManagerModal({ open: false, team: null }); setSelectedManagerId(null); }}
okText="保存"
cancelText="取消"
width={480}
>
<div style={{ marginTop: 16 }}>
<Typography.Text style={{ fontSize: 13, color: '#64748b', display: 'block', marginBottom: 8 }}>
{managerModal.team?.name || '-'}
</Typography.Text>
<Typography.Text style={{ fontSize: 13, color: '#64748b', display: 'block', marginBottom: 12 }}>
</Typography.Text>
<Select
style={{ width: '100%' }}
placeholder="选择管理人(可清空取消)"
value={selectedManagerId}
onChange={(v) => setSelectedManagerId(v || null)}
allowClear
loading={managerLoading}
optionFilterProp="label"
options={members.map((m) => ({
value: m.id,
label: `${m.username}${m.phone ? ` (${m.phone})` : ''}`,
}))}
showSearch
/>
</div>
</Modal>
</div>
);
};
+2
View File
@@ -146,6 +146,8 @@ export interface AdminTeam {
memberCount: number;
createdAt: string;
updatedAt?: string | null;
managerId?: string | null;
managerName?: string | null;
}
export interface AdminTeamOption {