flnxi-Wu/app/admin/_components/users/UserEditModal.tsx
WuYongJie 8705b9846a
Some checks failed
CI Check / check (push) Has been cancelled
feat: 统一弹窗样式系统与分类管理优化
- 创建统一的Modal组件,采用Apple风格设计
- 实现毛玻璃效果、圆角、动画和细边框描边
- 修复弹窗打开时背景滚动问题
- 修复分类管理中重复卡片和数组字符串显示问题
- 优化选项解析逻辑,添加去重和过滤机制
2025-11-25 09:37:00 +08:00

229 lines
9.3 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 { useThemeStyles, cn } 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) {
const styles = useThemeStyles()
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 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-slate-100 [&::-webkit-scrollbar-track]:dark:bg-slate-800 [&::-webkit-scrollbar-thumb]:bg-slate-300 [&::-webkit-scrollbar-thumb]:dark:bg-slate-600 [&::-webkit-scrollbar-thumb]:rounded-full">
<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(styles.buttonStyles.ghost, styles.buttonModifiers.icon, 'text-slate-700 dark:text-slate-200')}
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={styles.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(styles.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(
styles.buttonStyles.secondary,
'w-full justify-center max-w-xs', // 限制最大宽度,避免按钮太长
// 基础状态:白色背景,深色文字
'bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-200',
// 管理员状态:紫色背景,白色文字(使用 !important 确保覆盖基础文字颜色)
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(
styles.buttonStyles.secondary,
'bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-200', // 显式添加文字颜色,确保在浅色模式下清晰可见
user.isActive
? 'bg-rose-100 dark:bg-rose-900/30 text-rose-800 dark:text-rose-300 hover:bg-rose-200 dark:hover:bg-rose-900/50' // 使用更深的文字颜色
: 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-800 dark:text-emerald-300 hover:bg-emerald-200 dark:hover:bg-emerald-900/50', // 使用更深的文字颜色
)}
>
{user.isActive ? '禁用用户' : '激活用户'}
</button>
<button
type="button"
onClick={onToggleDownload}
className={cn(
styles.buttonStyles.secondary,
'bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-900 dark:text-slate-200', // 显式添加文字颜色,确保在浅色模式下清晰可见
user.canDownload
? 'bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 hover:bg-amber-200 dark:hover:bg-amber-900/50' // 使用更深的文字颜色
: 'bg-blue-100 dark:bg-blue-900/30 text-blue-800 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(
styles.buttonStyles.primary,
'w-full justify-center',
// 统一UI样式已包含颜色,不需要额外添加
)}
>
转移最高权限
</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(
styles.buttonStyles.danger,
'w-full justify-center',
// 统一UI样式已包含颜色,不需要额外添加
)}
>
删除用户
</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(
styles.buttonStyles.secondary,
'bg-white dark:bg-slate-800 border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-200' // 显式添加背景色、边框和文字色
)}
>
取消
</button>
<button
type="button"
onClick={onSave}
className={cn(
styles.buttonStyles.primary,
'bg-indigo-600 hover:bg-indigo-700 text-white' // 显式添加背景色和文字色
)}
>
保存
</button>
</div>
</div>
</div>
)
}