import { useEffect, useState } from 'react'; import { channelAccessApi, type ChannelMember } from '@/core-bridge/channelAccess'; import { errorMessage } from '@/core-bridge/errors'; import type { RoleInfo } from '@/core-bridge/types'; import { PERMISSIONS, ROLE_COLORS } from '@/lib/roles'; import { useToast } from '@/store/toast'; import { useWorkspace } from '@/store/workspace'; import { Button } from '@/ui/Button'; import { Icon } from '@/ui/Icon'; import styles from './RolesEditor.module.css'; const COLOR_LABEL = { gray: '灰', blue: '蓝', green: '绿', amber: '黄', red: '红', purple: '紫' } as const; function RoleForm({ role, members, onDone }: { role?: RoleInfo; members: ChannelMember[]; onDone: (id?: string) => void }) { const people = useWorkspace((s) => s.people); const createRole = useWorkspace((s) => s.createRole); const updateRole = useWorkspace((s) => s.updateRole); const deleteRole = useWorkspace((s) => s.deleteRole); const setRoleMembers = useWorkspace((s) => s.setRoleMembers); const [name, setName] = useState(role?.name ?? ''); const [prefix, setPrefix] = useState(role?.prefix ?? ''); const [suffix, setSuffix] = useState(role?.suffix ?? ''); const [color, setColor] = useState(role?.color ?? 'gray'); const [permissions, setPermissions] = useState>(new Set(role?.permissions)); const [chosen, setChosen] = useState>(new Set(role?.members)); const [busy, setBusy] = useState(false); const [confirmDelete, setConfirmDelete] = useState(false); const toggle = (set: ReadonlySet, id: string) => { const next = new Set(set); if (next.has(id)) next.delete(id); else next.add(id); return next; }; async function run(task: () => Promise) { setBusy(true); try { onDone(await task()); } catch (cause) { useToast.getState().push({ kind: 'error', text: `操作失败:${errorMessage(cause)}` }); } finally { setBusy(false); } } const save = () => run(async () => { const request = { name: name.trim(), prefix: prefix.trim() || null, suffix: suffix.trim() || null, color, permissions: [...permissions] }; let id = role?.id; if (id) await updateRole(id, request); else id = await createRole(request); await setRoleMembers(id, [...chosen]); useToast.getState().push({ kind: 'ok', text: '身份组已保存' }); return id; }); const remove = () => run(async () => { if (role) await deleteRole(role.id); return undefined; }); const sample = `${prefix.trim()}${prefix.trim() ? ' ' : ''}你的名字${suffix.trim() ? ' ' : ''}${suffix.trim()}`; return
{ event.preventDefault(); if (name.trim()) void save(); }}>
颜色 · 聊天里显示为 {sample}
{ROLE_COLORS.map((item) => )}
权限 {PERMISSIONS.map((item) => )}
成员 · {chosen.size} {members.length ? members.map((member) => ) :

还没有其他成员。成员接受邀请后可以加入身份组。

}
{role ? (confirmDelete ? <> : ) : null}
; } /** 空间设置里的「身份组」页:左边是身份组列表,右边是编辑表单 */ export function RolesEditor() { const channelId = useWorkspace((s) => s.channel?.id); const roles = useWorkspace((s) => s.roles); const [members, setMembers] = useState([]); const [selected, setSelected] = useState(''); useEffect(() => { if (!channelId) return; let active = true; channelAccessApi.members(channelId).then((rows) => { if (active) setMembers(rows.filter((row) => row.role !== 'owner')); }).catch(() => {}); return () => { active = false; }; }, [channelId]); const role = roles.find((item) => item.id === selected); const first = role ?? (selected === '' ? roles[0] : undefined); const creating = !first; return
    {roles.map((item) =>
  • )}
setSelected(id ?? '')} />
; }