fix: use drawer resource picker for rocket config
This commit is contained in:
parent
20c00c14d5
commit
5bbb158f57
@ -0,0 +1,442 @@
|
|||||||
|
<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,
|
||||||
|
Select,
|
||||||
|
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')"
|
||||||
|
>
|
||||||
|
<Space class="toolbar" wrap>
|
||||||
|
<Select
|
||||||
|
v-if="!fixedType"
|
||||||
|
v-model:value="query.type"
|
||||||
|
:options="typeOptions"
|
||||||
|
option-label-prop="label"
|
||||||
|
style="width: 180px"
|
||||||
|
@change="handleTypeChange"
|
||||||
|
/>
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
@ -29,14 +29,10 @@ import {
|
|||||||
saveVoiceRoomRocketLevelConfigs,
|
saveVoiceRoomRocketLevelConfigs,
|
||||||
saveVoiceRoomRocketRewardConfigs,
|
saveVoiceRoomRocketRewardConfigs,
|
||||||
} from '#/api/legacy/resident-activity';
|
} from '#/api/legacy/resident-activity';
|
||||||
import { listBadgePictureBySysOrigin } from '#/api/legacy/badge';
|
|
||||||
import { listGiftBySysOrigin } from '#/api/legacy/gift';
|
|
||||||
import {
|
|
||||||
listNotFamilyBySysOriginType,
|
|
||||||
listSysOriginTypeList,
|
|
||||||
} from '#/api/legacy/props';
|
|
||||||
import { getAllowedSysOrigins } from '#/views/system/shared';
|
import { getAllowedSysOrigins } from '#/views/system/shared';
|
||||||
|
|
||||||
|
import ResourceSelectDrawer from './components/resource-select-drawer.vue';
|
||||||
|
|
||||||
defineOptions({ name: 'ResidentVoiceRoomRocketPage' });
|
defineOptions({ name: 'ResidentVoiceRoomRocketPage' });
|
||||||
|
|
||||||
const RIYADH_TIMEZONE = 'Asia/Riyadh';
|
const RIYADH_TIMEZONE = 'Asia/Riyadh';
|
||||||
@ -95,12 +91,6 @@ const rewardLoading = ref(false);
|
|||||||
const configSaving = ref(false);
|
const configSaving = ref(false);
|
||||||
const levelSaving = ref(false);
|
const levelSaving = ref(false);
|
||||||
const rewardSaving = ref(false);
|
const rewardSaving = ref(false);
|
||||||
const rocketResourceLoading = ref(false);
|
|
||||||
const rocketResourceRows = ref<ResourceOption[]>([]);
|
|
||||||
const rewardResourceLoading = reactive<Record<string, boolean>>({});
|
|
||||||
const rewardResourceRows = reactive<Record<string, ResourceOption[]>>({});
|
|
||||||
|
|
||||||
const ROCKET_RESOURCE_TYPE = 'CUSTOMIZE';
|
|
||||||
|
|
||||||
const configForm = reactive({
|
const configForm = reactive({
|
||||||
broadcastDurationSeconds: 7,
|
broadcastDurationSeconds: 7,
|
||||||
@ -120,6 +110,23 @@ const configForm = reactive({
|
|||||||
const levelRows = ref<LevelRow[]>([]);
|
const levelRows = ref<LevelRow[]>([]);
|
||||||
const rewardRows = ref<RewardRow[]>([]);
|
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 = [
|
const sceneOptions = [
|
||||||
{ label: '贡献第一', value: 'TOP1' },
|
{ label: '贡献第一', value: 'TOP1' },
|
||||||
{ label: '点火人', value: 'IGNITE' },
|
{ label: '点火人', value: 'IGNITE' },
|
||||||
@ -303,93 +310,6 @@ function normalizeRewardRows(rows: Array<Record<string, any>> | undefined) {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePropsResource(item: Record<string, any>, type: string): ResourceOption {
|
|
||||||
return {
|
|
||||||
amount: item.amount,
|
|
||||||
cover: String(item.cover || item.img || item.icon || ''),
|
|
||||||
id: item.id,
|
|
||||||
name: String(item.name || item.title || item.remark || item.id || ''),
|
|
||||||
remark: item.remark,
|
|
||||||
sourceUrl: String(item.sourceUrl || item.url || item.cover || ''),
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeGiftResource(item: Record<string, any>): ResourceOption {
|
|
||||||
return {
|
|
||||||
amount: item.giftCandy,
|
|
||||||
cover: String(item.giftPhoto || ''),
|
|
||||||
id: item.id,
|
|
||||||
name: String(item.giftName || item.id || ''),
|
|
||||||
sourceUrl: String(item.giftSourceUrl || item.giftPhoto || ''),
|
|
||||||
type: 'GIFT',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeBadgeResource(item: Record<string, any>): ResourceOption {
|
|
||||||
return {
|
|
||||||
cover: String(item.selectUrl || item.cover || ''),
|
|
||||||
id: item.badgeConfigId || item.id,
|
|
||||||
name: String(item.badgeName || item.name || item.id || ''),
|
|
||||||
sourceUrl: String(item.animationUrl || item.selectUrl || item.cover || ''),
|
|
||||||
type: 'BADGE',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function optionLabel(item: ResourceOption) {
|
|
||||||
return `${item.id} / ${item.name}`.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toResourceSelectOptions(rows: ResourceOption[]) {
|
|
||||||
return rows
|
|
||||||
.filter((item) => item.id !== undefined && item.id !== null && item.id !== '')
|
|
||||||
.map((item) => ({
|
|
||||||
label: optionLabel(item),
|
|
||||||
value: String(item.id),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function toRocketResourceSelectOptions(
|
|
||||||
record: Record<string, any>,
|
|
||||||
key: 'rocketAnimationUrl' | 'rocketIconUrl',
|
|
||||||
) {
|
|
||||||
const options = rocketResourceRows.value
|
|
||||||
.map((item) => {
|
|
||||||
const value =
|
|
||||||
key === 'rocketIconUrl'
|
|
||||||
? String(item.cover || item.sourceUrl || '')
|
|
||||||
: String(item.sourceUrl || item.cover || '');
|
|
||||||
return {
|
|
||||||
label: optionLabel(item),
|
|
||||||
value,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.filter((item) => item.value);
|
|
||||||
const current = String(record[key] || '');
|
|
||||||
if (current && !options.some((item) => item.value === current)) {
|
|
||||||
options.unshift({ label: current, value: current });
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadRocketResources() {
|
|
||||||
if (!selectedSysOrigin.value || rocketResourceLoading.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rocketResourceLoading.value = true;
|
|
||||||
try {
|
|
||||||
const result = await listSysOriginTypeList(
|
|
||||||
selectedSysOrigin.value,
|
|
||||||
ROCKET_RESOURCE_TYPE,
|
|
||||||
);
|
|
||||||
rocketResourceRows.value = (result || []).map((item) =>
|
|
||||||
normalizePropsResource(item, ROCKET_RESOURCE_TYPE),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
rocketResourceLoading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function rewardResourceType(rewardType: string) {
|
function rewardResourceType(rewardType: string) {
|
||||||
const value = String(rewardType || '').trim();
|
const value = String(rewardType || '').trim();
|
||||||
const mapping: Record<string, string> = {
|
const mapping: Record<string, string> = {
|
||||||
@ -403,57 +323,65 @@ function rewardResourceType(rewardType: string) {
|
|||||||
return mapping[value] || '';
|
return mapping[value] || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rewardTypeText(rewardType: string) {
|
||||||
|
return (
|
||||||
|
rewardTypeOptions.find((item) => item.value === rewardType)?.label ||
|
||||||
|
rewardType ||
|
||||||
|
'资源'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function isRewardResourceDisabled(row: Record<string, any>) {
|
function isRewardResourceDisabled(row: Record<string, any>) {
|
||||||
return row.rewardType === 'GOLD' || row.rewardType === 'NONE';
|
return row.rewardType === 'GOLD' || row.rewardType === 'NONE';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadRewardResources(rewardType: string) {
|
function openLevelResourcePicker(
|
||||||
const type = String(rewardType || '').trim();
|
row: Record<string, any>,
|
||||||
const sourceType = rewardResourceType(type);
|
field: 'rocketAnimationUrl' | 'rocketIconUrl',
|
||||||
if (
|
) {
|
||||||
!selectedSysOrigin.value ||
|
resourcePickerTarget.value = {
|
||||||
!sourceType ||
|
field,
|
||||||
(rewardResourceRows[type] || []).length > 0
|
kind: 'level',
|
||||||
) {
|
row,
|
||||||
return;
|
};
|
||||||
}
|
resourcePickerTitle.value =
|
||||||
rewardResourceLoading[type] = true;
|
field === 'rocketIconUrl' ? '选择火箭图标资源' : '选择发射动画资源';
|
||||||
try {
|
resourcePickerDefaultType.value = 'ALL_PROPS';
|
||||||
if (type === 'GIFT') {
|
resourcePickerFixedType.value = false;
|
||||||
const result = await listGiftBySysOrigin(selectedSysOrigin.value);
|
resourcePickerOpen.value = true;
|
||||||
rewardResourceRows[type] = (result || []).map(normalizeGiftResource);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (type === 'BADGE') {
|
|
||||||
const result = await listBadgePictureBySysOrigin(
|
|
||||||
selectedSysOrigin.value,
|
|
||||||
'ACTIVITY',
|
|
||||||
);
|
|
||||||
rewardResourceRows[type] = (result || []).map(normalizeBadgeResource);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const loader =
|
|
||||||
type === 'VIP_CARD' ? listNotFamilyBySysOriginType : listSysOriginTypeList;
|
|
||||||
const result = await loader(selectedSysOrigin.value, sourceType);
|
|
||||||
rewardResourceRows[type] = (result || []).map((item) =>
|
|
||||||
normalizePropsResource(item, sourceType),
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
rewardResourceLoading[type] = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function rewardResourceOptions(row: Record<string, any>) {
|
function openRewardResourcePicker(row: Record<string, any>) {
|
||||||
const rows = rewardResourceRows[row.rewardType] || [];
|
if (isRewardResourceDisabled(row)) {
|
||||||
const options = toResourceSelectOptions(rows);
|
return;
|
||||||
const current = String(row.rewardItemId || '').trim();
|
}
|
||||||
if (current && !options.some((item) => item.value === current)) {
|
const sourceType = rewardResourceType(row.rewardType);
|
||||||
options.unshift({
|
if (!sourceType) {
|
||||||
label: `${current} / ${row.rewardName || '已配置资源'}`,
|
return;
|
||||||
value: current,
|
}
|
||||||
});
|
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 = '';
|
||||||
}
|
}
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRewardSceneChange(row: Record<string, any>) {
|
function handleRewardSceneChange(row: Record<string, any>) {
|
||||||
@ -463,21 +391,32 @@ function handleRewardSceneChange(row: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRewardResourceChange(
|
function handleResourceSelect(item: ResourceOption) {
|
||||||
row: Record<string, any>,
|
const target = resourcePickerTarget.value;
|
||||||
value?: null | number | string,
|
if (!target) {
|
||||||
) {
|
|
||||||
const selected =
|
|
||||||
(rewardResourceRows[row.rewardType] || []).find(
|
|
||||||
(item) => String(item.id) === String(value),
|
|
||||||
) || null;
|
|
||||||
if (!selected) {
|
|
||||||
row.rewardItemId = value || null;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
row.rewardItemId = selected.id;
|
if (target.kind === 'level') {
|
||||||
row.rewardName = selected.name;
|
const value =
|
||||||
row.rewardCover = selected.cover || selected.sourceUrl || '';
|
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() {
|
async function loadConfig() {
|
||||||
@ -523,7 +462,7 @@ async function loadRewards() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadAll() {
|
async function loadAll() {
|
||||||
await Promise.all([loadConfig(), loadLevels(), loadRewards(), loadRocketResources()]);
|
await Promise.all([loadConfig(), loadLevels(), loadRewards()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveConfig() {
|
async function saveConfig() {
|
||||||
@ -630,7 +569,6 @@ function handleRewardTypeChange(row: Record<string, any>) {
|
|||||||
if (row.rewardName === '金币' || row.rewardName === '轮空') {
|
if (row.rewardName === '金币' || row.rewardName === '轮空') {
|
||||||
row.rewardName = '';
|
row.rewardName = '';
|
||||||
}
|
}
|
||||||
void loadRewardResources(row.rewardType);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateRewards() {
|
function validateRewards() {
|
||||||
@ -714,10 +652,6 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
selectedSysOrigin,
|
selectedSysOrigin,
|
||||||
() => {
|
() => {
|
||||||
rocketResourceRows.value = [];
|
|
||||||
Object.keys(rewardResourceRows).forEach((key) => {
|
|
||||||
delete rewardResourceRows[key];
|
|
||||||
});
|
|
||||||
void loadAll();
|
void loadAll();
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
@ -865,30 +799,62 @@ watch(rewardLevel, () => {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'rocketIconUrl'">
|
<template v-else-if="column.key === 'rocketIconUrl'">
|
||||||
<Select
|
<div class="resource-cell">
|
||||||
v-model:value="record.rocketIconUrl"
|
<div class="resource-cell__preview">
|
||||||
:loading="rocketResourceLoading"
|
<img
|
||||||
:options="toRocketResourceSelectOptions(record, 'rocketIconUrl')"
|
v-if="record.rocketIconUrl"
|
||||||
allow-clear
|
:src="record.rocketIconUrl"
|
||||||
class="table-resource-select"
|
alt=""
|
||||||
option-filter-prop="label"
|
/>
|
||||||
show-search
|
<span>{{ resourceUrlName(record.rocketIconUrl) }}</span>
|
||||||
@focus="loadRocketResources"
|
</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>
|
||||||
<template v-else-if="column.key === 'rocketAnimationUrl'">
|
<template v-else-if="column.key === 'rocketAnimationUrl'">
|
||||||
<Select
|
<div class="resource-cell">
|
||||||
v-model:value="record.rocketAnimationUrl"
|
<div class="resource-cell__preview">
|
||||||
:loading="rocketResourceLoading"
|
<img
|
||||||
:options="
|
v-if="record.rocketAnimationUrl"
|
||||||
toRocketResourceSelectOptions(record, 'rocketAnimationUrl')
|
:src="record.rocketAnimationUrl"
|
||||||
"
|
alt=""
|
||||||
allow-clear
|
/>
|
||||||
class="table-resource-select"
|
<span>{{ resourceUrlName(record.rocketAnimationUrl) }}</span>
|
||||||
option-filter-prop="label"
|
</div>
|
||||||
show-search
|
<Space>
|
||||||
@focus="loadRocketResources"
|
<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>
|
||||||
<template v-else-if="column.key === 'shakeThresholdPercent'">
|
<template v-else-if="column.key === 'shakeThresholdPercent'">
|
||||||
<Input
|
<Input
|
||||||
@ -966,18 +932,32 @@ watch(rewardLevel, () => {
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'rewardItemId'">
|
<template v-else-if="column.key === 'rewardItemId'">
|
||||||
<Select
|
<div class="resource-cell">
|
||||||
v-model:value="record.rewardItemId"
|
<div class="resource-cell__summary">
|
||||||
:disabled="isRewardResourceDisabled(record)"
|
<span v-if="isRewardResourceDisabled(record)">-</span>
|
||||||
:loading="rewardResourceLoading[record.rewardType]"
|
<span v-else-if="record.rewardItemId">
|
||||||
:options="rewardResourceOptions(record)"
|
{{ record.rewardName || '已选资源' }} / ID:
|
||||||
allow-clear
|
{{ record.rewardItemId }}
|
||||||
class="table-resource-select"
|
</span>
|
||||||
option-filter-prop="label"
|
<span v-else>未选择</span>
|
||||||
show-search
|
</div>
|
||||||
@change="(value) => handleRewardResourceChange(record, value)"
|
<Space v-if="!isRewardResourceDisabled(record)">
|
||||||
@focus="() => loadRewardResources(record.rewardType)"
|
<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>
|
||||||
<template v-else-if="column.key === 'rewardName'">
|
<template v-else-if="column.key === 'rewardName'">
|
||||||
<Input
|
<Input
|
||||||
@ -1058,6 +1038,16 @@ watch(rewardLevel, () => {
|
|||||||
</Card>
|
</Card>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
|
<ResourceSelectDrawer
|
||||||
|
:default-type="resourcePickerDefaultType"
|
||||||
|
:fixed-type="resourcePickerFixedType"
|
||||||
|
:open="resourcePickerOpen"
|
||||||
|
:sys-origin="selectedSysOrigin"
|
||||||
|
:title="resourcePickerTitle"
|
||||||
|
@close="resourcePickerOpen = false"
|
||||||
|
@select="handleResourceSelect"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
@ -1121,14 +1111,43 @@ watch(rewardLevel, () => {
|
|||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-resource-select {
|
|
||||||
width: 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-number {
|
.table-number {
|
||||||
width: 110px;
|
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 {
|
.cover-preview {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user