feat(Materials): add new materials navigation and href functions
All checks were successful
Frontend CI / build-and-push-web (push) Successful in 2m59s
Frontend CI / ota-android (push) Successful in 18m11s
Frontend CI / build-android-apk (push) Successful in 1h21m54s

- Introduced href functions for accessing materials, including hrefMaterials, hrefMaterialSubject, and hrefMaterialDetail.
- Updated AppsScreen to include a new entry for materials, enhancing the app's educational resources section.
- Exported material types in index.ts to support the new materials functionality.
This commit is contained in:
lafay
2026-03-25 21:17:17 +08:00
parent 619f08275c
commit 9529ea39c4
14 changed files with 2099 additions and 0 deletions

View File

@@ -0,0 +1,289 @@
/**
* 学习资料 Repository
* 提供学习资料的数据访问接口
*/
import type {
MaterialSubject,
MaterialFile,
MaterialFileType,
} from '../../types/material';
import { api } from '../../services/api';
// ==================== 接口定义 ====================
export interface GetMaterialsParams {
subjectId?: string;
fileType?: MaterialFileType;
keyword?: string;
sortBy?: 'createdAt' | 'downloadCount' | 'title';
sortOrder?: 'asc' | 'desc';
page?: number;
pageSize?: number;
}
export interface MaterialsResult {
materials: MaterialFile[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
}
// ==================== API 响应类型 ====================
interface SubjectResponse {
id: string;
name: string;
icon: string;
color: string;
description: string;
sort_order: number;
is_active: boolean;
material_count: number;
created_at: string;
updated_at: string;
}
interface MaterialFileResponse {
id: string;
subject_id: string;
title: string;
description: string;
file_type: string;
file_size: number;
file_url: string;
file_name: string;
download_count: number;
author_id: string;
tags: string[];
status: string;
created_at: string;
updated_at: string;
subject?: SubjectResponse;
}
// ==================== 转换函数 ====================
function toMaterialSubject(res: SubjectResponse): MaterialSubject {
return {
id: res.id,
name: res.name,
icon: res.icon,
color: res.color,
description: res.description,
sort_order: res.sort_order,
is_active: res.is_active,
material_count: res.material_count,
created_at: res.created_at,
updated_at: res.updated_at,
};
}
function toMaterialFile(res: MaterialFileResponse): MaterialFile {
return {
id: res.id,
subject_id: res.subject_id,
title: res.title,
description: res.description,
file_type: res.file_type as MaterialFileType,
file_size: res.file_size,
file_url: res.file_url,
file_name: res.file_name,
download_count: res.download_count,
author_id: res.author_id,
tags: res.tags || [],
status: res.status as 'active' | 'inactive',
created_at: res.created_at,
updated_at: res.updated_at,
subject: res.subject ? toMaterialSubject(res.subject) : undefined,
};
}
// ==================== Repository 实现 ====================
/**
* 学习资料数据仓库
* 使用真实 API
*/
export class MaterialRepository {
private static instance: MaterialRepository;
private constructor() {}
static getInstance(): MaterialRepository {
if (!MaterialRepository.instance) {
MaterialRepository.instance = new MaterialRepository();
}
return MaterialRepository.instance;
}
/**
* 获取学科分类列表
*/
async getSubjects(): Promise<MaterialSubject[]> {
try {
const response = await api.get<{ list: SubjectResponse[] }>('/materials/subjects');
return response.data.list.map(toMaterialSubject);
} catch (error) {
console.error('获取学科列表失败:', error);
return [];
}
}
/**
* 根据ID获取学科详情
*/
async getSubjectById(id: string): Promise<MaterialSubject | null> {
try {
const response = await api.get<SubjectResponse>(`/materials/subjects/${id}`);
return toMaterialSubject(response.data);
} catch (error) {
console.error('获取学科详情失败:', error);
return null;
}
}
/**
* 获取资料列表
*/
async getMaterials(params: GetMaterialsParams): Promise<MaterialsResult> {
const {
subjectId,
fileType,
keyword,
sortBy = 'createdAt',
sortOrder = 'desc',
page = 1,
pageSize = 20,
} = params;
try {
const queryParams: Record<string, any> = {
page,
page_size: pageSize,
};
if (subjectId) {
queryParams.subject_id = subjectId;
}
if (fileType) {
queryParams.file_type = fileType;
}
if (keyword) {
queryParams.keyword = keyword;
}
const response = await api.get<{
list: MaterialFileResponse[];
total: number;
page: number;
page_size: number;
has_more: boolean;
}>('/materials', queryParams);
return {
materials: response.data.list.map(toMaterialFile),
total: response.data.total,
page: response.data.page,
pageSize: response.data.page_size,
hasMore: response.data.has_more,
};
} catch (error) {
console.error('获取资料列表失败:', error);
return {
materials: [],
total: 0,
page: 1,
pageSize,
hasMore: false,
};
}
}
/**
* 根据ID获取资料详情
*/
async getMaterialById(id: string): Promise<MaterialFile | null> {
try {
const response = await api.get<MaterialFileResponse>(`/materials/${id}`);
return toMaterialFile(response.data);
} catch (error) {
console.error('获取资料详情失败:', error);
return null;
}
}
/**
* 搜索资料
*/
async searchMaterials(keyword: string, params?: { fileType?: MaterialFileType }): Promise<MaterialsResult> {
return this.getMaterials({
keyword,
fileType: params?.fileType,
});
}
/**
* 获取热门资料
*/
async getHotMaterials(limit: number = 10): Promise<MaterialFile[]> {
try {
const response = await api.get<{
list: MaterialFileResponse[];
total: number;
page: number;
page_size: number;
has_more: boolean;
}>('/materials', {
page: 1,
page_size: limit,
sort_by: 'download_count',
sort_order: 'desc',
});
return response.data.list.map(toMaterialFile);
} catch (error) {
console.error('获取热门资料失败:', error);
return [];
}
}
/**
* 获取最新资料
*/
async getLatestMaterials(limit: number = 10): Promise<MaterialFile[]> {
try {
const response = await api.get<{
list: MaterialFileResponse[];
total: number;
page: number;
page_size: number;
has_more: boolean;
}>('/materials', {
page: 1,
page_size: limit,
sort_by: 'created_at',
sort_order: 'desc',
});
return response.data.list.map(toMaterialFile);
} catch (error) {
console.error('获取最新资料失败:', error);
return [];
}
}
/**
* 获取资料的下载链接(增加下载次数)
*/
async downloadMaterial(id: string): Promise<{ file_url: string; file_name: string } | null> {
try {
const response = await api.get<{ file_url: string; file_name: string }>(`/materials/${id}/download`);
return response.data;
} catch (error) {
console.error('获取下载链接失败:', error);
return null;
}
}
}
// 导出单例
export const materialRepository = MaterialRepository.getInstance();