8315d5660a
Project CI / AI game creator shell Rust crates (push) Successful in 1m27s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m53s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell Rust lane 2/2 (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
- packages/shared/src/components/admin/ 新增 16 个后台共享组件(AdminPage/AdminPanel/AdminButton/AdminAlert/AdminTable/AdminField/AdminInfoList/AdminTagList/AdminPagination/AdminListPanel/AdminDialog 等)与随组件的 admin.css,经 @genarrative/shared/components 导出 - AdminListPanel 统一列表外壳:columns + renderRow、加载两口径(empty/line)、空态三态(state/text/row)、toolbar 与 pagination 槽位、showEmpty 判据、surface=plain(弹窗内列表) - AdminListPanel 新增列头排序(sortable/sortValue/sortDescription):正序→倒序→不排序循环、同步 th[aria-sort]、稳定排序 - AdminDialog 统一弹窗外壳(遮罩/标题行/关闭动作/role=dialog/aria-modal/Esc/点遮罩关闭/初始聚焦/as=form),12 处各页自写弹窗全部改用它 - AdminFormat 新增 formatAdminDateTime/formatAdminDate,删除 7 份重复的本地时间格式化实现,修掉列表里裸 ISO 时间 - 21 个文件里 26 处列表全部迁到 AdminListPanel(24 处 columns 形态、2 处弹窗内 plain),页内已无手写表头或 AdminTable 当 children 的写法;累计 45 个可排序列 - 页面壳归一:错误报告补 AdminPage wide + AdminPageHeading,详情弹窗移出列表面板;模板管理列表从共享 ui Table 换成 AdminTable - 样式收口:公共组件规则随组件进入共享 styles,清理 admin.css 重复按钮/面板规则,.admin-tag 与 .admin-table th 改 nowrap,埋点用户列参考按钮紧跟本行并留行距,错误报告修版心 - 新增 scripts/admin-web-fake-api.mjs 本地假 API(含 overview/dashboard/tracking/error-reports/feature-gates/邀请码/兑换码/任务/充值/项目工程/AGC 模型与模板/游戏/账号/用户详情夹具),用于本地核对渲染 - docs 同步:共享基础组件库与展示页(后台组件层/列表排序/收敛范围/本地核对)、decision-log 与 pitfalls 记录迁移口径、排序边界与踩坑 - 验证:npm run admin-web:typecheck、npx vitest run apps/admin-web packages/shared/src/components/admin(26 files / 244 tests)、npm run check:encoding、git diff --check、prettier、eslint 全部通过
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
import { AdminButton } from '@genarrative/shared/components';
|
|
import { UserRoundSearch } from 'lucide-react';
|
|
import { MouseEvent, useRef, useState } from 'react';
|
|
|
|
import { AdminUserDetailDialog } from './AdminUserDetailDialog';
|
|
|
|
interface AdminUserReferenceButtonProps {
|
|
token: string;
|
|
userId?: string | null;
|
|
publicUserCode?: string | null;
|
|
onUnauthorized: (message?: string) => void;
|
|
}
|
|
|
|
export function AdminUserReferenceButton({
|
|
token,
|
|
userId,
|
|
publicUserCode,
|
|
onUnauthorized,
|
|
}: AdminUserReferenceButtonProps) {
|
|
const [open, setOpen] = useState(false);
|
|
const triggerRef = useRef<HTMLButtonElement | null>(null);
|
|
const normalizedUserId = normalizeUserReference(userId);
|
|
const normalizedPublicUserCode = normalizeUserReference(publicUserCode);
|
|
const lookup = normalizedUserId
|
|
? { userId: normalizedUserId }
|
|
: normalizedPublicUserCode
|
|
? { publicUserCode: normalizedPublicUserCode }
|
|
: null;
|
|
|
|
if (!lookup) {
|
|
return null;
|
|
}
|
|
|
|
function openDialog(event: MouseEvent<HTMLButtonElement>) {
|
|
event.stopPropagation();
|
|
setOpen(true);
|
|
}
|
|
|
|
function closeDialog() {
|
|
setOpen(false);
|
|
window.requestAnimationFrame(() => triggerRef.current?.focus());
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<AdminButton
|
|
variant="ghost"
|
|
ref={triggerRef}
|
|
aria-label="查看用户信息"
|
|
title="查看用户信息"
|
|
type="button"
|
|
onClick={openDialog}
|
|
className="admin-user-reference-button"
|
|
>
|
|
<UserRoundSearch size={16} aria-hidden="true" />
|
|
</AdminButton>
|
|
{open ? (
|
|
<AdminUserDetailDialog
|
|
token={token}
|
|
{...lookup}
|
|
onClose={closeDialog}
|
|
onUnauthorized={onUnauthorized}
|
|
/>
|
|
) : null}
|
|
</>
|
|
);
|
|
}
|
|
|
|
function normalizeUserReference(value?: string | null) {
|
|
const normalized = value?.trim() ?? '';
|
|
if (!normalized || normalized.toLowerCase().startsWith('admin:')) {
|
|
return '';
|
|
}
|
|
return normalized;
|
|
}
|