seaislee1209 85f76d8543 feat: v0.8.2~v0.8.4 — 管理后台 UI 修复 + 团队详情重构 + 审计日志系统
v0.8.2: DatePicker/Select 暗色主题、公告跑马灯、Toast 全局化、失败原因 tooltip
v0.8.3: 团队详情抽屉→弹窗重构 + 修改秒数池功能 + member_count 修复
v0.8.4: AdminAuditLog 模型 + 12 处管理操作埋点 + 日志查询页面(/admin/logs)

审计日志覆盖所有管理员 mutation 操作(充值、修改额度、创建/禁用用户等),
记录操作人、变更前后值、IP 地址,支持按操作类型/操作人/日期筛选。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 01:18:44 +08:00

82 lines
2.3 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import styles from './Select.module.css';
interface SelectOption {
label: string;
value: string;
}
interface SelectProps {
options: SelectOption[];
value: string;
onChange: (value: string) => void;
placeholder?: string;
minWidth?: number;
}
export function Select({ options, value, onChange, placeholder = '请选择', minWidth = 120 }: SelectProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!open) return;
const handle = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) {
setOpen(false);
}
};
document.addEventListener('mousedown', handle);
return () => document.removeEventListener('mousedown', handle);
}, [open]);
const selected = options.find((o) => o.value === value);
return (
<div className={styles.wrapper} ref={ref}>
<button
type="button"
className={styles.trigger}
style={{ minWidth }}
onClick={() => setOpen(!open)}
>
{selected ? (
<span className={styles.label}>{selected.label}</span>
) : (
<span className={styles.placeholder}>{placeholder}</span>
)}
<svg
className={`${styles.arrow} ${open ? styles.arrowOpen : ''}`}
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
<div
className={`${styles.menu} ${open ? styles.open : ''}`}
style={{ minWidth }}
>
{options.map((opt) => (
<div
key={opt.value}
className={`${styles.item} ${value === opt.value ? styles.selected : ''}`}
onClick={() => {
onChange(opt.value);
setOpen(false);
}}
>
<span>{opt.label}</span>
<svg className={styles.check} width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<polyline points="20 6 9 17 4 12" />
</svg>
</div>
))}
</div>
</div>
);
}