refactor(后台表单): 抽取共享整数解析器

- pageUtils 新增严格的 parsePositiveInteger / parseNonNegativeInteger 与落库用 parsePositiveIntegerOrZero / parseNonNegativeIntegerOrZero。
- 账号配置、会员档位、充值商品三页删除各自的同名本地解析器,统一走共享实现,消除同名不同语义的校验漂移。
- 解析从 parseInt 改为 Number + isSafeInteger,'12abc' 一类脏输入不再被截断成 12。
This commit is contained in:
2026-10-03 17:58:39 +08:00
parent 066d14636a
commit 9f10423ff9
4 changed files with 47 additions and 43 deletions
@@ -23,7 +23,7 @@ import type {
} from '../api/adminApiTypes';
import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm';
import { membershipPlanLabels } from '../config/membershipDisplay';
import { handlePageError } from './pageUtils';
import { handlePageError, parsePositiveIntegerOrZero } from './pageUtils';
interface AdminMembershipPlanPageProps {
token: string;
@@ -106,11 +106,12 @@ export function AdminMembershipPlanPage({
}
setErrorMessage('');
const normalizedMonthPriceCents = parsePositiveInteger(monthPriceCents);
const normalizedYearPriceCents = parsePositiveInteger(yearPriceCents);
const normalizedMonthPriceCents =
parsePositiveIntegerOrZero(monthPriceCents);
const normalizedYearPriceCents = parsePositiveIntegerOrZero(yearPriceCents);
const normalizedConcurrentJobLimit =
parsePositiveInteger(concurrentJobLimit);
const normalizedPeriodPoints = parsePositiveInteger(periodPoints);
parsePositiveIntegerOrZero(concurrentJobLimit);
const normalizedPeriodPoints = parsePositiveIntegerOrZero(periodPoints);
if (plan !== 'normal') {
if (!normalizedMonthPriceCents || !normalizedYearPriceCents) {
setErrorMessage('月价与年价必须是大于 0 的整数分');
@@ -454,13 +455,3 @@ function formatPlanLimits(entry: ProfileMembershipPlanAdminResponse) {
function formatPrice(priceCents: number) {
return `¥${(priceCents / 100).toFixed(2)}`;
}
function parsePositiveInteger(value: string) {
const parsed = parseInteger(value);
return parsed > 0 ? parsed : 0;
}
function parseInteger(value: string) {
const parsed = Number(value);
return Number.isSafeInteger(parsed) ? parsed : 0;
}
@@ -18,7 +18,11 @@ import {
} from '../api/adminApiClient';
import type { ProfileWalletConfigAdminResponse } from '../api/adminApiTypes';
import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm';
import { handlePageError } from './pageUtils';
import {
handlePageError,
parseNonNegativeInteger,
parsePositiveInteger,
} from './pageUtils';
interface AdminProfileWalletConfigPageProps {
token: string;
@@ -200,16 +204,3 @@ export function AdminProfileWalletConfigPage({
</AdminPage>
);
}
function parsePositiveInteger(value: string) {
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : 0;
}
function parseNonNegativeInteger(value: string): number | null {
if (value.trim() === '') {
return null;
}
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
}
@@ -19,7 +19,11 @@ import {
} from '../api/adminApiClient';
import type { ProfileRechargeProductConfigAdminResponse } from '../api/adminApiTypes';
import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm';
import { handlePageError } from './pageUtils';
import {
handlePageError,
parseNonNegativeIntegerOrZero,
parsePositiveIntegerOrZero,
} from './pageUtils';
interface AdminRechargeProductPageProps {
token: string;
@@ -104,10 +108,10 @@ export function AdminRechargeProductPage({
const response = await upsertProfileRechargeProduct(token, {
productId: productId.trim(),
title: title.trim(),
priceCents: parsePositiveInteger(priceCents),
priceCents: parsePositiveIntegerOrZero(priceCents),
kind: 'points',
pointsAmount: parsePositiveInteger(pointsAmount),
bonusPoints: parseNonNegativeInteger(bonusPoints),
pointsAmount: parsePositiveIntegerOrZero(pointsAmount),
bonusPoints: parseNonNegativeIntegerOrZero(bonusPoints),
durationDays: 0,
badgeLabel: badgeLabel.trim(),
description: description.trim(),
@@ -358,16 +362,6 @@ function formatPrice(priceCents: number) {
return `¥${(priceCents / 100).toFixed(2)}`;
}
function parsePositiveInteger(value: string) {
const parsed = parseInteger(value);
return parsed > 0 ? parsed : 0;
}
function parseNonNegativeInteger(value: string) {
const parsed = parseInteger(value);
return parsed > 0 ? parsed : 0;
}
function parseInteger(value: string) {
const parsed = Number.parseInt(value, 10);
if (!Number.isFinite(parsed)) {
+28
View File
@@ -31,3 +31,31 @@ export function formatUnknownJson(value: unknown) {
return String(value);
}
}
/** 严格解析正安全整数:空串、小数、非正数与非数字都返回 `null`,用于表单校验。 */
export function parsePositiveInteger(value: string): number | null {
if (value.trim() === '') {
return null;
}
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null;
}
/** 严格解析非负安全整数:空串、小数、负数与非数字都返回 `null`,用于表单校验。 */
export function parseNonNegativeInteger(value: string): number | null {
if (value.trim() === '') {
return null;
}
const parsed = Number(value);
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
}
/** 落库用宽松解析:非法输入按 `0` 处理,避免后台保存被输入抖动打断。 */
export function parsePositiveIntegerOrZero(value: string): number {
return parsePositiveInteger(value) ?? 0;
}
/** 落库用宽松解析:非法输入按 `0` 处理,避免后台保存被输入抖动打断。 */
export function parseNonNegativeIntegerOrZero(value: string): number {
return parseNonNegativeInteger(value) ?? 0;
}