feat: extend VIP resource config
This commit is contained in:
parent
17e81a9759
commit
b8bbaec2ac
@ -102,7 +102,7 @@ const nobleVipNameOptions = NOBLE_VIP_OPTIONS.map((item) => ({
|
|||||||
value: item.value as any,
|
value: item.value as any,
|
||||||
}));
|
}));
|
||||||
const badgeTypeOptions = BADGE_TYPE_OPTIONS.filter((item) =>
|
const badgeTypeOptions = BADGE_TYPE_OPTIONS.filter((item) =>
|
||||||
['ACHIEVEMENT', 'ACTIVITY'].includes(String(item.value)),
|
['VIP', 'ACHIEVEMENT', 'ACTIVITY'].includes(String(item.value)),
|
||||||
).map((item) => ({
|
).map((item) => ({
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.value as any,
|
value: item.value as any,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export const PROPS_TYPES: OptionItem[] = [
|
|||||||
|
|
||||||
export const PROPS_RESOURCE_CONFIG_TYPES: OptionItem[] = [
|
export const PROPS_RESOURCE_CONFIG_TYPES: OptionItem[] = [
|
||||||
...PROPS_TYPES,
|
...PROPS_TYPES,
|
||||||
|
{ value: 'VIP_EFFECT_IMAGE', name: 'VIP动效图' },
|
||||||
{ value: 'BADGE', name: '徽章' },
|
{ value: 'BADGE', name: '徽章' },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -64,6 +65,7 @@ export const NOBLE_VIP_OPTIONS: OptionItem[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const BADGE_TYPE_OPTIONS: OptionItem[] = [
|
export const BADGE_TYPE_OPTIONS: OptionItem[] = [
|
||||||
|
{ value: 'VIP', name: '用户-VIP徽章' },
|
||||||
{ value: 'ACHIEVEMENT', name: '用户-成就徽章' },
|
{ value: 'ACHIEVEMENT', name: '用户-成就徽章' },
|
||||||
{ value: 'ADMINISTRATOR', name: '用户-管理员' },
|
{ value: 'ADMINISTRATOR', name: '用户-管理员' },
|
||||||
{ value: 'ACTIVITY', name: '用户-活动徽章' },
|
{ value: 'ACTIVITY', name: '用户-活动徽章' },
|
||||||
@ -128,6 +130,7 @@ export const PROPS_SOURCE_GROUP_TYPE_MAP: Record<
|
|||||||
CUSTOMIZE: { value: 'CUSTOMIZE', name: '自定义(只读)' },
|
CUSTOMIZE: { value: 'CUSTOMIZE', name: '自定义(只读)' },
|
||||||
FRAGMENTS: { value: 'FRAGMENTS', name: '碎片' },
|
FRAGMENTS: { value: 'FRAGMENTS', name: '碎片' },
|
||||||
DATA_CARD: { value: 'PROPS', name: '资料卡' },
|
DATA_CARD: { value: 'PROPS', name: '资料卡' },
|
||||||
|
VIP_EFFECT_IMAGE: { value: 'PROPS', name: 'VIP动效图' },
|
||||||
PROP_COUPON: { value: 'PROP_COUPON', name: '道具券' },
|
PROP_COUPON: { value: 'PROP_COUPON', name: '道具券' },
|
||||||
HONOR_ACTIVITY: { value: 'BADGE', name: '荣誉-活动' },
|
HONOR_ACTIVITY: { value: 'BADGE', name: '荣誉-活动' },
|
||||||
};
|
};
|
||||||
@ -290,6 +293,7 @@ export function isPropsSourceType(type?: null | string) {
|
|||||||
'NOBLE_VIP',
|
'NOBLE_VIP',
|
||||||
'RIDE',
|
'RIDE',
|
||||||
'THEME',
|
'THEME',
|
||||||
|
'VIP_EFFECT_IMAGE',
|
||||||
].includes(String(type || ''));
|
].includes(String(type || ''));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -32,11 +32,14 @@ import {
|
|||||||
defineOptions({ name: 'ResidentVipConfigPage' });
|
defineOptions({ name: 'ResidentVipConfigPage' });
|
||||||
|
|
||||||
const resourceFields = [
|
const resourceFields = [
|
||||||
{ key: 'badge', title: '徽章', type: 'BADGE' },
|
{ key: 'longBadge', title: '长徽章素材', type: 'BADGE', badgeTypes: ['VIP'] },
|
||||||
|
{ key: 'shortBadge', title: '短徽章素材', type: 'BADGE', badgeTypes: ['ACTIVITY', 'ACHIEVEMENT'] },
|
||||||
{ key: 'avatarFrame', title: '头像框', type: 'AVATAR_FRAME' },
|
{ key: 'avatarFrame', title: '头像框', type: 'AVATAR_FRAME' },
|
||||||
{ key: 'entryEffect', title: '进场特效', type: 'RIDE' },
|
{ key: 'entryEffect', title: '进场特效', type: 'RIDE' },
|
||||||
{ key: 'chatBubble', title: '聊天气泡', type: 'CHAT_BUBBLE' },
|
{ key: 'chatBubble', title: '聊天气泡', type: 'CHAT_BUBBLE' },
|
||||||
{ key: 'floatPicture', title: '飘窗', type: 'FLOAT_PICTURE' },
|
{ key: 'floatPicture', title: '飘窗', type: 'FLOAT_PICTURE' },
|
||||||
|
{ key: 'backgroundCard', title: '背景卡素材', type: 'DATA_CARD' },
|
||||||
|
{ key: 'effectImage', title: '动效图', type: 'VIP_EFFECT_IMAGE' },
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
type ResourceField = (typeof resourceFields)[number];
|
type ResourceField = (typeof resourceFields)[number];
|
||||||
@ -163,16 +166,19 @@ function createResource() {
|
|||||||
function createLevel(level: number) {
|
function createLevel(level: number) {
|
||||||
return {
|
return {
|
||||||
avatarFrame: createResource(),
|
avatarFrame: createResource(),
|
||||||
badge: createResource(),
|
backgroundCard: createResource(),
|
||||||
chatBubble: createResource(),
|
chatBubble: createResource(),
|
||||||
displayName: `VIP ${level}`,
|
displayName: `VIP ${level}`,
|
||||||
durationDays: 30,
|
durationDays: 30,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
entryEffect: createResource(),
|
entryEffect: createResource(),
|
||||||
|
effectImage: createResource(),
|
||||||
floatPicture: createResource(),
|
floatPicture: createResource(),
|
||||||
level,
|
level,
|
||||||
levelCode: `VIP${level}`,
|
levelCode: `VIP${level}`,
|
||||||
|
longBadge: createResource(),
|
||||||
priceGold: 0,
|
priceGold: 0,
|
||||||
|
shortBadge: createResource(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,12 +288,42 @@ async function loadResourceOptions(field: ResourceField) {
|
|||||||
}
|
}
|
||||||
resourceLoading[field.key] = true;
|
resourceLoading[field.key] = true;
|
||||||
try {
|
try {
|
||||||
resourceRows[field.key] = await listSysOriginTypeList(form.sysOrigin, field.type);
|
resourceRows[field.key] = filterResourceRowsByField(
|
||||||
|
await listSysOriginTypeList(form.sysOrigin, field.type),
|
||||||
|
field,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
resourceLoading[field.key] = false;
|
resourceLoading[field.key] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function filterResourceRowsByField(rows: Array<Record<string, any>>, field: ResourceField) {
|
||||||
|
if (!('badgeTypes' in field) || !field.badgeTypes?.length) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
const allowed = new Set(field.badgeTypes.map((item) => String(item).toUpperCase()));
|
||||||
|
return rows.filter((item) => allowed.has(resourceBadgeType(item)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function resourceBadgeType(value: Record<string, any>) {
|
||||||
|
const expand = parseJsonObject(value.expand);
|
||||||
|
return String(expand.badgeType || 'ACTIVITY').toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseJsonObject(value: unknown) {
|
||||||
|
if (typeof value !== 'string' || !value.trim()) {
|
||||||
|
return {} as Record<string, any>;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(value);
|
||||||
|
return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
|
||||||
|
? (parsed as Record<string, any>)
|
||||||
|
: ({} as Record<string, any>);
|
||||||
|
} catch {
|
||||||
|
return {} as Record<string, any>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadResourceOptionsByKey(key: string) {
|
async function loadResourceOptionsByKey(key: string) {
|
||||||
const field = findResourceField(key);
|
const field = findResourceField(key);
|
||||||
if (field) {
|
if (field) {
|
||||||
@ -329,17 +365,20 @@ function normalizeLevels(levels: Array<Record<string, any>> | undefined) {
|
|||||||
}
|
}
|
||||||
return Array.from({ length: 5 }, (_, index) => {
|
return Array.from({ length: 5 }, (_, index) => {
|
||||||
const level = index + 1;
|
const level = index + 1;
|
||||||
const item = levelMap.get(level) || createLevel(level);
|
const item = (levelMap.get(level) || createLevel(level)) as Record<string, any>;
|
||||||
return {
|
return {
|
||||||
...createLevel(level),
|
...createLevel(level),
|
||||||
displayName: String(item.displayName || `VIP ${level}`),
|
displayName: String(item.displayName || `VIP ${level}`),
|
||||||
enabled: Boolean(item.enabled),
|
enabled: Boolean(item.enabled),
|
||||||
priceGold: Number(item.priceGold || 0),
|
priceGold: Number(item.priceGold || 0),
|
||||||
badge: normalizeResource(item.badge),
|
longBadge: normalizeResource(item.longBadge || item['badge']),
|
||||||
|
shortBadge: normalizeResource(item.shortBadge),
|
||||||
avatarFrame: normalizeResource(item.avatarFrame),
|
avatarFrame: normalizeResource(item.avatarFrame),
|
||||||
entryEffect: normalizeResource(item.entryEffect),
|
entryEffect: normalizeResource(item.entryEffect),
|
||||||
chatBubble: normalizeResource(item.chatBubble),
|
chatBubble: normalizeResource(item.chatBubble),
|
||||||
floatPicture: normalizeResource(item.floatPicture),
|
floatPicture: normalizeResource(item.floatPicture),
|
||||||
|
backgroundCard: normalizeResource(item.backgroundCard),
|
||||||
|
effectImage: normalizeResource(item.effectImage),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -357,16 +396,20 @@ function buildSavePayload() {
|
|||||||
sysOrigin: form.sysOrigin,
|
sysOrigin: form.sysOrigin,
|
||||||
levels: (form.levels || []).map((item: Record<string, any>) => ({
|
levels: (form.levels || []).map((item: Record<string, any>) => ({
|
||||||
avatarFrame: buildResourceSavePayload(item.avatarFrame),
|
avatarFrame: buildResourceSavePayload(item.avatarFrame),
|
||||||
badge: buildResourceSavePayload(item.badge),
|
badge: buildResourceSavePayload(item.longBadge),
|
||||||
|
backgroundCard: buildResourceSavePayload(item.backgroundCard),
|
||||||
chatBubble: buildResourceSavePayload(item.chatBubble),
|
chatBubble: buildResourceSavePayload(item.chatBubble),
|
||||||
displayName: String(item.displayName || '').trim(),
|
displayName: String(item.displayName || '').trim(),
|
||||||
durationDays: 30,
|
durationDays: 30,
|
||||||
enabled: Boolean(item.enabled),
|
enabled: Boolean(item.enabled),
|
||||||
entryEffect: buildResourceSavePayload(item.entryEffect),
|
entryEffect: buildResourceSavePayload(item.entryEffect),
|
||||||
|
effectImage: buildResourceSavePayload(item.effectImage),
|
||||||
floatPicture: buildResourceSavePayload(item.floatPicture),
|
floatPicture: buildResourceSavePayload(item.floatPicture),
|
||||||
level: Number(item.level || 0),
|
level: Number(item.level || 0),
|
||||||
levelCode: `VIP${Number(item.level || 0)}`,
|
levelCode: `VIP${Number(item.level || 0)}`,
|
||||||
|
longBadge: buildResourceSavePayload(item.longBadge),
|
||||||
priceGold: Number(item.priceGold || 0),
|
priceGold: Number(item.priceGold || 0),
|
||||||
|
shortBadge: buildResourceSavePayload(item.shortBadge),
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -551,7 +594,7 @@ watch(
|
|||||||
:data-source="form.levels"
|
:data-source="form.levels"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
row-key="level"
|
row-key="level"
|
||||||
:scroll="{ x: 1700 }"
|
:scroll="{ x: 2600 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, record }">
|
<template #bodyCell="{ column, record }">
|
||||||
<template v-if="column.key === 'level'">
|
<template v-if="column.key === 'level'">
|
||||||
@ -600,7 +643,7 @@ watch(
|
|||||||
<div>固定 5 个等级,每次购买 30 天;同等级再次购买会续 30 天。</div>
|
<div>固定 5 个等级,每次购买 30 天;同等级再次购买会续 30 天。</div>
|
||||||
<div>资源从道具资源配置选择,保存时会记录当前资源快照。</div>
|
<div>资源从道具资源配置选择,保存时会记录当前资源快照。</div>
|
||||||
<div>用户只能升级,升级时支付目标等级和当前等级的差价,到期后资源自动失效。</div>
|
<div>用户只能升级,升级时支付目标等级和当前等级的差价,到期后资源自动失效。</div>
|
||||||
<div>升级成功后会把徽章、头像框、进场特效、聊天气泡和飘窗替换成新等级资源。</div>
|
<div>升级成功后会把长徽章、短徽章、头像框、进场特效、聊天气泡、飘窗和背景卡替换成新等级资源;动效图只用于 VIP 页面展示。</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Space class="actions" wrap>
|
<Space class="actions" wrap>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user