fix: 修复 /api/v1/school/courses 接口 gradeTags 前端显示数据丢失
- 后端: 增强 SchoolCourseController.parseJsonArray 兼容多种 JSON 格式 - 后端: 新增 SchoolCourseResponse,gradeTags/domainTags 规范为 String[] - 前端: 学校端课程列表/详情统一使用 parseGradeLevels 解析 gradeTags - 前端: 兼容 grade_tags/domain_tags snake_case 字段 Made-with: Cursor
This commit is contained in:
parent
efedb37cae
commit
81dd74662e
@ -375,16 +375,20 @@ export interface Course {
|
||||
description?: string;
|
||||
coverUrl?: string;
|
||||
coverImagePath?: string;
|
||||
pictureBookName?: string;
|
||||
category?: string;
|
||||
ageRange?: string;
|
||||
difficultyLevel?: string;
|
||||
durationMinutes?: number;
|
||||
duration?: number;
|
||||
objectives?: string;
|
||||
status: string;
|
||||
isSystem: number;
|
||||
version?: string;
|
||||
usageCount?: number;
|
||||
teacherCount?: number;
|
||||
gradeTags?: string[];
|
||||
domainTags?: string[];
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
publishedAt?: string;
|
||||
|
||||
@ -517,22 +517,26 @@ const previewModalVisible = ref(false);
|
||||
const previewFileUrl = ref('');
|
||||
const previewFileName = ref('');
|
||||
|
||||
// 年级
|
||||
// 年级(gradeTags 规范为 String[],与套餐管理对齐)
|
||||
const grades = computed(() => {
|
||||
if (!course.value.gradeTags) return [];
|
||||
const val = course.value.gradeTags;
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val;
|
||||
try {
|
||||
const tags = JSON.parse(course.value.gradeTags);
|
||||
return tags;
|
||||
const tags = JSON.parse(val);
|
||||
return Array.isArray(tags) ? tags : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// 领域标签(核心发展目标,翻译为中文)
|
||||
// 领域标签(domainTags 规范为 String[])
|
||||
const domainTags = computed(() => {
|
||||
if (!course.value.domainTags) return [];
|
||||
const val = course.value.domainTags;
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return translateDomainTags(val);
|
||||
try {
|
||||
const tags = JSON.parse(course.value.domainTags);
|
||||
const tags = JSON.parse(val);
|
||||
return translateDomainTags(Array.isArray(tags) ? tags : []);
|
||||
} catch {
|
||||
return [];
|
||||
|
||||
@ -228,7 +228,7 @@ const fetchCourseDetail = async () => {
|
||||
// 基本信息
|
||||
formData.basic.name = course.name;
|
||||
formData.basic.themeId = course.themeId;
|
||||
formData.basic.grades = course.gradeTags ? JSON.parse(course.gradeTags) : [];
|
||||
formData.basic.grades = Array.isArray(course.gradeTags) ? course.gradeTags : (course.gradeTags ? JSON.parse(course.gradeTags) : []);
|
||||
formData.basic.pictureBookName = course.pictureBookName || '';
|
||||
formData.basic.coreContent = course.coreContent || '';
|
||||
formData.basic.duration = course.duration || 25;
|
||||
|
||||
@ -485,11 +485,12 @@ const iterateCourse = (id: number) => {
|
||||
router.push(`/admin/packages/${id}/iterate`);
|
||||
};
|
||||
|
||||
const parseGradeTags = (gradeTags: string) => {
|
||||
const parseGradeTags = (gradeTags: string | string[] | undefined): string[] => {
|
||||
if (!gradeTags) return [];
|
||||
if (Array.isArray(gradeTags)) return gradeTags.map((t) => translateGradeTag(t));
|
||||
try {
|
||||
const tags = JSON.parse(gradeTags);
|
||||
return tags.map((t: string) => translateGradeTag(t));
|
||||
return Array.isArray(tags) ? tags.map((t: string) => translateGradeTag(t)) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -311,11 +311,12 @@ const viewRejectReason = (record: any) => {
|
||||
rejectReasonVisible.value = true;
|
||||
};
|
||||
|
||||
const parseGradeTags = (gradeTags: string) => {
|
||||
const parseGradeTags = (gradeTags: string | string[] | undefined): string[] => {
|
||||
if (!gradeTags) return [];
|
||||
if (Array.isArray(gradeTags)) return gradeTags.map((t) => translateGradeTag(t));
|
||||
try {
|
||||
const tags = JSON.parse(gradeTags);
|
||||
return tags.map((t: string) => translateGradeTag(t));
|
||||
return Array.isArray(tags) ? tags.map((t: string) => translateGradeTag(t)) : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
|
||||
@ -458,6 +458,7 @@ import {
|
||||
} from '@ant-design/icons-vue';
|
||||
import * as schoolApi from '@/api/school';
|
||||
import { translateDomainTags } from '@/utils/tagMaps';
|
||||
import { parseGradeLevels } from '@/api/collections';
|
||||
import FilePreviewModal from '@/components/FilePreviewModal.vue';
|
||||
|
||||
const router = useRouter();
|
||||
@ -528,29 +529,15 @@ const previewModalVisible = ref(false);
|
||||
const previewFileUrl = ref('');
|
||||
const previewFileName = ref('');
|
||||
|
||||
// 年级
|
||||
const grades = computed(() => {
|
||||
if (!course.value.gradeTags) return [];
|
||||
try {
|
||||
const tags = JSON.parse(course.value.gradeTags);
|
||||
return Array.isArray(tags) ? tags : [];
|
||||
} catch {
|
||||
// 可能已经是数组
|
||||
return Array.isArray(course.value.gradeTags) ? course.value.gradeTags : [];
|
||||
}
|
||||
});
|
||||
// 年级(统一使用 parseGradeLevels 解析,兼容多种格式)
|
||||
const grades = computed(() =>
|
||||
parseGradeLevels(course.value.gradeTags ?? course.value.grade_tags)
|
||||
);
|
||||
|
||||
// 领域标签(核心发展目标,翻译为中文)
|
||||
const domainTags = computed(() => {
|
||||
if (!course.value.domainTags) return [];
|
||||
try {
|
||||
const tags = JSON.parse(course.value.domainTags);
|
||||
const arr = Array.isArray(tags) ? tags : [];
|
||||
return translateDomainTags(arr);
|
||||
} catch {
|
||||
return Array.isArray(course.value.domainTags) ? translateDomainTags(course.value.domainTags) : [];
|
||||
}
|
||||
});
|
||||
const domainTags = computed(() =>
|
||||
translateDomainTags(parseGradeLevels(course.value.domainTags ?? course.value.domain_tags))
|
||||
);
|
||||
|
||||
// 是否有课程介绍内容
|
||||
const hasIntroContent = computed(() => {
|
||||
|
||||
@ -69,11 +69,11 @@
|
||||
<p class="course-book">《{{ course.pictureBookName }}》</p>
|
||||
|
||||
<div class="course-tags">
|
||||
<span v-for="tag in (course.gradeTags || []).slice(0, 2)" :key="tag" class="tag grade"
|
||||
<span v-for="tag in (course.gradeTags || [])" :key="tag" class="tag grade"
|
||||
:style="getGradeTagStyle(translateGradeTag(tag))">
|
||||
{{ translateGradeTag(tag) }}
|
||||
</span>
|
||||
<span v-for="tag in (course.domainTags || []).slice(0, 2)" :key="tag" class="tag domain"
|
||||
<span v-for="tag in (course.domainTags || [])" :key="tag" class="tag domain"
|
||||
:style="getDomainTagStyle(translateDomainTag(tag))">
|
||||
{{ translateDomainTag(tag) }}
|
||||
</span>
|
||||
@ -139,7 +139,8 @@
|
||||
</template>
|
||||
<div class="auth-content">
|
||||
<div class="auth-search">
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="输入课程名称搜索..." @search="searchCourses" size="large" />
|
||||
<a-input-search v-model:value="searchKeyword" placeholder="输入课程名称搜索..." @search="searchCourses"
|
||||
size="large" />
|
||||
</div>
|
||||
|
||||
<div class="available-courses" v-if="!authLoading && availableCourses.length > 0">
|
||||
@ -154,9 +155,9 @@
|
||||
</div>
|
||||
<div class="course-info">
|
||||
<div class="course-name-small">{{ course.name }}</div>
|
||||
<div class="course-book-small">《{{ course.pictureBookName }}》</div>
|
||||
<div class="course-book-small" v-if="course.pictureBookName">《{{ course.pictureBookName }}》</div>
|
||||
<div class="course-tags-small">
|
||||
<span v-for="tag in course.gradeTags.slice(0, 2)" :key="tag" class="tag-small">
|
||||
<span v-for="tag in (course.gradeTags || [])" :key="tag" class="tag-small">
|
||||
{{ tag }}
|
||||
</span>
|
||||
</div>
|
||||
@ -204,6 +205,7 @@ import {
|
||||
getGradeTagStyle,
|
||||
getDomainTagStyle,
|
||||
} from '@/utils/tagMaps';
|
||||
import { parseGradeLevels } from '@/api/collections';
|
||||
import * as schoolApi from '@/api/school';
|
||||
|
||||
const router = useRouter();
|
||||
@ -214,21 +216,6 @@ const searchKeyword = ref('');
|
||||
const selectedCourseIds = ref<number[]>([]);
|
||||
const selectedGrade = ref(''); // 选中的年级
|
||||
|
||||
// 解析标签(后端返回 JSON 字符串,需解析为数组)
|
||||
const parseTags = (val: any): string[] => {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val;
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(val);
|
||||
return Array.isArray(parsed) ? parsed : [];
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
// 年级选项
|
||||
const gradeOptions = [
|
||||
{ label: '全部', value: '' },
|
||||
@ -290,8 +277,6 @@ const loadCourses = async () => {
|
||||
const data = await schoolApi.getSchoolCourses(params);
|
||||
courses.value = (data || []).map((course: any) => ({
|
||||
...course,
|
||||
gradeTags: parseTags(course.gradeTags),
|
||||
domainTags: parseTags(course.domainTags),
|
||||
duration: course.duration ?? course.durationMinutes ?? 0,
|
||||
pictureUrl: course.pictureUrl ?? course.coverImagePath ?? course.coverUrl,
|
||||
authorized: course.authorized ?? true,
|
||||
|
||||
@ -539,27 +539,29 @@ const previewModalVisible = ref(false);
|
||||
const previewFileUrl = ref('');
|
||||
const previewFileName = ref('');
|
||||
|
||||
// 年级
|
||||
// 年级(gradeTags 规范为 String[],与套餐管理对齐)
|
||||
const grades = computed(() => {
|
||||
if (!course.value.gradeTags) return [];
|
||||
const val = course.value.gradeTags;
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return translateGradeTags(val);
|
||||
try {
|
||||
const tags = JSON.parse(course.value.gradeTags);
|
||||
const translated = Array.isArray(tags) ? tags : [];
|
||||
return translateGradeTags(translated);
|
||||
const tags = JSON.parse(val);
|
||||
return translateGradeTags(Array.isArray(tags) ? tags : []);
|
||||
} catch {
|
||||
return Array.isArray(course.value.gradeTags) ? translateGradeTags(course.value.gradeTags) : [];
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
// 领域标签(核心发展目标,翻译为中文)
|
||||
// 领域标签(domainTags 规范为 String[])
|
||||
const domainTags = computed(() => {
|
||||
if (!course.value.domainTags) return [];
|
||||
const val = course.value.domainTags;
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return translateDomainTags(val);
|
||||
try {
|
||||
const tags = JSON.parse(course.value.domainTags);
|
||||
const arr = Array.isArray(tags) ? tags : [];
|
||||
return translateDomainTags(arr);
|
||||
const tags = JSON.parse(val);
|
||||
return translateDomainTags(Array.isArray(tags) ? tags : []);
|
||||
} catch {
|
||||
return Array.isArray(course.value.domainTags) ? translateDomainTags(course.value.domainTags) : [];
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -232,10 +232,20 @@ const domainMap: Record<string, string> = {
|
||||
MATH: '数学', math: '数学',
|
||||
};
|
||||
|
||||
// 解析标签(后端可能返回 JSON 字符串或数组)
|
||||
// 解析标签(与套餐管理 parseGradeLevels 对齐,兼容多种格式)
|
||||
const parseTags = (val: any): string[] => {
|
||||
if (!val) return [];
|
||||
if (Array.isArray(val)) return val;
|
||||
if (Array.isArray(val)) {
|
||||
if (val.length === 0) return [];
|
||||
if (val[0]?.toString().startsWith('[')) {
|
||||
try {
|
||||
return JSON.parse(val.join(''));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
if (typeof val === 'string') {
|
||||
try {
|
||||
const parsed = JSON.parse(val);
|
||||
|
||||
@ -237,7 +237,7 @@ const loadCourseData = async () => {
|
||||
pictureBookName: data.sourceCourse?.name || '',
|
||||
theme: data.themeId ? { id: data.themeId, name: '' } : null,
|
||||
coverImagePath: data.coverImagePath,
|
||||
gradeTags: data.gradeTags ? JSON.parse(data.gradeTags) : [],
|
||||
gradeTags: Array.isArray(data.gradeTags) ? data.gradeTags : (data.gradeTags ? JSON.parse(data.gradeTags) : []),
|
||||
domainTags: data.domainTags ? JSON.parse(data.domainTags) : [],
|
||||
duration: data.duration || 25,
|
||||
coreContent: data.coreContent || '',
|
||||
|
||||
@ -265,7 +265,7 @@ const fetchDetail = async () => {
|
||||
// 基本信息
|
||||
formData.basic.name = data.name || '';
|
||||
formData.basic.themeId = data.themeId;
|
||||
formData.basic.grades = data.gradeTags ? JSON.parse(data.gradeTags) : [];
|
||||
formData.basic.grades = Array.isArray(data.gradeTags) ? data.gradeTags : (data.gradeTags ? JSON.parse(data.gradeTags) : []);
|
||||
formData.basic.pictureBookName = '';
|
||||
formData.basic.coreContent = data.coreContent || data.core_content || '';
|
||||
formData.basic.duration = data.duration || 25;
|
||||
|
||||
@ -3,12 +3,14 @@ package com.reading.platform.common.mapper;
|
||||
import com.reading.platform.dto.response.CourseResponse;
|
||||
import com.reading.platform.entity.CoursePackage;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Course Entity Mapper
|
||||
* gradeTags/domainTags 规范为 String[],与套餐管理适用年级对齐
|
||||
*/
|
||||
@Mapper(componentModel = "spring")
|
||||
public interface CoursePackageMapper {
|
||||
@ -18,6 +20,8 @@ public interface CoursePackageMapper {
|
||||
/**
|
||||
* Entity 转 Response
|
||||
*/
|
||||
@Mapping(target = "gradeTags", expression = "java(com.reading.platform.common.util.JsonUtils.parseStringArray(entity.getGradeTags()))")
|
||||
@Mapping(target = "domainTags", expression = "java(com.reading.platform.common.util.JsonUtils.parseStringArray(entity.getDomainTags()))")
|
||||
CourseResponse toVO(CoursePackage entity);
|
||||
|
||||
/**
|
||||
@ -28,5 +32,7 @@ public interface CoursePackageMapper {
|
||||
/**
|
||||
* Response 转 Entity(用于创建/更新时)
|
||||
*/
|
||||
@Mapping(target = "gradeTags", expression = "java(vo.getGradeTags() != null ? com.reading.platform.common.util.JsonUtils.toJson(vo.getGradeTags()) : null)")
|
||||
@Mapping(target = "domainTags", expression = "java(vo.getDomainTags() != null ? com.reading.platform.common.util.JsonUtils.toJson(vo.getDomainTags()) : null)")
|
||||
CoursePackage toEntity(CourseResponse vo);
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package com.reading.platform.common.util;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.TypeReference;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -223,6 +225,25 @@ public class JsonUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 数组为 String[](与套餐管理 gradeLevels 对齐)
|
||||
* 支持 ["小班","中班"] 或 小班,中班 格式
|
||||
*/
|
||||
public static String[] parseStringArray(String json) {
|
||||
if (!StringUtils.hasText(json)) {
|
||||
return new String[0];
|
||||
}
|
||||
try {
|
||||
if (json.trim().startsWith("[")) {
|
||||
return JSON.parseArray(json, String.class).toArray(new String[0]);
|
||||
}
|
||||
return json.split(",");
|
||||
} catch (Exception e) {
|
||||
log.warn("解析 JSON 数组失败: {}", json, e);
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建空的 JSONObject
|
||||
*
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
package com.reading.platform.controller.school;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.reading.platform.common.response.Result;
|
||||
import com.reading.platform.common.security.SecurityUtils;
|
||||
import com.reading.platform.dto.response.SchoolCourseResponse;
|
||||
import com.reading.platform.entity.CoursePackage;
|
||||
import com.reading.platform.service.CoursePackageService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
@ -15,6 +18,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 课程管理控制器(学校端)
|
||||
* gradeTags/domainTags 规范为 String[],与套餐管理适用年级对齐
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@ -27,21 +31,103 @@ public class SchoolCourseController {
|
||||
|
||||
@GetMapping
|
||||
@Operation(summary = "获取学校课程包列表")
|
||||
public Result<List<CoursePackage>> getSchoolCourses(
|
||||
public Result<List<SchoolCourseResponse>> getSchoolCourses(
|
||||
@RequestParam(required = false) String keyword,
|
||||
@RequestParam(required = false) String grade) {
|
||||
log.info("获取学校课程包列表,keyword={}, grade={}", keyword, grade);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
List<CoursePackage> courses = courseService.getTenantPackageCourses(tenantId, keyword, grade);
|
||||
return Result.success(courses);
|
||||
List<SchoolCourseResponse> list = courses.stream()
|
||||
.map(this::toSchoolCourseResponse)
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(list);
|
||||
}
|
||||
|
||||
@GetMapping("/{id}")
|
||||
@Operation(summary = "获取课程详情")
|
||||
public Result<CoursePackage> getSchoolCourse(@PathVariable Long id) {
|
||||
public Result<SchoolCourseResponse> getSchoolCourse(@PathVariable Long id) {
|
||||
log.info("获取课程详情,id={}", id);
|
||||
Long tenantId = SecurityUtils.getCurrentTenantId();
|
||||
CoursePackage course = courseService.getCourseByIdWithTenantCheck(id, tenantId);
|
||||
return Result.success(course);
|
||||
return Result.success(toSchoolCourseResponse(course));
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为学校端课程响应(gradeTags/domainTags 规范为 String[])
|
||||
*/
|
||||
private SchoolCourseResponse toSchoolCourseResponse(CoursePackage pkg) {
|
||||
return SchoolCourseResponse.builder()
|
||||
.id(pkg.getId())
|
||||
.tenantId(pkg.getTenantId())
|
||||
.name(pkg.getName())
|
||||
.code(pkg.getCode())
|
||||
.description(pkg.getDescription())
|
||||
.pictureBookName(pkg.getPictureBookName())
|
||||
.coverImagePath(pkg.getCoverImagePath())
|
||||
.coverUrl(pkg.getCoverUrl())
|
||||
.gradeTags(parseJsonArray(pkg.getGradeTags()))
|
||||
.domainTags(parseJsonArray(pkg.getDomainTags()))
|
||||
.duration(pkg.getDurationMinutes())
|
||||
.usageCount(pkg.getUsageCount())
|
||||
.teacherCount(pkg.getTeacherCount())
|
||||
.avgRating(pkg.getAvgRating())
|
||||
.status(pkg.getStatus())
|
||||
.createdAt(pkg.getCreatedAt())
|
||||
.updatedAt(pkg.getUpdatedAt())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 数组为 String[],兼容多种格式:
|
||||
* - 标准 JSON: ["小班","中班"]
|
||||
* - 逗号分隔: 小班,中班
|
||||
* - 错误格式(split 导致): ["[\"小班\"", " \"中班\""] -> 提取有效值
|
||||
*/
|
||||
private String[] parseJsonArray(String json) {
|
||||
if (!StringUtils.hasText(json)) {
|
||||
return new String[0];
|
||||
}
|
||||
String s = json.trim();
|
||||
try {
|
||||
if (s.startsWith("[")) {
|
||||
var list = JSON.parseArray(s, String.class);
|
||||
if (list != null && !list.isEmpty()) {
|
||||
// 检查是否为被错误 split 的格式,如 ["[\"小班\"", " \"中班\""]
|
||||
String first = list.get(0);
|
||||
if (first != null && first.startsWith("[\"") && !first.contains(",")) {
|
||||
return list.stream()
|
||||
.map(String::trim)
|
||||
.map(this::extractQuotedValue)
|
||||
.filter(v -> v != null && !v.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
return list.stream()
|
||||
.map(v -> v != null ? v.trim() : "")
|
||||
.filter(v -> !v.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
return java.util.Arrays.stream(s.split(","))
|
||||
.map(String::trim)
|
||||
.filter(v -> !v.isEmpty())
|
||||
.toArray(String[]::new);
|
||||
} catch (Exception e) {
|
||||
log.warn("解析 JSON 数组失败: {}", json, e);
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
private String extractQuotedValue(String s) {
|
||||
if (s == null) return null;
|
||||
s = s.trim();
|
||||
int start = s.indexOf('"');
|
||||
if (start >= 0) {
|
||||
int end = s.indexOf('"', start + 1);
|
||||
if (end > start) {
|
||||
return s.substring(start + 1, end).trim();
|
||||
}
|
||||
}
|
||||
return s.replaceAll("^\\[\"|\"\\]?$", "").trim();
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,11 +138,11 @@ public class CourseResponse {
|
||||
@Schema(description = "评估数据")
|
||||
private String assessmentData;
|
||||
|
||||
@Schema(description = "年级标签")
|
||||
private String gradeTags;
|
||||
@Schema(description = "年级标签(规范为数组,与套餐管理适用年级对齐)")
|
||||
private String[] gradeTags;
|
||||
|
||||
@Schema(description = "领域标签")
|
||||
private String domainTags;
|
||||
@Schema(description = "领域标签(规范为数组)")
|
||||
private String[] domainTags;
|
||||
|
||||
@Schema(description = "是否有集体课")
|
||||
private Integer hasCollectiveLesson;
|
||||
|
||||
@ -0,0 +1,68 @@
|
||||
package com.reading.platform.dto.response;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 学校端课程响应(gradeTags/domainTags 规范为 String[],与套餐管理适用年级对齐)
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@Schema(description = "学校端课程响应")
|
||||
public class SchoolCourseResponse {
|
||||
|
||||
@Schema(description = "ID")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "租户 ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "课程名称")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "课程编码")
|
||||
private String code;
|
||||
|
||||
@Schema(description = "描述")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "绘本名称")
|
||||
private String pictureBookName;
|
||||
|
||||
@Schema(description = "封面图片路径")
|
||||
private String coverImagePath;
|
||||
|
||||
@Schema(description = "封面 URL")
|
||||
private String coverUrl;
|
||||
|
||||
@Schema(description = "年级标签(规范为数组)")
|
||||
private String[] gradeTags;
|
||||
|
||||
@Schema(description = "领域标签(规范为数组)")
|
||||
private String[] domainTags;
|
||||
|
||||
@Schema(description = "课程时长(分钟)")
|
||||
private Integer duration;
|
||||
|
||||
@Schema(description = "使用次数")
|
||||
private Integer usageCount;
|
||||
|
||||
@Schema(description = "教师数量")
|
||||
private Integer teacherCount;
|
||||
|
||||
@Schema(description = "平均评分")
|
||||
private BigDecimal avgRating;
|
||||
|
||||
@Schema(description = "状态")
|
||||
private String status;
|
||||
|
||||
@Schema(description = "创建时间")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
private LocalDateTime updatedAt;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user