'use client' import Select from '@/components/ui/Select' import { useCallback, useEffect, useState } from 'react' import Link from 'next/link' import { useSession } from 'next-auth/react' import DepartmentShell from './DepartmentShell' type GroupRef = { slug: string; name: string } type DepartmentGroup = GroupRef & { attachedPath: string source: 'own' | 'inherit' | string } type Person = { userId: string username: string displayName: string jobNumber: string | null departmentPath: string | null departmentName: string | null departmentGroups: DepartmentGroup[] personalGroups: GroupRef[] } export default function UserPermissionsDashboard() { const { data: session, status } = useSession() const isSuperAdmin = Boolean((session?.user as { isSuperAdmin?: boolean } | undefined)?.isSuperAdmin) const [keyword, setKeyword] = useState('') const [people, setPeople] = useState([]) const [groups, setGroups] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [notice, setNotice] = useState(null) const [saving, setSaving] = useState(false) const search = useCallback(async (needle: string) => { if (!needle.trim()) { setPeople([]) return } setLoading(true) setError(null) try { const response = await fetch( `/api/organization/user-permissions?q=${encodeURIComponent(needle.trim())}`, { cache: 'no-store' }, ) const body = await response.json().catch(() => null) if (!response.ok) throw new Error(body?.error || '搜索失败') setPeople(body.people || []) setGroups(body.groups || []) } catch (searchError) { setError(searchError instanceof Error ? searchError.message : '搜索失败') } finally { setLoading(false) } }, []) useEffect(() => { if (status !== 'authenticated' || !isSuperAdmin) return const handle = window.setTimeout(() => { void search(keyword) }, 250) return () => window.clearTimeout(handle) }, [keyword, status, isSuperAdmin, search]) const mutate = async (body: Record, success: string) => { setSaving(true) setError(null) setNotice(null) try { const response = await fetch('/api/organization/user-permissions', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) const payload = await response.json().catch(() => null) if (!response.ok) throw new Error(payload?.error || '保存失败') setNotice(success) await search(keyword) } catch (saveError) { setError(saveError instanceof Error ? saveError.message : '保存失败') } finally { setSaving(false) } } return (

治理与设置

按人看权限

搜九千七这种人,看他部门带了哪组,自己又加了哪组。不要再改账号上的部门名。

回到权限组
setKeyword(event.target.value)} placeholder="搜花名 / 工号 / 账号,例如九千七" className="mt-4 h-11 w-full max-w-xl rounded-lg border border-[#e2e8f0] bg-white px-3 text-sm text-[#101828] outline-none focus:border-[#1677ff] focus:ring-2 focus:ring-[#5a9ce6]/40" />
{status === 'loading' ? (
加载中…
) : !isSuperAdmin ? (
只有超级管理员可以改人员权限。
) : (
{error && (
{error}
)} {notice && (
{notice}
)} {loading &&

搜索中…

} {!keyword.trim() ? (
先搜一个人。部门带来的组不能在这里拿掉,要去权限组里改部门名单。
) : people.length === 0 && !loading ? (
没有找到这个人,或他还没有钉钉绑定。
) : (
    {people.map((person) => { const personalSlugs = new Set(person.personalGroups.map((row) => row.slug)) const extraGroups = groups.filter((group) => !personalSlugs.has(group.slug)) return (
  • {person.displayName}

    @{person.username} {person.jobNumber ? ` · ${person.jobNumber}` : ''} {person.departmentName ? ` · ${person.departmentName}` : ''}

    {person.departmentPath && (

    {person.departmentPath}

    )}

    部门带来的组

    {person.departmentGroups.length === 0 ? (

    这个部门还没进组,暂时走旧的部门权限。

    ) : (
      {person.departmentGroups.map((group) => (
    • {group.name} {group.source === 'own' ? '本部门在组里' : `随 ${group.attachedPath.split('/').pop()}`}
    • ))}
    )}

    个人加的组

    {person.personalGroups.length === 0 ? (

    还没有单独加组

    ) : (
      {person.personalGroups.map((group) => (
    • {group.name}
    • ))}
    )} {extraGroups.length > 0 && ( )}
  • ) })}
)}
)}
) }