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>) {
|
export async function pageSpecifiedGiftWeeklyRankConfigs(params: Record<string, any>) {
|
||||||
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
return requestClient.get<LegacyPageResult<Record<string, any>>>(
|
||||||
`${SPECIFIED_GIFT_WEEKLY_RANK_BASE}/config/page`,
|
`${SPECIFIED_GIFT_WEEKLY_RANK_BASE}/config/page`,
|
||||||
|
|||||||
@ -16,11 +16,14 @@ import {
|
|||||||
Spin,
|
Spin,
|
||||||
Switch,
|
Switch,
|
||||||
Table,
|
Table,
|
||||||
|
Tabs,
|
||||||
|
TabPane,
|
||||||
Tag,
|
Tag,
|
||||||
} from 'antdv-next';
|
} from 'antdv-next';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getResidentRechargeRewardConfig,
|
getResidentRechargeRewardConfig,
|
||||||
|
pageResidentRechargeRewardClaimRecords,
|
||||||
saveResidentRechargeRewardConfig,
|
saveResidentRechargeRewardConfig,
|
||||||
} from '#/api/legacy/resident-activity';
|
} from '#/api/legacy/resident-activity';
|
||||||
import ActivityResourceGroupSelectDrawer from '#/views/props/components/activity-resource-group-select-drawer.vue';
|
import ActivityResourceGroupSelectDrawer from '#/views/props/components/activity-resource-group-select-drawer.vue';
|
||||||
@ -66,7 +69,9 @@ const sysOriginOptions = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const selectedSysOrigin = ref('');
|
const selectedSysOrigin = ref('');
|
||||||
|
const activeTab = ref('config');
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const recordLoading = ref(false);
|
||||||
const saving = ref(false);
|
const saving = ref(false);
|
||||||
const groupPickerOpen = ref(false);
|
const groupPickerOpen = ref(false);
|
||||||
const pickerLevelIndex = ref(-1);
|
const pickerLevelIndex = ref(-1);
|
||||||
@ -104,6 +109,31 @@ const statusMeta = computed(() => {
|
|||||||
return { color: 'default', text: '已关闭' };
|
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) {
|
function toAmountCents(value: number | string | undefined) {
|
||||||
const numeric = Number(value || 0);
|
const numeric = Number(value || 0);
|
||||||
if (!Number.isFinite(numeric)) {
|
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() {
|
function addLevel() {
|
||||||
const levels = form.levelConfigs as Array<Record<string, any>>;
|
const levels = form.levelConfigs as Array<Record<string, any>>;
|
||||||
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
const maxLevel = Math.max(0, ...levels.map((item) => Number(item.level || 0)));
|
||||||
@ -273,11 +332,55 @@ async function submitForm() {
|
|||||||
});
|
});
|
||||||
message.success('保存成功');
|
message.success('保存成功');
|
||||||
await loadConfig();
|
await loadConfig();
|
||||||
|
if (activeTab.value === 'claims') {
|
||||||
|
await loadClaimRecords();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
saving.value = false;
|
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(
|
watch(
|
||||||
sysOriginOptions,
|
sysOriginOptions,
|
||||||
(options) => {
|
(options) => {
|
||||||
@ -292,11 +395,19 @@ watch(
|
|||||||
selectedSysOrigin,
|
selectedSysOrigin,
|
||||||
(value) => {
|
(value) => {
|
||||||
if (value) {
|
if (value) {
|
||||||
|
recordQuery.cursor = 1;
|
||||||
void loadConfig();
|
void loadConfig();
|
||||||
|
void loadClaimRecords();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(activeTab, (value) => {
|
||||||
|
if (value === 'claims') {
|
||||||
|
void loadClaimRecords();
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -318,85 +429,167 @@ watch(
|
|||||||
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
<Button :loading="loading" @click="loadConfig">刷新</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row">
|
<Tabs v-model:activeKey="activeTab">
|
||||||
<Space align="center" wrap>
|
<TabPane key="config" tab="奖励配置">
|
||||||
<span class="field-label">启用</span>
|
<div class="config-row">
|
||||||
<Switch v-model:checked="form.enabled" />
|
<Space align="center" wrap>
|
||||||
<span class="field-label">统计时区</span>
|
<span class="field-label">启用</span>
|
||||||
<Input v-model:value="form.timezone" style="width: 180px" />
|
<Switch v-model:checked="form.enabled" />
|
||||||
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
<span class="field-label">统计时区</span>
|
||||||
</Space>
|
<Input v-model:value="form.timezone" style="width: 180px" />
|
||||||
</div>
|
<span class="sub-text">更新时间:{{ form.updateTime || '-' }}</span>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Table
|
<Table
|
||||||
:columns="levelColumns"
|
:columns="levelColumns"
|
||||||
:data-source="form.levelConfigs"
|
:data-source="form.levelConfigs"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
:pagination="false"
|
:pagination="false"
|
||||||
row-key="rowKey"
|
row-key="rowKey"
|
||||||
:scroll="{ x: 1020 }"
|
:scroll="{ x: 1020 }"
|
||||||
>
|
>
|
||||||
<template #bodyCell="{ column, index, record }">
|
<template #bodyCell="{ column, index, record }">
|
||||||
<template v-if="column.key === 'enabled'">
|
<template v-if="column.key === 'enabled'">
|
||||||
<Switch v-model:checked="record.enabled" />
|
<Switch v-model:checked="record.enabled" />
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'level'">
|
<template v-else-if="column.key === 'level'">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
v-model:value="record.level"
|
v-model:value="record.level"
|
||||||
:min="1"
|
:min="1"
|
||||||
:precision="0"
|
:precision="0"
|
||||||
style="width: 90px"
|
style="width: 90px"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.key === 'rechargeAmount'">
|
<template v-else-if="column.key === 'rechargeAmount'">
|
||||||
<InputNumber
|
<InputNumber
|
||||||
v-model:value="record.rechargeAmount"
|
v-model:value="record.rechargeAmount"
|
||||||
:min="0.01"
|
:min="0.01"
|
||||||
:precision="2"
|
: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"
|
style="width: 140px"
|
||||||
/>
|
/>
|
||||||
</template>
|
<Input
|
||||||
<template v-else-if="column.key === 'rewardGold'">
|
v-model:value="recordQuery.userId"
|
||||||
<InputNumber
|
allow-clear
|
||||||
v-model:value="record.rewardGold"
|
placeholder="用户长ID"
|
||||||
:min="0"
|
style="width: 180px"
|
||||||
:precision="0"
|
|
||||||
style="width: 140px"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
<Button :loading="recordLoading" type="primary" @click="searchClaimRecords">
|
||||||
<template v-else-if="column.key === 'rewardGroup'">
|
查询
|
||||||
<div class="reward-group-cell">
|
</Button>
|
||||||
<Space wrap>
|
</div>
|
||||||
<Button size="small" @click="openRewardPicker(index)">
|
<Table
|
||||||
{{ record.rewardGroupId ? '更换奖励组' : '选择奖励组' }}
|
:columns="recordColumns"
|
||||||
</Button>
|
:data-source="recordPage.records"
|
||||||
<Button
|
:loading="recordLoading"
|
||||||
v-if="record.rewardGroupId"
|
:pagination="{
|
||||||
danger
|
current: recordPage.current,
|
||||||
size="small"
|
pageSize: recordPage.size,
|
||||||
type="link"
|
showSizeChanger: true,
|
||||||
@click="clearRewardGroup(record)"
|
total: recordPage.total,
|
||||||
>
|
}"
|
||||||
清空
|
row-key="recordKey"
|
||||||
</Button>
|
:scroll="{ x: 1440 }"
|
||||||
</Space>
|
@change="handleClaimTableChange"
|
||||||
<div v-if="record.rewardGroupId" class="reward-group-name">
|
>
|
||||||
{{ record.rewardGroupName || '-' }} (ID: {{ record.rewardGroupId }})
|
<template #bodyCell="{ column, record }">
|
||||||
</div>
|
<template v-if="column.key === 'user'">
|
||||||
<div v-else class="sub-text">未选择奖励组</div>
|
<div class="user-cell">
|
||||||
<RewardRow v-if="record.rewardItems?.length" :list="record.rewardItems" />
|
<div
|
||||||
</div>
|
v-if="record.userAvatar"
|
||||||
</template>
|
class="user-avatar"
|
||||||
<template v-else-if="column.key === 'actions'">
|
:style="avatarBackgroundStyle(record.userAvatar)"
|
||||||
<Button danger type="link" @click="confirmRemoveLevel(index)">删除</Button>
|
/>
|
||||||
</template>
|
<div class="user-main">
|
||||||
</template>
|
<div class="user-name">{{ record.userNickname || '-' }}</div>
|
||||||
</Table>
|
<div class="sub-text">
|
||||||
|
{{ record.account || record.userId || '-' }}
|
||||||
<div class="actions">
|
<span v-if="record.countryCode"> · {{ record.countryCode }}</span>
|
||||||
<Button @click="addLevel">新增档位</Button>
|
</div>
|
||||||
<Button :loading="saving" type="primary" @click="submitForm">保存配置</Button>
|
</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>
|
</Card>
|
||||||
|
|
||||||
<ActivityResourceGroupSelectDrawer
|
<ActivityResourceGroupSelectDrawer
|
||||||
@ -442,6 +635,47 @@ watch(
|
|||||||
margin-top: 8px;
|
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 {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user