flnxi-Wu/app/admin/_components/users/UserEditModal.tsx
WuYongJie e4dcb6260a
Some checks failed
CI Check / check (push) Has been cancelled
feat: Admin页面重构 & UI三层架构系统
- 创建统一UI三层架构系统(基础层、颜色层、组合层)
- 拆分Admin页面为独立Tab组件(MonitorTab、UsersTab、DepartmentsTab等)
- 提取公共逻辑到Hooks(useToast、useDataFetch、useUserOperations等)
- 提取工具函数(validators、formatters、api等)
- 创建统一弹窗组件(Modal)和useModal Hook
- 添加错误边界和统一状态组件
- 完善类型定义和可访问性支持
- 更新文档(优化记录、优化计划、更新日志)
2025-11-22 15:17:10 +08:00

204 lines
7.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client'
import type { Department, User } from '../types'
import { buttonModifiers, buttonStyles, cn, inputStyles } from '@/lib/ui-styles'
import { formatDateTime } from '../../_utils/formatters'
interface UserEditModalProps {
isOpen: boolean
user: User | null
departments: Department[]
editingUsername: string
selectedDepartment: string
managerNames: string[]
showTransferButton: boolean
onChangeUsername: (value: string) => void
onChangeDepartment: (value: string) => void
onClose: () => void
onSave: () => void
onToggleRole: () => void
onToggleActive: () => void
onToggleDownload: () => void
onDelete: () => void
onOpenTransferModal: () => void
hasDepartments: boolean
}
export default function UserEditModal({
isOpen,
user,
departments,
editingUsername,
selectedDepartment,
managerNames,
showTransferButton,
onChangeUsername,
onChangeDepartment,
onClose,
onSave,
onToggleRole,
onToggleActive,
onToggleDownload,
onDelete,
onOpenTransferModal,
hasDepartments,
}: UserEditModalProps) {
if (!isOpen || !user) return null
return (
<div className="fixed inset-0 bg-black/50 z-50 flex items-center justify-center p-4">
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-2xl max-w-2xl w-full max-h-[90vh] overflow-y-auto border border-slate-200/70 dark:border-slate-700/80">
<div className="px-6 py-4 border-b border-slate-200 dark:border-slate-700">
<div className="flex items-start justify-between gap-4">
<div>
<h3 className="text-lg font-semibold text-slate-900 dark:text-white">编辑用户</h3>
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 break-all">{user.email}</p>
</div>
<button
type="button"
className={cn(buttonStyles.ghost, buttonModifiers.icon)}
aria-label="关闭编辑用户"
onClick={onClose}
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<div className="p-6 space-y-5">
<section className="grid gap-4 md:grid-cols-3">
<div>
<p className="text-sm text-slate-500 dark:text-slate-400">注册时间</p>
<p className="text-sm font-medium text-slate-900 dark:text-white">
{formatDateTime(user.createdAt)}
</p>
</div>
<div className="md:col-span-2">
<p className="text-sm text-slate-500 dark:text-slate-400">直属上级</p>
<p className="text-sm font-medium text-slate-900 dark:text-white">
{managerNames.length ? managerNames.join('、') : '暂无'}
</p>
</div>
</section>
<section className="space-y-2">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">用户名</label>
<input
type="text"
value={editingUsername}
onChange={(e) => onChangeUsername(e.target.value)}
className={inputStyles.base}
placeholder="请输入用户名"
/>
</section>
<section className="space-y-2">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">选择部门</label>
<select
value={selectedDepartment}
onChange={(e) => onChangeDepartment(e.target.value)}
className={cn(inputStyles.base, 'pr-10')}
>
<option value="">-- 未分配部门 --</option>
{departments.map((dept) => (
<option key={dept.id} value={dept.name}>
{dept.name}
</option>
))}
</select>
<p className="text-xs text-slate-500 dark:text-slate-400">
更改部门后将自动为其匹配该部门的管理员作为直属上级。
</p>
</section>
<section className="space-y-3">
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300">角色</label>
<button
type="button"
onClick={onToggleRole}
className={cn(
buttonStyles.secondary,
'w-full justify-center',
user.role === 'ADMIN' && 'bg-purple-600 text-white hover:bg-purple-700',
)}
>
{user.role === 'ADMIN' ? '管理员' : '设为管理员'}
</button>
</section>
<section className="grid grid-cols-1 md:grid-cols-2 gap-3">
<button
type="button"
onClick={onToggleActive}
className={cn(
buttonStyles.secondary,
user.isActive
? 'bg-rose-100 dark:bg-rose-900/30 text-rose-700 dark:text-rose-300 hover:bg-rose-200 dark:hover:bg-rose-900/50'
: 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300 hover:bg-emerald-200 dark:hover:bg-emerald-900/50',
)}
>
{user.isActive ? '禁用用户' : '激活用户'}
</button>
<button
type="button"
onClick={onToggleDownload}
className={cn(
buttonStyles.secondary,
user.canDownload
? 'bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-900/50'
: 'bg-blue-100 dark:bg-blue-900/30 text-blue-700 dark:text-blue-300 hover:bg-blue-200 dark:hover:bg-blue-900/50',
)}
>
{user.canDownload ? '禁止下载' : '允许下载'}
</button>
</section>
{showTransferButton && (
<section className="space-y-2 border-t border-slate-200 dark:border-slate-700 pt-4">
<button
type="button"
onClick={onOpenTransferModal}
className={cn(buttonStyles.primary, 'w-full justify-center gap-2')}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7l5 5m0 0l-5 5m5-5H6" />
</svg>
转移最高权限
</button>
<p className="text-xs text-slate-500 dark:text-slate-400">仅当前最高权限持有者可执行此操作。</p>
</section>
)}
<section className="space-y-2 border-t border-slate-200 dark:border-slate-700 pt-4">
<button
type="button"
onClick={onDelete}
className={cn(buttonStyles.danger, 'w-full justify-center')}
>
删除用户
</button>
</section>
{!hasDepartments && (
<div className="text-sm text-amber-700 dark:text-amber-300 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-3">
⚠️ 暂无部门,请先在系统设置中添加部门。
</div>
)}
</div>
<div className="px-6 py-4 border-t border-slate-200 dark:border-slate-700 flex justify-end gap-3">
<button type="button" onClick={onClose} className={cn(buttonStyles.ghost)}>
取消
</button>
<button type="button" onClick={onSave} className={cn(buttonStyles.primary)}>
保存
</button>
</div>
</div>
</div>
)
}