feat(admin): 用户体验体系化提升与高危缺陷修复

UX 缺陷修复:
- 校验失败不再卡死弹窗按钮(Users/Roles/Bills)
- 押金收取/批量收取/添加分期防重复提交;切换房型重置勾选
- AI 表单/批量确认不再出现"假成功"
- Dashboard 各数据模块独立加载,单接口失败不再整页清零
- 房间可视化加载失败显示错误态而非永久转圈
- 学生编辑表单回填前重置,避免字段残留污染
- 覆盖式导入增加二次确认;恢复默认考勤时段确认并同步表单
- 金数据匹配关闭前确认,同步中禁止误关

体验提升:
- 新增统一 QueryErrorState/QueryEmpty,20+ 页面加载失败显示错误态与重试
- 全局 ErrorBoundary + RouteKeeper 逐页兜底
- 新增 usePageVisible/useVisibleRefetch,保活页面切回自动刷新数据
- 新增首次登录角色引导 RoleTour 与业务闭环 NextStepHint 引导卡
- 重构 A2UI:useSubmissionState/useXCardSurface 收敛状态与命令生命周期,
  ArtifactErrorBoundary 渲染降级,图表空数据占位
- AI 助手欢迎语与建议话术按角色定制,会话列表空态引导
- 更新 a2ui-contract.md 契约文档说明实现现状
This commit is contained in:
2026-08-07 17:23:23 +08:00
parent db0d972633
commit 67435e46ca
54 changed files with 2298 additions and 1177 deletions

View File

@@ -11,6 +11,8 @@ import { message } from '../../ui/app-message';
import EditableCell from '../../components/EditableCell';
import PermissionButton from '../../components/PermissionButton';
import { usePermission } from '../../hooks/usePermission';
import { QueryErrorState } from '../../components/QueryState';
import { useVisibleRefetch } from '../../hooks/usePageVisible';
interface TeacherRow {
id: number;
@@ -68,24 +70,24 @@ const TeachersPage: React.FC = () => {
data: fetchResult = { list: [], total: 0 },
isLoading,
isFetching,
isError,
refetch,
} = useQuery<TeacherListResponse>({
queryKey: ['rbac', 'teachers', page, pageSize, search],
queryFn: async () => {
try {
return validateResponse<TeacherListResponse>(
teacherListSchema,
await api.get<TeacherListResponse>('/rbac/teachers', {
params: { search: search || undefined, page, pageSize },
}),
);
} catch {
return { list: [], total: 0 };
}
return validateResponse<TeacherListResponse>(
teacherListSchema,
await api.get<TeacherListResponse>('/rbac/teachers', {
params: { search: search || undefined, page, pageSize },
}),
);
},
});
const data = fetchResult.list;
const total = fetchResult.total;
const loading = isLoading || isFetching;
// RouteKeeper 保活页面切回时刷新教师列表
useVisibleRefetch(['rbac', 'teachers']);
const saveProfileMutation = useApiMutation(
async ({
@@ -248,39 +250,47 @@ const TeachersPage: React.FC = () => {
style={{ width: 220 }}
/>
</Space>
<Table
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
scroll={{ x: 1300 }}
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
pageSizeOptions: [20, 50, 100],
onChange: (nextPage, nextPageSize) => {
setPage(nextPage);
setPageSize(nextPageSize);
},
showTotal: (t) => `${t}`,
}}
expandable={{
rowExpandable: (r) => (r.classAssignments || []).length > 0,
expandedRowRender: (r) =>
r.classAssignments?.length ? (
<Space wrap>
{r.classAssignments.map((a, i) => (
<Tag key={i} color="blue">
{ROLE_TYPE_LABELS[a.roleType] || a.roleType}: {a.className}
{a.subject ? `${a.subject}` : ''}
</Tag>
))}
</Space>
) : null,
}}
/>
{isError ? (
<QueryErrorState
title="教师数据加载失败"
description="请检查网络后重试。"
onRetry={() => void refetch()}
/>
) : (
<Table
columns={columns}
dataSource={data}
rowKey="id"
loading={loading}
scroll={{ x: 1300 }}
pagination={{
current: page,
pageSize,
total,
showSizeChanger: true,
pageSizeOptions: [20, 50, 100],
onChange: (nextPage, nextPageSize) => {
setPage(nextPage);
setPageSize(nextPageSize);
},
showTotal: (t) => `${t}`,
}}
expandable={{
rowExpandable: (r) => (r.classAssignments || []).length > 0,
expandedRowRender: (r) =>
r.classAssignments?.length ? (
<Space wrap>
{r.classAssignments.map((a, i) => (
<Tag key={i} color="blue">
{ROLE_TYPE_LABELS[a.roleType] || a.roleType}: {a.className}
{a.subject ? `${a.subject}` : ''}
</Tag>
))}
</Space>
) : null,
}}
/>
)}
<Modal
title={`编辑档案 — ${profileModal?.name || ''}`}
open={!!profileModal && canEditTeachers}