fix: 课程套餐管理详情修复 - Long 序列化为 String 避免 JS 精度丢失,修复 PUT 课程列表 JSON 格式

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-16 11:53:27 +08:00
parent 691e0248a2
commit f5de4e613d
4 changed files with 48 additions and 24 deletions

View File

@ -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<CreatePackageData>) {
export function updatePackage(id: number | string, data: Partial<CreatePackageData>) {
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`);
}

View File

@ -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) {

View File

@ -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<any[]>([]);
const selectedRowKeys = ref<number[]>([]);
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
}
//

View File

@ -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);
};
}
}