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,
|
||||
saveVoiceRoomRocketRewardConfigs,
|
||||
} 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 ResourceSelectDrawer from './components/resource-select-drawer.vue';
|
||||
|
||||
defineOptions({ name: 'ResidentVoiceRoomRocketPage' });
|
||||
|
||||
const RIYADH_TIMEZONE = 'Asia/Riyadh';
|
||||
@ -95,12 +91,6 @@ const rewardLoading = ref(false);
|
||||
const configSaving = ref(false);
|
||||
const levelSaving = 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({
|
||||
broadcastDurationSeconds: 7,
|
||||
@ -120,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' },
|
||||
@ -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) {
|
||||
const value = String(rewardType || '').trim();
|
||||
const mapping: Record<string, string> = {
|
||||
@ -403,57 +323,65 @@ function rewardResourceType(rewardType: string) {
|
||||
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';
|
||||
}
|
||||
|
||||
async function loadRewardResources(rewardType: string) {
|
||||
const type = String(rewardType || '').trim();
|
||||
const sourceType = rewardResourceType(type);
|
||||
if (
|
||||
!selectedSysOrigin.value ||
|
||||
!sourceType ||
|
||||
(rewardResourceRows[type] || []).length > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
rewardResourceLoading[type] = true;
|
||||
try {
|
||||
if (type === 'GIFT') {
|
||||
const result = await listGiftBySysOrigin(selectedSysOrigin.value);
|
||||
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 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 rewardResourceOptions(row: Record<string, any>) {
|
||||
const rows = rewardResourceRows[row.rewardType] || [];
|
||||
const options = toResourceSelectOptions(rows);
|
||||
const current = String(row.rewardItemId || '').trim();
|
||||
if (current && !options.some((item) => item.value === current)) {
|
||||
options.unshift({
|
||||
label: `${current} / ${row.rewardName || '已配置资源'}`,
|
||||
value: current,
|
||||
});
|
||||
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 = '';
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function handleRewardSceneChange(row: Record<string, any>) {
|
||||
@ -463,21 +391,32 @@ function handleRewardSceneChange(row: Record<string, any>) {
|
||||
}
|
||||
}
|
||||
|
||||
function handleRewardResourceChange(
|
||||
row: Record<string, any>,
|
||||
value?: null | number | string,
|
||||
) {
|
||||
const selected =
|
||||
(rewardResourceRows[row.rewardType] || []).find(
|
||||
(item) => String(item.id) === String(value),
|
||||
) || null;
|
||||
if (!selected) {
|
||||
row.rewardItemId = value || null;
|
||||
function handleResourceSelect(item: ResourceOption) {
|
||||
const target = resourcePickerTarget.value;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
row.rewardItemId = selected.id;
|
||||
row.rewardName = selected.name;
|
||||
row.rewardCover = selected.cover || selected.sourceUrl || '';
|
||||
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() {
|
||||
@ -523,7 +462,7 @@ async function loadRewards() {
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
await Promise.all([loadConfig(), loadLevels(), loadRewards(), loadRocketResources()]);
|
||||
await Promise.all([loadConfig(), loadLevels(), loadRewards()]);
|
||||
}
|
||||
|
||||
async function saveConfig() {
|
||||
@ -630,7 +569,6 @@ function handleRewardTypeChange(row: Record<string, any>) {
|
||||
if (row.rewardName === '金币' || row.rewardName === '轮空') {
|
||||
row.rewardName = '';
|
||||
}
|
||||
void loadRewardResources(row.rewardType);
|
||||
}
|
||||
|
||||
function validateRewards() {
|
||||
@ -714,10 +652,6 @@ watch(
|
||||
watch(
|
||||
selectedSysOrigin,
|
||||
() => {
|
||||
rocketResourceRows.value = [];
|
||||
Object.keys(rewardResourceRows).forEach((key) => {
|
||||
delete rewardResourceRows[key];
|
||||
});
|
||||
void loadAll();
|
||||
},
|
||||
{ immediate: true },
|
||||
@ -865,30 +799,62 @@ watch(rewardLevel, () => {
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rocketIconUrl'">
|
||||
<Select
|
||||
v-model:value="record.rocketIconUrl"
|
||||
:loading="rocketResourceLoading"
|
||||
:options="toRocketResourceSelectOptions(record, 'rocketIconUrl')"
|
||||
allow-clear
|
||||
class="table-resource-select"
|
||||
option-filter-prop="label"
|
||||
show-search
|
||||
@focus="loadRocketResources"
|
||||
/>
|
||||
<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'">
|
||||
<Select
|
||||
v-model:value="record.rocketAnimationUrl"
|
||||
:loading="rocketResourceLoading"
|
||||
:options="
|
||||
toRocketResourceSelectOptions(record, 'rocketAnimationUrl')
|
||||
"
|
||||
allow-clear
|
||||
class="table-resource-select"
|
||||
option-filter-prop="label"
|
||||
show-search
|
||||
@focus="loadRocketResources"
|
||||
/>
|
||||
<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
|
||||
@ -966,18 +932,32 @@ watch(rewardLevel, () => {
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardItemId'">
|
||||
<Select
|
||||
v-model:value="record.rewardItemId"
|
||||
:disabled="isRewardResourceDisabled(record)"
|
||||
:loading="rewardResourceLoading[record.rewardType]"
|
||||
:options="rewardResourceOptions(record)"
|
||||
allow-clear
|
||||
class="table-resource-select"
|
||||
option-filter-prop="label"
|
||||
show-search
|
||||
@change="(value) => handleRewardResourceChange(record, value)"
|
||||
@focus="() => loadRewardResources(record.rewardType)"
|
||||
/>
|
||||
<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
|
||||
@ -1058,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>
|
||||
@ -1121,14 +1111,43 @@ watch(rewardLevel, () => {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
.table-resource-select {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user