feat(admin): show recharge reward recipients
This commit is contained in:
parent
864502db1c
commit
e5d2c94c04
@ -110,6 +110,15 @@ export async function saveResidentRechargeRewardConfig(data: Record<string, any>
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageResidentRechargeRewardClaimRecords(
|
||||
params: Record<string, any>,
|
||||
) {
|
||||
return requestClient.get<Record<string, any>>(
|
||||
`${RECHARGE_REWARD_BASE}/claim-record/page`,
|
||||
{ params },
|
||||
);
|
||||
}
|
||||
|
||||
export async function pageSpecifiedGiftWeeklyRankConfigs(params: Record<string, any>) {
|
||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||
`${SPECIFIED_GIFT_WEEKLY_RANK_BASE}/config/page`,
|
||||
|
||||
@ -16,11 +16,14 @@ import {
|
||||
Spin,
|
||||
Switch,
|
||||
Table,
|
||||
Tabs,
|
||||
TabPane,
|
||||
Tag,
|
||||
} from 'antdv-next';
|
||||
|
||||
import {
|
||||
getResidentRechargeRewardConfig,
|
||||
pageResidentRechargeRewardClaimRecords,
|
||||
saveResidentRechargeRewardConfig,
|
||||
} from '#/api/legacy/resident-activity';
|
||||
import ActivityResourceGroupSelectDrawer from '#/views/props/components/activity-resource-group-select-drawer.vue';
|
||||
@ -66,7 +69,9 @@ const sysOriginOptions = computed(() => {
|
||||
});
|
||||
|
||||
const selectedSysOrigin = ref('');
|
||||
const activeTab = ref('config');
|
||||
const loading = ref(false);
|
||||
const recordLoading = ref(false);
|
||||
const saving = ref(false);
|
||||
const groupPickerOpen = ref(false);
|
||||
const pickerLevelIndex = ref(-1);
|
||||
@ -104,6 +109,31 @@ const statusMeta = computed(() => {
|
||||
return { color: 'default', text: '已关闭' };
|
||||
});
|
||||
|
||||
const recordQuery = reactive<Record<string, any>>({
|
||||
cursor: 1,
|
||||
limit: 20,
|
||||
month: '',
|
||||
userId: '',
|
||||
});
|
||||
|
||||
const recordPage = reactive<Record<string, any>>({
|
||||
current: 1,
|
||||
month: '',
|
||||
records: [],
|
||||
size: 20,
|
||||
timezone: '',
|
||||
total: 0,
|
||||
});
|
||||
|
||||
const recordColumns = [
|
||||
{ dataIndex: 'user', key: 'user', title: '用户', width: 280 },
|
||||
{ dataIndex: 'rechargeMonth', key: 'rechargeMonth', title: '统计月份', width: 120 },
|
||||
{ dataIndex: 'rechargeAmount', key: 'rechargeAmount', title: '充值金额', width: 140 },
|
||||
{ dataIndex: 'reachedLevels', key: 'reachedLevels', title: '达到档位', width: 300 },
|
||||
{ dataIndex: 'claimedRewards', key: 'claimedRewards', title: '领取奖励', width: 420 },
|
||||
{ dataIndex: 'lastRechargeTime', key: 'lastRechargeTime', title: '最后充值时间', width: 180 },
|
||||
];
|
||||
|
||||
function toAmountCents(value: number | string | undefined) {
|
||||
const numeric = Number(value || 0);
|
||||
if (!Number.isFinite(numeric)) {
|
||||
@ -164,6 +194,35 @@ async function loadConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeRecordPage(result: null | Record<string, any> | undefined) {
|
||||
const value = result || {};
|
||||
recordPage.records = Array.isArray(value.records) ? value.records : [];
|
||||
recordPage.total = Number(value.total || 0);
|
||||
recordPage.current = Number(value.current || 1);
|
||||
recordPage.size = Number(value.size || 20);
|
||||
recordPage.month = String(value.month || '');
|
||||
recordPage.timezone = String(value.timezone || '');
|
||||
if (!recordQuery.month && recordPage.month) {
|
||||
recordQuery.month = recordPage.month;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadClaimRecords() {
|
||||
if (!selectedSysOrigin.value) {
|
||||
return;
|
||||
}
|
||||
recordLoading.value = true;
|
||||
try {
|
||||
const result = await pageResidentRechargeRewardClaimRecords({
|
||||
...recordQuery,
|
||||
sysOrigin: selectedSysOrigin.value,
|
||||
});
|
||||
normalizeRecordPage(result);
|
||||
} finally {
|
||||
recordLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function addLevel() {
|
||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
||||
@ -273,11 +332,55 @@ async function submitForm() {
|
||||
});
|
||||
message.success('保存成功');
|
||||
await loadConfig();
|
||||
if (activeTab.value === 'claims') {
|
||||
await loadClaimRecords();
|
||||
}
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function searchClaimRecords() {
|
||||
recordQuery.cursor = 1;
|
||||
void loadClaimRecords();
|
||||
}
|
||||
|
||||
function handleClaimTableChange(pagination: Record<string, any>) {
|
||||
recordQuery.cursor = Number(pagination.current || 1);
|
||||
recordQuery.limit = Number(pagination.pageSize || 20);
|
||||
void loadClaimRecords();
|
||||
}
|
||||
|
||||
function avatarBackgroundStyle(url: string) {
|
||||
return {
|
||||
backgroundImage: `url("${String(url || '').replaceAll('"', '%22')}")`,
|
||||
};
|
||||
}
|
||||
|
||||
function rewardText(item: Record<string, any>) {
|
||||
const parts: string[] = [];
|
||||
const rewardGold = Number(item.rewardGold || 0);
|
||||
if (rewardGold > 0) {
|
||||
parts.push(`${rewardGold} 金币`);
|
||||
}
|
||||
if (item.rewardGroupName || item.rewardGroupId) {
|
||||
parts.push(item.rewardGroupName || `奖励组 ${item.rewardGroupId}`);
|
||||
}
|
||||
const rewardItems = Array.isArray(item.rewardItems) ? item.rewardItems : [];
|
||||
if (rewardItems.length > 0) {
|
||||
parts.push(
|
||||
rewardItems
|
||||
.map((reward: Record<string, any>) => {
|
||||
const name = reward.name || reward.type || reward.content || '奖励';
|
||||
const quantity = Number(reward.quantity || 0);
|
||||
return quantity > 0 ? `${name} x${quantity}` : String(name);
|
||||
})
|
||||
.join('、'),
|
||||
);
|
||||
}
|
||||
return parts.length > 0 ? parts.join(' / ') : '-';
|
||||
}
|
||||
|
||||
watch(
|
||||
sysOriginOptions,
|
||||
(options) => {
|
||||
@ -292,11 +395,19 @@ watch(
|
||||
selectedSysOrigin,
|
||||
(value) => {
|
||||
if (value) {
|
||||
recordQuery.cursor = 1;
|
||||
void loadConfig();
|
||||
void loadClaimRecords();
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
watch(activeTab, (value) => {
|
||||
if (value === 'claims') {
|
||||
void loadClaimRecords();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -318,85 +429,167 @@ watch(
|
||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
||||
</div>
|
||||
|
||||
<div class="config-row">
|
||||
<Space align="center" wrap>
|
||||
<span class="field-label">启用</span>
|
||||
<Switch v-model:checked="form.enabled" />
|
||||
<span class="field-label">统计时区</span>
|
||||
<Input v-model:value="form.timezone" style="width: 180px" />
|
||||
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
||||
</Space>
|
||||
</div>
|
||||
<Tabs v-model:activeKey="activeTab">
|
||||
<TabPane key="config" tab="奖励配置">
|
||||
<div class="config-row">
|
||||
<Space align="center" wrap>
|
||||
<span class="field-label">启用</span>
|
||||
<Switch v-model:checked="form.enabled" />
|
||||
<span class="field-label">统计时区</span>
|
||||
<Input v-model:value="form.timezone" style="width: 180px" />
|
||||
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
:columns="levelColumns"
|
||||
:data-source="form.levelConfigs"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="rowKey"
|
||||
:scroll="{ x: 1020 }"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'enabled'">
|
||||
<Switch v-model:checked="record.enabled" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<InputNumber
|
||||
v-model:value="record.level"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
style="width: 90px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rechargeAmount'">
|
||||
<InputNumber
|
||||
v-model:value="record.rechargeAmount"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
<Table
|
||||
:columns="levelColumns"
|
||||
:data-source="form.levelConfigs"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
row-key="rowKey"
|
||||
:scroll="{ x: 1020 }"
|
||||
>
|
||||
<template #bodyCell="{ column, index, record }">
|
||||
<template v-if="column.key === 'enabled'">
|
||||
<Switch v-model:checked="record.enabled" />
|
||||
</template>
|
||||
<template v-else-if="column.key === 'level'">
|
||||
<InputNumber
|
||||
v-model:value="record.level"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
style="width: 90px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rechargeAmount'">
|
||||
<InputNumber
|
||||
v-model:value="record.rechargeAmount"
|
||||
:min="0.01"
|
||||
:precision="2"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGold'">
|
||||
<InputNumber
|
||||
v-model:value="record.rewardGold"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGroup'">
|
||||
<div class="reward-group-cell">
|
||||
<Space wrap>
|
||||
<Button size="small" @click="openRewardPicker(index)">
|
||||
{{ record.rewardGroupId ? '更换奖励组' : '选择奖励组' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rewardGroupId"
|
||||
danger
|
||||
size="small"
|
||||
type="link"
|
||||
@click="clearRewardGroup(record)"
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</Space>
|
||||
<div v-if="record.rewardGroupId" class="reward-group-name">
|
||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId }})
|
||||
</div>
|
||||
<div v-else class="sub-text">未选择奖励组</div>
|
||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<Button danger type="link" @click="confirmRemoveLevel(index)">删除</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="actions">
|
||||
<Button @click="addLevel">新增档位</Button>
|
||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
||||
</div>
|
||||
</TabPane>
|
||||
|
||||
<TabPane key="claims" tab="已领取人员">
|
||||
<div class="toolbar">
|
||||
<Input
|
||||
v-model:value="recordQuery.month"
|
||||
allow-clear
|
||||
placeholder="YYYY-MM"
|
||||
style="width: 140px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGold'">
|
||||
<InputNumber
|
||||
v-model:value="record.rewardGold"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
style="width: 140px"
|
||||
<Input
|
||||
v-model:value="recordQuery.userId"
|
||||
allow-clear
|
||||
placeholder="用户长ID"
|
||||
style="width: 180px"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rewardGroup'">
|
||||
<div class="reward-group-cell">
|
||||
<Space wrap>
|
||||
<Button size="small" @click="openRewardPicker(index)">
|
||||
{{ record.rewardGroupId ? '更换奖励组' : '选择奖励组' }}
|
||||
</Button>
|
||||
<Button
|
||||
v-if="record.rewardGroupId"
|
||||
danger
|
||||
size="small"
|
||||
type="link"
|
||||
@click="clearRewardGroup(record)"
|
||||
>
|
||||
清空
|
||||
</Button>
|
||||
</Space>
|
||||
<div v-if="record.rewardGroupId" class="reward-group-name">
|
||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId }})
|
||||
</div>
|
||||
<div v-else class="sub-text">未选择奖励组</div>
|
||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'actions'">
|
||||
<Button danger type="link" @click="confirmRemoveLevel(index)">删除</Button>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
|
||||
<div class="actions">
|
||||
<Button @click="addLevel">新增档位</Button>
|
||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
||||
</div>
|
||||
<Button :loading="recordLoading" type="primary" @click="searchClaimRecords">
|
||||
查询
|
||||
</Button>
|
||||
</div>
|
||||
<Table
|
||||
:columns="recordColumns"
|
||||
:data-source="recordPage.records"
|
||||
:loading="recordLoading"
|
||||
:pagination="{
|
||||
current: recordPage.current,
|
||||
pageSize: recordPage.size,
|
||||
showSizeChanger: true,
|
||||
total: recordPage.total,
|
||||
}"
|
||||
row-key="recordKey"
|
||||
:scroll="{ x: 1440 }"
|
||||
@change="handleClaimTableChange"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'user'">
|
||||
<div class="user-cell">
|
||||
<div
|
||||
v-if="record.userAvatar"
|
||||
class="user-avatar"
|
||||
:style="avatarBackgroundStyle(record.userAvatar)"
|
||||
/>
|
||||
<div class="user-main">
|
||||
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
||||
<div class="sub-text">
|
||||
{{ record.account || record.userId || '-' }}
|
||||
<span v-if="record.countryCode"> · {{ record.countryCode }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'rechargeAmount'">
|
||||
{{ record.rechargeAmount || '0.00' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'reachedLevels'">
|
||||
<Space wrap>
|
||||
<Tag
|
||||
v-for="item in record.reachedLevels || []"
|
||||
:key="`${record.recordKey}-level-${item.level}`"
|
||||
color="success"
|
||||
>
|
||||
Lv{{ item.level }} / {{ item.rechargeAmount }}
|
||||
</Tag>
|
||||
</Space>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'claimedRewards'">
|
||||
<div class="reward-summary">
|
||||
<div
|
||||
v-for="item in record.claimedRewards || []"
|
||||
:key="`${record.recordKey}-reward-${item.level}`"
|
||||
class="reward-line"
|
||||
>
|
||||
Lv{{ item.level }}:{{ rewardText(item) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</Table>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
<ActivityResourceGroupSelectDrawer
|
||||
@ -442,6 +635,47 @@ watch(
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.user-cell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
background-color: rgb(226 232 240);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
flex: 0 0 auto;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
.user-main {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.user-name {
|
||||
color: rgb(15 23 42);
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.reward-summary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.reward-line {
|
||||
color: rgb(15 23 42);
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user