fix(app): Phase 1.1 紧急修复 — SyncEngine 接入 + authorId + catch 异常处理
- feat(sync): SyncEngine 接入 EditorPage, 保存时 enqueue + 网络恢复自动 trySync - fix(editor): authorId 从 AuthBloc 获取, 替代硬编码 'local' - fix(bloc): class_bloc/calendar/profile/parent catch(_).全部改为 debugPrint - feat(editor): 编辑器工具栏拆分 (brush_panel/tag_panel/text_format_bar/dot_grid_painter) - feat(editor): EditorBloc 扩展 + EditorPage 增强 - feat(search): SearchBloc 扩展搜索功能 - feat(home): HomeBloc/HomePage 增强 - feat(auth): LoginPage 增强 - feat(templates): TemplateGalleryPage 重构 - fix(web): 管理端班级/日记页面修复 - fix(server): comment_service + theme_handler 修复 - docs: 添加全链路审计报告和验证截图
This commit is contained in:
@@ -2,9 +2,18 @@ import client from '../client';
|
||||
import type { SchoolClass, CreateClassReq, ClassMember, PaginatedResponse } from './types';
|
||||
|
||||
export const classApi = {
|
||||
/** 班级列表 — 后端返回纯数组,前端转换为 PaginatedResponse 格式 */
|
||||
list: (params?: { page?: number; page_size?: number }) =>
|
||||
client.get<{ success: boolean; data: PaginatedResponse<SchoolClass> }>('/diary/classes', { params })
|
||||
.then((r) => r.data.data),
|
||||
client.get<{ success: boolean; data: SchoolClass[] }>('/diary/classes', { params })
|
||||
.then((r) => {
|
||||
const raw = r.data.data;
|
||||
// 后端返回纯数组,包装为分页格式
|
||||
if (Array.isArray(raw)) {
|
||||
return { data: raw, total: raw.length, page: params?.page ?? 1, page_size: params?.page_size ?? 20 } as PaginatedResponse<SchoolClass>;
|
||||
}
|
||||
// 兼容:如果后端已升级为分页格式
|
||||
return raw as unknown as PaginatedResponse<SchoolClass>;
|
||||
}),
|
||||
|
||||
myClasses: () =>
|
||||
client.get<{ success: boolean; data: SchoolClass[] }>('/diary/classes/my')
|
||||
|
||||
@@ -19,7 +19,7 @@ interface DrawerFormProps {
|
||||
sections?: FormSection[];
|
||||
children?: React.ReactNode;
|
||||
columns?: 1 | 2;
|
||||
form?: ReturnType<typeof Form.useForm>[0];
|
||||
form?: ReturnType<typeof Form.useForm<Record<string, unknown>>>[0];
|
||||
onValuesChange?: (changedValues: Record<string, unknown>, allValues: Record<string, unknown>) => void;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export function DrawerForm({
|
||||
form: externalForm,
|
||||
onValuesChange,
|
||||
}: DrawerFormProps) {
|
||||
const [internalForm] = Form.useForm();
|
||||
const [internalForm] = Form.useForm<Record<string, unknown>>();
|
||||
const form = externalForm ?? internalForm;
|
||||
const isDark = useThemeMode();
|
||||
|
||||
@@ -45,7 +45,8 @@ export function DrawerForm({
|
||||
if (open) {
|
||||
form.resetFields();
|
||||
if (initialValues) {
|
||||
form.setFieldsValue(initialValues);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Ant Design 6 setFieldsValue requires Store type
|
||||
form.setFieldsValue(initialValues as any);
|
||||
}
|
||||
}
|
||||
}, [open, initialValues, form]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useState, useEffect, useMemo } from 'react';
|
||||
import { Layout, Avatar, Space, Dropdown, Tooltip, Spin, theme, Menu, message } from 'antd';
|
||||
import type { MenuItemType, SubMenuType } from 'antd/es/menu/hooks/useItems';
|
||||
import type { MenuItemType, SubMenuType } from 'antd/es/menu/interface';
|
||||
import {
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
Input,
|
||||
Tag,
|
||||
Drawer,
|
||||
Modal,
|
||||
Badge,
|
||||
Typography,
|
||||
message,
|
||||
@@ -26,14 +25,12 @@ import { PageContainer } from '../../components/PageContainer';
|
||||
import { DrawerForm } from '../../components/DrawerForm';
|
||||
import { useCrudDrawer } from '../../hooks/useCrudDrawer';
|
||||
import { usePaginatedData } from '../../hooks/usePaginatedData';
|
||||
import { useApiRequest } from '../../hooks/useApiRequest';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
export default function ClassList() {
|
||||
const isDark = useThemeMode();
|
||||
const { execute } = useApiRequest();
|
||||
|
||||
const {
|
||||
data: classes,
|
||||
@@ -41,13 +38,13 @@ export default function ClassList() {
|
||||
page,
|
||||
loading,
|
||||
refresh,
|
||||
} = usePaginatedData<SchoolClass>(async (p, pageSize) => {
|
||||
} = usePaginatedData<SchoolClass>(async (p: number, pageSize: number) => {
|
||||
const result = await classApi.list({ page: p, page_size: pageSize });
|
||||
return { data: result.data, total: result.total };
|
||||
}, 20);
|
||||
|
||||
// --- Create/Edit drawer ---
|
||||
const classDrawer = useCrudDrawer<SchoolClass>({
|
||||
const classDrawer = useCrudDrawer<{ version: number } & SchoolClass>({
|
||||
getId: (r) => r.id,
|
||||
onCreate: async (values) => {
|
||||
await classApi.create({ name: values.name as string, school_name: values.school_name as string | undefined });
|
||||
|
||||
@@ -31,7 +31,6 @@ import { commentApi } from '../../api/diary/comments';
|
||||
import { classApi } from '../../api/diary/classes';
|
||||
import type { JournalEntry, Comment, SchoolClass } from '../../api/diary/types';
|
||||
import { PageContainer } from '../../components/PageContainer';
|
||||
import { FilterBar } from '../../components/FilterBar';
|
||||
import { useApiRequest } from '../../hooks/useApiRequest';
|
||||
import { useThemeMode } from '../../hooks/useThemeMode';
|
||||
|
||||
@@ -443,7 +442,7 @@ export default function JournalList() {
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Divider orientation="left" style={{ fontSize: 15, fontWeight: 500 }}>
|
||||
<Divider style={{ fontSize: 15, fontWeight: 500, textAlign: 'left' }}>
|
||||
评论 ({comments.length})
|
||||
</Divider>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user