fix: keep cp gift relation type

This commit is contained in:
zhx 2026-06-08 17:35:25 +08:00
parent 4520e16470
commit ae8ae7db55
5 changed files with 56 additions and 15 deletions

View File

@ -81,6 +81,7 @@ test("resource APIs use generated admin paths", async () => {
await createGift({ await createGift({
chargeAssetType: "COIN", chargeAssetType: "COIN",
coinPrice: 10, coinPrice: 10,
cpRelationType: "",
effectiveAtMs: 1777766400000, effectiveAtMs: 1777766400000,
effectiveFromMs: 0, effectiveFromMs: 0,
effectiveToMs: 0, effectiveToMs: 0,
@ -131,6 +132,7 @@ test("resource APIs use generated admin paths", async () => {
await updateGift("rose", { await updateGift("rose", {
chargeAssetType: "COIN", chargeAssetType: "COIN",
coinPrice: 10, coinPrice: 10,
cpRelationType: "",
effectiveAtMs: 1777766400000, effectiveAtMs: 1777766400000,
effectiveFromMs: 0, effectiveFromMs: 0,
effectiveToMs: 0, effectiveToMs: 0,

View File

@ -126,6 +126,7 @@ export interface GiftDto {
presentationJson?: string; presentationJson?: string;
priceVersion?: string; priceVersion?: string;
giftTypeCode?: string; giftTypeCode?: string;
cpRelationType?: string;
chargeAssetType?: string; chargeAssetType?: string;
coinPrice?: number; coinPrice?: number;
effectiveFromMs?: number; effectiveFromMs?: number;
@ -158,6 +159,7 @@ export interface GiftPayload {
presentationJson: string; presentationJson: string;
priceVersion: string; priceVersion: string;
giftTypeCode: string; giftTypeCode: string;
cpRelationType: string;
chargeAssetType: string; chargeAssetType: string;
coinPrice: number; coinPrice: number;
effectiveAtMs: number; effectiveAtMs: number;

View File

@ -122,14 +122,11 @@ const emptyGroupWalletAssetItem = (walletAssetType) => ({
const emptyGiftForm = (gift = {}) => { const emptyGiftForm = (gift = {}) => {
const giftTypeCode = gift.giftTypeCode || "normal"; const giftTypeCode = gift.giftTypeCode || "normal";
const presentationJson = gift.presentationJson || "{}"; const presentationJson = gift.presentationJson || "{}";
const cpRelationType = const cpRelationType = cpRelationTypeFromGift({ ...gift, giftTypeCode, presentationJson }) || defaultCPRelationType;
giftTypeCode === cpGiftTypeCode
? cpRelationTypeFromPresentationJson(presentationJson) || defaultCPRelationType
: "";
return { return {
chargeAssetType: gift.chargeAssetType || "COIN", chargeAssetType: gift.chargeAssetType || "COIN",
coinPrice: gift.coinPrice === 0 || gift.coinPrice ? String(gift.coinPrice) : "", coinPrice: gift.coinPrice === 0 || gift.coinPrice ? String(gift.coinPrice) : "",
cpRelationType, cpRelationType: giftTypeCode === cpGiftTypeCode ? cpRelationType : "",
effectTypes: gift.effectTypes || [], effectTypes: gift.effectTypes || [],
effectiveFrom: msToDatetimeLocal(gift.effectiveFromMs), effectiveFrom: msToDatetimeLocal(gift.effectiveFromMs),
effectiveTo: msToDatetimeLocal(gift.effectiveToMs), effectiveTo: msToDatetimeLocal(gift.effectiveToMs),
@ -174,6 +171,18 @@ export function cpRelationTypeFromPresentationJson(presentationJson) {
return relationType || ""; return relationType || "";
} }
export function cpRelationTypeFromGift(gift = {}) {
if (gift?.giftTypeCode !== cpGiftTypeCode) {
return "";
}
return validCPRelationType(gift.cpRelationType) || cpRelationTypeFromPresentationJson(gift.presentationJson);
}
export function cpRelationTypeShortLabel(value) {
const label = cpRelationTypeLabels[validCPRelationType(value)] || String(value || "").trim();
return label.endsWith("礼物") ? label.slice(0, -2) : label;
}
export function normalizeGiftPresentationJson(presentationJson, giftTypeCode, cpRelationType) { export function normalizeGiftPresentationJson(presentationJson, giftTypeCode, cpRelationType) {
const rawValue = String(presentationJson || "").trim(); const rawValue = String(presentationJson || "").trim();
const isCPGift = giftTypeCode === cpGiftTypeCode; const isCPGift = giftTypeCode === cpGiftTypeCode;
@ -239,8 +248,12 @@ function parseGiftPresentationObject(presentationJson) {
} }
function normalizedCPRelationType(value) { function normalizedCPRelationType(value) {
return validCPRelationType(value) || defaultCPRelationType;
}
function validCPRelationType(value) {
const relationType = String(value || "").trim().toLowerCase(); const relationType = String(value || "").trim().toLowerCase();
return cpRelationTypeValues.has(relationType) ? relationType : defaultCPRelationType; return cpRelationTypeValues.has(relationType) ? relationType : "";
} }
function cpPresentationJson(basePresentation, cpRelationType) { function cpPresentationJson(basePresentation, cpRelationType) {
@ -1629,9 +1642,11 @@ function buildResourceGroupPayload(form) {
} }
function buildGiftPayload(form) { function buildGiftPayload(form) {
const cpRelationType = form.giftTypeCode === cpGiftTypeCode ? normalizedCPRelationType(form.cpRelationType) : "";
return { return {
chargeAssetType: form.chargeAssetType, chargeAssetType: form.chargeAssetType,
coinPrice: Number(form.coinPrice), coinPrice: Number(form.coinPrice),
cpRelationType,
effectiveAtMs: Date.now(), effectiveAtMs: Date.now(),
effectiveFromMs: datetimeLocalToMs(form.effectiveFrom), effectiveFromMs: datetimeLocalToMs(form.effectiveFrom),
effectiveToMs: datetimeLocalToMs(form.effectiveTo), effectiveToMs: datetimeLocalToMs(form.effectiveTo),
@ -1649,9 +1664,11 @@ function buildGiftPayload(form) {
} }
function buildGiftInlineSortPayload(gift, sortOrder) { function buildGiftInlineSortPayload(gift, sortOrder) {
const cpRelationType = cpRelationTypeFromGift(gift);
return { return {
chargeAssetType: gift.chargeAssetType || "COIN", chargeAssetType: gift.chargeAssetType || "COIN",
coinPrice: Number(gift.coinPrice || 0), coinPrice: Number(gift.coinPrice || 0),
cpRelationType: gift.giftTypeCode === cpGiftTypeCode ? cpRelationType || defaultCPRelationType : "",
effectiveAtMs: Date.now(), effectiveAtMs: Date.now(),
effectiveFromMs: Number(gift.effectiveFromMs || 0), effectiveFromMs: Number(gift.effectiveFromMs || 0),
effectiveToMs: Number(gift.effectiveToMs || 0), effectiveToMs: Number(gift.effectiveToMs || 0),

View File

@ -2,7 +2,9 @@ import { expect, test, vi } from "vitest";
import { import {
applyGiftPriceDefaults, applyGiftPriceDefaults,
applyGiftResourceSelection, applyGiftResourceSelection,
cpRelationTypeFromGift,
cpRelationTypeFromPresentationJson, cpRelationTypeFromPresentationJson,
cpRelationTypeShortLabel,
fetchAllOptionPages, fetchAllOptionPages,
normalizeGiftPresentationJson, normalizeGiftPresentationJson,
} from "./useResourcePages.js"; } from "./useResourcePages.js";
@ -67,6 +69,24 @@ test("reads CP relation type from gift presentation json", () => {
expect(cpRelationTypeFromPresentationJson('{"cp_relation_type":"unknown"}')).toBe(""); expect(cpRelationTypeFromPresentationJson('{"cp_relation_type":"unknown"}')).toBe("");
}); });
test("prefers explicit CP relation type and formats compact list label", () => {
expect(
cpRelationTypeFromGift({
cpRelationType: "brother",
giftTypeCode: "cp",
presentationJson: '{"cp_relation_type":"sister"}',
}),
).toBe("brother");
expect(
cpRelationTypeFromGift({
giftTypeCode: "cp",
presentationJson: '{"cp_relation_type":"sister"}',
}),
).toBe("sister");
expect(cpRelationTypeShortLabel("brother")).toBe("兄弟");
expect(cpRelationTypeShortLabel("cp")).toBe("CP");
});
test("normalizes CP gift presentation json for submit", () => { test("normalizes CP gift presentation json for submit", () => {
const nextJson = normalizeGiftPresentationJson( const nextJson = normalizeGiftPresentationJson(
'{"other":1,"cp_relation_type":"brother","cpRelationLabel":"old"}', '{"other":1,"cp_relation_type":"brother","cpRelationLabel":"old"}',

View File

@ -34,14 +34,17 @@ import { createRegionColumnFilter } from "@/shared/ui/RegionFilterControl.jsx";
import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js"; import { createOptionsColumnFilter, createTextColumnFilter } from "@/shared/ui/tableFilters.js";
import { formatMillis } from "@/shared/utils/time.js"; import { formatMillis } from "@/shared/utils/time.js";
import { import {
cpRelationTypeLabels,
cpRelationTypeOptions, cpRelationTypeOptions,
defaultGiftTypeOptions, defaultGiftTypeOptions,
resourceStatusFilters, resourceStatusFilters,
} from "@/features/resources/constants.js"; } from "@/features/resources/constants.js";
import { GiftTypeConfigDialog } from "@/features/resources/components/GiftTypeConfigDialog.jsx"; import { GiftTypeConfigDialog } from "@/features/resources/components/GiftTypeConfigDialog.jsx";
import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx"; import { ResourceSelectField } from "@/features/resources/components/ResourceSelectDrawer.jsx";
import { cpRelationTypeFromPresentationJson, useGiftListPage } from "@/features/resources/hooks/useResourcePages.js"; import {
cpRelationTypeFromGift,
cpRelationTypeShortLabel,
useGiftListPage,
} from "@/features/resources/hooks/useResourcePages.js";
import styles from "@/features/resources/resources.module.css"; import styles from "@/features/resources/resources.module.css";
const effectOptions = [ const effectOptions = [
@ -667,13 +670,10 @@ function giftTypeLabel(value, options = defaultGiftTypeOptions) {
} }
function GiftTypeIdentity({ gift, giftTypeOptions }) { function GiftTypeIdentity({ gift, giftTypeOptions }) {
const relationType = gift.giftTypeCode === "cp" ? cpRelationTypeFromPresentationJson(gift.presentationJson) : ""; const typeLabel = giftTypeLabel(gift.giftTypeCode, giftTypeOptions);
return ( const relationType = gift.giftTypeCode === "cp" ? cpRelationTypeFromGift(gift) || "cp" : "";
<div className={styles.stack}> const relationLabel = relationType ? cpRelationTypeShortLabel(relationType) : "";
<span>{giftTypeLabel(gift.giftTypeCode, giftTypeOptions)}</span> return <span>{relationLabel ? `${typeLabel}${relationLabel}` : typeLabel}</span>;
{relationType ? <span className="admin-tag">{cpRelationTypeLabels[relationType] || relationType}</span> : null}
</div>
);
} }
function giftPriceLabel(gift) { function giftPriceLabel(gift) {