Merge remote-tracking branch 'origin/master'

# Conflicts:
#	reading-platform-frontend/src/views/admin/collections/CollectionEditView.vue
This commit is contained in:
En 2026-03-19 12:34:35 +08:00
commit efedb37cae
9 changed files with 185 additions and 90 deletions

View File

@ -202,9 +202,21 @@ export function getCollectionStatusInfo(status: string) {
return COLLECTION_STATUS_MAP[status] || { label: status, color: 'default' }; return COLLECTION_STATUS_MAP[status] || { label: status, color: 'default' };
} }
// 解析适用年级 // 解析适用年级(统一处理列表展示与创建时的格式)
export function parseGradeLevels(gradeLevels: string | string[]): string[] { export function parseGradeLevels(gradeLevels: string | string[] | undefined): string[] {
if (Array.isArray(gradeLevels)) return gradeLevels; if (!gradeLevels) return [];
if (Array.isArray(gradeLevels)) {
if (gradeLevels.length === 0) return [];
// 兼容后端错误格式:["[\"小班\"", " \"中班\""] -> 拼接后解析
if (gradeLevels[0]?.toString().startsWith('[')) {
try {
return JSON.parse(gradeLevels.join(''));
} catch {
return [];
}
}
return gradeLevels;
}
try { try {
return JSON.parse(gradeLevels || '[]'); return JSON.parse(gradeLevels || '[]');
} catch { } catch {

View File

@ -31,10 +31,14 @@
</a-popconfirm> </a-popconfirm>
</template> </template>
<!-- 已下架状态显示重新发布按钮 --> <!-- 已下架状态显示编辑删除重新发布按钮 -->
<template v-else-if="collection?.status === 'ARCHIVED'"> <template v-else-if="collection?.status === 'ARCHIVED'">
<a-button type="primary" @click="editCollection">编辑</a-button>
<a-popconfirm title="确定要重新发布此套餐吗?" @confirm="handleRepublish"> <a-popconfirm title="确定要重新发布此套餐吗?" @confirm="handleRepublish">
<a-button type="primary">重新发布</a-button> <a-button>重新发布</a-button>
</a-popconfirm>
<a-popconfirm title="确定要删除此套餐吗?" @confirm="handleDelete">
<a-button danger>删除</a-button>
</a-popconfirm> </a-popconfirm>
</template> </template>
@ -284,10 +288,12 @@ const handleAddPackage = async () => {
addingPackage.value = true; addingPackage.value = true;
try { try {
// //
const currentPackageIds = collection.value?.packages?.map(p => p.id) || []; const currentPackageIds = [...new Set(collection.value?.packages?.map(p => p.id) || [])];
// ID // ID
const newPackageIds = [...currentPackageIds, selectedPackageId.value]; const newPackageIds = currentPackageIds.includes(selectedPackageId.value)
? currentPackageIds
: [...currentPackageIds, selectedPackageId.value];
await collectionsApi.setCollectionPackages(route.params.id, newPackageIds); await collectionsApi.setCollectionPackages(route.params.id, newPackageIds);
message.success('添加成功'); message.success('添加成功');
@ -304,8 +310,8 @@ const handleAddPackage = async () => {
// //
const handleRemovePackage = async (packageId: number) => { const handleRemovePackage = async (packageId: number) => {
try { try {
// //
const currentPackageIds = collection.value?.packages?.map(p => p.id) || []; const currentPackageIds = [...new Set(collection.value?.packages?.map(p => p.id) || [])];
const newPackageIds = currentPackageIds.filter(id => id !== packageId); const newPackageIds = currentPackageIds.filter(id => id !== packageId);
await collectionsApi.setCollectionPackages(route.params.id, newPackageIds); await collectionsApi.setCollectionPackages(route.params.id, newPackageIds);

View File

@ -6,38 +6,25 @@
</template> </template>
<template #extra> <template #extra>
<a-button type="primary" @click="handleCreate"> <a-button type="primary" @click="handleCreate">
<template #icon><PlusOutlined /></template> <template #icon>
<PlusOutlined />
</template>
新建套餐 新建套餐
</a-button> </a-button>
</template> </template>
<!-- 筛选 --> <!-- 筛选 -->
<div class="filter-section"> <div class="filter-section">
<a-select <a-select v-model:value="filters.status" placeholder="状态筛选" style="width: 150px" allowClear @change="fetchData">
v-model:value="filters.status"
placeholder="状态筛选"
style="width: 150px"
allowClear
@change="fetchData"
>
<a-select-option value="DRAFT">草稿</a-select-option> <a-select-option value="DRAFT">草稿</a-select-option>
<a-select-option value="PENDING">待审核</a-select-option>
<a-select-option value="APPROVED">已通过</a-select-option>
<a-select-option value="PUBLISHED">已发布</a-select-option> <a-select-option value="PUBLISHED">已发布</a-select-option>
<a-select-option value="ARCHIVED">已下架</a-select-option> <a-select-option value="ARCHIVED">已下架</a-select-option>
<a-select-option value="REJECTED">已驳回</a-select-option>
</a-select> </a-select>
</div> </div>
<!-- 表格 --> <!-- 表格 -->
<a-table <a-table :columns="columns" :data-source="dataSource" :loading="loading" :pagination="pagination" row-key="id"
:columns="columns" @change="handleTableChange">
:data-source="dataSource"
:loading="loading"
:pagination="pagination"
row-key="id"
@change="handleTableChange"
>
<template #bodyCell="{ column, record }"> <template #bodyCell="{ column, record }">
<template v-if="column.key === 'price'"> <template v-if="column.key === 'price'">
{{ formatPrice(record.price) }} {{ formatPrice(record.price) }}
@ -57,6 +44,14 @@
<template v-if="record.status === 'DRAFT'"> <template v-if="record.status === 'DRAFT'">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button> <a-button type="link" size="small" @click="handleView(record)">查看</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button> <a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
<a-tooltip v-if="(record.packageCount || 0) === 0" title="请先添加至少一个课程包">
<span>
<a-button type="primary" size="small" disabled>发布</a-button>
</span>
</a-tooltip>
<a-popconfirm v-else title="确定要发布此套餐吗?发布后学校端将可以查看。" @confirm="handlePublish(record)">
<a-button type="primary" size="small">发布</a-button>
</a-popconfirm>
<a-popconfirm title="确定要删除吗?" @confirm="handleDelete(record)"> <a-popconfirm title="确定要删除吗?" @confirm="handleDelete(record)">
<a-button type="link" size="small" danger>删除</a-button> <a-button type="link" size="small" danger>删除</a-button>
</a-popconfirm> </a-popconfirm>
@ -72,6 +67,14 @@
<template v-else-if="record.status === 'REJECTED'"> <template v-else-if="record.status === 'REJECTED'">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button> <a-button type="link" size="small" @click="handleView(record)">查看</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">修改</a-button> <a-button type="link" size="small" @click="handleEdit(record)">修改</a-button>
<a-tooltip v-if="(record.packageCount || 0) === 0" title="请先添加至少一个课程包">
<span>
<a-button type="primary" size="small" disabled>发布</a-button>
</span>
</a-tooltip>
<a-popconfirm v-else title="确定要发布此套餐吗?发布后学校端将可以查看。" @confirm="handlePublish(record)">
<a-button type="primary" size="small">发布</a-button>
</a-popconfirm>
</template> </template>
<!-- 已通过状态 --> <!-- 已通过状态 -->
@ -91,9 +94,13 @@
<!-- 已下架状态 --> <!-- 已下架状态 -->
<template v-else-if="record.status === 'ARCHIVED'"> <template v-else-if="record.status === 'ARCHIVED'">
<a-button type="link" size="small" @click="handleView(record)">查看</a-button> <a-button type="link" size="small" @click="handleView(record)">查看</a-button>
<a-button type="link" size="small" @click="handleEdit(record)">编辑</a-button>
<a-popconfirm title="确定要重新发布吗?" @confirm="handleRepublish(record)"> <a-popconfirm title="确定要重新发布吗?" @confirm="handleRepublish(record)">
<a-button type="primary" size="small">重新发布</a-button> <a-button type="primary" size="small">重新发布</a-button>
</a-popconfirm> </a-popconfirm>
<a-popconfirm title="确定要删除吗?" @confirm="handleDelete(record)">
<a-button type="link" size="small" danger>删除</a-button>
</a-popconfirm>
</template> </template>
</a-space> </a-space>
</template> </template>

View File

@ -14,7 +14,7 @@
</div> </div>
</div> </div>
<div class="header-actions"> <div class="header-actions">
<a-button @click="editCourse"> <a-button v-if="course.status !== 'PUBLISHED'" @click="editCourse">
<EditOutlined /> 编辑 <EditOutlined /> 编辑
</a-button> </a-button>
<a-button @click="viewStats"> <a-button @click="viewStats">

View File

@ -218,6 +218,13 @@ const fetchCourseDetail = async () => {
const res = await getCourse(courseId.value) as any; const res = await getCourse(courseId.value) as any;
const course = res.data || res; const course = res.data || res;
//
if (course?.status === 'PUBLISHED') {
message.warning('已发布的课程包不允许编辑,请使用「迭代版本」创建新版本');
router.push(`/admin/packages/${courseId.value}`);
return;
}
// //
formData.basic.name = course.name; formData.basic.name = course.name;
formData.basic.themeId = course.themeId; formData.basic.themeId = course.themeId;

View File

@ -39,11 +39,12 @@
</a-form-item> </a-form-item>
<a-form-item label="适用年级" name="gradeLevels"> <a-form-item label="适用年级" name="gradeLevels">
<a-select v-model:value="form.gradeLevels" mode="multiple" placeholder="请选择适用年级" style="width: 300px"> <a-checkbox-group v-model:value="form.gradeLevels" disabled>
<a-select-option value="小班">小班</a-select-option> <a-checkbox value="小班">小班</a-checkbox>
<a-select-option value="中班">中班</a-select-option> <a-checkbox value="中班">中班</a-checkbox>
<a-select-option value="大班">大班</a-select-option> <a-checkbox value="大班">大班</a-checkbox>
</a-select> </a-checkbox-group>
<div v-if="form.gradeLevels.length === 0" class="grade-hint">根据已选课程包自动填充</div>
</a-form-item> </a-form-item>
<a-divider>课程包配置</a-divider> <a-divider>课程包配置</a-divider>
@ -62,11 +63,8 @@
<a-input-number v-model:value="record.sortOrder" :min="0" size="small" /> <a-input-number v-model:value="record.sortOrder" :min="0" size="small" />
</template> </template>
<template v-else-if="column.key === 'gradeLevel'"> <template v-else-if="column.key === 'gradeLevel'">
<a-select v-model:value="record.gradeLevel" size="small" style="width: 100px"> <a-tag v-for="g in (record.gradeLevels || [])" :key="g">{{ g }}</a-tag>
<a-select-option value="小班">小班</a-select-option> <span v-if="!record.gradeLevels?.length">-</span>
<a-select-option value="中班">中班</a-select-option>
<a-select-option value="大班">大班</a-select-option>
</a-select>
</template> </template>
<template v-else-if="column.key === 'action'"> <template v-else-if="column.key === 'action'">
<a-button type="link" size="small" danger @click="removePackage(index)">移除</a-button> <a-button type="link" size="small" danger @click="removePackage(index)">移除</a-button>
@ -115,7 +113,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'; import { ref, reactive, computed, watch, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { message } from 'ant-design-vue'; import { message } from 'ant-design-vue';
import type { FormInstance } from 'ant-design-vue'; import type { FormInstance } from 'ant-design-vue';
@ -145,18 +143,23 @@ const form = reactive({
gradeLevels: [] as string[], gradeLevels: [] as string[],
}); });
const selectedPackages = ref<{ packageId: number | string; gradeLevel: string; sortOrder: number; packageName: string }[]>([]); const selectedPackages = ref<{ packageId: number | string; gradeLevels: string[]; sortOrder: number; packageName: string }[]>([]);
// //
const formRules = { const formRules = {
name: [{ required: true, message: '请输入套餐名称' }], name: [{ required: true, message: '请输入套餐名称' }],
price: [{ required: true, message: '请输入价格' }], price: [{ required: true, message: '请输入价格' }],
gradeLevels: [{ required: true, message: '请选择适用年级' }], gradeLevels: [{
validator: () => {
if (selectedPackages.value.length === 0) return Promise.reject('请先添加至少一个课程包');
return Promise.resolve();
},
}],
}; };
const packageColumns = [ const packageColumns = [
{ title: '课程包', dataIndex: 'packageName', key: 'packageName' }, { title: '课程包', dataIndex: 'packageName', key: 'packageName' },
{ title: '年级', dataIndex: 'gradeLevel', key: 'gradeLevel', width: 120 }, { title: '年级', dataIndex: 'gradeLevels', key: 'gradeLevel', width: 120 },
{ title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 80 }, { title: '排序', dataIndex: 'sortOrder', key: 'sortOrder', width: 80 },
{ title: '操作', key: 'action', width: 80 }, { title: '操作', key: 'action', width: 80 },
]; ];
@ -174,7 +177,8 @@ const rowSelection = computed(() => ({
}, },
})); }));
const parseGradeTags = (tags: string) => { const parseGradeTags = (tags: string | string[]) => {
if (Array.isArray(tags)) return tags;
try { try {
return JSON.parse(tags || '[]'); return JSON.parse(tags || '[]');
} catch { } catch {
@ -182,6 +186,15 @@ const parseGradeTags = (tags: string) => {
} }
}; };
//
const syncGradeLevelsFromPackages = () => {
const grades = [...new Set(selectedPackages.value.flatMap((p) => p.gradeLevels || []).filter(Boolean))];
form.gradeLevels = grades;
};
//
watch(() => selectedPackages.value, syncGradeLevelsFromPackages, { deep: true });
const fetchPackageDetail = async () => { const fetchPackageDetail = async () => {
if (!isEdit.value) return; if (!isEdit.value) return;
@ -192,13 +205,13 @@ const fetchPackageDetail = async () => {
form.price = pkg.price / 100; form.price = pkg.price / 100;
form.discountPrice = pkg.discountPrice ? pkg.discountPrice / 100 : undefined; form.discountPrice = pkg.discountPrice ? pkg.discountPrice / 100 : undefined;
form.discountType = pkg.discountType; form.discountType = pkg.discountType;
form.gradeLevels = JSON.parse(pkg.gradeLevels || '[]');
//
selectedPackages.value = (pkg.packages || []).map((p: any) => ({ selectedPackages.value = (pkg.packages || []).map((p: any) => ({
packageId: p.id, packageId: p.id,
packageName: p.name, packageName: p.name,
gradeLevel: p.gradeLevels?.[0] || '小班', gradeLevels: Array.isArray(p.gradeLevels) ? p.gradeLevels : (p.gradeLevels ? [p.gradeLevels] : []),
sortOrder: p.sortOrder, sortOrder: p.sortOrder ?? 0,
})); }));
} catch (error) { } catch (error) {
message.error('获取套餐详情失败'); message.error('获取套餐详情失败');
@ -222,12 +235,15 @@ const handleAddPackages = () => {
const existingIds = new Set(selectedPackages.value.map((p) => p.packageId)); const existingIds = new Set(selectedPackages.value.map((p) => p.packageId));
const newPackages = availablePackages.value const newPackages = availablePackages.value
.filter((p) => selectedRowKeys.value.includes(p.id) && !existingIds.has(p.id)) .filter((p) => selectedRowKeys.value.includes(p.id) && !existingIds.has(p.id))
.map((p) => ({ .map((p) => {
const tags = parseGradeTags(p.gradeTags);
return {
packageId: p.id, packageId: p.id,
packageName: p.name, packageName: p.name,
gradeLevel: parseGradeTags(p.gradeTags)?.[0] || '小班', gradeLevels: Array.isArray(tags) && tags.length > 0 ? tags : ['小班'],
sortOrder: selectedPackages.value.length, sortOrder: selectedPackages.value.length,
})); };
});
selectedPackages.value.push(...newPackages); selectedPackages.value.push(...newPackages);
selectedRowKeys.value = []; selectedRowKeys.value = [];
@ -267,12 +283,13 @@ const handleSave = async () => {
id = res.id; // Long string id = res.id; // Long string
} }
// // sortOrder
if (selectedPackages.value.length > 0) { if (selectedPackages.value.length > 0) {
await setCollectionPackages( const sorted = [...selectedPackages.value].sort((a, b) => a.sortOrder - b.sortOrder);
id, const packageIds = [...new Map(sorted.map((p) => [p.packageId, p])).values()]
selectedPackages.value.map((p) => p.packageId), .sort((a, b) => a.sortOrder - b.sortOrder)
); .map((p) => p.packageId);
await setCollectionPackages(id, packageIds);
} }
message.success('保存成功'); message.success('保存成功');
@ -303,4 +320,10 @@ onMounted(() => {
.package-list { .package-list {
width: 100%; width: 100%;
} }
.grade-hint {
color: #999;
font-size: 12px;
margin-top: 4px;
}
</style> </style>

View File

@ -169,8 +169,19 @@ const statusTexts: Record<string, string> = {
const getStatusColor = (status: string) => statusColors[status] || 'default'; const getStatusColor = (status: string) => statusColors[status] || 'default';
const getStatusText = (status: string) => statusTexts[status] || status; const getStatusText = (status: string) => statusTexts[status] || status;
const parseGradeLevels = (gradeLevels: string | string[]) => { const parseGradeLevels = (gradeLevels: string | string[] | undefined): string[] => {
if (Array.isArray(gradeLevels)) return gradeLevels; if (!gradeLevels) return [];
if (Array.isArray(gradeLevels)) {
if (gradeLevels.length === 0) return [];
if (gradeLevels[0]?.toString().startsWith('[')) {
try {
return JSON.parse(gradeLevels.join(''));
} catch {
return [];
}
}
return gradeLevels;
}
try { try {
return JSON.parse(gradeLevels || '[]'); return JSON.parse(gradeLevels || '[]');
} catch { } catch {

View File

@ -222,13 +222,16 @@ public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper,
throw new BusinessException("课程套餐不存在"); throw new BusinessException("课程套餐不存在");
} }
// 去重保持首次出现顺序避免 uk_collection_package 唯一约束冲突
List<Long> distinctIds = packageIds.stream().distinct().collect(Collectors.toList());
// 验证课程包是否存在应用层外键约束 // 验证课程包是否存在应用层外键约束
if (!packageIds.isEmpty()) { if (!distinctIds.isEmpty()) {
List<CoursePackage> packages = packageMapper.selectList( List<CoursePackage> packages = packageMapper.selectList(
new LambdaQueryWrapper<CoursePackage>() new LambdaQueryWrapper<CoursePackage>()
.in(CoursePackage::getId, packageIds) .in(CoursePackage::getId, distinctIds)
); );
if (packages.size() != packageIds.size()) { if (packages.size() != distinctIds.size()) {
throw new BusinessException("存在无效的课程包 ID"); throw new BusinessException("存在无效的课程包 ID");
} }
} }
@ -240,16 +243,16 @@ public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper,
); );
// 创建新的关联 // 创建新的关联
for (int i = 0; i < packageIds.size(); i++) { for (int i = 0; i < distinctIds.size(); i++) {
CourseCollectionPackage association = new CourseCollectionPackage(); CourseCollectionPackage association = new CourseCollectionPackage();
association.setCollectionId(collectionId); association.setCollectionId(collectionId);
association.setPackageId(packageIds.get(i)); association.setPackageId(distinctIds.get(i));
association.setSortOrder(i + 1); association.setSortOrder(i + 1);
collectionPackageMapper.insert(association); collectionPackageMapper.insert(association);
} }
// 更新课程包数量复用前面已验证的 collection 变量 // 更新课程包数量复用前面已验证的 collection 变量
collection.setPackageCount(packageIds.size()); collection.setPackageCount(distinctIds.size());
collectionMapper.updateById(collection); collectionMapper.updateById(collection);
log.info("课程套餐的课程包设置完成"); log.info("课程套餐的课程包设置完成");
@ -500,6 +503,25 @@ public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper,
} }
} }
/**
* 解析适用年级数据库存储为 JSON 数组 ["小班","中班","大班"]需正确解析
*/
private String[] parseGradeLevels(String gradeLevels) {
if (!StringUtils.hasText(gradeLevels)) {
return new String[0];
}
try {
if (gradeLevels.trim().startsWith("[")) {
return JSON.parseArray(gradeLevels, String.class).toArray(new String[0]);
}
// 兼容旧数据逗号分隔格式
return gradeLevels.split(",");
} catch (Exception e) {
log.warn("解析 gradeLevels 失败: {}", gradeLevels, e);
return new String[0];
}
}
/** /**
* 转换为响应对象 * 转换为响应对象
*/ */
@ -525,7 +547,7 @@ public class CourseCollectionService extends ServiceImpl<CourseCollectionMapper,
.price(collection.getPrice()) .price(collection.getPrice())
.discountPrice(collection.getDiscountPrice()) .discountPrice(collection.getDiscountPrice())
.discountType(collection.getDiscountType()) .discountType(collection.getDiscountType())
.gradeLevels(collection.getGradeLevels() != null ? collection.getGradeLevels().split(",") : new String[0]) .gradeLevels(parseGradeLevels(collection.getGradeLevels()))
.packageCount(collection.getPackageCount()) .packageCount(collection.getPackageCount())
.status(collection.getStatus()) .status(collection.getStatus())
.submittedAt(collection.getSubmittedAt()) .submittedAt(collection.getSubmittedAt())

View File

@ -296,24 +296,24 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
entity.setIntroMethods(request.getIntroMethods()); entity.setIntroMethods(request.getIntroMethods());
entity.setIntroEvaluation(request.getIntroEvaluation()); entity.setIntroEvaluation(request.getIntroEvaluation());
entity.setIntroNotes(request.getIntroNotes()); entity.setIntroNotes(request.getIntroNotes());
entity.setScheduleRefData(request.getScheduleRefData()); entity.setScheduleRefData(emptyToNull(request.getScheduleRefData()));
entity.setEnvironmentConstruction(request.getEnvironmentConstruction()); entity.setEnvironmentConstruction(request.getEnvironmentConstruction());
entity.setThemeId(request.getThemeId()); entity.setThemeId(request.getThemeId());
entity.setPictureBookName(request.getPictureBookName()); entity.setPictureBookName(request.getPictureBookName());
entity.setEbookPaths(request.getEbookPaths()); entity.setEbookPaths(emptyToNull(request.getEbookPaths()));
entity.setAudioPaths(request.getAudioPaths()); entity.setAudioPaths(emptyToNull(request.getAudioPaths()));
entity.setVideoPaths(request.getVideoPaths()); entity.setVideoPaths(emptyToNull(request.getVideoPaths()));
entity.setOtherResources(request.getOtherResources()); entity.setOtherResources(emptyToNull(request.getOtherResources()));
entity.setPptPath(request.getPptPath()); entity.setPptPath(request.getPptPath());
entity.setPptName(request.getPptName()); entity.setPptName(request.getPptName());
entity.setPosterPaths(request.getPosterPaths()); entity.setPosterPaths(request.getPosterPaths());
entity.setTools(request.getTools()); entity.setTools(request.getTools());
entity.setStudentMaterials(request.getStudentMaterials()); entity.setStudentMaterials(request.getStudentMaterials());
entity.setLessonPlanData(request.getLessonPlanData()); entity.setLessonPlanData(emptyToNull(request.getLessonPlanData()));
entity.setActivitiesData(request.getActivitiesData()); entity.setActivitiesData(emptyToNull(request.getActivitiesData()));
entity.setAssessmentData(request.getAssessmentData()); entity.setAssessmentData(emptyToNull(request.getAssessmentData()));
entity.setGradeTags(request.getGradeTags()); entity.setGradeTags(emptyToNull(request.getGradeTags()));
entity.setDomainTags(request.getDomainTags()); entity.setDomainTags(emptyToNull(request.getDomainTags()));
entity.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0); entity.setHasCollectiveLesson(request.getHasCollectiveLesson() != null && request.getHasCollectiveLesson() ? 1 : 0);
return entity; return entity;
} }
@ -339,27 +339,34 @@ public class CoursePackageServiceImpl extends ServiceImpl<CoursePackageMapper, C
if (request.getIntroMethods() != null) entity.setIntroMethods(request.getIntroMethods()); if (request.getIntroMethods() != null) entity.setIntroMethods(request.getIntroMethods());
if (request.getIntroEvaluation() != null) entity.setIntroEvaluation(request.getIntroEvaluation()); if (request.getIntroEvaluation() != null) entity.setIntroEvaluation(request.getIntroEvaluation());
if (request.getIntroNotes() != null) entity.setIntroNotes(request.getIntroNotes()); if (request.getIntroNotes() != null) entity.setIntroNotes(request.getIntroNotes());
if (request.getScheduleRefData() != null) entity.setScheduleRefData(request.getScheduleRefData()); if (request.getScheduleRefData() != null) entity.setScheduleRefData(emptyToNull(request.getScheduleRefData()));
if (request.getEnvironmentConstruction() != null) entity.setEnvironmentConstruction(request.getEnvironmentConstruction()); if (request.getEnvironmentConstruction() != null) entity.setEnvironmentConstruction(request.getEnvironmentConstruction());
if (request.getThemeId() != null) entity.setThemeId(request.getThemeId()); if (request.getThemeId() != null) entity.setThemeId(request.getThemeId());
if (request.getPictureBookName() != null) entity.setPictureBookName(request.getPictureBookName()); if (request.getPictureBookName() != null) entity.setPictureBookName(request.getPictureBookName());
if (request.getEbookPaths() != null) entity.setEbookPaths(request.getEbookPaths()); if (request.getEbookPaths() != null) entity.setEbookPaths(emptyToNull(request.getEbookPaths()));
if (request.getAudioPaths() != null) entity.setAudioPaths(request.getAudioPaths()); if (request.getAudioPaths() != null) entity.setAudioPaths(emptyToNull(request.getAudioPaths()));
if (request.getVideoPaths() != null) entity.setVideoPaths(request.getVideoPaths()); if (request.getVideoPaths() != null) entity.setVideoPaths(emptyToNull(request.getVideoPaths()));
if (request.getOtherResources() != null) entity.setOtherResources(request.getOtherResources()); if (request.getOtherResources() != null) entity.setOtherResources(emptyToNull(request.getOtherResources()));
if (request.getPptPath() != null) entity.setPptPath(request.getPptPath()); if (request.getPptPath() != null) entity.setPptPath(request.getPptPath());
if (request.getPptName() != null) entity.setPptName(request.getPptName()); if (request.getPptName() != null) entity.setPptName(request.getPptName());
if (request.getPosterPaths() != null) entity.setPosterPaths(request.getPosterPaths()); if (request.getPosterPaths() != null) entity.setPosterPaths(request.getPosterPaths());
if (request.getTools() != null) entity.setTools(request.getTools()); if (request.getTools() != null) entity.setTools(request.getTools());
if (request.getStudentMaterials() != null) entity.setStudentMaterials(request.getStudentMaterials()); if (request.getStudentMaterials() != null) entity.setStudentMaterials(request.getStudentMaterials());
if (request.getLessonPlanData() != null) entity.setLessonPlanData(request.getLessonPlanData()); if (request.getLessonPlanData() != null) entity.setLessonPlanData(emptyToNull(request.getLessonPlanData()));
if (request.getActivitiesData() != null) entity.setActivitiesData(request.getActivitiesData()); if (request.getActivitiesData() != null) entity.setActivitiesData(emptyToNull(request.getActivitiesData()));
if (request.getAssessmentData() != null) entity.setAssessmentData(request.getAssessmentData()); if (request.getAssessmentData() != null) entity.setAssessmentData(emptyToNull(request.getAssessmentData()));
if (request.getGradeTags() != null) entity.setGradeTags(request.getGradeTags()); if (request.getGradeTags() != null) entity.setGradeTags(emptyToNull(request.getGradeTags()));
if (request.getDomainTags() != null) entity.setDomainTags(request.getDomainTags()); if (request.getDomainTags() != null) entity.setDomainTags(emptyToNull(request.getDomainTags()));
if (request.getHasCollectiveLesson() != null) entity.setHasCollectiveLesson(request.getHasCollectiveLesson() ? 1 : 0); if (request.getHasCollectiveLesson() != null) entity.setHasCollectiveLesson(request.getHasCollectiveLesson() ? 1 : 0);
} }
/**
* 将空字符串转为 null避免 MySQL JSON 列报错 "The document is empty"
*/
private static String emptyToNull(String value) {
return StringUtils.hasText(value) ? value : null;
}
private CourseLessonResponse toLessonResponse(CourseLesson lesson) { private CourseLessonResponse toLessonResponse(CourseLesson lesson) {
List<LessonStep> steps = courseLessonService.findSteps(lesson.getId()); List<LessonStep> steps = courseLessonService.findSteps(lesson.getId());
List<LessonStepResponse> stepResponses = steps.stream() List<LessonStepResponse> stepResponses = steps.stream()