feat: configure game king daily rewards
This commit is contained in:
parent
cc455ccfb8
commit
942e678f1a
@ -63,10 +63,11 @@ export function saveAslanGameKingRankRewards(id, rankRewards) {
|
||||
})
|
||||
}
|
||||
|
||||
export function listAslanGameKingSettlementRecords(id) {
|
||||
export function listAslanGameKingSettlementRecords(id, params) {
|
||||
return request({
|
||||
url: `${baseUrl}/settlement-records/${id}`,
|
||||
method: 'get'
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
@ -93,10 +94,11 @@ export function resolveAslanGameKingUnknownDeliveryItem(itemId, delivered) {
|
||||
})
|
||||
}
|
||||
|
||||
export function settleAslanGameKingActivity(id) {
|
||||
export function settleAslanGameKingActivity(id, params) {
|
||||
return request({
|
||||
url: `${baseUrl}/settlement/${id}`,
|
||||
method: 'post'
|
||||
method: 'post',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -90,7 +90,7 @@
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="后端判定活动已经开始:统计、结算、启用状态、七个奖项和六档排行奖励均已锁定;仅名称与说明可维护。"
|
||||
title="后端判定活动已经开始:统计、结算、启用状态、七个奖项和日榜/总榜排行奖励均已锁定;仅名称与说明可维护。"
|
||||
/>
|
||||
|
||||
<div class="section-title">基础配置</div>
|
||||
@ -193,7 +193,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排行周期">
|
||||
<el-input value="活动总榜(OVERALL)" disabled />
|
||||
<el-input value="日榜(DAILY)+ 活动总榜(OVERALL)" disabled />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
@ -260,14 +260,21 @@
|
||||
</el-table>
|
||||
<div class="weight-summary">当前总权重:{{ totalPrizeWeight }}</div>
|
||||
|
||||
<div class="section-title">排行榜结算奖励(每个榜单固定六档)</div>
|
||||
<div class="section-title">排行榜结算奖励(日榜 Top3 + 总榜六档)</div>
|
||||
<el-alert
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="消费榜和中奖榜分别配置六档奖励;名次范围不可增删或修改,失败记录可在结算记录中单独重试。"
|
||||
title="消费榜和中奖榜均配置 DAILY Top1/Top2/Top3,OVERALL 保留六档;名次范围不可修改。"
|
||||
/>
|
||||
<el-table :data="form.rankRewards" border class="config-table">
|
||||
<el-table-column label="周期" width="100" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.periodType === 'DAILY' ? 'success' : 'info'">
|
||||
{{ scope.row.periodType === 'DAILY' ? '日榜' : '总榜' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="榜单" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.rankingType === 'VICTORIOUS' ? 'warning' : 'primary'">
|
||||
@ -310,17 +317,30 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="dialog-actions">
|
||||
<el-select v-model="settlementPeriodType" style="width: 130px" @change="changeSettlementPeriod">
|
||||
<el-option label="活动总榜" value="OVERALL" />
|
||||
<el-option label="每日榜" value="DAILY" />
|
||||
</el-select>
|
||||
<el-date-picker
|
||||
v-if="settlementPeriodType === 'DAILY'"
|
||||
v-model="settlementStatDate"
|
||||
type="date"
|
||||
value-format="yyyy-MM-dd"
|
||||
placeholder="选择活动日期"
|
||||
style="width: 165px"
|
||||
@change="loadSettlements"
|
||||
/>
|
||||
<el-button icon="el-icon-refresh" :loading="settlementsLoading" @click="loadSettlements">刷新</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
icon="el-icon-s-check"
|
||||
:disabled="!settlementReady(selectedActivity)"
|
||||
:disabled="!manualSettlementReady()"
|
||||
:loading="settleLoading"
|
||||
@click="manualSettle"
|
||||
>
|
||||
手工结算
|
||||
</el-button>
|
||||
<span class="form-help">到达“结束时间 + 结算等待”后才可执行;UNKNOWN 必须按奖励项 ID 核账,禁止直接重发。</span>
|
||||
<span class="form-help">日榜按活动时区在自然日结束并等待配置分钟后冻结;UNKNOWN 必须核账,禁止直接重发。</span>
|
||||
</div>
|
||||
<el-table v-loading="settlementsLoading" :data="settlementRecords" border max-height="570">
|
||||
<el-table-column type="expand" width="50">
|
||||
@ -368,6 +388,12 @@
|
||||
</el-table>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="周期 / 日期" width="135" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div>{{ scope.row.periodType === 'DAILY' ? '日榜' : '总榜' }}</div>
|
||||
<div class="secondary-text">{{ scope.row.statDate || '-' }}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="榜单" width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.rankingType === 'VICTORIOUS' ? 'warning' : 'primary'">
|
||||
@ -564,7 +590,7 @@ import {
|
||||
settleAslanGameKingActivity
|
||||
} from '@/api/aslan-game-king'
|
||||
|
||||
const REQUIRED_RANK_RANGES = [
|
||||
const OVERALL_RANK_RANGES = [
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[3, 3],
|
||||
@ -572,7 +598,12 @@ const REQUIRED_RANK_RANGES = [
|
||||
[8, 10],
|
||||
[11, 30]
|
||||
]
|
||||
const DAILY_RANK_RANGES = [[1, 1], [2, 2], [3, 3]]
|
||||
const RANKING_TYPES = ['TYCOON', 'VICTORIOUS']
|
||||
const RANK_REWARD_SCOPES = [
|
||||
{ periodType: 'DAILY', ranges: DAILY_RANK_RANGES },
|
||||
{ periodType: 'OVERALL', ranges: OVERALL_RANK_RANGES }
|
||||
]
|
||||
|
||||
export default {
|
||||
name: 'ActivityGameKing',
|
||||
@ -605,6 +636,8 @@ export default {
|
||||
settlementsLoading: false,
|
||||
settleLoading: false,
|
||||
settlementRecords: [],
|
||||
settlementPeriodType: 'OVERALL',
|
||||
settlementStatDate: '',
|
||||
retryingRecordId: '',
|
||||
drawRecordsVisible: false,
|
||||
drawRecordsLoading: false,
|
||||
@ -673,10 +706,12 @@ export default {
|
||||
}))
|
||||
},
|
||||
emptyRankRewards() {
|
||||
return RANKING_TYPES.reduce((result, rankingType) => result.concat(
|
||||
REQUIRED_RANK_RANGES.map((range, index) => ({
|
||||
return RANK_REWARD_SCOPES.reduce((periodResult, scope) => periodResult.concat(
|
||||
RANKING_TYPES.reduce((boardResult, rankingType) => boardResult.concat(
|
||||
scope.ranges.map((range, index) => ({
|
||||
id: '',
|
||||
activityId: '',
|
||||
periodType: scope.periodType,
|
||||
rankingType,
|
||||
startRank: range[0],
|
||||
endRank: range[1],
|
||||
@ -685,6 +720,7 @@ export default {
|
||||
rewards: []
|
||||
}))
|
||||
), [])
|
||||
), [])
|
||||
},
|
||||
loadActivities() {
|
||||
this.listLoading = true
|
||||
@ -744,9 +780,11 @@ export default {
|
||||
mergeRankRewards(rankRewards) {
|
||||
const result = this.emptyRankRewards()
|
||||
rankRewards.forEach(item => {
|
||||
// 榜单类型和名次区间共同定位配置,避免两个榜单的同一名次奖励相互覆盖。
|
||||
// 周期、榜单类型和名次区间共同定位配置,避免 DAILY 与 OVERALL 同名次互相覆盖。
|
||||
const periodType = item.periodType || 'OVERALL'
|
||||
const rankingType = item.rankingType || 'TYCOON'
|
||||
const index = result.findIndex(target =>
|
||||
target.periodType === periodType &&
|
||||
target.rankingType === rankingType &&
|
||||
target.startRank === Number(item.startRank) &&
|
||||
target.endRank === Number(item.endRank)
|
||||
@ -777,7 +815,7 @@ export default {
|
||||
}
|
||||
const payload = this.buildSavePayload()
|
||||
this.saveLoading = true
|
||||
// 基础配置、七个奖项与两个榜单各六档奖励一次提交,由后端事务保证不会保存出半套配置。
|
||||
// 基础配置、七个奖项与 DAILY/OVERALL 双榜奖励一次提交,由后端事务保证不会保存出半套配置。
|
||||
saveAslanGameKingActivity(payload).then(() => {
|
||||
this.$message.success('活动配置已保存')
|
||||
this.editorVisible = false
|
||||
@ -823,15 +861,14 @@ export default {
|
||||
this.$message.error('请补全 7 个奖项的名称、图片、正整数权重、合法库存和奖励资源组')
|
||||
return false
|
||||
}
|
||||
const rangesValid = this.form.rankRewards.length === REQUIRED_RANK_RANGES.length * RANKING_TYPES.length &&
|
||||
this.form.rankRewards.every((item, index) =>
|
||||
item.rankingType === RANKING_TYPES[Math.floor(index / REQUIRED_RANK_RANGES.length)] &&
|
||||
Number(item.startRank) === REQUIRED_RANK_RANGES[index % REQUIRED_RANK_RANGES.length][0] &&
|
||||
Number(item.endRank) === REQUIRED_RANK_RANGES[index % REQUIRED_RANK_RANGES.length][1] &&
|
||||
Boolean(item.resourceGroupId)
|
||||
)
|
||||
const expectedKeys = this.emptyRankRewards().map(item =>
|
||||
`${item.periodType}:${item.rankingType}:${item.startRank}:${item.endRank}`)
|
||||
const actualKeys = this.form.rankRewards.map(item =>
|
||||
`${item.periodType}:${item.rankingType}:${Number(item.startRank)}:${Number(item.endRank)}`)
|
||||
const rangesValid = actualKeys.length === expectedKeys.length &&
|
||||
actualKeys.every((key, index) => key === expectedKeys[index] && Boolean(this.form.rankRewards[index].resourceGroupId))
|
||||
if (!rangesValid) {
|
||||
this.$message.error('两个榜单的六档名次范围必须保持固定,并为每档选择奖励资源组')
|
||||
this.$message.error('请完整配置两个榜单的 DAILY Top3 与 OVERALL 六档奖励资源组')
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@ -869,6 +906,7 @@ export default {
|
||||
payload.rankRewards = this.form.rankRewards.map(item => ({
|
||||
id: item.id || null,
|
||||
activityId: this.form.id || null,
|
||||
periodType: item.periodType,
|
||||
rankingType: item.rankingType,
|
||||
startRank: Number(item.startRank),
|
||||
endRank: Number(item.endRank),
|
||||
@ -889,6 +927,11 @@ export default {
|
||||
this.resourceSelection = { type, index }
|
||||
this.resourceSelectorVisible = true
|
||||
},
|
||||
firstResourceGroupCover(source) {
|
||||
const firstReward = Array.isArray(source.rewardConfigList) ? source.rewardConfigList[0] : null
|
||||
const cover = firstReward && (firstReward.cover || firstReward.coverUrl)
|
||||
return cover ? this.$getAccessImgUrl(String(cover).trim()) : ''
|
||||
},
|
||||
applyResourceGroup(source) {
|
||||
if (!this.resourceSelection) {
|
||||
return
|
||||
@ -898,6 +941,13 @@ export default {
|
||||
// Long ID 保留为字符串传回 Java,避免 Snowflake ID 在浏览器里发生精度截断。
|
||||
row.resourceGroupId = String(source.id)
|
||||
row.rewards = source.rewardConfigList || []
|
||||
if (this.resourceSelection.type === 'prize') {
|
||||
// 奖项图必须跟随当前资源组,切换资源组时覆盖旧图,避免 H5 展示与实际发奖内容不一致。
|
||||
row.prizeImage = this.firstResourceGroupCover(source)
|
||||
if (!row.prizeImage) {
|
||||
this.$message.warning('资源组第一个奖励没有封面图,请更换资源组或手动上传奖项图片')
|
||||
}
|
||||
}
|
||||
if (this.resourceSelection.type === 'rank' && !row.rewardName) {
|
||||
row.rewardName = source.name || ''
|
||||
}
|
||||
@ -919,26 +969,49 @@ export default {
|
||||
},
|
||||
openSettlements(row) {
|
||||
this.selectedActivity = row
|
||||
this.settlementPeriodType = 'OVERALL'
|
||||
this.settlementStatDate = ''
|
||||
this.settlementsVisible = true
|
||||
this.loadSettlements()
|
||||
},
|
||||
changeSettlementPeriod() {
|
||||
this.settlementRecords = []
|
||||
if (this.settlementPeriodType === 'OVERALL') {
|
||||
this.settlementStatDate = ''
|
||||
this.loadSettlements()
|
||||
}
|
||||
},
|
||||
loadSettlements() {
|
||||
if (!this.selectedActivity.id) {
|
||||
return
|
||||
}
|
||||
if (this.settlementPeriodType === 'DAILY' && !this.settlementStatDate) {
|
||||
this.settlementRecords = []
|
||||
return
|
||||
}
|
||||
const params = { periodType: this.settlementPeriodType }
|
||||
if (this.settlementPeriodType === 'DAILY') {
|
||||
params.statDate = this.settlementStatDate
|
||||
}
|
||||
this.settlementsLoading = true
|
||||
listAslanGameKingSettlementRecords(this.selectedActivity.id).then(res => {
|
||||
listAslanGameKingSettlementRecords(this.selectedActivity.id, params).then(res => {
|
||||
this.settlementRecords = Array.isArray(res.body) ? res.body : []
|
||||
}).finally(() => {
|
||||
this.settlementsLoading = false
|
||||
})
|
||||
},
|
||||
manualSettle() {
|
||||
this.$confirm('将按活动结束时最终消费榜生成 Top 30 奖励并立即尝试发放,确认继续?', '手工结算', {
|
||||
const daily = this.settlementPeriodType === 'DAILY'
|
||||
const params = { periodType: this.settlementPeriodType }
|
||||
if (daily) {
|
||||
params.statDate = this.settlementStatDate
|
||||
}
|
||||
const target = daily ? `${this.settlementStatDate} 日榜双榜 Top3` : '活动总榜双榜 Top30'
|
||||
this.$confirm(`将冻结${target}并立即尝试发放,确认继续?`, '手工结算', {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
this.settleLoading = true
|
||||
return settleAslanGameKingActivity(this.selectedActivity.id)
|
||||
return settleAslanGameKingActivity(this.selectedActivity.id, params)
|
||||
}).then(() => {
|
||||
this.$message.success('结算任务已执行')
|
||||
this.loadSettlements()
|
||||
@ -1054,6 +1127,13 @@ export default {
|
||||
hasStarted(row) {
|
||||
return Boolean(row && row.startTime && Date.now() >= Number(row.startTime))
|
||||
},
|
||||
manualSettlementReady() {
|
||||
if (this.settlementPeriodType === 'DAILY') {
|
||||
// 时区/DST 与首尾部分日期由服务端做最终校验;页面只保证运营明确选中日期。
|
||||
return Boolean(this.selectedActivity.id && this.settlementStatDate)
|
||||
}
|
||||
return this.settlementReady(this.selectedActivity)
|
||||
},
|
||||
settlementReady(row) {
|
||||
// 手工结算只负责首次冻结榜单;PROCESSING/PARTIAL_FAILED 的恢复与 UNKNOWN 核账
|
||||
// 走各自专用链路,避免运营把“继续发放”误当成再次生成快照。
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user