Compare commits
4 Commits
0a8bddbea0
...
f46a95df79
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f46a95df79 | ||
|
|
5bbb158f57 | ||
|
|
20c00c14d5 | ||
|
|
2f9ae0a10e |
@ -12,6 +12,8 @@ const RECHARGE_REWARD_BASE = '/go/resident-activity/recharge-reward';
|
||||
const TASK_CENTER_BASE = '/go/resident-activity/task-center';
|
||||
const VOICE_ROOM_RED_PACKET_BASE = '/go/resident-activity/voice-room-red-packet';
|
||||
const VOICE_ROOM_ROCKET_BASE = '/go/resident-activity/voice-room-rocket';
|
||||
const WHEEL_BASE = '/go/resident-activity/wheel';
|
||||
const SMASH_GOLDEN_EGG_BASE = '/go/resident-activity/smash-golden-egg';
|
||||
|
||||
export async function getResidentInviteConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>('/resident-activity/invite/config', {
|
||||
@ -100,6 +102,51 @@ export async function pageResidentVipUserStates(params: Record<string, any>) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function getResidentWheelConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${WHEEL_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveResidentWheelConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(`${WHEEL_BASE}/config/save`, data);
|
||||
}
|
||||
|
||||
export async function saveResidentWheelCategoryConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(
|
||||
`${WHEEL_BASE}/config/category/save`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageResidentWheelDrawRecords(params: Record<string, any>) {
|
||||
return requestClient.get<Record<string, any>>(`${WHEEL_BASE}/record/page`, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export async function getResidentSmashGoldenEggConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${SMASH_GOLDEN_EGG_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
});
|
||||
}
|
||||
|
||||
export async function saveResidentSmashGoldenEggConfig(data: Record<string, any>) {
|
||||
return requestClient.post<Record<string, any>>(
|
||||
`${SMASH_GOLDEN_EGG_BASE}/config/save`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageResidentSmashGoldenEggDrawRecords(
|
||||
params: Record<string, any>,
|
||||
) {
|
||||
return requestClient.get<Record<string, any>>(
|
||||
`${SMASH_GOLDEN_EGG_BASE}/draw-record/page`,
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getResidentRechargeRewardConfig(sysOrigin: string) {
|
||||
return requestClient.get<Record<string, any>>(`${RECHARGE_REWARD_BASE}/config`, {
|
||||
params: { sysOrigin },
|
||||
|
||||
@ -62,6 +62,12 @@ const ROUTE_MENU_OVERRIDES: Record<
|
||||
},
|
||||
};
|
||||
|
||||
const LOCAL_RESIDENT_ACTIVITY_FALLBACK_ROUTES = new Set([
|
||||
'ResidentWheelConfig',
|
||||
'ResidentSmashGoldenEggConfig',
|
||||
'ResidentVoiceRoomRocket',
|
||||
]);
|
||||
|
||||
function normalizeRoutePath(path?: string | null) {
|
||||
return String(path || '')
|
||||
.trim()
|
||||
@ -109,6 +115,7 @@ function buildAuthorizedMenuMatcher(menus: LegacyMenu[]) {
|
||||
const aliasSet = new Set<string>();
|
||||
const routerSet = new Set<string>();
|
||||
const titleSet = new Set<string>();
|
||||
let hasResidentActivityAccess = false;
|
||||
const queue = [...menus];
|
||||
|
||||
while (queue.length > 0) {
|
||||
@ -119,18 +126,34 @@ function buildAuthorizedMenuMatcher(menus: LegacyMenu[]) {
|
||||
if (item.alias) {
|
||||
aliasSet.add(String(item.alias));
|
||||
}
|
||||
if (item.router) {
|
||||
routerSet.add(normalizeRoutePath(item.router));
|
||||
const normalizedRouter = normalizeRoutePath(item.router);
|
||||
if (normalizedRouter) {
|
||||
routerSet.add(normalizedRouter);
|
||||
}
|
||||
if (item.menuType === 2 && item.menuName) {
|
||||
titleSet.add(String(item.menuName).trim());
|
||||
}
|
||||
const menuName = String(item.menuName || '').trim();
|
||||
if (
|
||||
String(item.alias || '') === 'ResidentActivityManager' ||
|
||||
menuName === '常驻活动' ||
|
||||
normalizedRouter === 'resident-activity/manager' ||
|
||||
normalizedRouter.startsWith('resident-activity/manager/')
|
||||
) {
|
||||
hasResidentActivityAccess = true;
|
||||
}
|
||||
if (Array.isArray(item.childrens) && item.childrens.length > 0) {
|
||||
queue.push(...item.childrens);
|
||||
}
|
||||
}
|
||||
|
||||
return (route: RouteRecordRaw) => {
|
||||
if (
|
||||
hasResidentActivityAccess &&
|
||||
LOCAL_RESIDENT_ACTIVITY_FALLBACK_ROUTES.has(String(route.name || ''))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
for (const alias of collectRouteAliases(route)) {
|
||||
if (aliasSet.has(alias)) {
|
||||
return true;
|
||||
|
||||
@ -23,6 +23,19 @@ const routes: RouteRecordRaw[] = [
|
||||
component: () => import('#/views/resident-activity/vip-config.vue'),
|
||||
meta: { title: 'VIP' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentWheelConfig',
|
||||
path: 'wheel/config',
|
||||
component: () => import('#/views/resident-activity/wheel-config.vue'),
|
||||
meta: { title: '转盘' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentSmashGoldenEggConfig',
|
||||
path: 'smash-golden-egg/config',
|
||||
component: () =>
|
||||
import('#/views/resident-activity/smash-golden-egg.vue'),
|
||||
meta: { title: '砸蛋' },
|
||||
},
|
||||
{
|
||||
name: 'ResidentRechargeRewardConfig',
|
||||
path: 'recharge-reward/config',
|
||||
|
||||
@ -1,14 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { listEmojiGroupsBySysOrigin } from '#/api/legacy/app-system';
|
||||
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
||||
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
||||
import {
|
||||
listNotFamilyBySysOriginType,
|
||||
listSysOriginTypeList,
|
||||
} from '#/api/legacy/props';
|
||||
|
||||
import {
|
||||
Drawer,
|
||||
Empty,
|
||||
@ -18,8 +10,32 @@ import {
|
||||
Spin,
|
||||
} from 'antdv-next';
|
||||
|
||||
import { listEmojiGroupsBySysOrigin } from '#/api/legacy/app-system';
|
||||
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
||||
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
||||
import {
|
||||
listNotFamilyBySysOriginType,
|
||||
listSysOriginTypeList,
|
||||
} from '#/api/legacy/props';
|
||||
|
||||
defineOptions({ name: 'OperatePropsSourceSelectDrawer' });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
defaultType?: string;
|
||||
open: boolean;
|
||||
sysOrigin: string;
|
||||
}>(),
|
||||
{
|
||||
defaultType: 'AVATAR_FRAME',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
select: [item: SourceItem];
|
||||
}>();
|
||||
|
||||
type SourceTypeOption = {
|
||||
label: string;
|
||||
value: string;
|
||||
@ -53,22 +69,6 @@ const typeSelectOptions = TYPE_OPTIONS.map((item) => ({
|
||||
value: item.value as any,
|
||||
}));
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
defaultType?: string;
|
||||
open: boolean;
|
||||
sysOrigin: string;
|
||||
}>(),
|
||||
{
|
||||
defaultType: 'AVATAR_FRAME',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
select: [item: SourceItem];
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const keyword = ref('');
|
||||
const query = reactive({
|
||||
@ -103,6 +103,89 @@ const currentList = computed(() => {
|
||||
);
|
||||
});
|
||||
|
||||
function firstPresent(record: Record<string, any>, keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = record?.[key];
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizeSourceItem(
|
||||
item: Record<string, any>,
|
||||
type: string,
|
||||
options: {
|
||||
coverKeys?: string[];
|
||||
idKeys?: string[];
|
||||
nameKeys?: string[];
|
||||
sourceUrlKeys?: string[];
|
||||
} = {},
|
||||
): SourceItem {
|
||||
const id = firstPresent(
|
||||
item,
|
||||
options.idKeys || [
|
||||
'id',
|
||||
'resourceId',
|
||||
'sourceId',
|
||||
'propsId',
|
||||
'propsSourceId',
|
||||
'badgeConfigId',
|
||||
'giftId',
|
||||
'groupId',
|
||||
],
|
||||
);
|
||||
return {
|
||||
amount: firstPresent(item, ['amount', 'giftCandy', 'price', 'goldAmount']),
|
||||
cover: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.coverKeys || [
|
||||
'cover',
|
||||
'coverUrl',
|
||||
'resourceCover',
|
||||
'resourceCoverUrl',
|
||||
'giftPhoto',
|
||||
'selectUrl',
|
||||
'icon',
|
||||
'smallIcon',
|
||||
],
|
||||
),
|
||||
),
|
||||
id: id as number | string,
|
||||
name: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.nameKeys || [
|
||||
'name',
|
||||
'resourceName',
|
||||
'sourceName',
|
||||
'giftName',
|
||||
'badgeName',
|
||||
'groupName',
|
||||
'title',
|
||||
'remark',
|
||||
],
|
||||
) || (id ? `资源 ${id}` : ''),
|
||||
),
|
||||
sourceUrl: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.sourceUrlKeys || [
|
||||
'sourceUrl',
|
||||
'resourceUrl',
|
||||
'url',
|
||||
'giftSourceUrl',
|
||||
'animationUrl',
|
||||
'playUrl',
|
||||
],
|
||||
),
|
||||
),
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
@ -150,56 +233,53 @@ async function loadSource(type: string) {
|
||||
store.loading = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
if (type === 'GIFT') {
|
||||
const result = await listGiftBySysOrigin(props.sysOrigin);
|
||||
store.list = (result || []).map((item) => ({
|
||||
amount: item.giftCandy,
|
||||
cover: item.giftPhoto,
|
||||
id: item.id,
|
||||
name: item.giftName,
|
||||
sourceUrl: item.giftSourceUrl,
|
||||
type,
|
||||
}));
|
||||
} else if (type === 'EMOJI') {
|
||||
const result = await listEmojiGroupsBySysOrigin(props.sysOrigin);
|
||||
store.list = (result || []).map((item) => ({
|
||||
amount: item.amount,
|
||||
cover: item.cover,
|
||||
id: item.id || '',
|
||||
name: item.groupName || '',
|
||||
sourceUrl: '',
|
||||
type,
|
||||
}));
|
||||
} else if (type === 'BADGE' || type === 'ROOM_BADGE') {
|
||||
const badgeType = type === 'BADGE' ? 'ACTIVITY' : 'ROOM_ACHIEVEMENT';
|
||||
const result = await listBadgePictureBySysOrigin(props.sysOrigin, badgeType);
|
||||
store.list = (result || []).map((item) => ({
|
||||
cover: item.selectUrl,
|
||||
id: item.badgeConfigId || '',
|
||||
name: item.badgeName || '',
|
||||
sourceUrl: item.animationUrl,
|
||||
type,
|
||||
}));
|
||||
} else if (type === 'NOBLE_VIP') {
|
||||
const result = await listNotFamilyBySysOriginType(props.sysOrigin, type);
|
||||
store.list = (result || []).map((item) => ({
|
||||
amount: item.amount,
|
||||
cover: item.cover,
|
||||
id: item.id || '',
|
||||
name: item.name || '',
|
||||
sourceUrl: item.sourceUrl,
|
||||
type,
|
||||
}));
|
||||
} else {
|
||||
const result = await listSysOriginTypeList(props.sysOrigin, type);
|
||||
store.list = (result || []).map((item) => ({
|
||||
amount: item.amount,
|
||||
cover: item.cover,
|
||||
id: item.id || '',
|
||||
name: item.name || '',
|
||||
sourceUrl: item.sourceUrl,
|
||||
type,
|
||||
}));
|
||||
switch (type) {
|
||||
case 'BADGE':
|
||||
case 'ROOM_BADGE': {
|
||||
const badgeType = type === 'BADGE' ? 'ACTIVITY' : 'ROOM_ACHIEVEMENT';
|
||||
const result = await listBadgePictureBySysOrigin(props.sysOrigin, badgeType);
|
||||
store.list = (result || []).map((item) =>
|
||||
normalizeSourceItem(item, type, {
|
||||
coverKeys: ['selectUrl', 'cover', 'coverUrl'],
|
||||
idKeys: ['badgeConfigId', 'id', 'badgeId'],
|
||||
nameKeys: ['badgeName', 'name'],
|
||||
sourceUrlKeys: ['animationUrl', 'sourceUrl', 'resourceUrl'],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'EMOJI': {
|
||||
const result = await listEmojiGroupsBySysOrigin(props.sysOrigin);
|
||||
store.list = (result || []).map((item) =>
|
||||
normalizeSourceItem(item, type, {
|
||||
idKeys: ['id', 'groupId'],
|
||||
nameKeys: ['groupName', 'name'],
|
||||
sourceUrlKeys: [],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'GIFT': {
|
||||
const result = await listGiftBySysOrigin(props.sysOrigin);
|
||||
store.list = (result || []).map((item) =>
|
||||
normalizeSourceItem(item, type, {
|
||||
coverKeys: ['giftPhoto', 'cover', 'coverUrl'],
|
||||
idKeys: ['id', 'giftId'],
|
||||
nameKeys: ['giftName', 'name'],
|
||||
sourceUrlKeys: ['giftSourceUrl', 'sourceUrl', 'resourceUrl'],
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'NOBLE_VIP': {
|
||||
const result = await listNotFamilyBySysOriginType(props.sysOrigin, type);
|
||||
store.list = (result || []).map((item) => normalizeSourceItem(item, type));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const result = await listSysOriginTypeList(props.sysOrigin, type);
|
||||
store.list = (result || []).map((item) => normalizeSourceItem(item, type));
|
||||
}
|
||||
}
|
||||
store.loaded = true;
|
||||
} finally {
|
||||
@ -258,7 +338,7 @@ function handleSelect(item: SourceItem) {
|
||||
:src="item.cover || 'https://dummyimage.com/88x88/e2e8f0/64748b&text=+'"
|
||||
alt=""
|
||||
class="source-cover"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div class="source-name">{{ item.name || '-' }}</div>
|
||||
<div class="source-meta">ID {{ item.id }}</div>
|
||||
|
||||
@ -0,0 +1,452 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
|
||||
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
||||
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
||||
import {
|
||||
listNotFamilyBySysOriginType,
|
||||
pagePropsSource,
|
||||
} from '#/api/legacy/props';
|
||||
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Empty,
|
||||
Input,
|
||||
Pagination,
|
||||
Space,
|
||||
Spin,
|
||||
} from 'antdv-next';
|
||||
|
||||
defineOptions({ name: 'ResidentActivityResourceSelectDrawer' });
|
||||
|
||||
type ResourceItem = {
|
||||
amount?: number | string;
|
||||
cover: string;
|
||||
id: number | string;
|
||||
name: string;
|
||||
sourceUrl: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
defaultType?: string;
|
||||
fixedType?: boolean;
|
||||
open: boolean;
|
||||
sysOrigin: string;
|
||||
title?: string;
|
||||
}>(),
|
||||
{
|
||||
defaultType: 'ALL_PROPS',
|
||||
fixedType: false,
|
||||
title: '选择资源',
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
close: [];
|
||||
select: [item: ResourceItem];
|
||||
}>();
|
||||
|
||||
const typeOptions = [
|
||||
{ label: '全部资源', value: 'ALL_PROPS' },
|
||||
{ label: '自定义', value: 'CUSTOMIZE' },
|
||||
{ label: '头像框', value: 'AVATAR_FRAME' },
|
||||
{ label: '座驾', value: 'RIDE' },
|
||||
{ label: '聊天气泡', value: 'CHAT_BUBBLE' },
|
||||
{ label: '贵族 VIP', value: 'NOBLE_VIP' },
|
||||
{ label: '主题背景', value: 'THEME' },
|
||||
{ label: '飘窗', value: 'FLOAT_PICTURE' },
|
||||
{ label: '礼物', value: 'GIFT' },
|
||||
{ label: '徽章', value: 'BADGE' },
|
||||
].map((item) => ({ ...item, value: item.value as any }));
|
||||
|
||||
const listOnlyTypes = new Set(['BADGE', 'GIFT', 'NOBLE_VIP']);
|
||||
|
||||
const loading = ref(false);
|
||||
const list = ref<ResourceItem[]>([]);
|
||||
const total = ref(0);
|
||||
|
||||
const query = reactive({
|
||||
cursor: 1,
|
||||
keyword: '',
|
||||
limit: 24,
|
||||
type: 'ALL_PROPS',
|
||||
});
|
||||
|
||||
const localRows = ref<ResourceItem[]>([]);
|
||||
|
||||
const filteredLocalRows = computed(() => {
|
||||
const keyword = query.keyword.trim().toLowerCase();
|
||||
if (!keyword) {
|
||||
return localRows.value;
|
||||
}
|
||||
return localRows.value.filter(
|
||||
(item) =>
|
||||
String(item.id).toLowerCase().includes(keyword) ||
|
||||
item.name.toLowerCase().includes(keyword),
|
||||
);
|
||||
});
|
||||
|
||||
function firstPresent(record: Record<string, any>, keys: string[]) {
|
||||
for (const key of keys) {
|
||||
const value = record?.[key];
|
||||
if (value !== undefined && value !== null && String(value).trim() !== '') {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function normalizeResource(
|
||||
item: Record<string, any>,
|
||||
type: string,
|
||||
options: {
|
||||
coverKeys?: string[];
|
||||
idKeys?: string[];
|
||||
nameKeys?: string[];
|
||||
sourceUrlKeys?: string[];
|
||||
} = {},
|
||||
): ResourceItem {
|
||||
const id = firstPresent(
|
||||
item,
|
||||
options.idKeys || ['id', 'resourceId', 'sourceId', 'propsId'],
|
||||
);
|
||||
return {
|
||||
amount: firstPresent(item, ['amount', 'giftCandy', 'price']),
|
||||
cover: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.coverKeys || [
|
||||
'cover',
|
||||
'coverUrl',
|
||||
'resourceCover',
|
||||
'resourceCoverUrl',
|
||||
'giftPhoto',
|
||||
'selectUrl',
|
||||
'icon',
|
||||
],
|
||||
),
|
||||
),
|
||||
id: id as number | string,
|
||||
name: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.nameKeys || [
|
||||
'name',
|
||||
'resourceName',
|
||||
'sourceName',
|
||||
'giftName',
|
||||
'badgeName',
|
||||
'title',
|
||||
'remark',
|
||||
],
|
||||
) || (id ? `资源 ${id}` : ''),
|
||||
),
|
||||
sourceUrl: String(
|
||||
firstPresent(
|
||||
item,
|
||||
options.sourceUrlKeys || [
|
||||
'sourceUrl',
|
||||
'resourceUrl',
|
||||
'url',
|
||||
'giftSourceUrl',
|
||||
'animationUrl',
|
||||
],
|
||||
),
|
||||
),
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
function applyLocalPage() {
|
||||
const rows = filteredLocalRows.value;
|
||||
total.value = rows.length;
|
||||
const start = (query.cursor - 1) * query.limit;
|
||||
list.value = rows.slice(start, start + query.limit);
|
||||
}
|
||||
|
||||
async function loadListType(type: string) {
|
||||
if (type === 'GIFT') {
|
||||
const result = await listGiftBySysOrigin(props.sysOrigin);
|
||||
localRows.value = (result || []).map((item) =>
|
||||
normalizeResource(item, type, {
|
||||
coverKeys: ['giftPhoto', 'cover'],
|
||||
idKeys: ['id', 'giftId'],
|
||||
nameKeys: ['giftName', 'name'],
|
||||
sourceUrlKeys: ['giftSourceUrl', 'sourceUrl'],
|
||||
}),
|
||||
);
|
||||
applyLocalPage();
|
||||
return;
|
||||
}
|
||||
if (type === 'BADGE') {
|
||||
const result = await listBadgePictureBySysOrigin(props.sysOrigin, 'ACTIVITY');
|
||||
localRows.value = (result || []).map((item) =>
|
||||
normalizeResource(item, type, {
|
||||
coverKeys: ['selectUrl', 'cover'],
|
||||
idKeys: ['badgeConfigId', 'id'],
|
||||
nameKeys: ['badgeName', 'name'],
|
||||
sourceUrlKeys: ['animationUrl', 'sourceUrl'],
|
||||
}),
|
||||
);
|
||||
applyLocalPage();
|
||||
return;
|
||||
}
|
||||
if (type === 'NOBLE_VIP') {
|
||||
const result = await listNotFamilyBySysOriginType(props.sysOrigin, type);
|
||||
localRows.value = (result || []).map((item) =>
|
||||
normalizeResource(item, type),
|
||||
);
|
||||
applyLocalPage();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadData(reset = false) {
|
||||
if (!props.open || !props.sysOrigin) {
|
||||
return;
|
||||
}
|
||||
if (reset) {
|
||||
query.cursor = 1;
|
||||
}
|
||||
loading.value = true;
|
||||
try {
|
||||
if (listOnlyTypes.has(query.type)) {
|
||||
await loadListType(query.type);
|
||||
return;
|
||||
}
|
||||
const keyword = query.keyword.trim();
|
||||
const params: Record<string, any> = {
|
||||
cursor: query.cursor,
|
||||
del: false,
|
||||
limit: query.limit,
|
||||
sysOrigin: props.sysOrigin,
|
||||
};
|
||||
if (query.type && query.type !== 'ALL_PROPS') {
|
||||
params.type = query.type;
|
||||
}
|
||||
if (keyword) {
|
||||
if (/^\d+$/.test(keyword)) {
|
||||
params.id = keyword;
|
||||
} else {
|
||||
params.name = keyword;
|
||||
}
|
||||
}
|
||||
const result = await pagePropsSource(params);
|
||||
list.value = (result.records || []).map((item) =>
|
||||
normalizeResource(item, String(item.type || query.type || '')),
|
||||
);
|
||||
total.value = result.total || 0;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleTypeChange(value: string) {
|
||||
query.type = value || 'ALL_PROPS';
|
||||
query.keyword = '';
|
||||
localRows.value = [];
|
||||
void loadData(true);
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
void loadData(true);
|
||||
}
|
||||
|
||||
function handlePageChange(page: number, pageSize: number) {
|
||||
query.cursor = page;
|
||||
query.limit = pageSize;
|
||||
if (listOnlyTypes.has(query.type)) {
|
||||
applyLocalPage();
|
||||
return;
|
||||
}
|
||||
void loadData();
|
||||
}
|
||||
|
||||
function handleSelect(item: ResourceItem) {
|
||||
emit('select', item);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(open) => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
query.type = props.defaultType || 'ALL_PROPS';
|
||||
query.keyword = '';
|
||||
query.cursor = 1;
|
||||
localRows.value = [];
|
||||
void loadData(true);
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.defaultType,
|
||||
(value) => {
|
||||
if (!props.open || !value) {
|
||||
return;
|
||||
}
|
||||
query.type = value;
|
||||
query.keyword = '';
|
||||
localRows.value = [];
|
||||
void loadData(true);
|
||||
},
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.sysOrigin,
|
||||
() => {
|
||||
localRows.value = [];
|
||||
if (props.open) {
|
||||
void loadData(true);
|
||||
}
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Drawer
|
||||
:open="open"
|
||||
destroy-on-close
|
||||
:title="title"
|
||||
width="920"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<div v-if="!fixedType" class="type-filter">
|
||||
<Button
|
||||
v-for="item in typeOptions"
|
||||
:key="item.value"
|
||||
size="small"
|
||||
:type="query.type === item.value ? 'primary' : 'default'"
|
||||
@click="handleTypeChange(item.value)"
|
||||
>
|
||||
{{ item.label }}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Space class="toolbar" wrap>
|
||||
<Input
|
||||
v-model:value="query.keyword"
|
||||
allow-clear
|
||||
placeholder="资源名称 / ID"
|
||||
style="width: 260px"
|
||||
@press-enter="handleSearch"
|
||||
/>
|
||||
<Button :loading="loading" type="primary" @click="handleSearch">
|
||||
搜索
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Spin :spinning="loading">
|
||||
<div v-if="list.length > 0" class="resource-grid">
|
||||
<button
|
||||
v-for="item in list"
|
||||
:key="`${item.type}-${item.id}`"
|
||||
class="resource-card"
|
||||
type="button"
|
||||
@click="handleSelect(item)"
|
||||
>
|
||||
<div class="resource-cover-wrap">
|
||||
<img
|
||||
:src="item.cover || 'https://dummyimage.com/88x88/e2e8f0/64748b&text=+'"
|
||||
alt=""
|
||||
class="resource-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="resource-name">{{ item.name || '-' }}</div>
|
||||
<div class="resource-meta">ID {{ item.id }}</div>
|
||||
<div class="resource-type">{{ item.type || '-' }}</div>
|
||||
</button>
|
||||
</div>
|
||||
<Empty v-else description="暂无可选资源" />
|
||||
</Spin>
|
||||
|
||||
<div class="pager">
|
||||
<Pagination
|
||||
:current="query.cursor"
|
||||
:page-size="query.limit"
|
||||
:total="total"
|
||||
show-size-changer
|
||||
@change="handlePageChange"
|
||||
@showSizeChange="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.toolbar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.type-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.resource-grid {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
|
||||
}
|
||||
|
||||
.resource-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
transition:
|
||||
border-color 0.2s ease,
|
||||
box-shadow 0.2s ease,
|
||||
transform 0.2s ease;
|
||||
}
|
||||
|
||||
.resource-card:hover {
|
||||
border-color: #2563eb;
|
||||
box-shadow: 0 12px 24px rgb(37 99 235 / 12%);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.resource-cover-wrap {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 88px;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.resource-cover {
|
||||
border-radius: 8px;
|
||||
height: 88px;
|
||||
object-fit: cover;
|
||||
width: 88px;
|
||||
}
|
||||
|
||||
.resource-name {
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
min-height: 40px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.resource-meta,
|
||||
.resource-type {
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
margin-top: 6px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.pager {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
1552
apps/src/views/resident-activity/smash-golden-egg.vue
Normal file
1552
apps/src/views/resident-activity/smash-golden-egg.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -31,6 +31,8 @@ import {
|
||||
} from '#/api/legacy/resident-activity';
|
||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||
|
||||
import ResourceSelectDrawer from './components/resource-select-drawer.vue';
|
||||
|
||||
defineOptions({ name: 'ResidentVoiceRoomRocketPage' });
|
||||
|
||||
const RIYADH_TIMEZONE = 'Asia/Riyadh';
|
||||
@ -41,7 +43,6 @@ type LevelRow = {
|
||||
id?: number | string;
|
||||
level: number;
|
||||
needEnergy: number;
|
||||
progressBarUrl: string;
|
||||
rocketAnimationUrl: string;
|
||||
rocketIconUrl: string;
|
||||
shakeThresholdPercent: string;
|
||||
@ -64,6 +65,16 @@ type RewardRow = {
|
||||
weight: number;
|
||||
};
|
||||
|
||||
type ResourceOption = {
|
||||
amount?: number | string;
|
||||
cover: string;
|
||||
id: number | string;
|
||||
name: string;
|
||||
remark?: string;
|
||||
sourceUrl: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
const sysOriginOptions = computed(() => {
|
||||
const options = getAllowedSysOrigins(accessStore.accessCodes || []);
|
||||
@ -99,6 +110,23 @@ const configForm = reactive({
|
||||
const levelRows = ref<LevelRow[]>([]);
|
||||
const rewardRows = ref<RewardRow[]>([]);
|
||||
|
||||
const resourcePickerOpen = ref(false);
|
||||
const resourcePickerTitle = ref('选择资源');
|
||||
const resourcePickerDefaultType = ref('ALL_PROPS');
|
||||
const resourcePickerFixedType = ref(false);
|
||||
const resourcePickerTarget = ref<
|
||||
| {
|
||||
field: 'rocketAnimationUrl' | 'rocketIconUrl';
|
||||
kind: 'level';
|
||||
row: Record<string, any>;
|
||||
}
|
||||
| {
|
||||
kind: 'reward';
|
||||
row: Record<string, any>;
|
||||
}
|
||||
| null
|
||||
>(null);
|
||||
|
||||
const sceneOptions = [
|
||||
{ label: '贡献第一', value: 'TOP1' },
|
||||
{ label: '点火人', value: 'IGNITE' },
|
||||
@ -113,6 +141,7 @@ const rewardTypeOptions = [
|
||||
{ label: 'VIP', value: 'VIP_CARD' },
|
||||
{ label: '礼物', value: 'GIFT' },
|
||||
{ label: '徽章', value: 'BADGE' },
|
||||
{ label: '轮空', value: 'NONE' },
|
||||
];
|
||||
|
||||
const levelOptions = computed(() =>
|
||||
@ -122,6 +151,12 @@ const levelOptions = computed(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
function rewardTypeOptionsForRow(row: Record<string, any>) {
|
||||
return row.rewardScene === 'IN_ROOM'
|
||||
? rewardTypeOptions
|
||||
: rewardTypeOptions.filter((item) => item.value !== 'NONE');
|
||||
}
|
||||
|
||||
const statusMeta = computed(() => {
|
||||
if (!configForm.configured) {
|
||||
return { color: 'warning', text: '未配置' };
|
||||
@ -134,10 +169,14 @@ const statusMeta = computed(() => {
|
||||
const levelColumns = [
|
||||
{ dataIndex: 'level', key: 'level', title: '等级', width: 90 },
|
||||
{ dataIndex: 'needEnergy', key: 'needEnergy', title: '所需能量', width: 160 },
|
||||
{ dataIndex: 'shakeThresholdPercent', key: 'shakeThresholdPercent', title: '晃动阈值(%)', width: 160 },
|
||||
{ dataIndex: 'rocketIconUrl', key: 'rocketIconUrl', title: '火箭图标', width: 260 },
|
||||
{ dataIndex: 'rocketAnimationUrl', key: 'rocketAnimationUrl', title: '发射动画', width: 260 },
|
||||
{ dataIndex: 'progressBarUrl', key: 'progressBarUrl', title: '进度条资源', width: 260 },
|
||||
{
|
||||
dataIndex: 'rocketAnimationUrl',
|
||||
key: 'rocketAnimationUrl',
|
||||
title: '发射动画',
|
||||
width: 260,
|
||||
},
|
||||
{ dataIndex: 'shakeThresholdPercent', key: 'shakeThresholdPercent', title: '晃动阈值(%)', width: 160 },
|
||||
{ dataIndex: 'enabled', key: 'enabled', title: '启用', width: 90 },
|
||||
{ dataIndex: 'sort', key: 'sort', title: '排序', width: 100 },
|
||||
];
|
||||
@ -145,9 +184,9 @@ const levelColumns = [
|
||||
const rewardColumns = [
|
||||
{ dataIndex: 'rewardScene', key: 'rewardScene', title: '奖励场景', width: 150 },
|
||||
{ dataIndex: 'rewardType', key: 'rewardType', title: '奖励类型', width: 150 },
|
||||
{ dataIndex: 'rewardItemId', key: 'rewardItemId', title: '资源ID', width: 170 },
|
||||
{ dataIndex: 'rewardItemId', key: 'rewardItemId', title: '资源', width: 220 },
|
||||
{ dataIndex: 'rewardName', key: 'rewardName', title: '奖励名称', width: 180 },
|
||||
{ dataIndex: 'rewardCover', key: 'rewardCover', title: '封面URL', width: 220 },
|
||||
{ dataIndex: 'rewardCover', key: 'rewardCover', title: '封面', width: 220 },
|
||||
{ dataIndex: 'rewardAmount', key: 'rewardAmount', title: '数量/金币/天数', width: 150 },
|
||||
{ dataIndex: 'expireDays', key: 'expireDays', title: '有效天数', width: 130 },
|
||||
{ dataIndex: 'weight', key: 'weight', title: '权重', width: 110 },
|
||||
@ -161,7 +200,6 @@ function createDefaultLevel(level: number): LevelRow {
|
||||
enabled: true,
|
||||
level,
|
||||
needEnergy: level * 100,
|
||||
progressBarUrl: '',
|
||||
rocketAnimationUrl: '',
|
||||
rocketIconUrl: '',
|
||||
shakeThresholdPercent: '95.0000',
|
||||
@ -246,7 +284,6 @@ function normalizeLevelRows(rows: Array<Record<string, any>> | undefined) {
|
||||
enabled: row.enabled === undefined ? true : Boolean(row.enabled),
|
||||
id: row.id,
|
||||
needEnergy: toNumber(row.needEnergy, level * 100),
|
||||
progressBarUrl: String(row.progressBarUrl || ''),
|
||||
rocketAnimationUrl: String(row.rocketAnimationUrl || ''),
|
||||
rocketIconUrl: String(row.rocketIconUrl || ''),
|
||||
shakeThresholdPercent: String(row.shakeThresholdPercent || '95.0000'),
|
||||
@ -273,6 +310,115 @@ function normalizeRewardRows(rows: Array<Record<string, any>> | undefined) {
|
||||
}));
|
||||
}
|
||||
|
||||
function rewardResourceType(rewardType: string) {
|
||||
const value = String(rewardType || '').trim();
|
||||
const mapping: Record<string, string> = {
|
||||
AVATAR_FRAME: 'AVATAR_FRAME',
|
||||
BADGE: 'BADGE',
|
||||
CHAT_BUBBLE: 'CHAT_BUBBLE',
|
||||
GIFT: 'GIFT',
|
||||
MOUNT: 'RIDE',
|
||||
VIP_CARD: 'NOBLE_VIP',
|
||||
};
|
||||
return mapping[value] || '';
|
||||
}
|
||||
|
||||
function rewardTypeText(rewardType: string) {
|
||||
return (
|
||||
rewardTypeOptions.find((item) => item.value === rewardType)?.label ||
|
||||
rewardType ||
|
||||
'资源'
|
||||
);
|
||||
}
|
||||
|
||||
function isRewardResourceDisabled(row: Record<string, any>) {
|
||||
return row.rewardType === 'GOLD' || row.rewardType === 'NONE';
|
||||
}
|
||||
|
||||
function openLevelResourcePicker(
|
||||
row: Record<string, any>,
|
||||
field: 'rocketAnimationUrl' | 'rocketIconUrl',
|
||||
) {
|
||||
resourcePickerTarget.value = {
|
||||
field,
|
||||
kind: 'level',
|
||||
row,
|
||||
};
|
||||
resourcePickerTitle.value =
|
||||
field === 'rocketIconUrl' ? '选择火箭图标资源' : '选择发射动画资源';
|
||||
resourcePickerDefaultType.value = 'ALL_PROPS';
|
||||
resourcePickerFixedType.value = false;
|
||||
resourcePickerOpen.value = true;
|
||||
}
|
||||
|
||||
function openRewardResourcePicker(row: Record<string, any>) {
|
||||
if (isRewardResourceDisabled(row)) {
|
||||
return;
|
||||
}
|
||||
const sourceType = rewardResourceType(row.rewardType);
|
||||
if (!sourceType) {
|
||||
return;
|
||||
}
|
||||
resourcePickerTarget.value = {
|
||||
kind: 'reward',
|
||||
row,
|
||||
};
|
||||
resourcePickerTitle.value = `选择${rewardTypeText(row.rewardType)}资源`;
|
||||
resourcePickerDefaultType.value = sourceType;
|
||||
resourcePickerFixedType.value = true;
|
||||
resourcePickerOpen.value = true;
|
||||
}
|
||||
|
||||
function clearLevelResource(
|
||||
row: Record<string, any>,
|
||||
field: 'rocketAnimationUrl' | 'rocketIconUrl',
|
||||
) {
|
||||
row[field] = '';
|
||||
}
|
||||
|
||||
function clearRewardResource(row: Record<string, any>) {
|
||||
row.rewardItemId = null;
|
||||
row.rewardCover = '';
|
||||
if (row.rewardName !== '金币' && row.rewardName !== '轮空') {
|
||||
row.rewardName = '';
|
||||
}
|
||||
}
|
||||
|
||||
function handleRewardSceneChange(row: Record<string, any>) {
|
||||
if (row.rewardScene !== 'IN_ROOM' && row.rewardType === 'NONE') {
|
||||
row.rewardType = 'GOLD';
|
||||
handleRewardTypeChange(row);
|
||||
}
|
||||
}
|
||||
|
||||
function handleResourceSelect(item: ResourceOption) {
|
||||
const target = resourcePickerTarget.value;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
if (target.kind === 'level') {
|
||||
const value =
|
||||
target.field === 'rocketIconUrl'
|
||||
? String(item.cover || item.sourceUrl || '')
|
||||
: String(item.sourceUrl || item.cover || '');
|
||||
target.row[target.field] = value;
|
||||
} else {
|
||||
target.row.rewardItemId = item.id;
|
||||
target.row.rewardName = item.name || String(item.id || '');
|
||||
target.row.rewardCover = item.cover || item.sourceUrl || '';
|
||||
}
|
||||
resourcePickerOpen.value = false;
|
||||
resourcePickerTarget.value = null;
|
||||
}
|
||||
|
||||
function resourceUrlName(url: string) {
|
||||
const text = String(url || '').trim();
|
||||
if (!text) {
|
||||
return '未选择';
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async function loadConfig() {
|
||||
if (!selectedSysOrigin.value) {
|
||||
return;
|
||||
@ -361,7 +507,6 @@ async function saveLevels() {
|
||||
id: row.id,
|
||||
level: row.level,
|
||||
needEnergy: toNumber(row.needEnergy, row.level * 100),
|
||||
progressBarUrl: row.progressBarUrl,
|
||||
rocketAnimationUrl: row.rocketAnimationUrl,
|
||||
rocketIconUrl: row.rocketIconUrl,
|
||||
shakeThresholdPercent: row.shakeThresholdPercent,
|
||||
@ -376,18 +521,19 @@ async function saveLevels() {
|
||||
}
|
||||
}
|
||||
|
||||
function addRewardRow(scene = 'TOP1') {
|
||||
function addRewardRow(scene = 'TOP1', rewardType = 'GOLD') {
|
||||
const isNone = rewardType === 'NONE';
|
||||
rewardRows.value.push({
|
||||
enabled: true,
|
||||
expireDays: null,
|
||||
level: rewardLevel.value,
|
||||
localKey: `new-${Date.now()}-${rewardRows.value.length}`,
|
||||
rewardAmount: 1,
|
||||
rewardAmount: isNone ? 0 : 1,
|
||||
rewardCover: '',
|
||||
rewardItemId: null,
|
||||
rewardName: scene === 'TOP1' ? '金币' : '',
|
||||
rewardName: isNone ? '轮空' : rewardType === 'GOLD' ? '金币' : '',
|
||||
rewardScene: scene,
|
||||
rewardType: 'GOLD',
|
||||
rewardType,
|
||||
sort: rewardRows.value.length + 1,
|
||||
weight: 1,
|
||||
});
|
||||
@ -398,12 +544,30 @@ function removeRewardRow(index: number) {
|
||||
}
|
||||
|
||||
function handleRewardTypeChange(row: Record<string, any>) {
|
||||
if (row.rewardType === 'NONE') {
|
||||
if (row.rewardScene !== 'IN_ROOM') {
|
||||
row.rewardScene = 'IN_ROOM';
|
||||
}
|
||||
row.rewardItemId = null;
|
||||
row.rewardCover = '';
|
||||
row.expireDays = null;
|
||||
row.rewardAmount = 0;
|
||||
row.rewardName = '轮空';
|
||||
return;
|
||||
}
|
||||
if (row.rewardType === 'GOLD') {
|
||||
row.rewardItemId = null;
|
||||
row.rewardCover = '';
|
||||
row.expireDays = null;
|
||||
if (!row.rewardName) {
|
||||
row.rewardName = '金币';
|
||||
}
|
||||
return;
|
||||
}
|
||||
row.rewardItemId = null;
|
||||
row.rewardCover = '';
|
||||
if (row.rewardName === '金币' || row.rewardName === '轮空') {
|
||||
row.rewardName = '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,12 +581,20 @@ function validateRewards() {
|
||||
message.warning('请选择奖励类型');
|
||||
return false;
|
||||
}
|
||||
if (row.rewardType === 'NONE' && row.rewardScene !== 'IN_ROOM') {
|
||||
message.warning('轮空只能配置在房用户奖励');
|
||||
return false;
|
||||
}
|
||||
if (!String(row.rewardName || '').trim()) {
|
||||
message.warning('请输入奖励名称');
|
||||
return false;
|
||||
}
|
||||
if (row.rewardType !== 'GOLD' && !String(row.rewardItemId || '').trim()) {
|
||||
message.warning('非金币奖励必须填写资源ID');
|
||||
if (
|
||||
row.rewardType !== 'GOLD' &&
|
||||
row.rewardType !== 'NONE' &&
|
||||
!String(row.rewardItemId || '').trim()
|
||||
) {
|
||||
message.warning('非金币奖励必须选择资源');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -439,14 +611,20 @@ async function saveRewards() {
|
||||
level: rewardLevel.value,
|
||||
rewards: rewardRows.value.map((row) => ({
|
||||
enabled: row.enabled,
|
||||
expireDays: toOptionalNumber(row.expireDays),
|
||||
expireDays:
|
||||
row.rewardType === 'GOLD' || row.rewardType === 'NONE'
|
||||
? undefined
|
||||
: toOptionalNumber(row.expireDays),
|
||||
id: row.id,
|
||||
level: rewardLevel.value,
|
||||
rewardAmount: toNumber(row.rewardAmount, 1),
|
||||
rewardCover: row.rewardCover,
|
||||
rewardAmount:
|
||||
row.rewardType === 'NONE' ? 0 : toNumber(row.rewardAmount, 1),
|
||||
rewardCover: row.rewardType === 'NONE' ? '' : row.rewardCover,
|
||||
rewardItemId:
|
||||
row.rewardType === 'GOLD' ? undefined : toOptionalString(row.rewardItemId),
|
||||
rewardName: row.rewardName.trim(),
|
||||
row.rewardType === 'GOLD' || row.rewardType === 'NONE'
|
||||
? undefined
|
||||
: toOptionalString(row.rewardItemId),
|
||||
rewardName: row.rewardType === 'NONE' ? '轮空' : row.rewardName.trim(),
|
||||
rewardScene: row.rewardScene,
|
||||
rewardType: row.rewardType,
|
||||
sort: toNumber(row.sort, 1),
|
||||
@ -605,7 +783,7 @@ watch(rewardLevel, () => {
|
||||
:data-source="levelRows"
|
||||
:loading="levelLoading"
|
||||
:pagination="false"
|
||||
:scroll="{ x: 1420 }"
|
||||
:scroll="{ x: 1240 }"
|
||||
row-key="level"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
@ -620,24 +798,70 @@ watch(rewardLevel, () => {
|
||||
class="table-number"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rocketIconUrl'">
|
||||
<div class="resource-cell">
|
||||
<div class="resource-cell__preview">
|
||||
<img
|
||||
v-if="record.rocketIconUrl"
|
||||
:src="record.rocketIconUrl"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ resourceUrlName(record.rocketIconUrl) }}</span>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openLevelResourcePicker(record, 'rocketIconUrl')"
|
||||
>
|
||||
{{ record.rocketIconUrl ? '更换资源' : '选择资源' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rocketIconUrl"
|
||||
size="small"
|
||||
@click="clearLevelResource(record, 'rocketIconUrl')"
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rocketAnimationUrl'">
|
||||
<div class="resource-cell">
|
||||
<div class="resource-cell__preview">
|
||||
<img
|
||||
v-if="record.rocketAnimationUrl"
|
||||
:src="record.rocketAnimationUrl"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ resourceUrlName(record.rocketAnimationUrl) }}</span>
|
||||
</div>
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="
|
||||
openLevelResourcePicker(record, 'rocketAnimationUrl')
|
||||
"
|
||||
>
|
||||
{{ record.rocketAnimationUrl ? '更换资源' : '选择资源' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rocketAnimationUrl"
|
||||
size="small"
|
||||
@click="clearLevelResource(record, 'rocketAnimationUrl')"
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'shakeThresholdPercent'">
|
||||
<Input
|
||||
v-model:value="record.shakeThresholdPercent"
|
||||
class="table-input"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rocketIconUrl'">
|
||||
<Input v-model:value="record.rocketIconUrl" class="table-input" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rocketAnimationUrl'">
|
||||
<Input
|
||||
v-model:value="record.rocketAnimationUrl"
|
||||
class="table-input"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'progressBarUrl'">
|
||||
<Input v-model:value="record.progressBarUrl" class="table-input" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'enabled'">
|
||||
<Switch v-model:checked="record.enabled" />
|
||||
</template>
|
||||
@ -676,6 +900,7 @@ watch(rewardLevel, () => {
|
||||
<Button @click="addRewardRow('TOP1')">添加贡献第一</Button>
|
||||
<Button @click="addRewardRow('IGNITE')">添加点火人</Button>
|
||||
<Button @click="addRewardRow('IN_ROOM')">添加在房用户</Button>
|
||||
<Button @click="addRewardRow('IN_ROOM', 'NONE')">添加在房轮空</Button>
|
||||
<Button type="primary" :loading="rewardSaving" @click="saveRewards">
|
||||
保存当前等级奖励
|
||||
</Button>
|
||||
@ -695,33 +920,67 @@ watch(rewardLevel, () => {
|
||||
v-model:value="record.rewardScene"
|
||||
:options="sceneOptions"
|
||||
class="table-select"
|
||||
@change="() => handleRewardSceneChange(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardType'">
|
||||
<Select
|
||||
v-model:value="record.rewardType"
|
||||
:options="rewardTypeOptions"
|
||||
:options="rewardTypeOptionsForRow(record)"
|
||||
class="table-select"
|
||||
@change="() => handleRewardTypeChange(record)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardItemId'">
|
||||
<div class="resource-cell">
|
||||
<div class="resource-cell__summary">
|
||||
<span v-if="isRewardResourceDisabled(record)">-</span>
|
||||
<span v-else-if="record.rewardItemId">
|
||||
{{ record.rewardName || '已选资源' }} / ID:
|
||||
{{ record.rewardItemId }}
|
||||
</span>
|
||||
<span v-else>未选择</span>
|
||||
</div>
|
||||
<Space v-if="!isRewardResourceDisabled(record)">
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
@click="openRewardResourcePicker(record)"
|
||||
>
|
||||
{{ record.rewardItemId ? '更换资源' : '选择资源' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rewardItemId"
|
||||
size="small"
|
||||
@click="clearRewardResource(record)"
|
||||
>
|
||||
清除
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardName'">
|
||||
<Input
|
||||
v-model:value="record.rewardItemId"
|
||||
:disabled="record.rewardType === 'GOLD'"
|
||||
v-model:value="record.rewardName"
|
||||
:disabled="record.rewardType === 'NONE'"
|
||||
class="table-input-small"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardName'">
|
||||
<Input v-model:value="record.rewardName" class="table-input-small" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardCover'">
|
||||
<Input v-model:value="record.rewardCover" class="table-input" />
|
||||
<div class="cover-preview">
|
||||
<img
|
||||
v-if="record.rewardCover"
|
||||
:src="record.rewardCover"
|
||||
alt=""
|
||||
/>
|
||||
<span>{{ record.rewardCover || '-' }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardAmount'">
|
||||
<InputNumber
|
||||
v-model:value="record.rewardAmount"
|
||||
:min="1"
|
||||
:disabled="record.rewardType === 'NONE'"
|
||||
:min="record.rewardType === 'NONE' ? 0 : 1"
|
||||
:precision="0"
|
||||
class="table-number"
|
||||
/>
|
||||
@ -729,7 +988,9 @@ watch(rewardLevel, () => {
|
||||
<template v-else-if="column.key === 'expireDays'">
|
||||
<InputNumber
|
||||
v-model:value="record.expireDays"
|
||||
:disabled="record.rewardType === 'GOLD'"
|
||||
:disabled="
|
||||
record.rewardType === 'GOLD' || record.rewardType === 'NONE'
|
||||
"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
class="table-number"
|
||||
@ -777,6 +1038,16 @@ watch(rewardLevel, () => {
|
||||
</Card>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
||||
<ResourceSelectDrawer
|
||||
:default-type="resourcePickerDefaultType"
|
||||
:fixed-type="resourcePickerFixedType"
|
||||
:open="resourcePickerOpen"
|
||||
:sys-origin="selectedSysOrigin"
|
||||
:title="resourcePickerTitle"
|
||||
@close="resourcePickerOpen = false"
|
||||
@select="handleResourceSelect"
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
@ -843,4 +1114,60 @@ watch(rewardLevel, () => {
|
||||
.table-number {
|
||||
width: 110px;
|
||||
}
|
||||
|
||||
.resource-cell {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
min-width: 220px;
|
||||
}
|
||||
|
||||
.resource-cell__preview {
|
||||
align-items: center;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
gap: 8px;
|
||||
min-height: 36px;
|
||||
}
|
||||
|
||||
.resource-cell__preview img {
|
||||
border-radius: 4px;
|
||||
flex: 0 0 auto;
|
||||
height: 32px;
|
||||
object-fit: cover;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
.resource-cell__preview span,
|
||||
.resource-cell__summary {
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
max-width: 220px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cover-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 220px;
|
||||
gap: 8px;
|
||||
color: #6b7280;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cover-preview img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.cover-preview span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
1158
apps/src/views/resident-activity/wheel-config.vue
Normal file
1158
apps/src/views/resident-activity/wheel-config.vue
Normal file
File diff suppressed because it is too large
Load Diff
1
var/chatapp3-admin-dev.pid
Normal file
1
var/chatapp3-admin-dev.pid
Normal file
@ -0,0 +1 @@
|
||||
4934
|
||||
Loading…
x
Reference in New Issue
Block a user