From 0bc9366e85b99bef73d93e83941240323558d66c Mon Sep 17 00:00:00 2001 From: duxingchen Date: Mon, 21 Sep 2026 17:50:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=88=86=E7=BB=84):=20=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E6=94=B9=E4=B8=BA=E6=96=B9=E5=9D=97=E9=9D=A2?= =?UTF-8?q?=E6=9D=BF=20+=20=E7=BB=84=E9=95=BF=E8=AE=BE=E7=BD=AE=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E6=98=8E=E7=A1=AE=E6=8C=89=E9=92=AE=EF=BC=88=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A4=9A=E7=BB=84=E9=95=BF=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修正理解偏差:上一轮把「一行一个」理解成了成员列表,实际指的是**搜索姓名 时的下拉** —— antd Select 展开后一人一行、还带账号,又长又占地方。 1) 搜索选择:去掉 Select,改为内联的「搜索框 + 方块多选网格」 · 候选人全铺成方块(auto-fill 网格),点方块选中/取消,**无滚动条** · 方块只显示中文名,账号挪到 title 悬停可见 · 搜索在前端过滤(LICA 十几人,一次拉全量更跟手) · 顶部实时显示「已选 N 人」;按钮文案带数量「添加选中的 N 人」 · 无匹配时给空状态,不会是一片空白 2) 组长设置:从「点方块切换」这种隐晦交互,改为**方块内常驻的明确按钮** · 未设组长 → 灰色「⚙ 设为组长」;已是组长 → 金色「👑 组长」 · 点击即切换,一眼看懂「他是不是组长、怎么改」 · 每个方块各管各的 —— **天然支持多个组长** 数据层本来就支持多组长(is_leader 是成员行上的独立字段);实测同一组 设两个组长并存,且两人的 /auth/me 都返回 is_leader=true。 顺带清理早先测试留下的数据污染: · 生产大组的可见范围曾被「验证改范围权限」的测试改成「生产+售后」, 已改回只有「生产制造」 · 删除了残留的「权限测试组」 --- frontend/src/pages/admin/AdminGroupsPage.tsx | 182 ++++++++++++++----- 1 file changed, 132 insertions(+), 50 deletions(-) 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 && ( + + )}
)}