diff --git a/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx b/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx index 0dc67a2bd..07c4ecea7 100644 --- a/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx +++ b/apps/admin-web/src/pages/AdminMembershipPlanPage.tsx @@ -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; -} diff --git a/apps/admin-web/src/pages/AdminProfileWalletConfigPage.tsx b/apps/admin-web/src/pages/AdminProfileWalletConfigPage.tsx index 434451913..3d157fcdc 100644 --- a/apps/admin-web/src/pages/AdminProfileWalletConfigPage.tsx +++ b/apps/admin-web/src/pages/AdminProfileWalletConfigPage.tsx @@ -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({ ); } - -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; -} diff --git a/apps/admin-web/src/pages/AdminRechargeProductPage.tsx b/apps/admin-web/src/pages/AdminRechargeProductPage.tsx index 488284c4b..a0bba36bb 100644 --- a/apps/admin-web/src/pages/AdminRechargeProductPage.tsx +++ b/apps/admin-web/src/pages/AdminRechargeProductPage.tsx @@ -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)) { diff --git a/apps/admin-web/src/pages/pageUtils.ts b/apps/admin-web/src/pages/pageUtils.ts index bea44fd7e..ed90abdd9 100644 --- a/apps/admin-web/src/pages/pageUtils.ts +++ b/apps/admin-web/src/pages/pageUtils.ts @@ -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; +}