diff --git a/reading-platform-frontend/src/api/package.ts b/reading-platform-frontend/src/api/package.ts index 30ae1c6..48ec87a 100644 --- a/reading-platform-frontend/src/api/package.ts +++ b/reading-platform-frontend/src/api/package.ts @@ -3,7 +3,7 @@ import { http } from './index'; // ==================== 套餐管理 ==================== export interface CoursePackage { - id: number; + id: number | string; // 后端 Long 序列化为 string,避免 JS 精度丢失 name: string; description?: string; price: number; @@ -22,7 +22,7 @@ export interface CoursePackage { } export interface PackageCourse { - id: number; // 课程 ID + id: number | string; // 课程 ID,后端 Long 序列化为 string name: string; // 课程名称 gradeLevel: string; // 适用年级 sortOrder: number; // 排序号 @@ -48,8 +48,8 @@ export function getPackageList(params?: PackageListParams) { return http.get<{ list: CoursePackage[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/admin/packages', { params }); } -// 获取套餐详情 -export function getPackageDetail(id: number) { +// 获取套餐详情(id 支持 number | string,避免大整数精度丢失) +export function getPackageDetail(id: number | string) { return http.get(`/v1/admin/packages/${id}`); } @@ -59,53 +59,54 @@ export function createPackage(data: CreatePackageData) { } // 更新套餐 -export function updatePackage(id: number, data: Partial) { +export function updatePackage(id: number | string, data: Partial) { return http.put(`/v1/admin/packages/${id}`, data); } // 删除套餐 -export function deletePackage(id: number) { +export function deletePackage(id: number | string) { return http.delete(`/v1/admin/packages/${id}`); } -// 设置套餐课程 +// 设置套餐课程(后端期望 JSON 数组 [courseId1, courseId2, ...]) export function setPackageCourses( - packageId: number, - courses: { courseId: number; gradeLevel: string; sortOrder?: number }[], + packageId: number | string, + courses: { courseId: number; gradeLevel?: string; sortOrder?: number }[], ) { - return http.put(`/v1/admin/packages/${packageId}/courses`, { courses }); + const courseIds = courses.map((c) => c.courseId); + return http.put(`/v1/admin/packages/${packageId}/courses`, courseIds); } // 添加课程到套餐 export function addCourseToPackage( - packageId: number, + packageId: number | string, data: { courseId: number; gradeLevel: string; sortOrder?: number }, ) { return http.post(`/v1/admin/packages/${packageId}/courses`, data); } // 从套餐移除课程 -export function removeCourseFromPackage(packageId: number, courseId: number) { +export function removeCourseFromPackage(packageId: number | string, courseId: number | string) { return http.delete(`/v1/admin/packages/${packageId}/courses/${courseId}`); } // 提交审核 -export function submitPackage(id: number) { +export function submitPackage(id: number | string) { return http.post(`/v1/admin/packages/${id}/submit`); } // 审核套餐 -export function reviewPackage(id: number, data: { approved: boolean; comment?: string }) { +export function reviewPackage(id: number | string, data: { approved: boolean; comment?: string }) { return http.post(`/v1/admin/packages/${id}/review`, data); } // 发布套餐 -export function publishPackage(id: number) { +export function publishPackage(id: number | string) { return http.post(`/v1/admin/packages/${id}/publish`); } // 下架套餐 -export function offlinePackage(id: number) { +export function offlinePackage(id: number | string) { return http.post(`/v1/admin/packages/${id}/offline`); } diff --git a/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue b/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue index b2b0e2d..f689c60 100644 --- a/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue +++ b/reading-platform-frontend/src/views/admin/packages/PackageDetailView.vue @@ -106,7 +106,7 @@ const formatDate = (date?: string) => { const fetchData = async () => { loading.value = true; try { - const id = Number(route.params.id); + const id = route.params.id as string; const res = await getPackageDetail(id); pkg.value = res; } catch (error) { @@ -122,7 +122,7 @@ const handleEdit = () => { const handleSubmit = async () => { try { - await submitPackage(Number(route.params.id)); + await submitPackage(route.params.id as string); message.success('提交成功'); fetchData(); } catch (error) { @@ -132,7 +132,7 @@ const handleSubmit = async () => { const handlePublish = async () => { try { - await publishPackage(Number(route.params.id)); + await publishPackage(route.params.id as string); message.success('发布成功'); fetchData(); } catch (error) { diff --git a/reading-platform-frontend/src/views/admin/packages/PackageEditView.vue b/reading-platform-frontend/src/views/admin/packages/PackageEditView.vue index ae73dcc..adf2040 100644 --- a/reading-platform-frontend/src/views/admin/packages/PackageEditView.vue +++ b/reading-platform-frontend/src/views/admin/packages/PackageEditView.vue @@ -125,13 +125,13 @@ const router = useRouter(); const route = useRoute(); const isEdit = computed(() => !!route.params.id); -const packageId = computed(() => Number(route.params.id)); +const packageId = computed(() => route.params.id as string | undefined); const saving = ref(false); const loadingCourses = ref(false); const showCourseSelector = ref(false); const availableCourses = ref([]); -const selectedRowKeys = ref([]); +const selectedRowKeys = ref<(number | string)[]>([]); const form = reactive({ name: '', @@ -142,7 +142,7 @@ const form = reactive({ gradeLevels: [] as string[], }); -const selectedCourses = ref<{ courseId: number; gradeLevel: string; sortOrder: number; courseName: string }[]>([]); +const selectedCourses = ref<{ courseId: number | string; gradeLevel: string; sortOrder: number; courseName: string }[]>([]); const courseColumns = [ { title: '课程包', dataIndex: 'courseName', key: 'courseName' }, @@ -239,12 +239,13 @@ const handleSave = async () => { gradeLevels: form.gradeLevels, }; - let id = packageId.value; + let id: number | string; if (isEdit.value) { + id = packageId.value!; await updatePackage(id, data); } else { const res = await createPackage(data) as any; - id = res.id; + id = res.id; // 后端 Long 序列化为 string } // 保存课程关联 diff --git a/reading-platform-java/src/main/java/com/reading/platform/common/config/JacksonConfig.java b/reading-platform-java/src/main/java/com/reading/platform/common/config/JacksonConfig.java new file mode 100644 index 0000000..3fff912 --- /dev/null +++ b/reading-platform-java/src/main/java/com/reading/platform/common/config/JacksonConfig.java @@ -0,0 +1,22 @@ +package com.reading.platform.common.config; + +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Jackson 配置:将 Long 序列化为 String,避免前端 JavaScript 精度丢失。 + * JavaScript Number 只能安全表示 2^53 以内的整数,雪花 ID 等大整数会丢失精度。 + */ +@Configuration +public class JacksonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jackson2ObjectMapperBuilderCustomizer() { + return builder -> { + builder.serializerByType(Long.class, ToStringSerializer.instance); + builder.serializerByType(Long.TYPE, ToStringSerializer.instance); + }; + } +}