443 lines
9.6 KiB
Vue
443 lines
9.6 KiB
Vue
<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>
|