|
|
@@ -704,6 +704,199 @@ class TextbookApiService
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 根据教材系列、年级和学期获取教材及其目录结构
|
|
|
+ *
|
|
|
+ * @param int $seriesId 教材系列ID
|
|
|
+ * @param int $grade 年级(1-12)
|
|
|
+ * @param int $semesterCode 学期代码(1=上册,2=下册)
|
|
|
+ * @param string $catalogFormat 目录格式(tree 或 flat)
|
|
|
+ * @return array 包含教材信息和目录结构的数组
|
|
|
+ */
|
|
|
+ public function getTextbookByFilter(int $seriesId, int $grade, int $semesterCode, string $catalogFormat = 'tree'): array
|
|
|
+ {
|
|
|
+ if ($this->useDatabase) {
|
|
|
+ try {
|
|
|
+ // 先查找符合条件且已发布的教材
|
|
|
+ $query = Textbook::query()
|
|
|
+ ->select('textbooks.*')
|
|
|
+ ->join('textbook_series', 'textbooks.series_id', '=', 'textbook_series.id')
|
|
|
+ ->where('textbooks.series_id', $seriesId)
|
|
|
+ ->where('textbooks.grade', $grade)
|
|
|
+ ->where('textbooks.semester', $semesterCode)
|
|
|
+ ->where('textbooks.status', 'published')
|
|
|
+ ->where('textbook_series.is_active', true);
|
|
|
+
|
|
|
+ $textbook = $query->first();
|
|
|
+
|
|
|
+ if (!$textbook) {
|
|
|
+ return [
|
|
|
+ 'success' => false,
|
|
|
+ 'message' => '未找到符合条件的教材',
|
|
|
+ 'data' => null,
|
|
|
+ 'meta' => [
|
|
|
+ 'series_id' => $seriesId,
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'semester_code' => $semesterCode,
|
|
|
+ 'semester_label' => $this->getSemesterLabel($semesterCode),
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取教材目录
|
|
|
+ $catalog = $this->getTextbookCatalog($textbook->id, $catalogFormat);
|
|
|
+
|
|
|
+ // 格式化教材信息
|
|
|
+ $textbookData = [
|
|
|
+ 'id' => $textbook->id,
|
|
|
+ 'name' => $textbook->official_title ?? '',
|
|
|
+ 'display_name' => $textbook->official_title ?? '',
|
|
|
+ 'cover' => $this->formatCoverUrl($textbook->cover_path ?? ''),
|
|
|
+ 'series_id' => $textbook->series_id,
|
|
|
+ 'series_name' => $textbook->series->name ?? '',
|
|
|
+ 'publisher' => $textbook->series->publisher ?? '',
|
|
|
+ 'stage' => $this->getStageLabel($textbook->stage ?? ''),
|
|
|
+ 'stage_code' => $textbook->stage ?? '',
|
|
|
+ 'grade' => $textbook->grade,
|
|
|
+ 'grade_label' => $this->getGradeLabel($textbook->grade, $textbook->stage ?? ''),
|
|
|
+ 'semester' => $this->getSemesterLabel($textbook->semester),
|
|
|
+ 'semester_code' => $textbook->semester,
|
|
|
+ 'module_type' => $textbook->module_type ?? null,
|
|
|
+ 'volume_no' => $textbook->volume_no ?? null,
|
|
|
+ 'isbn' => $textbook->isbn ?? '',
|
|
|
+ 'approval_year' => $textbook->approval_year ?? null,
|
|
|
+ 'curriculum_standard_year' => $textbook->curriculum_standard_year ?? null,
|
|
|
+ 'status' => $textbook->status ?? 'draft',
|
|
|
+ 'sort_order' => $textbook->sort_order ?? 0,
|
|
|
+ ];
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => true,
|
|
|
+ 'data' => [
|
|
|
+ 'textbook' => $textbookData,
|
|
|
+ 'catalog' => $catalog,
|
|
|
+ ],
|
|
|
+ 'meta' => [
|
|
|
+ 'series_id' => $seriesId,
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'grade_label' => $textbookData['grade_label'],
|
|
|
+ 'semester_code' => $semesterCode,
|
|
|
+ 'semester_label' => $textbookData['semester'],
|
|
|
+ 'catalog_format' => $catalogFormat,
|
|
|
+ ]
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('获取教材及目录失败', [
|
|
|
+ 'series_id' => $seriesId,
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'semester_code' => $semesterCode,
|
|
|
+ 'error' => $e->getMessage()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => false,
|
|
|
+ 'message' => '获取教材信息失败: ' . $e->getMessage(),
|
|
|
+ 'data' => null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 外部API调用(如果需要)
|
|
|
+ try {
|
|
|
+ $result = $this->request('GET', '/textbooks/filter', [
|
|
|
+ 'series_id' => $seriesId,
|
|
|
+ 'grade' => $grade,
|
|
|
+ 'semester_code' => $semesterCode,
|
|
|
+ 'catalog_format' => $catalogFormat,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'success' => true,
|
|
|
+ 'data' => $result['data'] ?? null,
|
|
|
+ 'meta' => $result['meta'] ?? []
|
|
|
+ ];
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::warning('Failed to fetch textbook by filter, returning empty result', ['error' => $e->getMessage()]);
|
|
|
+ return [
|
|
|
+ 'success' => false,
|
|
|
+ 'message' => '获取教材信息失败: ' . $e->getMessage(),
|
|
|
+ 'data' => null,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取学期标签
|
|
|
+ */
|
|
|
+ private function getSemesterLabel(?int $semester): string
|
|
|
+ {
|
|
|
+ return match ($semester) {
|
|
|
+ 1 => '上册',
|
|
|
+ 2 => '下册',
|
|
|
+ default => '',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取学段标签
|
|
|
+ */
|
|
|
+ private function getStageLabel(string $stage): string
|
|
|
+ {
|
|
|
+ return match ($stage) {
|
|
|
+ 'primary' => '小学',
|
|
|
+ 'junior' => '初中',
|
|
|
+ 'senior' => '高中',
|
|
|
+ default => $stage,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 格式化封面URL
|
|
|
+ */
|
|
|
+ private function formatCoverUrl(?string $coverPath): string
|
|
|
+ {
|
|
|
+ if (empty($coverPath)) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果已经是完整URL,直接返回
|
|
|
+ if (str_starts_with($coverPath, 'http://') || str_starts_with($coverPath, 'https://')) {
|
|
|
+ return $coverPath;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 本地存储路径,添加域名
|
|
|
+ return url('/storage/' . ltrim($coverPath, '/'));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取年级标签
|
|
|
+ */
|
|
|
+ private function getGradeLabel(?int $grade, string $stage): string
|
|
|
+ {
|
|
|
+ if ($grade === null) {
|
|
|
+ return '';
|
|
|
+ }
|
|
|
+
|
|
|
+ return match ($stage) {
|
|
|
+ 'primary' => $grade . '年级',
|
|
|
+ 'junior' => match ($grade) {
|
|
|
+ 7 => '七年级',
|
|
|
+ 8 => '八年级',
|
|
|
+ 9 => '九年级',
|
|
|
+ default => $grade . '年级',
|
|
|
+ },
|
|
|
+ 'senior' => match ($grade) {
|
|
|
+ 10 => '高一',
|
|
|
+ 11 => '高二',
|
|
|
+ 12 => '高三',
|
|
|
+ default => '高' . ($grade - 9),
|
|
|
+ },
|
|
|
+ default => $grade . '年级',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
private function buildCatalogTree(array $nodes): array
|
|
|
{
|
|
|
// 建立ID索引,包含children数组
|