修复提交

This commit is contained in:
tianmo committed 2026-09-30 17:24:50 +08:00
1 parent d8786eb28a
commit 5fe7f5d476
4 files changed
+68 -13

No files matched your search

@@ -0,0 +1,41 @@
import { describe, expect, it } from 'vitest';
import { RESOURCES } from './resource-registry';
/**
* 日期字段的转换护栏。
*
* 起因:后台加「首页新课」填发布日期,报 PrismaClientValidationError——
* 表单传的是 'YYYY-MM-DD',而 Prisma 的 DateTime 要完整时间,原样透传就被拒。
*/
describe('资源注册表的日期字段', () => {
const schema = RESOURCES['home-courses']!.create;
it('纯日期串会转成 Date(UTC 零点),不再原样透传给 Prisma', () => {
const parsed = schema.parse({ course_name: 'x', published_at: '2026-09-30' });
expect(parsed.published_at).toBeInstanceOf(Date);
expect((parsed.published_at as Date).toISOString()).toBe('2026-09-30T00:00:00.000Z');
});
it('转出来的日期能原样回填,不会串一天', () => {
const parsed = schema.parse({ course_name: 'x', published_at: '2026-09-30' });
// 后台显示/回填用的是 ISO 串前 10 位
expect((parsed.published_at as Date).toISOString().slice(0, 10)).toBe('2026-09-30');
});
it('完整时间串照样接受', () => {
const parsed = schema.parse({ course_name: 'x', published_at: '2026-09-30T12:34:56.000Z' });
expect((parsed.published_at as Date).toISOString()).toBe('2026-09-30T12:34:56.000Z');
});
it('空值按「不填」处理,不报错', () => {
expect(schema.parse({ course_name: 'x', published_at: '' }).published_at).toBeNull();
expect(schema.parse({ course_name: 'x', published_at: null }).published_at).toBeNull();
expect(schema.parse({ course_name: 'x' }).published_at).toBeUndefined();
});
it('乱填会被挡下,并给出可读的原因', () => {
const result = schema.safeParse({ course_name: 'x', published_at: '九月三十号' });
expect(result.success).toBe(false);
expect(JSON.stringify(result.error?.issues)).toContain('日期格式不正确');
});
});
@@ -9,7 +9,29 @@ import { z } from 'zod';
* 一个通用控制器 + 这张表覆盖了管理后台的绝大部分内容管理功能。
*/
const dateish = z.union([z.string(), z.date(), z.null()]);
/**
* 日期字段。后台表单传的是 'YYYY-MM-DD' 纯日期串,而 Prisma 的 DateTime 需要完整时间,
* 原样透传会抛 PrismaClientValidationError(首页新课加日期就报过这个)。
*
* 补成 **UTC 零点**,是为了跟后台的显示/回填对齐:那边是拿 ISO 串切前 10 位,
* 若按北京时间补,就会出现「显示 09-30 → 保存 → 回填成 09-29」串一天。
*/
const dateish = z
.union([z.string(), z.date(), z.null()])
.transform((value, ctx) => {
if (value === null) return null;
if (value instanceof Date) return value;
const text = value.trim();
if (!text) return null;
const parsed = new Date(/^\d{4}-\d{2}-\d{2}$/.test(text) ? `${text}T00:00:00.000Z` : text);
if (Number.isNaN(parsed.getTime())) {
ctx.addIssue({ code: z.ZodIssueCode.custom, message: `日期格式不正确:${value}` });
return z.NEVER;
}
return parsed;
});
const optionalDate = dateish.optional();
export interface ResourceDef {
@@ -136,4 +158,3 @@ export const RESOURCES: Record<string, ResourceDef> = {
export const READONLY_RESOURCES: Record<string, { delegate: string; orderBy: Record<string, 'asc' | 'desc'>[] }> = {
'free-lessons': { delegate: 'freeLesson', orderBy: [{ series_no: 'asc' }, { sort_order: 'asc' }] },
};