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 { export interface CoursePackage {
id: number; id: number | string; // 后端 Long 序列化为 string避免 JS 精度丢失
name: string; name: string;
description?: string; description?: string;
price: number; price: number;
@ -22,7 +22,7 @@ export interface CoursePackage {
} }
export interface PackageCourse { export interface PackageCourse {
id: number; // 课程 ID id: number | string; // 课程 ID,后端 Long 序列化为 string
name: string; // 课程名称 name: string; // 课程名称
gradeLevel: string; // 适用年级 gradeLevel: string; // 适用年级
sortOrder: number; // 排序号 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 }); return http.get<{ list: CoursePackage[]; total: number; pageNum: number; pageSize: number; pages: number }>('/v1/admin/packages', { params });
} }
// 获取套餐详情 // 获取套餐详情id 支持 number | string避免大整数精度丢失
export function getPackageDetail(id: number) { export function getPackageDetail(id: number | string) {
return http.get(`/v1/admin/packages/${id}`); 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); 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}`); return http.delete(`/v1/admin/packages/${id}`);
} }
// 设置套餐课程 // 设置套餐课程(后端期望 JSON 数组 [courseId1, courseId2, ...]
export function setPackageCourses( export function setPackageCourses(
packageId: number, packageId: number | string,
courses: { courseId: number; gradeLevel: string; sortOrder?: number }[], 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( export function addCourseToPackage(
packageId: number, packageId: number | string,
data: { courseId: number; gradeLevel: string; sortOrder?: number }, data: { courseId: number; gradeLevel: string; sortOrder?: number },
) { ) {
return http.post(`/v1/admin/packages/${packageId}/courses`, data); 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}`); 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`); 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); 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`); 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`); return http.post(`/v1/admin/packages/${id}/offline`);
} }

View File

@ -106,7 +106,7 @@ const formatDate = (date?: string) => {
const fetchData = async () => { const fetchData = async () => {
loading.value = true; loading.value = true;
try { try {
const id = Number(route.params.id); const id = route.params.id as string;
const res = await getPackageDetail(id); const res = await getPackageDetail(id);
pkg.value = res; pkg.value = res;
} catch (error) { } catch (error) {
@ -122,7 +122,7 @@ const handleEdit = () => {
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await submitPackage(Number(route.params.id)); await submitPackage(route.params.id as string);
message.success('提交成功'); message.success('提交成功');
fetchData(); fetchData();
} catch (error) { } catch (error) {
@ -132,7 +132,7 @@ const handleSubmit = async () => {
const handlePublish = async () => { const handlePublish = async () => {
try { try {
await publishPackage(Number(route.params.id)); await publishPackage(route.params.id as string);
message.success('发布成功'); message.success('发布成功');
fetchData(); fetchData();
} catch (error) { } catch (error) {

View File

@ -125,13 +125,13 @@ const router = useRouter();
const route = useRoute(); const route = useRoute();
const isEdit = computed(() => !!route.params.id); 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 saving = ref(false);
const loadingCourses = ref(false); const loadingCourses = ref(false);
const showCourseSelector = ref(false); const showCourseSelector = ref(false);
const availableCourses = ref<any[]>([]); const availableCourses = ref<any[]>([]);
const selectedRowKeys = ref<number[]>([]); const selectedRowKeys = ref<(number | string)[]>([]);
const form = reactive({ const form = reactive({
name: '', name: '',
@ -142,7 +142,7 @@ const form = reactive({
gradeLevels: [] as string[], 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 = [ const courseColumns = [
{ title: '课程包', dataIndex: 'courseName', key: 'courseName' }, { title: '课程包', dataIndex: 'courseName', key: 'courseName' },
@ -239,12 +239,13 @@ const handleSave = async () => {
gradeLevels: form.gradeLevels, gradeLevels: form.gradeLevels,
}; };
let id = packageId.value; let id: number | string;
if (isEdit.value) { if (isEdit.value) {
id = packageId.value!;
await updatePackage(id, data); await updatePackage(id, data);
} else { } else {
const res = await createPackage(data) as any; 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);
};
}
}