优化玩游戏中奖权重逻辑
This commit is contained in:
@@ -61,5 +61,24 @@ export default {
|
|||||||
url: '/core/dice/reward_config/DiceRewardConfig/destroy',
|
url: '/core/dice/reward_config/DiceRewardConfig/destroy',
|
||||||
data: params
|
data: params
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T1-T5、BIGWIN 权重配比:按档位分组获取配置列表
|
||||||
|
*/
|
||||||
|
weightRatioList() {
|
||||||
|
return request.get<Api.Common.ApiData>({
|
||||||
|
url: '/core/dice/reward_config/DiceRewardConfig/weightRatioList'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T1-T5、BIGWIN 权重配比:批量更新权重(同一档位权重之和必须等于 100%)
|
||||||
|
*/
|
||||||
|
batchUpdateWeights(items: Array<{ id: number; weight: number }>) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/core/dice/reward_config/DiceRewardConfig/batchUpdateWeights',
|
||||||
|
data: { items }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,27 +8,14 @@
|
|||||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
||||||
<template #left>
|
<template #left>
|
||||||
<ElSpace wrap>
|
<ElSpace wrap>
|
||||||
<!-- <ElButton-->
|
<ElButton
|
||||||
<!-- v-permission="'dice:reward_config:index:save'"-->
|
v-permission="'dice:reward_config:index:update'"
|
||||||
<!-- @click="showDialog('add')"-->
|
type="primary"
|
||||||
<!-- v-ripple-->
|
@click="weightRatioVisible = true"
|
||||||
<!-- >-->
|
v-ripple
|
||||||
<!-- <template #icon>-->
|
>
|
||||||
<!-- <ArtSvgIcon icon="ri:add-fill" />-->
|
T1-T5 与 BIGWIN 权重配比
|
||||||
<!-- </template>-->
|
</ElButton>
|
||||||
<!-- 新增-->
|
|
||||||
<!-- </ElButton>-->
|
|
||||||
<!-- <ElButton-->
|
|
||||||
<!-- v-permission="'dice:reward_config:index:destroy'"-->
|
|
||||||
<!-- :disabled="selectedRows.length === 0"-->
|
|
||||||
<!-- @click="deleteSelectedRows(api.delete, refreshData)"-->
|
|
||||||
<!-- v-ripple-->
|
|
||||||
<!-- >-->
|
|
||||||
<!-- <template #icon>-->
|
|
||||||
<!-- <ArtSvgIcon icon="ri:delete-bin-5-line" />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- 删除-->
|
|
||||||
<!-- </ElButton>-->
|
|
||||||
</ElSpace>
|
</ElSpace>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
@@ -71,6 +58,8 @@
|
|||||||
:data="dialogData"
|
:data="dialogData"
|
||||||
@success="refreshData"
|
@success="refreshData"
|
||||||
/>
|
/>
|
||||||
|
<!-- T1-T5、BIGWIN 权重配比弹窗 -->
|
||||||
|
<WeightRatioDialog v-model="weightRatioVisible" @success="refreshData" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -80,6 +69,9 @@
|
|||||||
import api from '../../api/reward_config/index'
|
import api from '../../api/reward_config/index'
|
||||||
import TableSearch from './modules/table-search.vue'
|
import TableSearch from './modules/table-search.vue'
|
||||||
import EditDialog from './modules/edit-dialog.vue'
|
import EditDialog from './modules/edit-dialog.vue'
|
||||||
|
import WeightRatioDialog from './modules/weight-ratio-dialog.vue'
|
||||||
|
|
||||||
|
const weightRatioVisible = ref(false)
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = ref<Record<string, unknown>>({
|
const searchForm = ref<Record<string, unknown>>({
|
||||||
|
|||||||
@@ -102,9 +102,11 @@
|
|||||||
set: (value) => emit('update:modelValue', value)
|
set: (value) => emit('update:modelValue', value)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** tier=BIGWIN 且 grid_number 为 5 或 30 时权重固定 100%,不可修改 */
|
/** BIGWIN 且 grid_number 为 5 或 30 时豹子概率不可修改 */
|
||||||
const isWeightFixed100 = computed(
|
const isWeightDisabled = computed(
|
||||||
() => formData.tier === 'BIGWIN' && (formData.grid_number === 5 || formData.grid_number === 30)
|
() =>
|
||||||
|
formData.tier === 'BIGWIN' &&
|
||||||
|
(formData.grid_number === 5 || formData.grid_number === 30)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -231,8 +233,12 @@
|
|||||||
} else if (payload.grid_number === 5 || payload.grid_number === 30) {
|
} else if (payload.grid_number === 5 || payload.grid_number === 30) {
|
||||||
payload.weight = 100
|
payload.weight = 100
|
||||||
} else {
|
} else {
|
||||||
const w = Number(payload.weight)
|
if (payload.grid_number === 5 || payload.grid_number === 30) {
|
||||||
payload.weight = Number.isNaN(w) ? 0 : Math.max(0, Math.min(100, w))
|
payload.weight = 100
|
||||||
|
} else {
|
||||||
|
const w = Number(payload.weight)
|
||||||
|
payload.weight = Number.isNaN(w) ? 0 : Math.max(0, Math.min(100, w))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (props.dialogType === 'add') {
|
if (props.dialogType === 'add') {
|
||||||
await api.save(payload)
|
await api.save(payload)
|
||||||
|
|||||||
@@ -0,0 +1,375 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="T1-T5 与 BIGWIN 权重配比"
|
||||||
|
width="600px"
|
||||||
|
align-center
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<el-tabs v-model="activeTier" type="card">
|
||||||
|
<el-tab-pane v-for="t in tierKeys" :key="t" :label="t" :name="t">
|
||||||
|
<div v-if="getTierItems(t).length === 0" class="empty-tip"> 该档位暂无配置数据 </div>
|
||||||
|
<template v-else>
|
||||||
|
<div class="chart-wrap">
|
||||||
|
<ArtBarChart
|
||||||
|
:x-axis-data="getTierChartLabels(t)"
|
||||||
|
:data="getTierChartData(t)"
|
||||||
|
height="220px"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="weight-sum" v-if="t !== 'BIGWIN'">
|
||||||
|
当前档位权重合计:<strong :class="{ over: getTierSumForValidation(t) !== 100 }">{{
|
||||||
|
getTierSumForValidation(t).toFixed(1)
|
||||||
|
}}</strong>
|
||||||
|
/ 100(须等于 100%)
|
||||||
|
</div>
|
||||||
|
<div class="weight-sum weight-sum-bigwin" v-else>
|
||||||
|
BIGWIN 为豹子权重,单独设定每条 0–100%,无合计要求
|
||||||
|
</div>
|
||||||
|
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
||||||
|
<el-table-column label="色子点数" prop="grid_number" width="50" align="center" />
|
||||||
|
<el-table-column
|
||||||
|
label="实际中奖金额"
|
||||||
|
prop="real_ev"
|
||||||
|
width="90"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="显示文本"
|
||||||
|
prop="ui_text"
|
||||||
|
min-width="80"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="备注"
|
||||||
|
prop="remark"
|
||||||
|
min-width="80"
|
||||||
|
align="center"
|
||||||
|
show-overflow-tooltip
|
||||||
|
/>
|
||||||
|
<el-table-column label="权重(%)" min-width="200" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<div class="weight-cell-vertical">
|
||||||
|
<div class="weight-slider-wrap">
|
||||||
|
<el-slider
|
||||||
|
:model-value="getItemWeight(row)"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
size="small"
|
||||||
|
:disabled="isWeightDisabled(row, t)"
|
||||||
|
class="weight-slider"
|
||||||
|
@update:model-value="
|
||||||
|
(v: number | number[]) => setItemWeight(row, normalizeSliderValue(v))
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="weight-input-wrap">
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
:disabled="isWeightDisabled(row, t) || getItemWeight(row) <= 0"
|
||||||
|
@click="adjustWeight(row, -1)"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</el-button>
|
||||||
|
<el-input-number
|
||||||
|
:model-value="getItemWeight(row)"
|
||||||
|
:min="0"
|
||||||
|
:max="100"
|
||||||
|
:step="1"
|
||||||
|
:precision="1"
|
||||||
|
:disabled="isWeightDisabled(row, t)"
|
||||||
|
controls-position="right"
|
||||||
|
size="small"
|
||||||
|
class="weight-input"
|
||||||
|
@update:model-value="(v: number | undefined) => setItemWeight(row, v ?? 0)"
|
||||||
|
/>
|
||||||
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
link
|
||||||
|
:disabled="isWeightDisabled(row, t) || getItemWeight(row) >= 100"
|
||||||
|
@click="adjustWeight(row, 1)"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</template>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="handleClose">取消</el-button>
|
||||||
|
<el-button type="primary" :loading="submitting" @click="handleSubmit">提交</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import api from '../../../api/reward_config/index'
|
||||||
|
import ArtBarChart from '@/components/core/charts/art-bar-chart/index.vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
const TIER_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'] as const
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (v) => emit('update:modelValue', v)
|
||||||
|
})
|
||||||
|
|
||||||
|
const tierKeys = TIER_KEYS
|
||||||
|
const activeTier = ref('T1')
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
/** 按档位分组的数据:{ T1: [...], T2: [...], ... },每项为可修改 weight 的副本 */
|
||||||
|
const grouped = ref<Record<string, Array<Record<string, unknown> & { weight: number }>>>({
|
||||||
|
T1: [],
|
||||||
|
T2: [],
|
||||||
|
T3: [],
|
||||||
|
T4: [],
|
||||||
|
T5: [],
|
||||||
|
BIGWIN: []
|
||||||
|
})
|
||||||
|
|
||||||
|
function getTierItems(tier: string) {
|
||||||
|
return grouped.value[tier] ?? []
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTierChartLabels(tier: string): string[] {
|
||||||
|
const items = getTierItems(tier)
|
||||||
|
return items.map((r) => (r.ui_text ? String(r.ui_text) : `点数 ${r.grid_number ?? ''}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTierChartData(tier: string): number[] {
|
||||||
|
const items = getTierItems(tier)
|
||||||
|
return items.map((r) => getItemWeight(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用于 T1–T5 校验与展示的档位权重和(仅 T1–T5 使用;BIGWIN 不要求合计) */
|
||||||
|
function getTierSumForValidation(tier: string): number {
|
||||||
|
const items = getTierItems(tier)
|
||||||
|
return items.reduce((s, r) => s + getItemWeight(r), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getItemWeight(row: Record<string, unknown> & { weight?: number }): number {
|
||||||
|
const w = row.weight
|
||||||
|
if (typeof w === 'number' && !Number.isNaN(w)) return Math.max(0, Math.min(100, w))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function setItemWeight(row: Record<string, unknown> & { weight: number }, value: number) {
|
||||||
|
row.weight = Math.max(0, Math.min(100, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustWeight(row: Record<string, unknown> & { weight: number }, delta: number) {
|
||||||
|
const cur = getItemWeight(row)
|
||||||
|
setItemWeight(row, cur + delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** T4、T5 及 BIGWIN 的 grid_number=5、30 不可修改权重(固定 100%) */
|
||||||
|
function isWeightDisabled(row: Record<string, unknown>, tier: string): boolean {
|
||||||
|
if (tier === 'T4' || tier === 'T5') return true
|
||||||
|
if (tier === 'BIGWIN' && (row.grid_number === 5 || row.grid_number === 30)) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeSliderValue(v: number | number[]): number {
|
||||||
|
if (Array.isArray(v)) return (v[0] ?? 0) as number
|
||||||
|
return (v ?? 0) as number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从接口返回值中解析出按档位分组的对象。
|
||||||
|
* 兼容:1) 直接返回 { T1: [], T2: [], ... } 2) 包装在 data 中 { data: { T1: [], ... } } 3) 平铺数组按 tier 分组
|
||||||
|
*/
|
||||||
|
function parseWeightRatioPayload(res: any): Record<string, Array<Record<string, unknown>>> {
|
||||||
|
if (!res || typeof res !== 'object') return {}
|
||||||
|
const hasTierKeys = (obj: any) =>
|
||||||
|
obj && typeof obj === 'object' && TIER_KEYS.some((k) => Array.isArray(obj[k]))
|
||||||
|
if (hasTierKeys(res)) return res as Record<string, Array<Record<string, unknown>>>
|
||||||
|
if (hasTierKeys(res?.data)) return res.data as Record<string, Array<Record<string, unknown>>>
|
||||||
|
if (hasTierKeys(res?.data?.data))
|
||||||
|
return res.data.data as Record<string, Array<Record<string, unknown>>>
|
||||||
|
if (Array.isArray(res)) {
|
||||||
|
return res.reduce(
|
||||||
|
(acc: Record<string, Array<Record<string, unknown>>>, r: Record<string, unknown>) => {
|
||||||
|
const t = (r.tier as string) || ''
|
||||||
|
if (t && TIER_KEYS.includes(t as (typeof TIER_KEYS)[number])) {
|
||||||
|
if (!acc[t]) acc[t] = []
|
||||||
|
acc[t].push(r)
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{} as Record<string, Array<Record<string, unknown>>>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
api
|
||||||
|
.weightRatioList()
|
||||||
|
.then((res: any) => {
|
||||||
|
const data = parseWeightRatioPayload(res)
|
||||||
|
const next: Record<string, Array<Record<string, unknown> & { weight: number }>> = {
|
||||||
|
T1: [],
|
||||||
|
T2: [],
|
||||||
|
T3: [],
|
||||||
|
T4: [],
|
||||||
|
T5: [],
|
||||||
|
BIGWIN: []
|
||||||
|
}
|
||||||
|
for (const t of TIER_KEYS) {
|
||||||
|
const list = Array.isArray(data[t]) ? data[t] : []
|
||||||
|
next[t] = list.map((r) => ({
|
||||||
|
...r,
|
||||||
|
weight:
|
||||||
|
typeof r.weight === 'number' && !Number.isNaN(r.weight)
|
||||||
|
? r.weight
|
||||||
|
: Number(r.weight) || 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
grouped.value = next
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
ElMessage.error('获取权重配比数据失败')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateAll(): boolean {
|
||||||
|
for (const t of TIER_KEYS) {
|
||||||
|
if (t === 'BIGWIN') continue
|
||||||
|
const items = getTierItems(t)
|
||||||
|
if (items.length === 0) continue
|
||||||
|
const sum = getTierSumForValidation(t)
|
||||||
|
if (Math.abs(sum - 100) > 0.01) {
|
||||||
|
ElMessage.warning(`档位 ${t} 的权重之和必须等于 100%,当前为 ${sum.toFixed(1)}%`)
|
||||||
|
activeTier.value = t
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 收集所有档位的 id + weight,同一 id 出现多次时合并权重(避免后端只保留最后一次导致档位合计不足 100%)。
|
||||||
|
* T4、T5、BIGWIN 的 5/30 不可修改,提交时固定为 100。
|
||||||
|
*/
|
||||||
|
function collectItems(): Array<{ id: number; weight: number }> {
|
||||||
|
const byId = new Map<number, number>()
|
||||||
|
for (const t of TIER_KEYS) {
|
||||||
|
for (const row of getTierItems(t)) {
|
||||||
|
const id = row.id != null ? Number(row.id) : 0
|
||||||
|
if (id >= 0 && !Number.isNaN(id)) {
|
||||||
|
const w = isWeightDisabled(row, t) ? 100 : getItemWeight(row)
|
||||||
|
byId.set(id, Math.min(100, (byId.get(id) ?? 0) + w))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Array.from(byId.entries()).map(([id, weight]) => ({ id, weight }))
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
if (!validateAll()) return
|
||||||
|
const items = collectItems()
|
||||||
|
if (items.length === 0) {
|
||||||
|
ElMessage.info('没有可提交的配置')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
submitting.value = true
|
||||||
|
api
|
||||||
|
.batchUpdateWeights(items)
|
||||||
|
.then(() => {
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
emit('success')
|
||||||
|
handleClose()
|
||||||
|
})
|
||||||
|
.catch((e: { message?: string }) => {
|
||||||
|
ElMessage.error(e?.message ?? '保存失败')
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
submitting.value = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(open) => {
|
||||||
|
if (open) {
|
||||||
|
loadData()
|
||||||
|
activeTier.value = 'T1'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.chart-wrap {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.weight-sum {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
.over {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.weight-sum-bigwin {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
.weight-table {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.weight-cell-vertical {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.weight-slider-wrap {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 0 8px;
|
||||||
|
.weight-slider {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.weight-input-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
.weight-input {
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.empty-tip {
|
||||||
|
padding: 24px;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -123,4 +123,35 @@ class DiceRewardConfigController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T1-T5、BIGWIN 权重配比:按档位分组返回配置列表
|
||||||
|
* @param Request $request
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
#[Permission('奖励配置列表', 'dice:reward_config:index:index')]
|
||||||
|
public function weightRatioList(Request $request): Response
|
||||||
|
{
|
||||||
|
$data = $this->logic->getListGroupedByTier();
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* T1-T5、BIGWIN 权重配比:批量更新权重(T1-T5 同档位权重和须为 100%,BIGWIN 为豹子权重单独设定、无合计要求)
|
||||||
|
* @param Request $request
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
#[Permission('奖励配置修改', 'dice:reward_config:index:update')]
|
||||||
|
public function batchUpdateWeights(Request $request): Response
|
||||||
|
{
|
||||||
|
$items = $request->post('items', []);
|
||||||
|
if (!is_array($items)) {
|
||||||
|
return $this->fail('参数 items 必须为数组');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$this->logic->batchUpdateWeights($items);
|
||||||
|
return $this->success('保存成功');
|
||||||
|
} catch (\plugin\saiadmin\exception\ApiException $e) {
|
||||||
|
return $this->fail($e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use plugin\saiadmin\basic\think\BaseLogic;
|
|||||||
use plugin\saiadmin\exception\ApiException;
|
use plugin\saiadmin\exception\ApiException;
|
||||||
use plugin\saiadmin\utils\Helper;
|
use plugin\saiadmin\utils\Helper;
|
||||||
use app\dice\model\reward_config\DiceRewardConfig;
|
use app\dice\model\reward_config\DiceRewardConfig;
|
||||||
|
use support\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 奖励配置逻辑层
|
* 奖励配置逻辑层
|
||||||
@@ -57,4 +58,85 @@ class DiceRewardConfigLogic extends BaseLogic
|
|||||||
$data['weight'] = max(0, min(100, $w));
|
$data['weight'] = max(0, min(100, $w));
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按档位分组返回奖励配置列表(用于 T1-T5、BIGWIN 权重配比)
|
||||||
|
* @return array<string, array> 键为 T1|T2|T3|T4|T5|BIGWIN,值为该档位下的配置行数组
|
||||||
|
*/
|
||||||
|
public function getListGroupedByTier(): array
|
||||||
|
{
|
||||||
|
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5', 'BIGWIN'];
|
||||||
|
$list = $this->model->whereIn('tier', $tiers)->order('tier')->order('id')->select()->toArray();
|
||||||
|
$grouped = [];
|
||||||
|
foreach ($tiers as $t) {
|
||||||
|
$grouped[$t] = [];
|
||||||
|
}
|
||||||
|
foreach ($list as $row) {
|
||||||
|
$tier = isset($row['tier']) ? (string) $row['tier'] : '';
|
||||||
|
if ($tier !== '' && isset($grouped[$tier])) {
|
||||||
|
$grouped[$tier][] = $row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新权重:T1-T5 同档位权重之和必须等于 100;BIGWIN 为豹子权重单独设定,不校验合计
|
||||||
|
* @param array<int, array{id: int, weight: float}> $items 元素为 [ id => 配置ID, weight => 0-100 ]
|
||||||
|
* @throws ApiException 当单条 weight 非法或 T1-T5 某档位权重和≠100 时
|
||||||
|
*/
|
||||||
|
public function batchUpdateWeights(array $items): void
|
||||||
|
{
|
||||||
|
if (empty($items)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$items = array_values($items);
|
||||||
|
$ids = [];
|
||||||
|
$weightById = [];
|
||||||
|
foreach ($items as $item) {
|
||||||
|
if (!is_array($item)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$id = isset($item['id']) ? (int) $item['id'] : 0;
|
||||||
|
$w = isset($item['weight']) ? (float) $item['weight'] : 0;
|
||||||
|
if ($id < 0) {
|
||||||
|
throw new ApiException('存在无效的配置ID');
|
||||||
|
}
|
||||||
|
if ($w < 0 || $w > 100) {
|
||||||
|
throw new ApiException('权重必须在 0-100 之间');
|
||||||
|
}
|
||||||
|
$ids[] = $id;
|
||||||
|
$w = max(0, min(100, $w));
|
||||||
|
$weightById[$id] = isset($weightById[$id]) ? min(100, $weightById[$id] + $w) : $w;
|
||||||
|
}
|
||||||
|
$list = $this->model->whereIn('id', array_unique($ids))->field('id,tier,grid_number')->select()->toArray();
|
||||||
|
$idToTier = [];
|
||||||
|
foreach ($list as $r) {
|
||||||
|
$id = isset($r['id']) ? (int) $r['id'] : 0;
|
||||||
|
$idToTier[$id] = isset($r['tier']) ? (string) $r['tier'] : '';
|
||||||
|
}
|
||||||
|
$sumByTier = [];
|
||||||
|
foreach ($weightById as $id => $w) {
|
||||||
|
$tier = $idToTier[$id] ?? '';
|
||||||
|
if ($tier === '') {
|
||||||
|
throw new ApiException('配置ID ' . $id . ' 不存在或档位为空');
|
||||||
|
}
|
||||||
|
if ($tier === 'BIGWIN') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isset($sumByTier[$tier])) {
|
||||||
|
$sumByTier[$tier] = 0;
|
||||||
|
}
|
||||||
|
$sumByTier[$tier] += $w;
|
||||||
|
}
|
||||||
|
foreach ($sumByTier as $tier => $sum) {
|
||||||
|
if (abs($sum - 100)) {
|
||||||
|
throw new ApiException('档位 ' . $tier . ' 的权重之和必须等于 100%,当前为 ' . round($sum, 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($weightById as $id => $w) {
|
||||||
|
DiceRewardConfig::where('id', $id)->update(['weight' => $w]);
|
||||||
|
}
|
||||||
|
DiceRewardConfig::refreshCache();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,19 +110,15 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
$sEnd = isset($row['s_end_index']) ? (int) $row['s_end_index'] : 0;
|
$sEnd = isset($row['s_end_index']) ? (int) $row['s_end_index'] : 0;
|
||||||
if ($sEnd !== 0) {
|
if (!isset($bySEndIndex[$sEnd])) {
|
||||||
if (!isset($bySEndIndex[$sEnd])) {
|
$bySEndIndex[$sEnd] = [];
|
||||||
$bySEndIndex[$sEnd] = [];
|
|
||||||
}
|
|
||||||
$bySEndIndex[$sEnd][] = $row;
|
|
||||||
}
|
}
|
||||||
|
$bySEndIndex[$sEnd][] = $row;
|
||||||
$nEnd = isset($row['n_end_index']) ? (int) $row['n_end_index'] : 0;
|
$nEnd = isset($row['n_end_index']) ? (int) $row['n_end_index'] : 0;
|
||||||
if ($nEnd !== 0) {
|
if (!isset($byNEndIndex[$nEnd])) {
|
||||||
if (!isset($byNEndIndex[$nEnd])) {
|
$byNEndIndex[$nEnd] = [];
|
||||||
$byNEndIndex[$nEnd] = [];
|
|
||||||
}
|
|
||||||
$byNEndIndex[$nEnd][] = $row;
|
|
||||||
}
|
}
|
||||||
|
$byNEndIndex[$nEnd][] = $row;
|
||||||
}
|
}
|
||||||
$minRealEv = empty($list) ? 0.0 : (float) min(array_column($list, 'real_ev'));
|
$minRealEv = empty($list) ? 0.0 : (float) min(array_column($list, 'real_ev'));
|
||||||
self::$instance = [
|
self::$instance = [
|
||||||
|
|||||||
@@ -103,6 +103,8 @@ Route::group('/core', function () {
|
|||||||
fastRoute('dice/player_ticket_record/DicePlayerTicketRecord', \app\dice\controller\player_ticket_record\DicePlayerTicketRecordController::class);
|
fastRoute('dice/player_ticket_record/DicePlayerTicketRecord', \app\dice\controller\player_ticket_record\DicePlayerTicketRecordController::class);
|
||||||
Route::get('/dice/player_ticket_record/DicePlayerTicketRecord/getPlayerOptions', [\app\dice\controller\player_ticket_record\DicePlayerTicketRecordController::class, 'getPlayerOptions']);
|
Route::get('/dice/player_ticket_record/DicePlayerTicketRecord/getPlayerOptions', [\app\dice\controller\player_ticket_record\DicePlayerTicketRecordController::class, 'getPlayerOptions']);
|
||||||
fastRoute('dice/reward_config/DiceRewardConfig', \app\dice\controller\reward_config\DiceRewardConfigController::class);
|
fastRoute('dice/reward_config/DiceRewardConfig', \app\dice\controller\reward_config\DiceRewardConfigController::class);
|
||||||
|
Route::get('/dice/reward_config/DiceRewardConfig/weightRatioList', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'weightRatioList']);
|
||||||
|
Route::post('/dice/reward_config/DiceRewardConfig/batchUpdateWeights', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdateWeights']);
|
||||||
fastRoute('dice/lottery_config/DiceLotteryConfig', \app\dice\controller\lottery_config\DiceLotteryConfigController::class);
|
fastRoute('dice/lottery_config/DiceLotteryConfig', \app\dice\controller\lottery_config\DiceLotteryConfigController::class);
|
||||||
Route::get('/dice/lottery_config/DiceLotteryConfig/getOptions', [\app\dice\controller\lottery_config\DiceLotteryConfigController::class, 'getOptions']);
|
Route::get('/dice/lottery_config/DiceLotteryConfig/getOptions', [\app\dice\controller\lottery_config\DiceLotteryConfigController::class, 'getOptions']);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user