Compare commits
3 Commits
8c79bc81c1
...
a3ec8f47f4
| Author | SHA1 | Date | |
|---|---|---|---|
| a3ec8f47f4 | |||
|
|
128b89241e | ||
|
|
271e02032c |
113
docs/前端API开发规范-Orval.md
Normal file
113
docs/前端API开发规范-Orval.md
Normal file
@ -0,0 +1,113 @@
|
||||
# 前端 API 开发规范(Orval 生成代码)
|
||||
|
||||
本规范面向 `reading-platform-frontend`,以 `src/api/generated/` 为**接口类型与路径的唯一真源**,通过 Orval 从后端 OpenAPI 自动生成 TypeScript 类型 + 客户端方法。
|
||||
|
||||
## 1. 目录与职责边界
|
||||
|
||||
- **`reading-platform-frontend/src/api/generated/`**:Orval 自动生成目录,**禁止手改**。
|
||||
- `api.ts`:`getReadingPlatformAPI()` 工厂函数,返回包含全部接口方法的对象。
|
||||
- `model/`:OpenAPI 生成的 DTO/VO/Result/PageResult/Params 类型。
|
||||
- **`reading-platform-frontend/src/api/client.ts`**:项目侧的“统一入口/别名层”,导出 `readingApi`(完整客户端实例)以及常用的类型工具(解包、分页别名等)。
|
||||
- **`reading-platform-frontend/src/api/*.ts`**:业务侧“适配层”(可选),用于:
|
||||
- 兼容既有页面期望的“扁平结构/字段名/返回形态”
|
||||
- 补齐 OpenAPI 暂未覆盖的历史接口(短期过渡)
|
||||
- 汇聚跨接口的业务逻辑(例如组合请求、额外校验)
|
||||
|
||||
## 2. 基本原则(必须遵守)
|
||||
|
||||
- **生成代码只读**:不得在 `src/api/generated/**` 内做任何手工修改(包括修复类型、改路径、加字段)。
|
||||
- **以生成类型为准**:参数/返回类型优先使用 `src/api/generated/model` 导出的类型,避免手写 `interface` 漂移。
|
||||
- **对外只暴露稳定的业务接口**:页面/组件尽量通过 `src/api/*.ts`(适配层)或 `src/api/client.ts`(直接调用)访问,避免散落调用方式导致难以迁移。
|
||||
|
||||
## 3. 推荐调用方式
|
||||
|
||||
### 3.1 直接使用 Orval 客户端
|
||||
|
||||
- 统一从 `src/api/client.ts` 引入:
|
||||
- `readingApi`: `getReadingPlatformAPI()` 的实例
|
||||
- `ApiResultOf` / `UnwrapResult` / `PageDataOf` 等类型工具
|
||||
|
||||
示例(以 `Result<T>` 为包裹结构):
|
||||
|
||||
```ts
|
||||
import { readingApi } from "@/api/client";
|
||||
import type { ResultTenant } from "@/api/generated/model";
|
||||
|
||||
async function loadTenant(id: number) {
|
||||
const res = (await readingApi.getTenant(id)) as ResultTenant;
|
||||
return res.data; // T(可能为 undefined,取决于后端返回与类型定义)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 使用“适配层”稳定返回结构
|
||||
|
||||
当页面已经依赖历史返回结构(例如直接要 `items/total/page/pageSize`),在 `src/api/*.ts` 内做一次性适配,页面只消费适配后的结构。
|
||||
|
||||
分页适配建议统一输出:
|
||||
|
||||
- `items: T[]`
|
||||
- `total: number`
|
||||
- `page: number`
|
||||
- `pageSize: number`
|
||||
|
||||
## 4. Result / PageResult 约定与解包
|
||||
|
||||
后端统一响应通常为:
|
||||
|
||||
- **普通接口**:`Result<T>`,字段一般为 `code/message/data`
|
||||
- **分页接口**:`Result<PageResult<T>>`,字段一般为 `items/total/page/pageSize`
|
||||
|
||||
在生成代码中常见类型形态:
|
||||
|
||||
- `ResultXXX`(如 `ResultTenant`、`ResultUserInfoResponse`)
|
||||
- `ResultPageResultXXX`(如 `ResultPageResultTenant`)
|
||||
- `PageResultXXX`(如 `PageResultTenant`)
|
||||
|
||||
建议做法:
|
||||
|
||||
- **组件/页面层尽量不要直接处理 `ResultXXX`**,而是由适配层解包并做兜底(空数组、默认分页参数等)。
|
||||
- **严禁在页面散落 `as any`**;确需兼容时,集中在 `src/api/*.ts` 适配层进行,并在适配层内把“最终对页面返回的类型”定义清楚。
|
||||
|
||||
## 5. 命名与重复接口(`getXxx`/`getXxx1`/`getXxx2`)
|
||||
|
||||
由于不同角色端点(teacher/school/parent/admin)可能存在同名资源,Orval 在生成时会用 `1/2/3` 后缀消歧,例如:
|
||||
|
||||
- `getTask`(teacher) vs `getTask1`(school) vs `getTask2`(parent)
|
||||
|
||||
规范建议:
|
||||
|
||||
- **业务层不要直接暴露带数字后缀的方法名**;
|
||||
- 在 `src/api/*.ts` 中封装为语义化名称,例如:
|
||||
- `teacherGetTask` / `schoolGetTask` / `parentGetTask`
|
||||
- 或按模块拆分到 `src/api/teacher/task.ts` 等(如后续重构允许)
|
||||
|
||||
## 6. 何时需要更新生成代码
|
||||
|
||||
当后端 Controller 或 DTO/VO 发生变更:
|
||||
|
||||
1. 后端更新 OpenAPI(Knife4j/SpringDoc)
|
||||
2. 前端更新规范并重新生成(项目已有脚本):
|
||||
|
||||
```bash
|
||||
cd reading-platform-frontend
|
||||
npm run api:update
|
||||
```
|
||||
|
||||
3. 提交生成物(通常包含 `api-spec.*` 与 `src/api/generated/**`)
|
||||
|
||||
> 注意:如果某接口在后端已存在但 OpenAPI 未导出(例如缺少注解/返回类型不规范),应优先修后端文档,而不是在前端“硬编码路径”长期绕过。
|
||||
|
||||
## 7. 禁止事项(高频踩坑)
|
||||
|
||||
- **禁止**:手改 `src/api/generated/**`(下次生成会被覆盖,且会引入不可追踪差异)。
|
||||
- **禁止**:页面里手写 axios 调用去访问 `/api/v1/...`(除非 OpenAPI 暂缺且已在适配层集中兜底)。
|
||||
- **禁止**:在业务代码中扩散 `any` 来“快速通过类型检查”。
|
||||
|
||||
## 8. 迁移策略(从旧 `http` 到 Orval)
|
||||
|
||||
若已有模块使用 `src/api/index.ts` 的 `http.get/post/...`:
|
||||
|
||||
- **短期**:保留旧实现,但新增/变更接口优先走 `readingApi`
|
||||
- **中期**:逐模块把旧 `http` 调用替换为 `readingApi`,并在适配层维持页面不改
|
||||
- **长期**:页面全面只依赖适配层/生成客户端,减少重复封装
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { http } from "./index";
|
||||
import { readingApi, GetTenantPageResult } from "./client";
|
||||
import { readingApi } from "./client";
|
||||
import type { ResultPageResultTenant, Tenant as ApiTenant } from "./generated/model";
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
@ -202,8 +202,25 @@ export interface TenantQuotaUpdateRequest {
|
||||
|
||||
// ==================== 租户管理 ====================
|
||||
|
||||
export const getTenants = (params: TenantQueryParams) =>
|
||||
readingApi.getTenantPage(params);
|
||||
export const getTenants = (
|
||||
params: TenantQueryParams,
|
||||
): Promise<{
|
||||
items: Tenant[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> =>
|
||||
readingApi.getTenantPage(params as any).then((res) => {
|
||||
const wrapped = res as ResultPageResultTenant;
|
||||
const pageData = wrapped.data;
|
||||
|
||||
return {
|
||||
items: (pageData?.items as unknown as ApiTenant[] as Tenant[]) ?? [],
|
||||
total: pageData?.total ?? 0,
|
||||
page: pageData?.page ?? params.page ?? 1,
|
||||
pageSize: pageData?.pageSize ?? params.pageSize ?? 10,
|
||||
};
|
||||
});
|
||||
|
||||
export const getTenant = (id: number): Promise<TenantDetail> =>
|
||||
readingApi.getTenant(id).then((res) => res.data as any);
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
import { readingApi } from "./client";
|
||||
import type {
|
||||
LoginRequest,
|
||||
LoginResponse as ApiLoginResponse,
|
||||
ResultLoginResponse,
|
||||
ResultUserInfoResponse,
|
||||
UserInfoResponse,
|
||||
} from "./generated/model";
|
||||
|
||||
export type LoginParams = LoginRequest;
|
||||
// 兼容现有登录页字段命名(account)
|
||||
export interface LoginParams {
|
||||
account: string;
|
||||
password: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
// Java 后端返回的平铺结构(保持与现有业务使用一致)
|
||||
export interface LoginResponse extends Required<
|
||||
@ -30,7 +34,13 @@ export interface UserProfile {
|
||||
|
||||
// 登录
|
||||
export function login(params: LoginParams): Promise<LoginResponse> {
|
||||
return readingApi.login(params).then((res) => {
|
||||
return readingApi
|
||||
.login({
|
||||
username: params.account,
|
||||
password: params.password,
|
||||
role: params.role,
|
||||
})
|
||||
.then((res) => {
|
||||
const wrapped = res as ResultLoginResponse;
|
||||
const data = (wrapped.data ?? {}) as ApiLoginResponse;
|
||||
|
||||
@ -42,7 +52,7 @@ export function login(params: LoginParams): Promise<LoginResponse> {
|
||||
role: (data.role as LoginResponse["role"]) ?? "teacher",
|
||||
tenantId: data.tenantId,
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 登出
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { readingApi } from "./client";
|
||||
const { http } = require("./index");
|
||||
import type {
|
||||
ResultListCourseLesson,
|
||||
CourseLesson as ApiCourseLesson,
|
||||
@ -68,13 +69,8 @@ export interface CreateStepData {
|
||||
// ==================== 超管端 API ====================
|
||||
|
||||
// 获取课程列表(系统课程课时)
|
||||
export function getLessonList(courseId: number) {
|
||||
return readingApi.getLessons1(courseId).then((res) => {
|
||||
const wrapped = res as ResultListCourseLesson;
|
||||
return wrapped.data?.items ?? [];
|
||||
});
|
||||
}
|
||||
|
||||
export const getLessonList = readingApi.getLessons1;
|
||||
// 获取课程详情
|
||||
export function getLessonDetail(courseId: number, lessonId: number) {
|
||||
return readingApi
|
||||
@ -100,7 +96,6 @@ export function updateLesson(
|
||||
data: Partial<CreateLessonData>,
|
||||
) {
|
||||
// Orval 接口需要同时提供 courseId 和 lessonId,这里仅有 lessonId 时保留旧实现
|
||||
const { http } = require("./index");
|
||||
return http.put(`/api/v1/admin/courses/0/lessons/${lessonId}`, data);
|
||||
}
|
||||
|
||||
@ -111,7 +106,6 @@ export function deleteLesson(courseId: number, lessonId: number) {
|
||||
|
||||
// 重新排序课程
|
||||
export function reorderLessons(courseId: number, lessonIds: number[]) {
|
||||
const { http } = require("./index");
|
||||
return http.put(`/api/v1/admin/courses/${courseId}/lessons/reorder`, {
|
||||
lessonIds,
|
||||
});
|
||||
@ -121,7 +115,6 @@ export function reorderLessons(courseId: number, lessonIds: number[]) {
|
||||
|
||||
// 获取环节列表
|
||||
export function getStepList(courseId: number, lessonId: number) {
|
||||
const { http } = require("./index");
|
||||
return http.get(
|
||||
`/api/v1/admin/courses/${courseId}/lessons/${lessonId}/steps`,
|
||||
);
|
||||
@ -133,7 +126,6 @@ export function createStep(
|
||||
lessonId: number,
|
||||
data: CreateStepData,
|
||||
) {
|
||||
const { http } = require("./index");
|
||||
return http.post(
|
||||
`/api/v1/admin/courses/${courseId}/lessons/${lessonId}/steps`,
|
||||
data,
|
||||
@ -142,13 +134,11 @@ export function createStep(
|
||||
|
||||
// 更新环节
|
||||
export function updateStep(stepId: number, data: Partial<CreateStepData>) {
|
||||
const { http } = require("./index");
|
||||
return http.put(`/api/v1/admin/courses/0/lessons/steps/${stepId}`, data);
|
||||
}
|
||||
|
||||
// 删除环节
|
||||
export function deleteStep(courseId: number, lessonId: number, stepId: number) {
|
||||
const { http } = require("./index");
|
||||
return http.delete(
|
||||
`/api/v1/admin/courses/${courseId}/lessons/steps/${stepId}`,
|
||||
);
|
||||
@ -160,7 +150,6 @@ export function reorderSteps(
|
||||
lessonId: number,
|
||||
stepIds: number[],
|
||||
) {
|
||||
const { http } = require("./index");
|
||||
return http.put(
|
||||
`/admin/courses/${courseId}/lessons/${lessonId}/steps/reorder`,
|
||||
{ stepIds },
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import { http } from "./index";
|
||||
import { readingApi, UnwrapResult, ApiResultOf, GetPackages1Result } from "./client";
|
||||
import {
|
||||
readingApi,
|
||||
UnwrapResult,
|
||||
ApiResultOf,
|
||||
GetPackages1Result,
|
||||
} from "./client";
|
||||
|
||||
// ==================== 套餐管理 ====================
|
||||
|
||||
@ -48,16 +53,10 @@ export interface CreatePackageData {
|
||||
gradeLevels: string[];
|
||||
}
|
||||
|
||||
type AdminPackageResult = UnwrapResult<ApiResultOf<"getPackage1">>
|
||||
type AdminPackageListResult = GetPackages1Result
|
||||
type AdminPackageResult = UnwrapResult<ApiResultOf<"getPackage1">>;
|
||||
|
||||
// 获取套餐列表(管理员端)
|
||||
export function getPackageList(
|
||||
params?: PackageListParams,
|
||||
): Promise<AdminPackageListResult> {
|
||||
return readingApi.getPackages1(params as any).then((res) => res.data as any);
|
||||
}
|
||||
|
||||
export const getPackageList = readingApi.getPackages1;
|
||||
// 获取套餐详情(管理员端)
|
||||
export function getPackageDetail(id: number): Promise<AdminPackageResult> {
|
||||
return readingApi.getPackage1(id).then((res) => res.data as any);
|
||||
|
||||
@ -87,7 +87,7 @@ export const getLibraries = (params?: {
|
||||
readingApi.getLibraries(params as any).then((res) => {
|
||||
const list = (res.data as any as ResourceLibrary[]) || [];
|
||||
const page = params?.page ?? 1;
|
||||
const pageSize = params?.pageSize ?? list.length || 10;
|
||||
const pageSize = params?.pageSize ?? (list.length || 10);
|
||||
return {
|
||||
items: list,
|
||||
total: list.length,
|
||||
|
||||
@ -114,7 +114,7 @@ export function updateSchoolCourseLesson(
|
||||
data: Partial<SchoolCourseLesson>,
|
||||
) {
|
||||
return http.put(
|
||||
`/school/school-courses/${schoolCourseId}/lessons/${lessonId}`,
|
||||
`/api/v1/school/school-courses/${schoolCourseId}/lessons/${lessonId}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
@ -191,7 +191,7 @@ export function updateTeacherSchoolCourseLesson(
|
||||
data: Partial<SchoolCourseLesson>,
|
||||
) {
|
||||
return http.put(
|
||||
`/teacher/school-courses/${schoolCourseId}/lessons/${lessonId}`,
|
||||
`/api/v1/teacher/school-courses/${schoolCourseId}/lessons/${lessonId}`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
@ -158,41 +158,43 @@ export interface PackageUsage {
|
||||
|
||||
export const getTeachers = (params: TeacherQueryParams) =>
|
||||
http.get<{ items: Teacher[]; total: number; page: number; pageSize: number }>(
|
||||
"/school/teachers",
|
||||
"/api/v1/school/teachers",
|
||||
{ params },
|
||||
);
|
||||
|
||||
export const getTeacher = (id: number) =>
|
||||
http.get<Teacher>(`/school/teachers/${id}`);
|
||||
http.get<Teacher>(`/api/v1/school/teachers/${id}`);
|
||||
|
||||
export const createTeacher = (data: CreateTeacherDto) =>
|
||||
http.post<Teacher>("/school/teachers", data);
|
||||
http.post<Teacher>("/api/v1/school/teachers", data);
|
||||
|
||||
export const updateTeacher = (id: number, data: Partial<CreateTeacherDto>) =>
|
||||
http.put<Teacher>(`/school/teachers/${id}`, data);
|
||||
http.put<Teacher>(`/api/v1/school/teachers/${id}`, data);
|
||||
|
||||
export const deleteTeacher = (id: number) =>
|
||||
http.delete(`/api/v1/school/teachers/${id}`);
|
||||
|
||||
export const resetTeacherPassword = (id: number) =>
|
||||
http.post<{ tempPassword: string }>(`/school/teachers/${id}/reset-password`);
|
||||
http.post<{ tempPassword: string }>(
|
||||
`/api/v1/school/teachers/${id}/reset-password`,
|
||||
);
|
||||
|
||||
// ==================== 学生管理 ====================
|
||||
|
||||
export const getStudents = (params: StudentQueryParams) =>
|
||||
http.get<{ items: Student[]; total: number; page: number; pageSize: number }>(
|
||||
"/school/students",
|
||||
"/api/v1/school/students",
|
||||
{ params },
|
||||
);
|
||||
|
||||
export const getStudent = (id: number) =>
|
||||
http.get<Student>(`/school/students/${id}`);
|
||||
http.get<Student>(`/api/v1/school/students/${id}`);
|
||||
|
||||
export const createStudent = (data: CreateStudentDto) =>
|
||||
http.post<Student>("/school/students", data);
|
||||
http.post<Student>("/api/v1/school/students", data);
|
||||
|
||||
export const updateStudent = (id: number, data: Partial<CreateStudentDto>) =>
|
||||
http.put<Student>(`/school/students/${id}`, data);
|
||||
http.put<Student>(`/api/v1/school/students/${id}`, data);
|
||||
|
||||
export const deleteStudent = (id: number) =>
|
||||
http.delete(`/api/v1/school/students/${id}`);
|
||||
@ -212,7 +214,7 @@ export interface ImportTemplate {
|
||||
}
|
||||
|
||||
export const getStudentImportTemplate = () =>
|
||||
http.get<ImportTemplate>("/school/students/import/template");
|
||||
http.get<ImportTemplate>("/api/v1/school/students/import/template");
|
||||
|
||||
export const importStudents = (
|
||||
file: File,
|
||||
@ -232,16 +234,16 @@ export const importStudents = (
|
||||
|
||||
// ==================== 班级管理 ====================
|
||||
|
||||
export const getClasses = () => http.get<ClassInfo[]>("/school/classes");
|
||||
export const getClasses = () => http.get<ClassInfo[]>("/api/v1/school/classes");
|
||||
|
||||
export const getClass = (id: number) =>
|
||||
http.get<ClassInfo>(`/school/classes/${id}`);
|
||||
http.get<ClassInfo>(`/api/v1/school/classes/${id}`);
|
||||
|
||||
export const createClass = (data: CreateClassDto) =>
|
||||
http.post<ClassInfo>("/school/classes", data);
|
||||
http.post<ClassInfo>("/api/v1/school/classes", data);
|
||||
|
||||
export const updateClass = (id: number, data: Partial<CreateClassDto>) =>
|
||||
http.put<ClassInfo>(`/school/classes/${id}`, data);
|
||||
http.put<ClassInfo>(`/api/v1/school/classes/${id}`, data);
|
||||
|
||||
export const deleteClass = (id: number) =>
|
||||
http.delete(`/api/v1/school/classes/${id}`);
|
||||
@ -256,35 +258,37 @@ export const getClassStudents = (
|
||||
page: number;
|
||||
pageSize: number;
|
||||
class?: ClassInfo;
|
||||
}>(`/school/classes/${classId}/students`, { params });
|
||||
}>(`/api/v1/school/classes/${classId}/students`, { params });
|
||||
|
||||
// ==================== 统计数据 ====================
|
||||
|
||||
export const getSchoolStats = () => http.get<SchoolStats>("/school/stats");
|
||||
export const getSchoolStats = () =>
|
||||
http.get<SchoolStats>("/api/v1/school/stats");
|
||||
|
||||
export const getActiveTeachers = (limit?: number) =>
|
||||
http.get<Array<{ id: number; name: string; lessonCount: number }>>(
|
||||
"/school/stats/teachers",
|
||||
"/api/v1/school/stats/teachers",
|
||||
{ params: { limit } },
|
||||
);
|
||||
|
||||
export const getCourseUsageStats = () =>
|
||||
http.get<Array<{ courseId: number; courseName: string; usageCount: number }>>(
|
||||
"/school/stats/courses",
|
||||
"/api/v1/school/stats/courses",
|
||||
);
|
||||
|
||||
export const getRecentActivities = (limit?: number) =>
|
||||
http.get<Array<{ id: number; type: string; title: string; time: string }>>(
|
||||
"/school/stats/activities",
|
||||
"/api/v1/school/stats/activities",
|
||||
{ params: { limit } },
|
||||
);
|
||||
|
||||
// ==================== 套餐信息(旧API,保留兼容) ====================
|
||||
|
||||
export const getPackageInfo = () => http.get<PackageInfo>("/school/package");
|
||||
export const getPackageInfo = () =>
|
||||
http.get<PackageInfo>("/api/v1/school/package");
|
||||
|
||||
export const getPackageUsage = () =>
|
||||
http.get<PackageUsage>("/school/package/usage");
|
||||
http.get<PackageUsage>("/api/v1/school/package/usage");
|
||||
|
||||
// ==================== 套餐管理(新API) ====================
|
||||
|
||||
@ -326,10 +330,10 @@ export interface RenewPackageDto {
|
||||
}
|
||||
|
||||
export const getTenantPackages = () =>
|
||||
http.get<TenantPackage[]>("/school/packages");
|
||||
http.get<TenantPackage[]>("/api/v1/school/packages");
|
||||
|
||||
export const renewPackage = (packageId: number, data: RenewPackageDto) =>
|
||||
http.post<TenantPackage>(`/school/packages/${packageId}/renew`, data);
|
||||
http.post<TenantPackage>(`/api/v1/school/packages/${packageId}/renew`, data);
|
||||
|
||||
// ==================== 系统设置 ====================
|
||||
|
||||
@ -355,25 +359,26 @@ export interface UpdateSettingsDto {
|
||||
notifyOnGrowth?: boolean;
|
||||
}
|
||||
|
||||
export const getSettings = () => http.get<SystemSettings>("/school/settings");
|
||||
export const getSettings = () =>
|
||||
http.get<SystemSettings>("/api/v1/school/settings");
|
||||
|
||||
export const updateSettings = (data: UpdateSettingsDto) =>
|
||||
http.put<SystemSettings>("/school/settings", data);
|
||||
http.put<SystemSettings>("/api/v1/school/settings", data);
|
||||
|
||||
// ==================== 课程管理 ====================
|
||||
|
||||
export const getSchoolCourses = () => http.get<any[]>("/school/courses");
|
||||
export const getSchoolCourses = () => http.get<any[]>("/api/v1/school/courses");
|
||||
|
||||
export const getSchoolCourse = (id: number) =>
|
||||
http.get<any>(`/school/courses/${id}`);
|
||||
http.get<any>(`/api/v1/school/courses/${id}`);
|
||||
|
||||
// ==================== 班级教师管理 ====================
|
||||
|
||||
export const getClassTeachers = (classId: number) =>
|
||||
http.get<ClassTeacher[]>(`/school/classes/${classId}/teachers`);
|
||||
http.get<ClassTeacher[]>(`/api/v1/school/classes/${classId}/teachers`);
|
||||
|
||||
export const addClassTeacher = (classId: number, data: AddClassTeacherDto) =>
|
||||
http.post<ClassTeacher>(`/school/classes/${classId}/teachers`, data);
|
||||
http.post<ClassTeacher>(`/api/v1/school/classes/${classId}/teachers`, data);
|
||||
|
||||
export const updateClassTeacher = (
|
||||
classId: number,
|
||||
@ -381,25 +386,27 @@ export const updateClassTeacher = (
|
||||
data: UpdateClassTeacherDto,
|
||||
) =>
|
||||
http.put<ClassTeacher>(
|
||||
`/school/classes/${classId}/teachers/${teacherId}`,
|
||||
`/api/v1/school/classes/${classId}/teachers/${teacherId}`,
|
||||
data,
|
||||
);
|
||||
|
||||
export const removeClassTeacher = (classId: number, teacherId: number) =>
|
||||
http.delete<{ message: string }>(
|
||||
`/school/classes/${classId}/teachers/${teacherId}`,
|
||||
`/api/v1/school/classes/${classId}/teachers/${teacherId}`,
|
||||
);
|
||||
|
||||
// ==================== 学生调班 ====================
|
||||
|
||||
export const transferStudent = (studentId: number, data: TransferStudentDto) =>
|
||||
http.post<{ message: string }>(
|
||||
`/school/students/${studentId}/transfer`,
|
||||
`/api/v1/school/students/${studentId}/transfer`,
|
||||
data,
|
||||
);
|
||||
|
||||
export const getStudentClassHistory = (studentId: number) =>
|
||||
http.get<StudentClassHistory[]>(`/school/students/${studentId}/history`);
|
||||
http.get<StudentClassHistory[]>(
|
||||
`/api/v1/school/students/${studentId}/history`,
|
||||
);
|
||||
|
||||
// ==================== 排课管理 ====================
|
||||
|
||||
@ -479,22 +486,22 @@ export const getSchedules = (params?: ScheduleQueryParams) =>
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/school/schedules", { params });
|
||||
}>("/api/v1/school/schedules", { params });
|
||||
|
||||
export const getSchedule = (id: number) =>
|
||||
http.get<SchedulePlan>(`/school/schedules/${id}`);
|
||||
http.get<SchedulePlan>(`/api/v1/school/schedules/${id}`);
|
||||
|
||||
export const createSchedule = (data: CreateScheduleDto) =>
|
||||
http.post<SchedulePlan>("/school/schedules", data);
|
||||
http.post<SchedulePlan>("/api/v1/school/schedules", data);
|
||||
|
||||
export const updateSchedule = (id: number, data: UpdateScheduleDto) =>
|
||||
http.put<SchedulePlan>(`/school/schedules/${id}`, data);
|
||||
http.put<SchedulePlan>(`/api/v1/school/schedules/${id}`, data);
|
||||
|
||||
export const cancelSchedule = (id: number) =>
|
||||
http.delete<{ message: string }>(`/school/schedules/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/school/schedules/${id}`);
|
||||
|
||||
export const getTimetable = (params: TimetableQueryParams) =>
|
||||
http.get<TimetableItem[]>("/school/schedules/timetable", { params });
|
||||
http.get<TimetableItem[]>("/api/v1/school/schedules/timetable", { params });
|
||||
|
||||
export interface BatchScheduleItem {
|
||||
classId: number;
|
||||
@ -513,7 +520,7 @@ export interface BatchCreateResult {
|
||||
}
|
||||
|
||||
export const batchCreateSchedules = (schedules: BatchScheduleItem[]) =>
|
||||
http.post<BatchCreateResult>("/school/schedules/batch", { schedules });
|
||||
http.post<BatchCreateResult>("/api/v1/school/schedules/batch", { schedules });
|
||||
|
||||
// ==================== 趋势与分布统计 ====================
|
||||
|
||||
@ -529,12 +536,14 @@ export interface CourseDistributionItem {
|
||||
}
|
||||
|
||||
export const getLessonTrend = (months?: number) =>
|
||||
http.get<LessonTrendItem[]>("/school/stats/lesson-trend", {
|
||||
http.get<LessonTrendItem[]>("/api/v1/school/stats/lesson-trend", {
|
||||
params: { months },
|
||||
});
|
||||
|
||||
export const getCourseDistribution = () =>
|
||||
http.get<CourseDistributionItem[]>("/school/stats/course-distribution");
|
||||
http.get<CourseDistributionItem[]>(
|
||||
"/api/v1/school/stats/course-distribution",
|
||||
);
|
||||
|
||||
// ==================== 数据导出 ====================
|
||||
|
||||
@ -668,24 +677,29 @@ export interface ApplyTemplateDto {
|
||||
export const getScheduleTemplates = (params?: {
|
||||
classId?: number;
|
||||
courseId?: number;
|
||||
}) => http.get<ScheduleTemplate[]>("/school/schedule-templates", { params });
|
||||
}) =>
|
||||
http.get<ScheduleTemplate[]>("/api/v1/school/schedule-templates", { params });
|
||||
|
||||
export const getScheduleTemplate = (id: number) =>
|
||||
http.get<ScheduleTemplate>(`/school/schedule-templates/${id}`);
|
||||
http.get<ScheduleTemplate>(`/api/v1/school/schedule-templates/${id}`);
|
||||
|
||||
export const createScheduleTemplate = (data: CreateScheduleTemplateDto) =>
|
||||
http.post<ScheduleTemplate>("/school/schedule-templates", data);
|
||||
http.post<ScheduleTemplate>("/api/v1/school/schedule-templates", data);
|
||||
|
||||
export const updateScheduleTemplate = (
|
||||
id: number,
|
||||
data: UpdateScheduleTemplateDto,
|
||||
) => http.put<ScheduleTemplate>(`/school/schedule-templates/${id}`, data);
|
||||
) =>
|
||||
http.put<ScheduleTemplate>(`/api/v1/school/schedule-templates/${id}`, data);
|
||||
|
||||
export const deleteScheduleTemplate = (id: number) =>
|
||||
http.delete<{ message: string }>(`/school/schedule-templates/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/school/schedule-templates/${id}`);
|
||||
|
||||
export const applyScheduleTemplate = (id: number, data: ApplyTemplateDto) =>
|
||||
http.post<SchedulePlan>(`/school/schedule-templates/${id}/apply`, data);
|
||||
http.post<SchedulePlan>(
|
||||
`/api/v1/school/schedule-templates/${id}/apply`,
|
||||
data,
|
||||
);
|
||||
|
||||
// ==================== 操作日志 ====================
|
||||
|
||||
@ -723,15 +737,15 @@ export const getOperationLogs = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/school/operation-logs", { params });
|
||||
}>("/api/v1/school/operation-logs", { params });
|
||||
|
||||
export const getOperationLogStats = (startDate?: string, endDate?: string) =>
|
||||
http.get<OperationLogStats>("/school/operation-logs/stats", {
|
||||
http.get<OperationLogStats>("/api/v1/school/operation-logs/stats", {
|
||||
params: { startDate, endDate },
|
||||
});
|
||||
|
||||
export const getOperationLogById = (id: number) =>
|
||||
http.get<OperationLog>(`/school/operation-logs/${id}`);
|
||||
http.get<OperationLog>(`/api/v1/school/operation-logs/${id}`);
|
||||
|
||||
// ==================== 任务模板 API ====================
|
||||
|
||||
@ -784,22 +798,24 @@ export const getTaskTemplates = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/school/task-templates", { params });
|
||||
}>("/api/v1/school/task-templates", { params });
|
||||
|
||||
export const getTaskTemplate = (id: number) =>
|
||||
http.get<TaskTemplate>(`/school/task-templates/${id}`);
|
||||
http.get<TaskTemplate>(`/api/v1/school/task-templates/${id}`);
|
||||
|
||||
export const getDefaultTaskTemplate = (taskType: string) =>
|
||||
http.get<TaskTemplate | null>(`/school/task-templates/default/${taskType}`);
|
||||
http.get<TaskTemplate | null>(
|
||||
`/api/v1/school/task-templates/default/${taskType}`,
|
||||
);
|
||||
|
||||
export const createTaskTemplate = (data: CreateTaskTemplateDto) =>
|
||||
http.post<TaskTemplate>("/school/task-templates", data);
|
||||
http.post<TaskTemplate>("/api/v1/school/task-templates", data);
|
||||
|
||||
export const updateTaskTemplate = (id: number, data: UpdateTaskTemplateDto) =>
|
||||
http.put<TaskTemplate>(`/school/task-templates/${id}`, data);
|
||||
http.put<TaskTemplate>(`/api/v1/school/task-templates/${id}`, data);
|
||||
|
||||
export const deleteTaskTemplate = (id: number) =>
|
||||
http.delete<{ message: string }>(`/school/task-templates/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/school/task-templates/${id}`);
|
||||
|
||||
// ==================== 任务统计 API ====================
|
||||
|
||||
@ -838,16 +854,17 @@ export interface MonthlyTaskStats {
|
||||
rate: number;
|
||||
}
|
||||
|
||||
export const getTaskStats = () => http.get<TaskStats>("/school/tasks/stats");
|
||||
export const getTaskStats = () =>
|
||||
http.get<TaskStats>("/api/v1/school/tasks/stats");
|
||||
|
||||
export const getTaskStatsByType = () =>
|
||||
http.get<TaskStatsByType>("/school/tasks/stats/by-type");
|
||||
http.get<TaskStatsByType>("/api/v1/school/tasks/stats/by-type");
|
||||
|
||||
export const getTaskStatsByClass = () =>
|
||||
http.get<TaskStatsByClass[]>("/school/tasks/stats/by-class");
|
||||
http.get<TaskStatsByClass[]>("/api/v1/school/tasks/stats/by-class");
|
||||
|
||||
export const getMonthlyTaskStats = (months?: number) =>
|
||||
http.get<MonthlyTaskStats[]>("/school/tasks/stats/monthly", {
|
||||
http.get<MonthlyTaskStats[]>("/api/v1/school/tasks/stats/monthly", {
|
||||
params: { months },
|
||||
});
|
||||
|
||||
@ -920,24 +937,25 @@ export const getSchoolTasks = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/school/tasks", { params });
|
||||
}>("/api/v1/school/tasks", { params });
|
||||
|
||||
export const getSchoolTask = (id: number) =>
|
||||
http.get<SchoolTask>(`/school/tasks/${id}`);
|
||||
http.get<SchoolTask>(`/api/v1/school/tasks/${id}`);
|
||||
|
||||
export const createSchoolTask = (data: CreateSchoolTaskDto) =>
|
||||
http.post<SchoolTask>("/school/tasks", data);
|
||||
http.post<SchoolTask>("/api/v1/school/tasks", data);
|
||||
|
||||
export const updateSchoolTask = (id: number, data: UpdateSchoolTaskDto) =>
|
||||
http.put<SchoolTask>(`/school/tasks/${id}`, data);
|
||||
http.put<SchoolTask>(`/api/v1/school/tasks/${id}`, data);
|
||||
|
||||
export const deleteSchoolTask = (id: number) =>
|
||||
http.delete<{ message: string }>(`/school/tasks/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/school/tasks/${id}`);
|
||||
|
||||
export const getSchoolTaskCompletions = (taskId: number) =>
|
||||
http.get<TaskCompletion[]>(`/school/tasks/${taskId}/completions`);
|
||||
http.get<TaskCompletion[]>(`/api/v1/school/tasks/${taskId}/completions`);
|
||||
|
||||
export const getSchoolClasses = () => http.get<ClassInfo[]>("/school/classes");
|
||||
export const getSchoolClasses = () =>
|
||||
http.get<ClassInfo[]>("/api/v1/school/classes");
|
||||
|
||||
// ==================== 数据报告 API ====================
|
||||
|
||||
@ -976,16 +994,16 @@ export interface StudentReport {
|
||||
}
|
||||
|
||||
export const getReportOverview = () =>
|
||||
http.get<ReportOverview>("/school/reports/overview");
|
||||
http.get<ReportOverview>("/api/v1/school/reports/overview");
|
||||
|
||||
export const getTeacherReports = () =>
|
||||
http.get<TeacherReport[]>("/school/reports/teachers");
|
||||
http.get<TeacherReport[]>("/api/v1/school/reports/teachers");
|
||||
|
||||
export const getCourseReports = () =>
|
||||
http.get<CourseReport[]>("/school/reports/courses");
|
||||
http.get<CourseReport[]>("/api/v1/school/reports/courses");
|
||||
|
||||
export const getStudentReports = () =>
|
||||
http.get<StudentReport[]>("/school/reports/students");
|
||||
http.get<StudentReport[]>("/api/v1/school/reports/students");
|
||||
|
||||
// ==================== 家长管理 ====================
|
||||
|
||||
@ -1040,41 +1058,43 @@ export interface AddChildDto {
|
||||
|
||||
export const getParents = (params?: ParentQueryParams) =>
|
||||
http.get<{ items: Parent[]; total: number; page: number; pageSize: number }>(
|
||||
"/school/parents",
|
||||
"/api/v1/school/parents",
|
||||
{ params },
|
||||
);
|
||||
|
||||
export const getParent = (id: number) =>
|
||||
http.get<Parent>(`/school/parents/${id}`);
|
||||
http.get<Parent>(`/api/v1/school/parents/${id}`);
|
||||
|
||||
export const createParent = (data: CreateParentDto) =>
|
||||
http.post<Parent>("/school/parents", data);
|
||||
http.post<Parent>("/api/v1/school/parents", data);
|
||||
|
||||
export const updateParent = (id: number, data: UpdateParentDto) =>
|
||||
http.put<Parent>(`/school/parents/${id}`, data);
|
||||
http.put<Parent>(`/api/v1/school/parents/${id}`, data);
|
||||
|
||||
export const deleteParent = (id: number) =>
|
||||
http.delete<{ message: string }>(`/school/parents/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/school/parents/${id}`);
|
||||
|
||||
export const resetParentPassword = (id: number) =>
|
||||
http.post<{ tempPassword: string }>(`/school/parents/${id}/reset-password`);
|
||||
http.post<{ tempPassword: string }>(
|
||||
`/api/v1/school/parents/${id}/reset-password`,
|
||||
);
|
||||
|
||||
export const getParentChildren = async (
|
||||
parentId: number,
|
||||
): Promise<ParentChild[]> => {
|
||||
const parent = await http.get<Parent & { children: ParentChild[] }>(
|
||||
`/school/parents/${parentId}`,
|
||||
`/api/v1/school/parents/${parentId}`,
|
||||
);
|
||||
return parent.children || [];
|
||||
};
|
||||
|
||||
export const addChildToParent = (parentId: number, data: AddChildDto) =>
|
||||
http.post<ParentChild>(
|
||||
`/school/parents/${parentId}/children/${data.studentId}`,
|
||||
`/api/v1/school/parents/${parentId}/children/${data.studentId}`,
|
||||
{ relationship: data.relationship },
|
||||
);
|
||||
|
||||
export const removeChildFromParent = (parentId: number, studentId: number) =>
|
||||
http.delete<{ message: string }>(
|
||||
`/school/parents/${parentId}/children/${studentId}`,
|
||||
`/api/v1/school/parents/${parentId}/children/${studentId}`,
|
||||
);
|
||||
|
||||
@ -92,11 +92,12 @@ export const getTasks = (params?: {
|
||||
keyword?: string;
|
||||
}) =>
|
||||
http.get<{ items: Task[]; total: number; page: number; pageSize: number }>(
|
||||
"/school/tasks",
|
||||
"/api/v1/school/tasks",
|
||||
{ params },
|
||||
);
|
||||
|
||||
export const getTask = (id: number) => http.get<Task>(`/school/tasks/${id}`);
|
||||
export const getTask = (id: number) =>
|
||||
http.get<Task>(`/api/v1/school/tasks/${id}`);
|
||||
|
||||
export const getTaskCompletions = (
|
||||
taskId: number,
|
||||
@ -111,13 +112,13 @@ export const getTaskCompletions = (
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>(`/school/tasks/${taskId}/completions`, { params });
|
||||
}>(`/api/v1/school/tasks/${taskId}/completions`, { params });
|
||||
|
||||
export const createTask = (data: CreateTaskDto) =>
|
||||
http.post<Task>("/school/tasks", data);
|
||||
http.post<Task>("/api/v1/school/tasks", data);
|
||||
|
||||
export const updateTask = (id: number, data: UpdateTaskDto) =>
|
||||
http.put<Task>(`/school/tasks/${id}`, data);
|
||||
http.put<Task>(`/api/v1/school/tasks/${id}`, data);
|
||||
|
||||
export const deleteTask = (id: number) =>
|
||||
http.delete(`/api/v1/school/tasks/${id}`);
|
||||
@ -128,7 +129,7 @@ export const updateTaskCompletion = (
|
||||
data: UpdateCompletionDto,
|
||||
) =>
|
||||
http.put<TaskCompletion>(
|
||||
`/school/tasks/${taskId}/completions/${studentId}`,
|
||||
`/api/v1/school/tasks/${taskId}/completions/${studentId}`,
|
||||
data,
|
||||
);
|
||||
|
||||
@ -145,12 +146,12 @@ export const getTeacherTasks = (params?: {
|
||||
keyword?: string;
|
||||
}) =>
|
||||
http.get<{ items: Task[]; total: number; page: number; pageSize: number }>(
|
||||
"/teacher/tasks",
|
||||
"/api/v1/teacher/tasks",
|
||||
{ params },
|
||||
);
|
||||
|
||||
export const getTeacherTask = (id: number) =>
|
||||
http.get<Task>(`/teacher/tasks/${id}`);
|
||||
http.get<Task>(`/api/v1/teacher/tasks/${id}`);
|
||||
|
||||
export const getTeacherTaskCompletions = (
|
||||
taskId: number,
|
||||
@ -165,13 +166,13 @@ export const getTeacherTaskCompletions = (
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>(`/teacher/tasks/${taskId}/completions`, { params });
|
||||
}>(`/api/v1/teacher/tasks/${taskId}/completions`, { params });
|
||||
|
||||
export const createTeacherTask = (data: CreateTaskDto) =>
|
||||
http.post<Task>("/teacher/tasks", data);
|
||||
http.post<Task>("/api/v1/teacher/tasks", data);
|
||||
|
||||
export const updateTeacherTask = (id: number, data: UpdateTaskDto) =>
|
||||
http.put<Task>(`/teacher/tasks/${id}`, data);
|
||||
http.put<Task>(`/api/v1/teacher/tasks/${id}`, data);
|
||||
|
||||
export const deleteTeacherTask = (id: number) =>
|
||||
http.delete(`/api/v1/teacher/tasks/${id}`);
|
||||
@ -182,7 +183,7 @@ export const updateTeacherTaskCompletion = (
|
||||
data: UpdateCompletionDto,
|
||||
) =>
|
||||
http.put<TaskCompletion>(
|
||||
`/teacher/tasks/${taskId}/completions/${studentId}`,
|
||||
`/api/v1/teacher/tasks/${taskId}/completions/${studentId}`,
|
||||
data,
|
||||
);
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ export function getTeacherCourse(id: number): Promise<unknown> {
|
||||
|
||||
// 获取教师的班级列表
|
||||
export function getTeacherClasses(): Promise<TeacherClass[]> {
|
||||
return http.get("/teacher/courses/classes");
|
||||
return http.get("/api/v1/teacher/courses/classes");
|
||||
}
|
||||
|
||||
// 获取教师所有学生列表(跨班级)
|
||||
@ -115,7 +115,7 @@ export function getTeacherStudents(params?: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get("/teacher/students", { params });
|
||||
return http.get("/api/v1/teacher/students", { params });
|
||||
}
|
||||
|
||||
// 获取班级学生列表
|
||||
@ -190,7 +190,7 @@ export function getLessons(params?: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get("/teacher/lessons", { params });
|
||||
return http.get("/api/v1/teacher/lessons", { params });
|
||||
}
|
||||
|
||||
// 获取单个授课记录详情
|
||||
@ -200,7 +200,7 @@ export function getLesson(id: number): Promise<unknown> {
|
||||
|
||||
// 创建授课记录(备课)
|
||||
export function createLesson(data: CreateLessonDto): Promise<unknown> {
|
||||
return http.post("/teacher/lessons", data);
|
||||
return http.post("/api/v1/teacher/lessons", data);
|
||||
}
|
||||
|
||||
// 开始上课
|
||||
@ -319,16 +319,18 @@ export interface DashboardData {
|
||||
}
|
||||
|
||||
export const getTeacherDashboard = () =>
|
||||
http.get<DashboardData>("/teacher/dashboard");
|
||||
http.get<DashboardData>("/api/v1/teacher/dashboard");
|
||||
|
||||
export const getTodayLessons = () =>
|
||||
http.get<DashboardData["todayLessons"]>("/teacher/dashboard/today");
|
||||
http.get<DashboardData["todayLessons"]>("/api/v1/teacher/dashboard/today");
|
||||
|
||||
export const getRecommendedCourses = () =>
|
||||
http.get<DashboardData["recommendedCourses"]>("/teacher/dashboard/recommend");
|
||||
http.get<DashboardData["recommendedCourses"]>(
|
||||
"/api/v1/teacher/dashboard/recommend",
|
||||
);
|
||||
|
||||
export const getWeeklyStats = () =>
|
||||
http.get<DashboardData["weeklyStats"]>("/teacher/dashboard/weekly");
|
||||
http.get<DashboardData["weeklyStats"]>("/api/v1/teacher/dashboard/weekly");
|
||||
|
||||
// ==================== 教师统计趋势 ====================
|
||||
|
||||
@ -344,12 +346,12 @@ export interface TeacherCourseUsageItem {
|
||||
}
|
||||
|
||||
export const getTeacherLessonTrend = (months?: number) =>
|
||||
http.get<TeacherLessonTrendItem[]>("/teacher/dashboard/lesson-trend", {
|
||||
http.get<TeacherLessonTrendItem[]>("/api/v1/teacher/dashboard/lesson-trend", {
|
||||
params: { months },
|
||||
});
|
||||
|
||||
export const getTeacherCourseUsage = () =>
|
||||
http.get<TeacherCourseUsageItem[]>("/teacher/dashboard/course-usage");
|
||||
http.get<TeacherCourseUsageItem[]>("/api/v1/teacher/dashboard/course-usage");
|
||||
|
||||
// ==================== 课程反馈 API ====================
|
||||
|
||||
@ -432,12 +434,12 @@ export function getSchoolFeedbacks(params: FeedbackQueryParams): Promise<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get("/school/feedbacks", { params });
|
||||
return http.get("/api/v1/school/feedbacks", { params });
|
||||
}
|
||||
|
||||
// 获取反馈统计
|
||||
export function getFeedbackStats(): Promise<FeedbackStats> {
|
||||
return http.get("/school/feedbacks/stats");
|
||||
return http.get("/api/v1/school/feedbacks/stats");
|
||||
}
|
||||
|
||||
// 获取教师自己的反馈列表
|
||||
@ -447,12 +449,12 @@ export function getTeacherFeedbacks(params: FeedbackQueryParams): Promise<{
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}> {
|
||||
return http.get("/teacher/feedbacks", { params });
|
||||
return http.get("/api/v1/teacher/feedbacks", { params });
|
||||
}
|
||||
|
||||
// 获取教师自己的反馈统计
|
||||
export function getTeacherFeedbackStats(): Promise<FeedbackStats> {
|
||||
return http.get("/teacher/feedbacks/stats");
|
||||
return http.get("/api/v1/teacher/feedbacks/stats");
|
||||
}
|
||||
|
||||
// ==================== 排课管理 API ====================
|
||||
@ -516,26 +518,28 @@ export const getTeacherSchedules = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/teacher/schedules", { params });
|
||||
}>("/api/v1/teacher/schedules", { params });
|
||||
|
||||
export const getTeacherTimetable = (params: {
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
}) =>
|
||||
http.get<TeacherTimetableItem[]>("/teacher/schedules/timetable", { params });
|
||||
http.get<TeacherTimetableItem[]>("/api/v1/teacher/schedules/timetable", {
|
||||
params,
|
||||
});
|
||||
|
||||
export const getTodayTeacherSchedules = () =>
|
||||
http.get<TeacherSchedule[]>("/teacher/schedules/today");
|
||||
http.get<TeacherSchedule[]>("/api/v1/teacher/schedules/today");
|
||||
|
||||
export const createTeacherSchedule = (data: CreateTeacherScheduleDto) =>
|
||||
http.post<TeacherSchedule>("/teacher/schedules", data);
|
||||
http.post<TeacherSchedule>("/api/v1/teacher/schedules", data);
|
||||
export const updateTeacherSchedule = (
|
||||
id: number,
|
||||
data: Partial<CreateTeacherScheduleDto> & { status?: string },
|
||||
) => http.put<TeacherSchedule>(`/teacher/schedules/${id}`, data);
|
||||
) => http.put<TeacherSchedule>(`/api/v1/teacher/schedules/${id}`, data);
|
||||
|
||||
export const cancelTeacherSchedule = (id: number) =>
|
||||
http.delete<{ message: string }>(`/teacher/schedules/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/teacher/schedules/${id}`);
|
||||
|
||||
// ==================== 阅读任务 API ====================
|
||||
|
||||
@ -609,10 +613,10 @@ export const getTeacherTasks = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/teacher/tasks", { params });
|
||||
}>("/api/v1/teacher/tasks", { params });
|
||||
|
||||
export const getTeacherTask = (id: number) =>
|
||||
http.get<TeacherTask>(`/teacher/tasks/${id}`);
|
||||
http.get<TeacherTask>(`/api/v1/teacher/tasks/${id}`);
|
||||
|
||||
export const getTeacherTaskCompletions = (
|
||||
taskId: number,
|
||||
@ -627,18 +631,18 @@ export const getTeacherTaskCompletions = (
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>(`/teacher/tasks/${taskId}/completions`, { params });
|
||||
}>(`/api/v1/teacher/tasks/${taskId}/completions`, { params });
|
||||
|
||||
export const createTeacherTask = (data: CreateTeacherTaskDto) =>
|
||||
http.post<TeacherTask>("/teacher/tasks", data);
|
||||
http.post<TeacherTask>("/api/v1/teacher/tasks", data);
|
||||
|
||||
export const updateTeacherTask = (
|
||||
id: number,
|
||||
data: Partial<CreateTeacherTaskDto> & { status?: string },
|
||||
) => http.put<TeacherTask>(`/teacher/tasks/${id}`, data);
|
||||
) => http.put<TeacherTask>(`/api/v1/teacher/tasks/${id}`, data);
|
||||
|
||||
export const deleteTeacherTask = (id: number) =>
|
||||
http.delete<{ message: string }>(`/teacher/tasks/${id}`);
|
||||
http.delete<{ message: string }>(`/api/v1/teacher/tasks/${id}`);
|
||||
|
||||
export const updateTaskCompletion = (
|
||||
taskId: number,
|
||||
@ -646,12 +650,12 @@ export const updateTaskCompletion = (
|
||||
data: UpdateTaskCompletionDto,
|
||||
) =>
|
||||
http.put<TaskCompletion>(
|
||||
`/teacher/tasks/${taskId}/completions/${studentId}`,
|
||||
`/api/v1/teacher/tasks/${taskId}/completions/${studentId}`,
|
||||
data,
|
||||
);
|
||||
|
||||
export const sendTaskReminder = (taskId: number) =>
|
||||
http.post<{ message: string }>(`/teacher/tasks/${taskId}/remind`);
|
||||
http.post<{ message: string }>(`/api/v1/teacher/tasks/${taskId}/remind`);
|
||||
|
||||
// ==================== 任务模板 API ====================
|
||||
|
||||
@ -702,16 +706,18 @@ export const getTaskTemplates = (params?: {
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}>("/teacher/task-templates", { params });
|
||||
}>("/api/v1/teacher/task-templates", { params });
|
||||
|
||||
export const getTaskTemplate = (id: number) =>
|
||||
http.get<TaskTemplate>(`/teacher/task-templates/${id}`);
|
||||
http.get<TaskTemplate>(`/api/v1/teacher/task-templates/${id}`);
|
||||
|
||||
export const getDefaultTaskTemplate = (taskType: string) =>
|
||||
http.get<TaskTemplate | null>(`/teacher/task-templates/default/${taskType}`);
|
||||
http.get<TaskTemplate | null>(
|
||||
`/api/v1/teacher/task-templates/default/${taskType}`,
|
||||
);
|
||||
|
||||
export const createTaskFromTemplate = (data: CreateTaskFromTemplateDto) =>
|
||||
http.post<TeacherTask>("/teacher/tasks/from-template", data);
|
||||
http.post<TeacherTask>("/api/v1/teacher/tasks/from-template", data);
|
||||
|
||||
// ==================== 任务统计 API ====================
|
||||
|
||||
@ -750,15 +756,16 @@ export interface MonthlyTaskStats {
|
||||
rate: number;
|
||||
}
|
||||
|
||||
export const getTaskStats = () => http.get<TaskStats>("/teacher/tasks/stats");
|
||||
export const getTaskStats = () =>
|
||||
http.get<TaskStats>("/api/v1/teacher/tasks/stats");
|
||||
|
||||
export const getTaskStatsByType = () =>
|
||||
http.get<TaskStatsByType>("/teacher/tasks/stats/by-type");
|
||||
http.get<TaskStatsByType>("/api/v1/teacher/tasks/stats/by-type");
|
||||
|
||||
export const getTaskStatsByClass = () =>
|
||||
http.get<TaskStatsByClass[]>("/teacher/tasks/stats/by-class");
|
||||
http.get<TaskStatsByClass[]>("/api/v1/teacher/tasks/stats/by-class");
|
||||
|
||||
export const getMonthlyTaskStats = (months?: number) =>
|
||||
http.get<MonthlyTaskStats[]>("/teacher/tasks/stats/monthly", {
|
||||
http.get<MonthlyTaskStats[]>("/api/v1/teacher/tasks/stats/monthly", {
|
||||
params: { months },
|
||||
});
|
||||
|
||||
37
reading-platform-frontend/src/components.d.ts
vendored
37
reading-platform-frontend/src/components.d.ts
vendored
@ -7,22 +7,59 @@ export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AAlert: typeof import('ant-design-vue/es')['Alert']
|
||||
AAvatar: typeof import('ant-design-vue/es')['Avatar']
|
||||
ABadge: typeof import('ant-design-vue/es')['Badge']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
ACard: typeof import('ant-design-vue/es')['Card']
|
||||
ACheckbox: typeof import('ant-design-vue/es')['Checkbox']
|
||||
ACol: typeof import('ant-design-vue/es')['Col']
|
||||
ADescriptions: typeof import('ant-design-vue/es')['Descriptions']
|
||||
ADescriptionsItem: typeof import('ant-design-vue/es')['DescriptionsItem']
|
||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
||||
ADrawer: typeof import('ant-design-vue/es')['Drawer']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AEmpty: typeof import('ant-design-vue/es')['Empty']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
|
||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
||||
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
|
||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
||||
AList: typeof import('ant-design-vue/es')['List']
|
||||
AListItem: typeof import('ant-design-vue/es')['ListItem']
|
||||
AListItemMeta: typeof import('ant-design-vue/es')['ListItemMeta']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
AModal: typeof import('ant-design-vue/es')['Modal']
|
||||
APageHeader: typeof import('ant-design-vue/es')['PageHeader']
|
||||
APagination: typeof import('ant-design-vue/es')['Pagination']
|
||||
APopconfirm: typeof import('ant-design-vue/es')['Popconfirm']
|
||||
ARadio: typeof import('ant-design-vue/es')['Radio']
|
||||
ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup']
|
||||
ARangePicker: typeof import('ant-design-vue/es')['RangePicker']
|
||||
ARow: typeof import('ant-design-vue/es')['Row']
|
||||
ASelect: typeof import('ant-design-vue/es')['Select']
|
||||
ASelectOption: typeof import('ant-design-vue/es')['SelectOption']
|
||||
ASkeleton: typeof import('ant-design-vue/es')['Skeleton']
|
||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
ASubMenu: typeof import('ant-design-vue/es')['SubMenu']
|
||||
ASwitch: typeof import('ant-design-vue/es')['Switch']
|
||||
ATable: typeof import('ant-design-vue/es')['Table']
|
||||
ATabPane: typeof import('ant-design-vue/es')['TabPane']
|
||||
ATabs: typeof import('ant-design-vue/es')['Tabs']
|
||||
ATag: typeof import('ant-design-vue/es')['Tag']
|
||||
ATextarea: typeof import('ant-design-vue/es')['Textarea']
|
||||
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
|
||||
ATypographyText: typeof import('ant-design-vue/es')['TypographyText']
|
||||
AUpload: typeof import('ant-design-vue/es')['Upload']
|
||||
FilePreviewModal: typeof import('./components/FilePreviewModal.vue')['default']
|
||||
FileUploader: typeof import('./components/course/FileUploader.vue')['default']
|
||||
LessonConfigPanel: typeof import('./components/course/LessonConfigPanel.vue')['default']
|
||||
|
||||
@ -33,99 +33,91 @@
|
||||
<a-spin :spinning="loading">
|
||||
<div class="class-grid" v-if="classes.length > 0">
|
||||
<!-- 班级卡片 -->
|
||||
<div
|
||||
v-for="cls in classes"
|
||||
:key="cls.id"
|
||||
class="class-card"
|
||||
:class="'grade-' + getGradeKey(cls.grade)"
|
||||
@click="viewClassDetail(cls)"
|
||||
>
|
||||
<!-- 年级标签 -->
|
||||
<div class="grade-badge" :class="'badge-' + getGradeKey(cls.grade)">
|
||||
<span class="badge-icon">
|
||||
<TagsOutlined />
|
||||
</span>
|
||||
<span class="badge-text">{{ cls.grade }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 角色标签 -->
|
||||
<div class="role-badge" :class="'role-' + cls.myRole?.toLowerCase()">
|
||||
{{ getRoleLabel(cls.myRole) }}
|
||||
<span v-if="cls.isPrimary" class="primary-mark">班主任</span>
|
||||
</div>
|
||||
|
||||
<!-- 班级信息 -->
|
||||
<div class="class-header">
|
||||
<div class="class-avatar" :style="{ background: getGradeGradient(cls.grade) }">
|
||||
<span class="avatar-text">{{ getGradeInitial(cls.grade) }}</span>
|
||||
<div v-for="cls in classes" :key="cls.id" class="class-card" :class="'grade-' + getGradeKey(cls.grade)"
|
||||
@click="viewClassDetail(cls)">
|
||||
<!-- 年级标签 -->
|
||||
<div class="grade-badge" :class="'badge-' + getGradeKey(cls.grade)">
|
||||
<span class="badge-icon">
|
||||
<TagsOutlined />
|
||||
</span>
|
||||
<span class="badge-text">{{ cls.grade }}</span>
|
||||
</div>
|
||||
<div class="class-name">{{ cls.name }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="class-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon-wrapper pink">
|
||||
<TeamOutlined />
|
||||
<!-- 角色标签 -->
|
||||
<div class="role-badge" :class="'role-' + cls.myRole?.toLowerCase()">
|
||||
{{ getRoleLabel(cls.myRole) }}
|
||||
<span v-if="cls.isPrimary" class="primary-mark">班主任</span>
|
||||
</div>
|
||||
|
||||
<!-- 班级信息 -->
|
||||
<div class="class-header">
|
||||
<div class="class-avatar" :style="{ background: getGradeGradient(cls.grade) }">
|
||||
<span class="avatar-text">{{ getGradeInitial(cls.grade) }}</span>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ cls.studentCount }}</div>
|
||||
<div class="stat-label">名学生</div>
|
||||
<div class="class-name">{{ cls.name }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 统计信息 -->
|
||||
<div class="class-stats">
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon-wrapper pink">
|
||||
<TeamOutlined />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ cls.studentCount }}</div>
|
||||
<div class="stat-label">名学生</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon-wrapper blue">
|
||||
<ReadOutlined />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ cls.lessonCount }}</div>
|
||||
<div class="stat-label">授课次数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-divider"></div>
|
||||
<div class="stat-item">
|
||||
<div class="stat-icon-wrapper blue">
|
||||
<ReadOutlined />
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
<div class="stat-number">{{ cls.lessonCount }}</div>
|
||||
<div class="stat-label">授课次数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 进度条 -->
|
||||
<div class="class-progress">
|
||||
<div class="progress-label">本月授课进度</div>
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-fill"
|
||||
:style="{
|
||||
<!-- 进度条 -->
|
||||
<div class="class-progress">
|
||||
<div class="progress-label">本月授课进度</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" :style="{
|
||||
width: Math.min((cls.lessonCount / 20) * 100, 100) + '%',
|
||||
background: getGradeGradient(cls.grade)
|
||||
}"
|
||||
></div>
|
||||
}"></div>
|
||||
</div>
|
||||
<div class="progress-text">{{ Math.min(Math.round((cls.lessonCount / 20) * 100), 100) }}%</div>
|
||||
</div>
|
||||
<div class="progress-text">{{ Math.min(Math.round((cls.lessonCount / 20) * 100), 100) }}%</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="class-actions">
|
||||
<button class="action-btn primary" @click.stop="prepareLesson(cls)">
|
||||
<EditOutlined />
|
||||
备课
|
||||
</button>
|
||||
<button class="action-btn" @click.stop="viewStudents(cls)">
|
||||
<UsergroupAddOutlined />
|
||||
学生
|
||||
</button>
|
||||
<button class="action-btn" @click.stop="viewHistory(cls)">
|
||||
<BarChartOutlined />
|
||||
记录
|
||||
</button>
|
||||
<!-- 操作按钮 -->
|
||||
<div class="class-actions">
|
||||
<button class="action-btn primary" @click.stop="prepareLesson(cls)">
|
||||
<EditOutlined />
|
||||
备课
|
||||
</button>
|
||||
<button class="action-btn" @click.stop="viewStudents(cls)">
|
||||
<UsergroupAddOutlined />
|
||||
学生
|
||||
</button>
|
||||
<button class="action-btn" @click.stop="viewHistory(cls)">
|
||||
<BarChartOutlined />
|
||||
记录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && classes.length === 0" class="empty-state">
|
||||
<div class="empty-icon-wrapper">
|
||||
<HomeOutlined />
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!loading && classes.length === 0" class="empty-state">
|
||||
<div class="empty-icon-wrapper">
|
||||
<HomeOutlined />
|
||||
</div>
|
||||
<h3 class="empty-title">暂无分配班级</h3>
|
||||
<p class="empty-desc">请联系学校管理员为您分配班级</p>
|
||||
</div>
|
||||
<h3 class="empty-title">暂无分配班级</h3>
|
||||
<p class="empty-desc">请联系学校管理员为您分配班级</p>
|
||||
</div>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
@ -195,7 +187,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const totalStudents = computed(() => {
|
||||
return classes.value.reduce((sum, cls) => sum + cls.studentCount, 0);
|
||||
return classes.value?.reduce((sum, cls) => sum + cls.studentCount, 0) || 0;
|
||||
});
|
||||
|
||||
const getGradeKey = (grade: string) => {
|
||||
|
||||
@ -1,24 +1,24 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import { resolve } from 'path';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import { resolve } from "path";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { AntDesignVueResolver } from "unplugin-vue-components/resolvers";
|
||||
import viteCompression from "vite-plugin-compression";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
AutoImport({
|
||||
imports: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
'pinia',
|
||||
"vue",
|
||||
"vue-router",
|
||||
"pinia",
|
||||
{
|
||||
'ant-design-vue': ['message', 'notification', 'Modal'],
|
||||
"ant-design-vue": ["message", "notification", "Modal"],
|
||||
},
|
||||
],
|
||||
dts: 'src/auto-imports.d.ts',
|
||||
dts: "src/auto-imports.d.ts",
|
||||
}),
|
||||
Components({
|
||||
resolvers: [
|
||||
@ -26,31 +26,32 @@ export default defineConfig({
|
||||
importStyle: false,
|
||||
}),
|
||||
],
|
||||
dts: 'src/components.d.ts',
|
||||
dts: "src/components.d.ts",
|
||||
}),
|
||||
viteCompression({
|
||||
verbose: true,
|
||||
disable: false,
|
||||
threshold: 10240,
|
||||
algorithm: 'gzip',
|
||||
ext: '.gz',
|
||||
algorithm: "gzip",
|
||||
ext: ".gz",
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src'),
|
||||
"@": resolve(__dirname, "src"),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5173,
|
||||
host: true,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
"/api": {
|
||||
// target: 'http://localhost:8080',
|
||||
target: "http://192.168.1.110:8080",
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/uploads': {
|
||||
target: 'http://localhost:8080',
|
||||
"/uploads": {
|
||||
target: "http://192.168.1.110:8080",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
@ -59,10 +60,16 @@ export default defineConfig({
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'ant-design-vue': ['ant-design-vue', '@ant-design/icons-vue'],
|
||||
'echarts': ['echarts'],
|
||||
'fullcalendar': ['@fullcalendar/vue3', '@fullcalendar/core', '@fullcalendar/daygrid', '@fullcalendar/timegrid', '@fullcalendar/interaction'],
|
||||
'dayjs': ['dayjs'],
|
||||
"ant-design-vue": ["ant-design-vue", "@ant-design/icons-vue"],
|
||||
echarts: ["echarts"],
|
||||
fullcalendar: [
|
||||
"@fullcalendar/vue3",
|
||||
"@fullcalendar/core",
|
||||
"@fullcalendar/daygrid",
|
||||
"@fullcalendar/timegrid",
|
||||
"@fullcalendar/interaction",
|
||||
],
|
||||
dayjs: ["dayjs"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user