diff --git a/frontend/src/pages/admin/AdminGroupsPage.tsx b/frontend/src/pages/admin/AdminGroupsPage.tsx index d636e06..b8ec84d 100644 --- a/frontend/src/pages/admin/AdminGroupsPage.tsx +++ b/frontend/src/pages/admin/AdminGroupsPage.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, useMemo } from "react"; -import { Table, Button, Modal, Input, Checkbox, Tag, App, Spin, Empty, Select } from "antd"; -import { Plus, Pencil, Trash2, UserPlus, Crown, ShieldCheck } from "lucide-react"; +import { Table, Button, Modal, Input, Checkbox, Tag, App, Spin, Empty } from "antd"; +import { Plus, Pencil, Trash2, UserPlus, Crown, ShieldCheck, Search, Check } from "lucide-react"; import { useAuth } from "../../contexts/AuthContext"; import { isSuperAdmin } from "../../constants/task"; import { extractErrorMessage } from "../../utils/errorMessage"; @@ -56,6 +56,7 @@ export default function AdminGroupsPage() { const [candidates, setCandidates] = useState([]); const [picked, setPicked] = useState([]); // 多选 const [pickedLeader, setPickedLeader] = useState(false); + const [candidateKeyword, setCandidateKeyword] = useState(""); const [adding, setAdding] = useState(false); const load = useCallback(async () => { @@ -91,6 +92,21 @@ export default function AdminGroupsPage() { // 大组列表(下拉用) const topGroups = useMemo(() => groups.filter((g) => g.parent_id === null), [groups]); + // 候选人按关键字过滤 —— 前端过滤即可:LICA 才十几人,一次拉全量更跟手 + const visibleCandidates = useMemo(() => { + const kw = candidateKeyword.trim().toLowerCase(); + if (!kw) return candidates; + return candidates.filter( + (c) => c.full_name.toLowerCase().includes(kw) || c.username.toLowerCase().includes(kw) + ); + }, [candidates, candidateKeyword]); + + function togglePick(username: string) { + setPicked((prev) => + prev.includes(username) ? prev.filter((u) => u !== username) : [...prev, username] + ); + } + // ============================================================ // 编辑 // ============================================================ @@ -194,6 +210,7 @@ export default function AdminGroupsPage() { setCandidates(await fetchMemberCandidates()); setPicked([]); setPickedLeader(false); + setCandidateKeyword(""); } catch (err) { message.error(extractErrorMessage(err, "加载人员失败")); } @@ -392,30 +409,45 @@ export default function AdminGroupsPage() { {detail.members.map((m) => (
{ - if (detail.can_manage_members) handleToggleLeader(m.user_id, !m.is_leader); - }} - className={`group relative flex h-11 select-none items-center justify-center rounded-lg border px-2 text-sm transition-colors ${ + title={`${m.display_name}(${m.user_id})`} + className={`group relative flex h-[58px] flex-col items-center justify-center rounded-lg border px-2 transition-colors ${ m.is_leader - ? "border-amber-300 bg-amber-50 font-medium text-amber-800" - : "border-gray-200 bg-white text-gray-700" - } ${ - detail.can_manage_members - ? "cursor-pointer hover:border-blue-300 hover:bg-blue-50" - : "" - }`} + ? "border-amber-300 bg-amber-50" + : "border-gray-200 bg-white" + } ${detail.can_manage_members ? "hover:border-blue-300" : ""}`} > - {m.is_leader && ( - - )} - {m.display_name} + + {m.display_name} + - {/* 移除:默认藏起来,悬停该方块才出现,避免误点 */} + {/* 组长:一个**始终可见**的明确按钮,而不是"点方块"这种隐晦交互。 + 一个人是不是组长、怎么改,一眼就能看到。 + 每个方块各管各的 —— 因此**天然支持多个组长**。 */} + {detail.can_manage_members ? ( + + ) : ( + m.is_leader && ( + + + 组长 + + ) + )} + + {/* 移出:悬停才出现,避免误点 */} {detail.can_manage_members && ( + ); + })} +
+ )} + +
+ setPickedLeader(e.target.checked)} + > + 同时设为组长 + +
+ + +
+ {picked.length > 0 && ( + + )}
)}