Compare commits
6 Commits
1f8d76e80b
...
84d499145d
| Author | SHA1 | Date | |
|---|---|---|---|
| 84d499145d | |||
| 54aa0bd34f | |||
| 1a748745cb | |||
| bc034727b0 | |||
| e56c3ada34 | |||
| 296991f53a |
@@ -1,79 +0,0 @@
|
|||||||
import request from '@/utils/http'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 色子奖池配置 API接口
|
|
||||||
*/
|
|
||||||
export default {
|
|
||||||
/**
|
|
||||||
* 获取数据列表(DiceLotteryConfig)
|
|
||||||
* @param params 搜索参数
|
|
||||||
* @returns 数据列表
|
|
||||||
*/
|
|
||||||
list(params: Record<string, any>) {
|
|
||||||
return request.get<Api.Common.ApiPage>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/index',
|
|
||||||
params
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取 DiceLotteryConfig 列表数据,仅含 id、name,用于 lottery_config_id 下拉
|
|
||||||
* @returns DiceLotteryConfig['id','name'] 列表
|
|
||||||
*/
|
|
||||||
async getOptions(): Promise<Array<{ id: number; name: string }>> {
|
|
||||||
const res = await request.get<any>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/getOptions'
|
|
||||||
})
|
|
||||||
const rows = (res?.data ?? []) as Array<{ id: number; name: string }>
|
|
||||||
return Array.isArray(rows)
|
|
||||||
? rows.map((r) => ({ id: Number(r.id), name: String(r.name ?? r.id ?? '') }))
|
|
||||||
: []
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取数据
|
|
||||||
* @param id 数据ID
|
|
||||||
* @returns 数据详情
|
|
||||||
*/
|
|
||||||
read(id: number | string) {
|
|
||||||
return request.get<Api.Common.ApiData>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/read?id=' + id
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建数据
|
|
||||||
* @param params 数据参数
|
|
||||||
* @returns 执行结果
|
|
||||||
*/
|
|
||||||
save(params: Record<string, any>) {
|
|
||||||
return request.post<any>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/save',
|
|
||||||
data: params
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新数据
|
|
||||||
* @param params 数据参数
|
|
||||||
* @returns 执行结果
|
|
||||||
*/
|
|
||||||
update(params: Record<string, any>) {
|
|
||||||
return request.put<any>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/update',
|
|
||||||
data: params
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除数据
|
|
||||||
* @param id 数据ID
|
|
||||||
* @returns 执行结果
|
|
||||||
*/
|
|
||||||
delete(params: Record<string, any>) {
|
|
||||||
return request.del<any>({
|
|
||||||
url: '/dice/lottery_config/DiceLotteryConfig/destroy',
|
|
||||||
data: params
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import request from '@/utils/http'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 色子奖池配置 API 接口
|
||||||
|
*/
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 获取数据列表(DiceLotteryPoolConfig)
|
||||||
|
* @param params 搜索参数
|
||||||
|
* @returns 数据列表
|
||||||
|
*/
|
||||||
|
list(params: Record<string, any>) {
|
||||||
|
return request.get<Api.Common.ApiPage>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/index',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 DiceLotteryPoolConfig 列表数据,仅含 id、name,用于 lottery_config_id 下拉
|
||||||
|
* @returns DiceLotteryPoolConfig['id','name'] 列表
|
||||||
|
*/
|
||||||
|
async getOptions(): Promise<Array<{ id: number; name: string }>> {
|
||||||
|
const res = await request.get<any>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/getOptions'
|
||||||
|
})
|
||||||
|
const rows = (res?.data ?? []) as Array<{ id: number; name: string }>
|
||||||
|
return Array.isArray(rows)
|
||||||
|
? rows.map((r) => ({ id: Number(r.id), name: String(r.name ?? r.id ?? '') }))
|
||||||
|
: []
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取数据
|
||||||
|
* @param id 数据ID
|
||||||
|
* @returns 数据详情
|
||||||
|
*/
|
||||||
|
read(id: number | string) {
|
||||||
|
return request.get<Api.Common.ApiData>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/read?id=' + id
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建数据
|
||||||
|
* @param params 数据参数
|
||||||
|
* @returns 执行结果
|
||||||
|
*/
|
||||||
|
save(params: Record<string, any>) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/save',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新数据
|
||||||
|
* @param params 数据参数
|
||||||
|
* @returns 执行结果
|
||||||
|
*/
|
||||||
|
update(params: Record<string, any>) {
|
||||||
|
return request.put<any>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/update',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除数据
|
||||||
|
* @param id 数据ID
|
||||||
|
* @returns 执行结果
|
||||||
|
*/
|
||||||
|
delete(params: Record<string, any>) {
|
||||||
|
return request.del<any>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/destroy',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前彩金池(Redis 实例化,无则按 type=0 创建),含 profit_amount 实时值
|
||||||
|
*/
|
||||||
|
getCurrentPool() {
|
||||||
|
return request.get<{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
safety_line: number
|
||||||
|
t1_weight: number
|
||||||
|
t2_weight: number
|
||||||
|
t3_weight: number
|
||||||
|
t4_weight: number
|
||||||
|
t5_weight: number
|
||||||
|
profit_amount: number
|
||||||
|
}>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/getCurrentPool'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前彩金池:仅 safety_line、t1_weight~t5_weight,不可改 profit_amount
|
||||||
|
*/
|
||||||
|
updateCurrentPool(params: {
|
||||||
|
safety_line?: number
|
||||||
|
t1_weight?: number
|
||||||
|
t2_weight?: number
|
||||||
|
t3_weight?: number
|
||||||
|
t4_weight?: number
|
||||||
|
t5_weight?: number
|
||||||
|
}) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/dice/lottery_pool_config/DiceLotteryPoolConfig/updateCurrentPool',
|
||||||
|
data: params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取彩金池配置选项(DiceLotteryConfig.id、name),供 lottery_config_id 下拉使用
|
* 获取彩金池配置选项(DiceLotteryPoolConfig.id、name),供 lottery_config_id 下拉使用
|
||||||
* @returns [ { id, name } ]
|
* @returns [ { id, name } ]
|
||||||
*/
|
*/
|
||||||
async getLotteryConfigOptions(): Promise<Array<{ id: number; name: string }>> {
|
async getLotteryConfigOptions(): Promise<Array<{ id: number; name: string }>> {
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,29 +7,13 @@
|
|||||||
<!-- 表格头部 -->
|
<!-- 表格头部 -->
|
||||||
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
|
||||||
<template #left>
|
<template #left>
|
||||||
<!-- <ElSpace wrap>-->
|
<ElButton
|
||||||
<!-- <ElButton-->
|
v-permission="'dice:lottery_pool_config:index:index'"
|
||||||
<!-- v-permission="'dice:lottery_config:index:save'"-->
|
type="primary"
|
||||||
<!-- @click="showDialog('add')"-->
|
@click="showCurrentPoolDialog"
|
||||||
<!-- v-ripple-->
|
>
|
||||||
<!-- >-->
|
查看当前彩金池
|
||||||
<!-- <template #icon>-->
|
</ElButton>
|
||||||
<!-- <ArtSvgIcon icon="ri:add-fill" />-->
|
|
||||||
<!-- </template>-->
|
|
||||||
<!-- 新增-->
|
|
||||||
<!-- </ElButton>-->
|
|
||||||
<!-- <ElButton-->
|
|
||||||
<!-- v-permission="'dice:lottery_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>-->
|
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
@@ -50,15 +34,15 @@
|
|||||||
<template #operation="{ row }">
|
<template #operation="{ row }">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<SaButton
|
<SaButton
|
||||||
v-permission="'dice:lottery_config:index:update'"
|
v-permission="'dice:lottery_pool_config:index:update'"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="showDialog('edit', row)"
|
@click="showDialog('edit', row)"
|
||||||
/>
|
/>
|
||||||
<!-- <SaButton-->
|
<!-- <SaButton-->
|
||||||
<!-- v-permission="'dice:lottery_config:index:destroy'"-->
|
<!-- v-permission="'dice:lottery_pool_config:index:destroy'"-->
|
||||||
<!-- type="error"-->
|
<!-- type="error"-->
|
||||||
<!-- @click="deleteRow(row, api.delete, refreshData)"-->
|
<!-- @click="deleteRow(row, api.delete, refreshData)"-->
|
||||||
<!-- />-->
|
<!-- />-->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ArtTable>
|
</ArtTable>
|
||||||
@@ -71,15 +55,18 @@
|
|||||||
:data="dialogData"
|
:data="dialogData"
|
||||||
@success="refreshData"
|
@success="refreshData"
|
||||||
/>
|
/>
|
||||||
|
<!-- 当前彩金池弹窗 -->
|
||||||
|
<CurrentPoolDialog v-model="currentPoolVisible" @success="refreshData" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useTable } from '@/hooks/core/useTable'
|
import { useTable } from '@/hooks/core/useTable'
|
||||||
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
import { useSaiAdmin } from '@/composables/useSaiAdmin'
|
||||||
import api from '../../api/lottery_config/index'
|
import api from '../../api/lottery_pool_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 CurrentPoolDialog from './modules/current-pool-dialog.vue'
|
||||||
|
|
||||||
// 搜索表单
|
// 搜索表单
|
||||||
const searchForm = ref({
|
const searchForm = ref({
|
||||||
@@ -172,14 +159,10 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 编辑配置
|
// 编辑配置
|
||||||
const {
|
const { dialogType, dialogVisible, dialogData, showDialog, handleSelectionChange } = useSaiAdmin()
|
||||||
dialogType,
|
|
||||||
dialogVisible,
|
const currentPoolVisible = ref(false)
|
||||||
dialogData,
|
function showCurrentPoolDialog() {
|
||||||
showDialog,
|
currentPoolVisible.value = true
|
||||||
// deleteRow,
|
}
|
||||||
// deleteSelectedRows,
|
|
||||||
handleSelectionChange
|
|
||||||
// selectedRows
|
|
||||||
} = useSaiAdmin()
|
|
||||||
</script>
|
</script>
|
||||||
@@ -0,0 +1,292 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog
|
||||||
|
v-model="visible"
|
||||||
|
title="当前彩金池"
|
||||||
|
width="560px"
|
||||||
|
align-center
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<div v-if="loading && !pool" class="flex justify-center py-8">加载中...</div>
|
||||||
|
<template v-else-if="pool">
|
||||||
|
<div class="pool-info mb-4">
|
||||||
|
<div class="flex items-center gap-2 mb-3">
|
||||||
|
<span class="text-gray-500">池子名称:</span>
|
||||||
|
<span>{{ pool.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="profit-row mb-3">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-gray-500">彩金池盈利(profit_amount):</span>
|
||||||
|
<span class="font-mono text-lg" :class="profitAmountClass">{{ displayProfitAmount }}</span>
|
||||||
|
<span class="realtime-badge">实时</span>
|
||||||
|
</div>
|
||||||
|
<div class="profit-calc-hint">
|
||||||
|
计算方式:每局抽奖累加 (100 − 该局中奖档位 real_ev),弹窗打开期间每 2 秒自动刷新
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tip-block">
|
||||||
|
<div class="tip-title">抽奖档位规则</div>
|
||||||
|
<div class="tip-content">
|
||||||
|
当彩金池盈利 <strong>低于安全线</strong> 时,按<strong>玩家</strong>的 T*_weight 权重抽取抽奖档位;
|
||||||
|
当彩金池盈利 <strong>高于或等于安全线</strong> 时,按<strong>当前彩金池</strong>的 T*_weight 权重抽取档位。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
|
||||||
|
<el-form-item label="安全线" prop="safety_line">
|
||||||
|
<el-input-number
|
||||||
|
v-model="formData.safety_line"
|
||||||
|
:min="0"
|
||||||
|
:precision="2"
|
||||||
|
style="width: 100%"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="T1池权重(%)" prop="t1_weight">
|
||||||
|
<el-slider v-model="formData.t1_weight" :min="0" :max="100" :step="1" show-input />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="T2池权重(%)" prop="t2_weight">
|
||||||
|
<el-slider v-model="formData.t2_weight" :min="0" :max="100" :step="1" show-input />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="T3池权重(%)" prop="t3_weight">
|
||||||
|
<el-slider v-model="formData.t3_weight" :min="0" :max="100" :step="1" show-input />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="T4池权重(%)" prop="t4_weight">
|
||||||
|
<el-slider v-model="formData.t4_weight" :min="0" :max="100" :step="1" show-input />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="T5池权重(%)" prop="t5_weight">
|
||||||
|
<el-slider v-model="formData.t5_weight" :min="0" :max="100" :step="1" show-input />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<div class="text-gray-500 text-sm">
|
||||||
|
五个池权重总和:<span :class="weightsSum !== 100 ? 'text-red-500' : ''">{{
|
||||||
|
weightsSum
|
||||||
|
}}</span
|
||||||
|
>% / 100%(须为100%)
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="handleClose">关闭</el-button>
|
||||||
|
<el-button type="primary" :loading="saving" :disabled="!pool" @click="handleSubmit">
|
||||||
|
保存权重与安全线
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import api from '../../../api/lottery_pool_config/index'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
interface PoolData {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
safety_line: number
|
||||||
|
t1_weight: number
|
||||||
|
t2_weight: number
|
||||||
|
t3_weight: number
|
||||||
|
t4_weight: number
|
||||||
|
t5_weight: number
|
||||||
|
profit_amount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{ modelValue: boolean }>()
|
||||||
|
const emit = defineEmits<{ (e: 'update:modelValue', v: boolean): void; (e: 'success'): void }>()
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (v) => emit('update:modelValue', v)
|
||||||
|
})
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const saving = ref(false)
|
||||||
|
const pool = ref<PoolData | null>(null)
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
|
||||||
|
const formData = reactive({
|
||||||
|
safety_line: 0,
|
||||||
|
t1_weight: 0,
|
||||||
|
t2_weight: 0,
|
||||||
|
t3_weight: 0,
|
||||||
|
t4_weight: 0,
|
||||||
|
t5_weight: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const rules: FormRules = {
|
||||||
|
safety_line: [{ required: true, message: '请输入安全线', trigger: 'blur' }],
|
||||||
|
t1_weight: [{ required: true, message: '请输入T1权重', trigger: 'blur' }],
|
||||||
|
t2_weight: [{ required: true, message: '请输入T2权重', trigger: 'blur' }],
|
||||||
|
t3_weight: [{ required: true, message: '请输入T3权重', trigger: 'blur' }],
|
||||||
|
t4_weight: [{ required: true, message: '请输入T4权重', trigger: 'blur' }],
|
||||||
|
t5_weight: [{ required: true, message: '请输入T5权重', trigger: 'blur' }]
|
||||||
|
}
|
||||||
|
|
||||||
|
const weightsSum = computed(
|
||||||
|
() =>
|
||||||
|
formData.t1_weight +
|
||||||
|
formData.t2_weight +
|
||||||
|
formData.t3_weight +
|
||||||
|
formData.t4_weight +
|
||||||
|
formData.t5_weight
|
||||||
|
)
|
||||||
|
|
||||||
|
const displayProfitAmount = computed(() => {
|
||||||
|
const v = pool.value?.profit_amount
|
||||||
|
if (v == null || Number.isNaN(v)) return '-'
|
||||||
|
return Number(v).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
const profitAmountClass = computed(() => {
|
||||||
|
const v = pool.value?.profit_amount
|
||||||
|
if (v == null) return ''
|
||||||
|
if (v > 0) return 'text-green-600'
|
||||||
|
if (v < 0) return 'text-red-600'
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
|
||||||
|
let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||||
|
const POLL_INTERVAL = 2000
|
||||||
|
|
||||||
|
async function loadPool() {
|
||||||
|
if (!visible.value) return
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
const res = await api.getCurrentPool()
|
||||||
|
const data = res as unknown as PoolData
|
||||||
|
if (data && typeof data === 'object') {
|
||||||
|
pool.value = data
|
||||||
|
formData.safety_line = data.safety_line ?? 0
|
||||||
|
formData.t1_weight = data.t1_weight ?? 0
|
||||||
|
formData.t2_weight = data.t2_weight ?? 0
|
||||||
|
formData.t3_weight = data.t3_weight ?? 0
|
||||||
|
formData.t4_weight = data.t4_weight ?? 0
|
||||||
|
formData.t5_weight = data.t5_weight ?? 0
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message ?? '获取彩金池失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPolling() {
|
||||||
|
stopPolling()
|
||||||
|
pollTimer = setInterval(() => {
|
||||||
|
if (!visible.value) {
|
||||||
|
stopPolling()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
api.getCurrentPool().then((res) => {
|
||||||
|
const data = res as unknown as PoolData
|
||||||
|
if (pool.value && data && typeof data === 'object' && data.profit_amount != null) {
|
||||||
|
pool.value.profit_amount = data.profit_amount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, POLL_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopPolling() {
|
||||||
|
if (pollTimer) {
|
||||||
|
clearInterval(pollTimer)
|
||||||
|
pollTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!formRef.value || !pool.value) return
|
||||||
|
if (weightsSum.value !== 100) {
|
||||||
|
ElMessage.warning('T1~T5 权重合计须为 100%')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await formRef.value.validate()
|
||||||
|
saving.value = true
|
||||||
|
await api.updateCurrentPool({
|
||||||
|
safety_line: formData.safety_line,
|
||||||
|
t1_weight: formData.t1_weight,
|
||||||
|
t2_weight: formData.t2_weight,
|
||||||
|
t3_weight: formData.t3_weight,
|
||||||
|
t4_weight: formData.t4_weight,
|
||||||
|
t5_weight: formData.t5_weight
|
||||||
|
})
|
||||||
|
ElMessage.success('保存成功')
|
||||||
|
await loadPool()
|
||||||
|
emit('success')
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e?.message) ElMessage.error(e.message)
|
||||||
|
} finally {
|
||||||
|
saving.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
stopPolling()
|
||||||
|
visible.value = false
|
||||||
|
pool.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(open) => {
|
||||||
|
if (open) {
|
||||||
|
loadPool().then(() => startPolling())
|
||||||
|
} else {
|
||||||
|
stopPolling()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
onUnmounted(() => stopPolling())
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pool-info {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profit-calc-hint {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.realtime-badge {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--el-color-success-light-9);
|
||||||
|
color: var(--el-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-block {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
border-left: 3px solid var(--el-color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-content {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tip-content strong {
|
||||||
|
color: var(--el-color-primary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -78,7 +78,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '../../../api/lottery_config/index'
|
import api from '../../../api/lottery_pool_config/index'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
const usernameFormatter = (row: Record<string, any>) =>
|
const usernameFormatter = (row: Record<string, any>) =>
|
||||||
row?.dicePlayer?.username ?? row?.player_id ?? '-'
|
row?.dicePlayer?.username ?? row?.player_id ?? '-'
|
||||||
const lotteryConfigNameFormatter = (row: Record<string, any>) =>
|
const lotteryConfigNameFormatter = (row: Record<string, any>) =>
|
||||||
row?.diceLotteryConfig?.name ?? row?.lottery_config_id ?? '-'
|
row?.diceLotteryPoolConfig?.name ?? row?.lottery_config_id ?? '-'
|
||||||
const rewardTierFormatter = (row: Record<string, any>) =>
|
const rewardTierFormatter = (row: Record<string, any>) =>
|
||||||
row?.diceRewardConfig?.tier ?? row?.reward_config_id ?? '-'
|
row?.diceRewardConfig?.tier ?? row?.reward_config_id ?? '-'
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="art-full-height">
|
<div class="art-full-height">
|
||||||
<!-- 搜索面板 -->
|
<!-- ???? -->
|
||||||
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
<TableSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams" />
|
||||||
|
|
||||||
<ElCard class="art-table-card" shadow="never">
|
<ElCard class="art-table-card" shadow="never">
|
||||||
<!-- 表格头部 -->
|
<!-- ???? -->
|
||||||
<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>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<ArtSvgIcon icon="ri:add-fill" />
|
<ArtSvgIcon icon="ri:add-fill" />
|
||||||
</template>
|
</template>
|
||||||
新增
|
??
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
v-permission="'dice:player:index:destroy'"
|
v-permission="'dice:player:index:destroy'"
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<ArtSvgIcon icon="ri:delete-bin-5-line" />
|
<ArtSvgIcon icon="ri:delete-bin-5-line" />
|
||||||
</template>
|
</template>
|
||||||
删除
|
??
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</ElSpace>
|
</ElSpace>
|
||||||
</template>
|
</template>
|
||||||
</ArtTableHeader>
|
</ArtTableHeader>
|
||||||
|
|
||||||
<!-- 表格 -->
|
<!-- ?? -->
|
||||||
<ArtTable
|
<ArtTable
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
@pagination:size-change="handleSizeChange"
|
@pagination:size-change="handleSizeChange"
|
||||||
@pagination:current-change="handleCurrentChange"
|
@pagination:current-change="handleCurrentChange"
|
||||||
>
|
>
|
||||||
<!-- 状态:开关直接修改 -->
|
<!-- ????????? -->
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<ElSwitch
|
<ElSwitch
|
||||||
v-permission="'dice:player:index:update'"
|
v-permission="'dice:player:index:update'"
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
@change="(v: string | number | boolean) => handleStatusChange(row, v ? 1 : 0)"
|
@change="(v: string | number | boolean) => handleStatusChange(row, v ? 1 : 0)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- 平台币:tag 可点击打开钱包操作弹窗 -->
|
<!-- ????tag ??????????? -->
|
||||||
<template #coin="{ row }">
|
<template #coin="{ row }">
|
||||||
<ElTag
|
<ElTag
|
||||||
type="info"
|
type="info"
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
{{ row.coin ?? 0 }}
|
{{ row.coin ?? 0 }}
|
||||||
</ElTag>
|
</ElTag>
|
||||||
</template>
|
</template>
|
||||||
<!-- 操作列 -->
|
<!-- ??? -->
|
||||||
<template #operation="{ row }">
|
<template #operation="{ row }">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<SaButton
|
<SaButton
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
</ArtTable>
|
</ArtTable>
|
||||||
</ElCard>
|
</ElCard>
|
||||||
|
|
||||||
<!-- 编辑弹窗 -->
|
<!-- ???? -->
|
||||||
<EditDialog
|
<EditDialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:dialog-type="dialogType"
|
:dialog-type="dialogType"
|
||||||
@@ -88,7 +88,7 @@
|
|||||||
@success="refreshData"
|
@success="refreshData"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 钱包操作弹窗(加点/扣点) -->
|
<!-- ?????????/??? -->
|
||||||
<WalletOperateDialog
|
<WalletOperateDialog
|
||||||
v-model="walletDialogVisible"
|
v-model="walletDialogVisible"
|
||||||
:player="walletOperatePlayer"
|
:player="walletOperatePlayer"
|
||||||
@@ -105,7 +105,7 @@
|
|||||||
import EditDialog from './modules/edit-dialog.vue'
|
import EditDialog from './modules/edit-dialog.vue'
|
||||||
import WalletOperateDialog from './modules/WalletOperateDialog.vue'
|
import WalletOperateDialog from './modules/WalletOperateDialog.vue'
|
||||||
|
|
||||||
// 搜索表单
|
// ????
|
||||||
const searchForm = ref({
|
const searchForm = ref({
|
||||||
username: undefined,
|
username: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
@@ -115,23 +115,23 @@
|
|||||||
lottery_config_id: undefined
|
lottery_config_id: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
// 搜索处理
|
// ????
|
||||||
const handleSearch = (params: Record<string, any>) => {
|
const handleSearch = (params: Record<string, any>) => {
|
||||||
Object.assign(searchParams, params)
|
Object.assign(searchParams, params)
|
||||||
getData()
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 权重列带 % 的 formatter(ColumnOption.formatter 仅接收 row)
|
// ???? % ? formatter?ColumnOption.formatter ??? row?
|
||||||
const weightFormatter = (prop: string) => (row: any) => {
|
const weightFormatter = (prop: string) => (row: any) => {
|
||||||
const cellValue = row[prop]
|
const cellValue = row[prop]
|
||||||
return cellValue != null && cellValue !== '' ? `${cellValue}%` : '-'
|
return cellValue != null && cellValue !== '' ? `${cellValue}%` : '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 彩金池配置列:lottery_config_id 关联 DiceLotteryConfig,显示 name
|
// ???????lottery_config_id ?? DiceLotteryPoolConfig??? name
|
||||||
const lotteryConfigNameFormatter = (row: any) =>
|
const lotteryConfigNameFormatter = (row: any) =>
|
||||||
row?.diceLotteryConfig?.name ?? (row?.lottery_config_id ? `#${row.lottery_config_id}` : '自定义')
|
row?.diceLotteryPoolConfig?.name ?? (row?.lottery_config_id ? `#${row.lottery_config_id}` : '???')
|
||||||
|
|
||||||
// 表格配置
|
// ????
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
columnChecks,
|
columnChecks,
|
||||||
@@ -150,73 +150,73 @@
|
|||||||
apiFn: api.list,
|
apiFn: api.list,
|
||||||
columnsFactory: () => [
|
columnsFactory: () => [
|
||||||
{ type: 'selection' },
|
{ type: 'selection' },
|
||||||
{ prop: 'username', label: '用户名', align: 'center' },
|
{ prop: 'username', label: '???', align: 'center' },
|
||||||
{ prop: 'phone', label: '手机号', align: 'center' },
|
{ prop: 'phone', label: '???', align: 'center' },
|
||||||
{ prop: 'name', label: '昵称', align: 'center' },
|
{ prop: 'name', label: '??', align: 'center' },
|
||||||
{
|
{
|
||||||
prop: 'status',
|
prop: 'status',
|
||||||
label: '状态',
|
label: '??',
|
||||||
width: 88,
|
width: 88,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'coin',
|
prop: 'coin',
|
||||||
label: '平台币',
|
label: '???',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'lottery_config_id',
|
prop: 'lottery_config_id',
|
||||||
label: '彩金池配置',
|
label: '?????',
|
||||||
width: 120,
|
width: 120,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (row: any) => lotteryConfigNameFormatter(row)
|
formatter: (row: any) => lotteryConfigNameFormatter(row)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 't1_weight',
|
prop: 't1_weight',
|
||||||
label: 'T1池权重',
|
label: 'T1???',
|
||||||
width: 80,
|
width: 80,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: weightFormatter('t1_weight')
|
formatter: weightFormatter('t1_weight')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 't2_weight',
|
prop: 't2_weight',
|
||||||
label: 'T2池权重',
|
label: 'T2???',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: weightFormatter('t2_weight')
|
formatter: weightFormatter('t2_weight')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 't3_weight',
|
prop: 't3_weight',
|
||||||
label: 'T3池权重',
|
label: 'T3???',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: weightFormatter('t3_weight')
|
formatter: weightFormatter('t3_weight')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 't4_weight',
|
prop: 't4_weight',
|
||||||
label: 'T4池权重',
|
label: 'T4???',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: weightFormatter('t4_weight')
|
formatter: weightFormatter('t4_weight')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 't5_weight',
|
prop: 't5_weight',
|
||||||
label: 'T5池权重',
|
label: 'T5???',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: weightFormatter('t5_weight')
|
formatter: weightFormatter('t5_weight')
|
||||||
},
|
},
|
||||||
{ prop: 'total_ticket_count', label: '总抽奖次数', align: 'center' },
|
{ prop: 'total_ticket_count', label: '?????', align: 'center' },
|
||||||
{ prop: 'paid_ticket_count', label: '购买抽奖次数', align: 'center' },
|
{ prop: 'paid_ticket_count', label: '??????', align: 'center' },
|
||||||
{ prop: 'free_ticket_count', label: '赠送抽奖次数', align: 'center' },
|
{ prop: 'free_ticket_count', label: '??????', align: 'center' },
|
||||||
{ prop: 'create_time', label: '创建时间', align: 'center' },
|
{ prop: 'create_time', label: '????', align: 'center' },
|
||||||
{ prop: 'update_time', label: '更新时间', align: 'center' },
|
{ prop: 'update_time', label: '????', align: 'center' },
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
label: '操作',
|
label: '??',
|
||||||
width: 100,
|
width: 100,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 状态开关切换(列表内直接修改)
|
// ???????????????
|
||||||
const handleStatusChange = async (row: Record<string, any>, status: number) => {
|
const handleStatusChange = async (row: Record<string, any>, status: number) => {
|
||||||
row._statusLoading = true
|
row._statusLoading = true
|
||||||
try {
|
try {
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 编辑配置
|
// ????
|
||||||
const {
|
const {
|
||||||
dialogType,
|
dialogType,
|
||||||
dialogVisible,
|
dialogVisible,
|
||||||
@@ -251,7 +251,7 @@
|
|||||||
selectedRows
|
selectedRows
|
||||||
} = useSaiAdmin()
|
} = useSaiAdmin()
|
||||||
|
|
||||||
// 钱包操作弹窗(从平台币 tag 点击打开)
|
// ??????????? tag ?????
|
||||||
const walletDialogVisible = ref(false)
|
const walletDialogVisible = ref(false)
|
||||||
type WalletPlayer = { id: number; username?: string; coin?: number }
|
type WalletPlayer = { id: number; username?: string; coin?: number }
|
||||||
const walletOperatePlayer = ref<WalletPlayer | null>(null)
|
const walletOperatePlayer = ref<WalletPlayer | null>(null)
|
||||||
|
|||||||
@@ -170,7 +170,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import api from '../../../api/player/index'
|
import api from '../../../api/player/index'
|
||||||
import lotteryConfigApi from '../../../api/lottery_config/index'
|
import lotteryConfigApi from '../../../api/lottery_pool_config/index'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
import type { FormInstance, FormRules } from 'element-plus'
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@
|
|||||||
const isExpanded = ref<boolean>(false)
|
const isExpanded = ref<boolean>(false)
|
||||||
const lotteryConfigOptions = ref<Array<{ id: number; name: string }>>([])
|
const lotteryConfigOptions = ref<Array<{ id: number; name: string }>>([])
|
||||||
|
|
||||||
/** 从玩家控制器获取 DiceLotteryConfig id/name 列表,用于 lottery_config_id 筛选 */
|
/** 从玩家控制器获取 DiceLotteryPoolConfig id/name 列表,用于 lottery_config_id 筛选 */
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
lotteryConfigOptions.value = await api.getLotteryConfigOptions()
|
lotteryConfigOptions.value = await api.getLotteryConfigOptions()
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -1,40 +1,43 @@
|
|||||||
# 数据库配置
|
# 数据库配置
|
||||||
DB_TYPE = mysql
|
DB_TYPE=mysql
|
||||||
DB_HOST = 127.0.0.1
|
DB_HOST=127.0.0.1
|
||||||
DB_PORT = 3306
|
DB_PORT=3306
|
||||||
DB_NAME = saiadmin
|
DB_NAME=saiadmin
|
||||||
DB_USER = root
|
DB_USER=root
|
||||||
DB_PASSWORD = 123456
|
DB_PASSWORD=123456
|
||||||
DB_PREFIX =
|
DB_PREFIX=
|
||||||
|
DB_POOL_MAX=32
|
||||||
|
DB_POOL_MIN=4
|
||||||
|
|
||||||
# 缓存方式,支持file|redis(API 用户登录缓存需使用 redis)
|
# 缓存方式,支持file|redis(API 用户登录缓存需使用 redis)
|
||||||
CACHE_MODE = redis
|
CACHE_MODE=redis
|
||||||
|
REDIS_POOL_MAX=32
|
||||||
|
|
||||||
# Redis配置
|
# Redis配置
|
||||||
REDIS_HOST = 127.0.0.1
|
REDIS_HOST=127.0.0.1
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD = ''
|
REDIS_PASSWORD=''
|
||||||
REDIS_DB = 0
|
REDIS_DB=0
|
||||||
|
|
||||||
# 游戏地址,用于 /api/v1/getGameUrl 返回
|
# 游戏地址,用于 /api/v1/getGameUrl 返回
|
||||||
GAME_URL = dice-game.yuliao666.top
|
GAME_URL=dice-game.yuliao666.top
|
||||||
|
|
||||||
# API 鉴权与用户(可选,不填则用默认值)
|
# API 鉴权与用户(可选,不填则用默认值)
|
||||||
# authToken 签名密钥(必填,与客户端约定,用于 signature 校验)
|
# authToken 签名密钥(必填,与客户端约定,用于 signature 校验)
|
||||||
API_AUTH_TOKEN_SECRET = xF75oK91TQj13s0UmNIr1NBWMWGfflNO
|
API_AUTH_TOKEN_SECRET=xF75oK91TQj13s0UmNIr1NBWMWGfflNO
|
||||||
# authToken 时间戳允许误差秒数,防重放,默认 300
|
# authToken 时间戳允许误差秒数,防重放,默认 300
|
||||||
API_AUTH_TOKEN_TIME_TOLERANCE = 300
|
API_AUTH_TOKEN_TIME_TOLERANCE=300
|
||||||
API_AUTH_TOKEN_EXP = 86400
|
API_AUTH_TOKEN_EXP=86400
|
||||||
# API_USER_TOKEN_EXP = 604800
|
# API_USER_TOKEN_EXP=604800
|
||||||
API_USER_CACHE_EXPIRE = 86400
|
API_USER_CACHE_EXPIRE=86400
|
||||||
API_USER_ENCRYPT_KEY = Wj818SK8dhKBKNOY3PUTmZfhQDMCXEZi
|
API_USER_ENCRYPT_KEY=Wj818SK8dhKBKNOY3PUTmZfhQDMCXEZi
|
||||||
|
|
||||||
# 验证码配置,支持cache|session
|
# 验证码配置,支持cache|session
|
||||||
CAPTCHA_MODE = cache
|
CAPTCHA_MODE=cache
|
||||||
LOGIN_CAPTCHA_ENABLE = false
|
LOGIN_CAPTCHA_ENABLE=false
|
||||||
|
|
||||||
#前端目录
|
#前端目录
|
||||||
FRONTEND_DIR = saiadmin-vue
|
FRONTEND_DIR=saiadmin-vue
|
||||||
|
|
||||||
#生成环境
|
#生成环境
|
||||||
APP_DEBUG = false
|
APP_DEBUG=false
|
||||||
@@ -5,7 +5,7 @@ namespace app\api\logic;
|
|||||||
|
|
||||||
use app\api\cache\UserCache;
|
use app\api\cache\UserCache;
|
||||||
use app\api\service\LotteryService;
|
use app\api\service\LotteryService;
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use app\dice\model\play_record\DicePlayRecord;
|
use app\dice\model\play_record\DicePlayRecord;
|
||||||
use app\dice\model\player\DicePlayer;
|
use app\dice\model\player\DicePlayer;
|
||||||
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
|
use app\dice\model\player_ticket_record\DicePlayerTicketRecord;
|
||||||
@@ -70,19 +70,30 @@ class PlayStartLogic
|
|||||||
$lotteryService = LotteryService::getOrCreate($playerId);
|
$lotteryService = LotteryService::getOrCreate($playerId);
|
||||||
$ticketType = LotteryService::drawTicketType($paid, $free);
|
$ticketType = LotteryService::drawTicketType($paid, $free);
|
||||||
$config = $ticketType === self::LOTTERY_TYPE_PAID
|
$config = $ticketType === self::LOTTERY_TYPE_PAID
|
||||||
? ($lotteryService->getConfigType0Id() ? DiceLotteryConfig::find($lotteryService->getConfigType0Id()) : null)
|
? ($lotteryService->getConfigType0Id() ? DiceLotteryPoolConfig::find($lotteryService->getConfigType0Id()) : null)
|
||||||
: ($lotteryService->getConfigType1Id() ? DiceLotteryConfig::find($lotteryService->getConfigType1Id()) : null);
|
: ($lotteryService->getConfigType1Id() ? DiceLotteryPoolConfig::find($lotteryService->getConfigType1Id()) : null);
|
||||||
|
// 未找到付费/免费对应配置时,统一回退到 type=0 的彩金池,保证所有玩家累加同一彩金池
|
||||||
|
if (!$config) {
|
||||||
|
$config = DiceLotteryPoolConfig::where('type', 0)->find();
|
||||||
|
}
|
||||||
if (!$config) {
|
if (!$config) {
|
||||||
throw new ApiException('奖池配置不存在');
|
throw new ApiException('奖池配置不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 按玩家权重抽取档位;若该档位无奖励或该方向下均无可用路径则重新摇取档位
|
// 彩金池盈利低于安全线时按玩家权重抽档,高于或等于安全线时按奖池权重抽档
|
||||||
|
$poolProfit = (float) ($config->profit_amount ?? $config->ev ?? 0);
|
||||||
|
$safetyLine = (int) ($config->safety_line ?? 0);
|
||||||
|
$usePoolWeights = $poolProfit >= $safetyLine;
|
||||||
|
|
||||||
|
// 按上述规则抽取档位;若该档位无奖励或该方向下均无可用路径则重新摇取档位
|
||||||
$maxTierRetry = 10;
|
$maxTierRetry = 10;
|
||||||
$chosen = null;
|
$chosen = null;
|
||||||
$startCandidates = [];
|
$startCandidates = [];
|
||||||
$tier = null;
|
$tier = null;
|
||||||
for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
|
for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
|
||||||
$tier = LotteryService::drawTierByPlayerWeights($player);
|
$tier = $usePoolWeights
|
||||||
|
? LotteryService::drawTierByWeights($config)
|
||||||
|
: LotteryService::drawTierByPlayerWeights($player);
|
||||||
$tierRewards = DiceRewardConfig::getCachedByTier($tier);
|
$tierRewards = DiceRewardConfig::getCachedByTier($tier);
|
||||||
if (empty($tierRewards)) {
|
if (empty($tierRewards)) {
|
||||||
Log::warning("档位 {$tier} 无任何奖励配置,重新摇取档位");
|
Log::warning("档位 {$tier} 无任何奖励配置,重新摇取档位");
|
||||||
@@ -122,6 +133,7 @@ class PlayStartLogic
|
|||||||
// 当抽到的 grid_number 为 5/10/15/20/25/30 时,可出豹子;其中 grid_number=5 与 30 固定 100% 豹子(BIGWIN 约定)
|
// 当抽到的 grid_number 为 5/10/15/20/25/30 时,可出豹子;其中 grid_number=5 与 30 固定 100% 豹子(BIGWIN 约定)
|
||||||
$superWinCoin = 0;
|
$superWinCoin = 0;
|
||||||
$isWin = 0;
|
$isWin = 0;
|
||||||
|
$bigWinRealEv = 0.0; // BIGWIN 档位的真实资金结算,用于从彩金池盈利中一并扣除
|
||||||
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
if (in_array($rollNumber, self::SUPER_WIN_GRID_NUMBERS, true)) {
|
||||||
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
|
$bigWinConfig = DiceRewardConfig::getCachedByTierAndGridNumber('BIGWIN', $rollNumber);
|
||||||
$alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
|
$alwaysSuperWin = in_array($rollNumber, self::SUPER_WIN_ALWAYS_GRID_NUMBERS, true);
|
||||||
@@ -136,9 +148,12 @@ class PlayStartLogic
|
|||||||
if ($doSuperWin) {
|
if ($doSuperWin) {
|
||||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||||
$isWin = 1;
|
$isWin = 1;
|
||||||
$superWinCoin = $bigWinConfig !== null
|
if ($bigWinConfig !== null) {
|
||||||
? 100 + (float) ($bigWinConfig['real_ev'] ?? 0)
|
$bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
|
||||||
: self::SUPER_WIN_BONUS;
|
$superWinCoin = 100 + $bigWinRealEv;
|
||||||
|
} else {
|
||||||
|
$superWinCoin = self::SUPER_WIN_BONUS;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
|
$rollArray = $this->generateNonSuperWinRollArrayWithSum($rollNumber);
|
||||||
}
|
}
|
||||||
@@ -174,6 +189,7 @@ class PlayStartLogic
|
|||||||
$rewardWinCoin,
|
$rewardWinCoin,
|
||||||
$isWin,
|
$isWin,
|
||||||
$realEv,
|
$realEv,
|
||||||
|
$bigWinRealEv,
|
||||||
$direction,
|
$direction,
|
||||||
$startIndex,
|
$startIndex,
|
||||||
$targetIndex,
|
$targetIndex,
|
||||||
@@ -230,12 +246,22 @@ class PlayStartLogic
|
|||||||
|
|
||||||
$p->save();
|
$p->save();
|
||||||
|
|
||||||
// 累加彩金池盈利额度(累加值为 -real_ev)。若 dice_lottery_config 表有 ev 字段则执行
|
// 彩金池盈利累加:每局累加 (100 - 本局总 real_ev),其中本局总 real_ev = 普通档位 real_ev + BIGWIN.real_ev(如触发)
|
||||||
|
// 需确保表有 profit_amount 字段(见 db/dice_lottery_config_add_profit_amount.sql)
|
||||||
|
$totalRealEv = $realEv + $bigWinRealEv;
|
||||||
|
$addProfit = 100 - $totalRealEv;
|
||||||
try {
|
try {
|
||||||
DiceLotteryConfig::where('id', $configId)->update([
|
DiceLotteryPoolConfig::where('id', $configId)->update([
|
||||||
'ev' => Db::raw('IFNULL(ev,0) - ' . (float) $realEv),
|
'profit_amount' => Db::raw('IFNULL(profit_amount,0) + ' . (float) $addProfit),
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::warning('彩金池盈利累加失败,请确认表 dice_lottery_config 已存在 profit_amount 字段并执行 db/dice_lottery_config_add_profit_amount.sql', [
|
||||||
|
'config_id' => $configId,
|
||||||
|
'add_profit' => $addProfit,
|
||||||
|
'real_ev' => $realEv,
|
||||||
|
'bigwin_ev' => $bigWinRealEv,
|
||||||
|
'message' => $e->getMessage(),
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $_) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DicePlayerWalletRecord::create([
|
DicePlayerWalletRecord::create([
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\api\service;
|
namespace app\api\service;
|
||||||
|
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use app\dice\model\player\DicePlayer;
|
use app\dice\model\player\DicePlayer;
|
||||||
use support\think\Cache;
|
use support\think\Cache;
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class LotteryService
|
|||||||
return self::REDIS_KEY_START_INDEX . $playerId;
|
return self::REDIS_KEY_START_INDEX . $playerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 从 Redis 加载或根据玩家与 DiceLotteryConfig 创建并保存 */
|
/** 从 Redis 加载或根据玩家与 DiceLotteryPoolConfig 创建并保存 */
|
||||||
public static function getOrCreate(int $playerId): self
|
public static function getOrCreate(int $playerId): self
|
||||||
{
|
{
|
||||||
$key = self::getRedisKey($playerId);
|
$key = self::getRedisKey($playerId);
|
||||||
@@ -56,8 +56,8 @@ class LotteryService
|
|||||||
if (!$player) {
|
if (!$player) {
|
||||||
throw new \RuntimeException('玩家不存在');
|
throw new \RuntimeException('玩家不存在');
|
||||||
}
|
}
|
||||||
$config0 = DiceLotteryConfig::where('type', 0)->find();
|
$config0 = DiceLotteryPoolConfig::where('type', 0)->find();
|
||||||
$config1 = DiceLotteryConfig::where('type', 1)->find();
|
$config1 = DiceLotteryPoolConfig::where('type', 1)->find();
|
||||||
$s = new self($playerId);
|
$s = new self($playerId);
|
||||||
$s->configType0Id = $config0 ? (int) $config0->id : null;
|
$s->configType0Id = $config0 ? (int) $config0->id : null;
|
||||||
$s->configType1Id = $config1 ? (int) $config1->id : null;
|
$s->configType1Id = $config1 ? (int) $config1->id : null;
|
||||||
@@ -84,7 +84,7 @@ class LotteryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 根据奖池配置的 t1_weight..t5_weight 权重随机抽取档位 T1-T5 */
|
/** 根据奖池配置的 t1_weight..t5_weight 权重随机抽取档位 T1-T5 */
|
||||||
public static function drawTierByWeights(DiceLotteryConfig $config): string
|
public static function drawTierByWeights(DiceLotteryPoolConfig $config): string
|
||||||
{
|
{
|
||||||
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
|
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
|
||||||
$weights = [
|
$weights = [
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
// | Author: your name
|
// | Author: your name
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\controller\lottery_config;
|
namespace app\dice\controller\lottery_pool_config;
|
||||||
|
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use plugin\saiadmin\basic\BaseController;
|
use plugin\saiadmin\basic\BaseController;
|
||||||
use app\dice\logic\lottery_config\DiceLotteryConfigLogic;
|
use app\dice\logic\lottery_pool_config\DiceLotteryPoolConfigLogic;
|
||||||
use app\dice\validate\lottery_config\DiceLotteryConfigValidate;
|
use app\dice\validate\lottery_pool_config\DiceLotteryPoolConfigValidate;
|
||||||
use plugin\saiadmin\service\Permission;
|
use plugin\saiadmin\service\Permission;
|
||||||
use support\Request;
|
use support\Request;
|
||||||
use support\Response;
|
use support\Response;
|
||||||
@@ -17,27 +17,27 @@ use support\Response;
|
|||||||
/**
|
/**
|
||||||
* 色子奖池配置控制器
|
* 色子奖池配置控制器
|
||||||
*/
|
*/
|
||||||
class DiceLotteryConfigController extends BaseController
|
class DiceLotteryPoolConfigController extends BaseController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->logic = new DiceLotteryConfigLogic();
|
$this->logic = new DiceLotteryPoolConfigLogic();
|
||||||
$this->validate = new DiceLotteryConfigValidate;
|
$this->validate = new DiceLotteryPoolConfigValidate;
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 DiceLotteryConfig 列表数据,仅含 id、name,用于 lottery_config_id 下拉(值为 id,显示为 name)
|
* 获取 DiceLotteryPoolConfig 列表数据,仅含 id、name,用于 lottery_config_id 下拉(值为 id,显示为 name)
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response 返回 [ ['id' => int, 'name' => string], ... ]
|
* @return Response 返回 [ ['id' => int, 'name' => string], ... ]
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置列表', 'dice:lottery_config:index:index')]
|
#[Permission('色子奖池配置列表', 'dice:lottery_pool_config:index:index')]
|
||||||
public function getOptions(Request $request): Response
|
public function getOptions(Request $request): Response
|
||||||
{
|
{
|
||||||
$list = DiceLotteryConfig::field('id,name')->order('id', 'asc')->select();
|
$list = DiceLotteryPoolConfig::field('id,name')->order('id', 'asc')->select();
|
||||||
$data = $list->map(function ($item) {
|
$data = $list->map(function ($item) {
|
||||||
return ['id' => (int) $item['id'], 'name' => (string) ($item['name'] ?? '')];
|
return ['id' => (int) $item['id'], 'name' => (string) ($item['name'] ?? '')];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
@@ -49,7 +49,7 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置列表', 'dice:lottery_config:index:index')]
|
#[Permission('色子奖池配置列表', 'dice:lottery_pool_config:index:index')]
|
||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
$where = $request->more([
|
$where = $request->more([
|
||||||
@@ -66,7 +66,7 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置读取', 'dice:lottery_config:index:read')]
|
#[Permission('色子奖池配置读取', 'dice:lottery_pool_config:index:read')]
|
||||||
public function read(Request $request): Response
|
public function read(Request $request): Response
|
||||||
{
|
{
|
||||||
$id = $request->input('id', '');
|
$id = $request->input('id', '');
|
||||||
@@ -84,7 +84,7 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置添加', 'dice:lottery_config:index:save')]
|
#[Permission('色子奖池配置添加', 'dice:lottery_pool_config:index:save')]
|
||||||
public function save(Request $request): Response
|
public function save(Request $request): Response
|
||||||
{
|
{
|
||||||
$data = $request->post();
|
$data = $request->post();
|
||||||
@@ -102,7 +102,7 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置修改', 'dice:lottery_config:index:update')]
|
#[Permission('色子奖池配置修改', 'dice:lottery_pool_config:index:update')]
|
||||||
public function update(Request $request): Response
|
public function update(Request $request): Response
|
||||||
{
|
{
|
||||||
$data = $request->post();
|
$data = $request->post();
|
||||||
@@ -120,7 +120,7 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('色子奖池配置删除', 'dice:lottery_config:index:destroy')]
|
#[Permission('色子奖池配置删除', 'dice:lottery_pool_config:index:destroy')]
|
||||||
public function destroy(Request $request): Response
|
public function destroy(Request $request): Response
|
||||||
{
|
{
|
||||||
$ids = $request->post('ids', '');
|
$ids = $request->post('ids', '');
|
||||||
@@ -135,4 +135,25 @@ class DiceLotteryConfigController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前彩金池(Redis 实例化,无则按 type=0 创建)
|
||||||
|
* 返回含 profit_amount 实时值,供前端轮询展示
|
||||||
|
*/
|
||||||
|
#[Permission('色子奖池配置列表', 'dice:lottery_pool_config:index:index')]
|
||||||
|
public function getCurrentPool(Request $request): Response
|
||||||
|
{
|
||||||
|
$data = $this->logic->getCurrentPool();
|
||||||
|
return $this->success($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前彩金池:仅可修改 safety_line、t1_weight~t5_weight,不可修改 profit_amount
|
||||||
|
*/
|
||||||
|
#[Permission('色子奖池配置修改', 'dice:lottery_pool_config:index:update')]
|
||||||
|
public function updateCurrentPool(Request $request): Response
|
||||||
|
{
|
||||||
|
$data = $request->post();
|
||||||
|
$this->logic->updateCurrentPool($data);
|
||||||
|
return $this->success('保存成功');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ use plugin\saiadmin\basic\BaseController;
|
|||||||
use app\dice\logic\play_record\DicePlayRecordLogic;
|
use app\dice\logic\play_record\DicePlayRecordLogic;
|
||||||
use app\dice\validate\play_record\DicePlayRecordValidate;
|
use app\dice\validate\play_record\DicePlayRecordValidate;
|
||||||
use app\dice\model\player\DicePlayer;
|
use app\dice\model\player\DicePlayer;
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use app\dice\model\reward_config\DiceRewardConfig;
|
use app\dice\model\reward_config\DiceRewardConfig;
|
||||||
use plugin\saiadmin\service\Permission;
|
use plugin\saiadmin\service\Permission;
|
||||||
use support\Request;
|
use support\Request;
|
||||||
@@ -58,7 +58,7 @@ class DicePlayRecordController extends BaseController
|
|||||||
$query->with([
|
$query->with([
|
||||||
'dicePlayer',
|
'dicePlayer',
|
||||||
'diceRewardConfig',
|
'diceRewardConfig',
|
||||||
'diceLotteryConfig',
|
'diceLotteryPoolConfig',
|
||||||
]);
|
]);
|
||||||
$data = $this->logic->getList($query);
|
$data = $this->logic->getList($query);
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
@@ -85,7 +85,7 @@ class DicePlayRecordController extends BaseController
|
|||||||
#[Permission('玩家抽奖记录列表', 'dice:play_record:index:index')]
|
#[Permission('玩家抽奖记录列表', 'dice:play_record:index:index')]
|
||||||
public function getLotteryConfigOptions(Request $request): Response
|
public function getLotteryConfigOptions(Request $request): Response
|
||||||
{
|
{
|
||||||
$list = DiceLotteryConfig::field('id,name')->select();
|
$list = DiceLotteryPoolConfig::field('id,name')->select();
|
||||||
$data = $list->map(function ($item) {
|
$data = $list->map(function ($item) {
|
||||||
return ['id' => $item['id'], 'name' => $item['name'] ?? ''];
|
return ['id' => $item['id'], 'name' => $item['name'] ?? ''];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?php
|
<?php
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
// | saiadmin [ saiadmin快速开发框架 ]
|
// | saiadmin [ saiadmin?????? ]
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
// | Author: your name
|
// | Author: your name
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\controller\player;
|
namespace app\dice\controller\player;
|
||||||
|
|
||||||
use app\dice\helper\AdminScopeHelper;
|
use app\dice\helper\AdminScopeHelper;
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use plugin\saiadmin\app\model\system\SystemUser;
|
use plugin\saiadmin\app\model\system\SystemUser;
|
||||||
use plugin\saiadmin\basic\BaseController;
|
use plugin\saiadmin\basic\BaseController;
|
||||||
use app\dice\logic\player\DicePlayerLogic;
|
use app\dice\logic\player\DicePlayerLogic;
|
||||||
@@ -17,12 +17,12 @@ use support\Request;
|
|||||||
use support\Response;
|
use support\Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大富翁-玩家控制器
|
* ???-?????
|
||||||
*/
|
*/
|
||||||
class DicePlayerController extends BaseController
|
class DicePlayerController extends BaseController
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* ????
|
||||||
*/
|
*/
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
@@ -32,14 +32,14 @@ class DicePlayerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取彩金池配置选项(DiceLotteryConfig.id、name),供前端 lottery_config_id 下拉使用
|
* ??????????DiceLotteryPoolConfig.id?name????? lottery_config_id ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response 返回 [ ['id' => int, 'name' => string], ... ]
|
* @return Response ?? [ ['id' => int, 'name' => string], ... ]
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家列表', 'dice:player:index:index')]
|
#[Permission('???-????', 'dice:player:index:index')]
|
||||||
public function getLotteryConfigOptions(Request $request): Response
|
public function getLotteryConfigOptions(Request $request): Response
|
||||||
{
|
{
|
||||||
$list = DiceLotteryConfig::field('id,name')->order('id', 'asc')->select();
|
$list = DiceLotteryPoolConfig::field('id,name')->order('id', 'asc')->select();
|
||||||
$data = $list->map(function ($item) {
|
$data = $list->map(function ($item) {
|
||||||
return ['id' => (int) $item['id'], 'name' => (string) ($item['name'] ?? '')];
|
return ['id' => (int) $item['id'], 'name' => (string) ($item['name'] ?? '')];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
@@ -47,12 +47,12 @@ class DicePlayerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取后台管理员选项(SystemUser.id、username、realname),供 admin_id 下拉使用
|
* ??????????SystemUser.id?username?realname??? admin_id ????
|
||||||
* 根据当前登录用户权限过滤(超级管理员可见全部,普通管理员按部门)
|
* ????????????????????????????????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response 返回 [ ['id' => int, 'username' => string, 'realname' => string], ... ]
|
* @return Response ?? [ ['id' => int, 'username' => string, 'realname' => string], ... ]
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家列表', 'dice:player:index:index')]
|
#[Permission('???-????', 'dice:player:index:index')]
|
||||||
public function getSystemUserOptions(Request $request): Response
|
public function getSystemUserOptions(Request $request): Response
|
||||||
{
|
{
|
||||||
$query = SystemUser::field('id,username,realname')->where('status', 1)->order('id', 'asc');
|
$query = SystemUser::field('id,username,realname')->where('status', 1)->order('id', 'asc');
|
||||||
@@ -76,11 +76,11 @@ class DicePlayerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数据列表
|
* ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家列表', 'dice:player:index:index')]
|
#[Permission('???-????', 'dice:player:index:index')]
|
||||||
public function index(Request $request): Response
|
public function index(Request $request): Response
|
||||||
{
|
{
|
||||||
$where = $request->more([
|
$where = $request->more([
|
||||||
@@ -93,60 +93,60 @@ class DicePlayerController extends BaseController
|
|||||||
]);
|
]);
|
||||||
$query = $this->logic->search($where);
|
$query = $this->logic->search($where);
|
||||||
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null);
|
AdminScopeHelper::applyAdminScope($query, $this->adminInfo ?? null);
|
||||||
$query->with(['diceLotteryConfig']);
|
$query->with(['diceLotteryPoolConfig']);
|
||||||
$data = $this->logic->getList($query);
|
$data = $this->logic->getList($query);
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 读取数据
|
* ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家读取', 'dice:player:index:read')]
|
#[Permission('???-????', 'dice:player:index:read')]
|
||||||
public function read(Request $request): Response
|
public function read(Request $request): Response
|
||||||
{
|
{
|
||||||
$id = $request->input('id', '');
|
$id = $request->input('id', '');
|
||||||
$model = $this->logic->read($id);
|
$model = $this->logic->read($id);
|
||||||
if (!$model) {
|
if (!$model) {
|
||||||
return $this->fail('未查找到信息');
|
return $this->fail('??????');
|
||||||
}
|
}
|
||||||
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
||||||
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
||||||
return $this->fail('无权限查看该玩家');
|
return $this->fail('????????');
|
||||||
}
|
}
|
||||||
$data = is_array($model) ? $model : $model->toArray();
|
$data = is_array($model) ? $model : $model->toArray();
|
||||||
return $this->success($data);
|
return $this->success($data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 保存数据
|
* ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家添加', 'dice:player:index:save')]
|
#[Permission('???-????', 'dice:player:index:save')]
|
||||||
public function save(Request $request): Response
|
public function save(Request $request): Response
|
||||||
{
|
{
|
||||||
$data = $request->post();
|
$data = $request->post();
|
||||||
$this->validate('save', $data);
|
$this->validate('save', $data);
|
||||||
// 新增时若未选择管理员,默认使用当前登录用户
|
// ?????????????????????
|
||||||
if (empty($data['admin_id']) && isset($this->adminInfo['id']) && (int) $this->adminInfo['id'] > 0) {
|
if (empty($data['admin_id']) && isset($this->adminInfo['id']) && (int) $this->adminInfo['id'] > 0) {
|
||||||
$data['admin_id'] = (int) $this->adminInfo['id'];
|
$data['admin_id'] = (int) $this->adminInfo['id'];
|
||||||
}
|
}
|
||||||
$result = $this->logic->add($data);
|
$result = $this->logic->add($data);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return $this->success('添加成功');
|
return $this->success('????');
|
||||||
} else {
|
} else {
|
||||||
return $this->fail('添加失败');
|
return $this->fail('????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新数据
|
* ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家修改', 'dice:player:index:update')]
|
#[Permission('???-????', 'dice:player:index:update')]
|
||||||
public function update(Request $request): Response
|
public function update(Request $request): Response
|
||||||
{
|
{
|
||||||
$data = $request->post();
|
$data = $request->post();
|
||||||
@@ -155,55 +155,55 @@ class DicePlayerController extends BaseController
|
|||||||
if ($model) {
|
if ($model) {
|
||||||
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
||||||
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
||||||
return $this->fail('无权限修改该玩家');
|
return $this->fail('????????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$result = $this->logic->edit($data['id'], $data);
|
$result = $this->logic->edit($data['id'], $data);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return $this->success('修改成功');
|
return $this->success('????');
|
||||||
} else {
|
} else {
|
||||||
return $this->fail('修改失败');
|
return $this->fail('????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅更新状态(列表内开关用)
|
* ?????????????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家修改', 'dice:player:index:update')]
|
#[Permission('???-????', 'dice:player:index:update')]
|
||||||
public function updateStatus(Request $request): Response
|
public function updateStatus(Request $request): Response
|
||||||
{
|
{
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
$status = $request->input('status');
|
$status = $request->input('status');
|
||||||
if ($id === null || $id === '') {
|
if ($id === null || $id === '') {
|
||||||
return $this->fail('缺少 id');
|
return $this->fail('?? id');
|
||||||
}
|
}
|
||||||
if ($status === null || $status === '') {
|
if ($status === null || $status === '') {
|
||||||
return $this->fail('缺少 status');
|
return $this->fail('?? status');
|
||||||
}
|
}
|
||||||
$model = $this->logic->read($id);
|
$model = $this->logic->read($id);
|
||||||
if ($model) {
|
if ($model) {
|
||||||
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
||||||
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
if ($allowedIds !== null && !in_array((int) ($model->admin_id ?? 0), $allowedIds, true)) {
|
||||||
return $this->fail('无权限修改该玩家');
|
return $this->fail('????????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->logic->edit($id, ['status' => (int) $status]);
|
$this->logic->edit($id, ['status' => (int) $status]);
|
||||||
return $this->success('修改成功');
|
return $this->success('????');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除数据
|
* ????
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
#[Permission('大富翁-玩家删除', 'dice:player:index:destroy')]
|
#[Permission('???-????', 'dice:player:index:destroy')]
|
||||||
public function destroy(Request $request): Response
|
public function destroy(Request $request): Response
|
||||||
{
|
{
|
||||||
$ids = $request->post('ids', '');
|
$ids = $request->post('ids', '');
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return $this->fail('请选择要删除的数据');
|
return $this->fail('?????????');
|
||||||
}
|
}
|
||||||
$ids = is_array($ids) ? $ids : explode(',', (string) $ids);
|
$ids = is_array($ids) ? $ids : explode(',', (string) $ids);
|
||||||
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
$allowedIds = AdminScopeHelper::getAllowedAdminIds($this->adminInfo ?? null);
|
||||||
@@ -218,14 +218,14 @@ class DicePlayerController extends BaseController
|
|||||||
}
|
}
|
||||||
$ids = $validIds;
|
$ids = $validIds;
|
||||||
if (empty($ids)) {
|
if (empty($ids)) {
|
||||||
return $this->fail('无权限删除所选玩家');
|
return $this->fail('?????????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$result = $this->logic->destroy($ids);
|
$result = $this->logic->destroy($ids);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return $this->success('删除成功');
|
return $this->success('????');
|
||||||
} else {
|
} else {
|
||||||
return $this->fail('删除失败');
|
return $this->fail('????');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
// +----------------------------------------------------------------------
|
|
||||||
// | saiadmin [ saiadmin快速开发框架 ]
|
|
||||||
// +----------------------------------------------------------------------
|
|
||||||
// | Author: your name
|
|
||||||
// +----------------------------------------------------------------------
|
|
||||||
namespace app\dice\logic\lottery_config;
|
|
||||||
|
|
||||||
use plugin\saiadmin\basic\think\BaseLogic;
|
|
||||||
use plugin\saiadmin\exception\ApiException;
|
|
||||||
use plugin\saiadmin\utils\Helper;
|
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 色子奖池配置逻辑层
|
|
||||||
*/
|
|
||||||
class DiceLotteryConfigLogic extends BaseLogic
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* 构造函数
|
|
||||||
*/
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->model = new DiceLotteryConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
<?php
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | saiadmin [ saiadmin快速开发框架 ]
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Author: your name
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
namespace app\dice\logic\lottery_pool_config;
|
||||||
|
|
||||||
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
|
use plugin\saiadmin\basic\think\BaseLogic;
|
||||||
|
use plugin\saiadmin\exception\ApiException;
|
||||||
|
use plugin\saiadmin\utils\Helper;
|
||||||
|
use support\think\Cache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 色子奖池配置逻辑层
|
||||||
|
*/
|
||||||
|
class DiceLotteryPoolConfigLogic extends BaseLogic
|
||||||
|
{
|
||||||
|
/** Redis 当前彩金池(type=0 实例)key,无则按 type=0 创建 */
|
||||||
|
private const REDIS_KEY_CURRENT_POOL = 'api:game:lottery_pool:default';
|
||||||
|
|
||||||
|
private const EXPIRE = 86400 * 7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->model = new DiceLotteryPoolConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前彩金池:从 Redis 读取实例,profit_amount 每次从 DB 实时读取以保证与抽奖累加一致
|
||||||
|
*
|
||||||
|
* @return array{id:int,name:string,safety_line:int,t1_weight:int,t2_weight:int,t3_weight:int,t4_weight:int,t5_weight:int,profit_amount:float}
|
||||||
|
*/
|
||||||
|
public function getCurrentPool(): array
|
||||||
|
{
|
||||||
|
$cached = Cache::get(self::REDIS_KEY_CURRENT_POOL);
|
||||||
|
if ($cached && is_string($cached)) {
|
||||||
|
$data = json_decode($cached, true);
|
||||||
|
if (is_array($data)) {
|
||||||
|
$config = DiceLotteryPoolConfig::find($data['id'] ?? 0);
|
||||||
|
$profit = 0.0;
|
||||||
|
if ($config) {
|
||||||
|
$profit = isset($config->profit_amount) ? (float) $config->profit_amount : (isset($config->ev) ? (float) $config->ev : 0.0);
|
||||||
|
} else {
|
||||||
|
$profit = (float) ($data['profit_amount'] ?? 0);
|
||||||
|
}
|
||||||
|
$data['profit_amount'] = $profit;
|
||||||
|
return $data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$config = DiceLotteryPoolConfig::where('type', 0)->find();
|
||||||
|
if (!$config) {
|
||||||
|
throw new ApiException('未找到 type=0 的奖池配置,请先创建');
|
||||||
|
}
|
||||||
|
$row = $config->toArray();
|
||||||
|
$profitAmount = isset($row['profit_amount']) ? (float) $row['profit_amount'] : (isset($row['ev']) ? (float) $row['ev'] : 0.0);
|
||||||
|
$pool = [
|
||||||
|
'id' => (int) $row['id'],
|
||||||
|
'name' => (string) ($row['name'] ?? ''),
|
||||||
|
'safety_line' => (int) ($row['safety_line'] ?? 0),
|
||||||
|
't1_weight' => (int) ($row['t1_weight'] ?? 0),
|
||||||
|
't2_weight' => (int) ($row['t2_weight'] ?? 0),
|
||||||
|
't3_weight' => (int) ($row['t3_weight'] ?? 0),
|
||||||
|
't4_weight' => (int) ($row['t4_weight'] ?? 0),
|
||||||
|
't5_weight' => (int) ($row['t5_weight'] ?? 0),
|
||||||
|
'profit_amount' => $profitAmount,
|
||||||
|
];
|
||||||
|
Cache::set(self::REDIS_KEY_CURRENT_POOL, json_encode($pool), self::EXPIRE);
|
||||||
|
return $pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前彩金池:仅允许修改 safety_line、t1_weight~t5_weight,不修改 profit_amount
|
||||||
|
* 同时更新 Redis 与 DB 中 type=0 的记录
|
||||||
|
*
|
||||||
|
* @param array{safety_line?:int,t1_weight?:int,t2_weight?:int,t3_weight?:int,t4_weight?:int,t5_weight?:int} $data
|
||||||
|
*/
|
||||||
|
public function updateCurrentPool(array $data): void
|
||||||
|
{
|
||||||
|
$pool = $this->getCurrentPool();
|
||||||
|
$id = (int) $pool['id'];
|
||||||
|
$config = DiceLotteryPoolConfig::find($id);
|
||||||
|
if (!$config) {
|
||||||
|
throw new ApiException('奖池配置不存在');
|
||||||
|
}
|
||||||
|
$allow = ['safety_line', 't1_weight', 't2_weight', 't3_weight', 't4_weight', 't5_weight'];
|
||||||
|
$update = [];
|
||||||
|
foreach ($allow as $k) {
|
||||||
|
if (array_key_exists($k, $data)) {
|
||||||
|
if ($k === 'safety_line') {
|
||||||
|
$update[$k] = (int) $data[$k];
|
||||||
|
} else {
|
||||||
|
$update[$k] = max(0, min(100, (int) $data[$k]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (empty($update)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
DiceLotteryPoolConfig::where('id', $id)->update($update);
|
||||||
|
$pool = array_merge($pool, $update);
|
||||||
|
$refreshed = DiceLotteryPoolConfig::find($id);
|
||||||
|
$pool['profit_amount'] = $refreshed && (isset($refreshed->profit_amount) || isset($refreshed->ev))
|
||||||
|
? (float) ($refreshed->profit_amount ?? $refreshed->ev)
|
||||||
|
: (float) ($pool['profit_amount'] ?? 0);
|
||||||
|
Cache::set(self::REDIS_KEY_CURRENT_POOL, json_encode($pool), self::EXPIRE);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
// | Author: your name
|
// | Author: your name
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\model\lottery_config;
|
namespace app\dice\model\lottery_pool_config;
|
||||||
|
|
||||||
use plugin\saiadmin\basic\think\BaseModel;
|
use plugin\saiadmin\basic\think\BaseModel;
|
||||||
|
|
||||||
@@ -25,8 +25,9 @@ use plugin\saiadmin\basic\think\BaseModel;
|
|||||||
* @property $t3_weight T3池权重
|
* @property $t3_weight T3池权重
|
||||||
* @property $t4_weight T4池权重
|
* @property $t4_weight T4池权重
|
||||||
* @property $t5_weight T5池权重
|
* @property $t5_weight T5池权重
|
||||||
|
* @property $profit_amount 池子累计盈利(每局抽奖累加 100-real_ev,仅展示不可编辑)
|
||||||
*/
|
*/
|
||||||
class DiceLotteryConfig extends BaseModel
|
class DiceLotteryPoolConfig extends BaseModel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 数据表主键
|
* 数据表主键
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\model\play_record;
|
namespace app\dice\model\play_record;
|
||||||
|
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
use app\dice\model\player\DicePlayer;
|
use app\dice\model\player\DicePlayer;
|
||||||
use app\dice\model\reward_config\DiceRewardConfig;
|
use app\dice\model\reward_config\DiceRewardConfig;
|
||||||
use plugin\saiadmin\basic\think\BaseModel;
|
use plugin\saiadmin\basic\think\BaseModel;
|
||||||
@@ -71,12 +71,12 @@ class DicePlayRecord extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 彩金配置
|
* 彩金池配置
|
||||||
* 关联模型 diceLotteryConfig
|
* 关联模型 diceLotteryPoolConfig
|
||||||
*/
|
*/
|
||||||
public function diceLotteryConfig(): BelongsTo
|
public function diceLotteryPoolConfig(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(DiceLotteryConfig::class, 'lottery_config_id', 'id');
|
return $this->belongsTo(DiceLotteryPoolConfig::class, 'lottery_config_id', 'id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按玩家用户名模糊(dicePlayer.username) */
|
/** 按玩家用户名模糊(dicePlayer.username) */
|
||||||
@@ -93,13 +93,13 @@ class DicePlayRecord extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 按彩金池配置名称模糊(diceLotteryConfig.name) */
|
/** 按彩金池配置名称模糊(diceLotteryPoolConfig.name) */
|
||||||
public function searchLotteryConfigNameAttr($query, $value)
|
public function searchLotteryConfigNameAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value === '' || $value === null) {
|
if ($value === '' || $value === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$ids = DiceLotteryConfig::where('name', 'like', '%' . $value . '%')->column('id');
|
$ids = DiceLotteryPoolConfig::where('name', 'like', '%' . $value . '%')->column('id');
|
||||||
if (!empty($ids)) {
|
if (!empty($ids)) {
|
||||||
$query->whereIn('lottery_config_id', $ids);
|
$query->whereIn('lottery_config_id', $ids);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
namespace app\dice\model\player;
|
namespace app\dice\model\player;
|
||||||
|
|
||||||
use plugin\saiadmin\basic\think\BaseModel;
|
use plugin\saiadmin\basic\think\BaseModel;
|
||||||
use app\dice\model\lottery_config\DiceLotteryConfig;
|
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 大富翁-玩家模型
|
* 大富翁-玩家模型
|
||||||
@@ -78,14 +78,14 @@ class DicePlayer extends BaseModel
|
|||||||
if ($name === null || $name === '') {
|
if ($name === null || $name === '') {
|
||||||
$model->setAttr('name', $uid);
|
$model->setAttr('name', $uid);
|
||||||
}
|
}
|
||||||
// 创建玩家时:未指定则自动保存 lottery_config_id 为 DiceLotteryConfig type=0 的 id,没有则为 0
|
// 创建玩家时:未指定则自动保存 lottery_config_id 为 DiceLotteryPoolConfig type=0 的 id,没有则为 0
|
||||||
try {
|
try {
|
||||||
$lotteryConfigId = $model->getAttr('lottery_config_id');
|
$lotteryConfigId = $model->getAttr('lottery_config_id');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$lotteryConfigId = null;
|
$lotteryConfigId = null;
|
||||||
}
|
}
|
||||||
if ($lotteryConfigId === null || $lotteryConfigId === '' || (int) $lotteryConfigId === 0) {
|
if ($lotteryConfigId === null || $lotteryConfigId === '' || (int) $lotteryConfigId === 0) {
|
||||||
$config = DiceLotteryConfig::where('type', 0)->find();
|
$config = DiceLotteryPoolConfig::where('type', 0)->find();
|
||||||
$model->setAttr('lottery_config_id', $config ? (int) $config->id : 0);
|
$model->setAttr('lottery_config_id', $config ? (int) $config->id : 0);
|
||||||
}
|
}
|
||||||
// 彩金池权重默认取 type=0 的奖池配置
|
// 彩金池权重默认取 type=0 的奖池配置
|
||||||
@@ -93,11 +93,11 @@ class DicePlayer extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从 DiceLotteryConfig type=0 取 t1_weight~t5_weight 作为玩家未设置时的默认值
|
* 从 DiceLotteryPoolConfig type=0 取 t1_weight~t5_weight 作为玩家未设置时的默认值
|
||||||
*/
|
*/
|
||||||
protected static function setDefaultWeightsFromLotteryConfig(DicePlayer $model): void
|
protected static function setDefaultWeightsFromLotteryConfig(DicePlayer $model): void
|
||||||
{
|
{
|
||||||
$config = DiceLotteryConfig::where('type', 0)->find();
|
$config = DiceLotteryPoolConfig::where('type', 0)->find();
|
||||||
if (!$config) {
|
if (!$config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -185,8 +185,8 @@ class DicePlayer extends BaseModel
|
|||||||
/**
|
/**
|
||||||
* 关联彩金池配置
|
* 关联彩金池配置
|
||||||
*/
|
*/
|
||||||
public function diceLotteryConfig()
|
public function diceLotteryPoolConfig()
|
||||||
{
|
{
|
||||||
return $this->belongsTo(DiceLotteryConfig::class, 'lottery_config_id', 'id');
|
return $this->belongsTo(DiceLotteryPoolConfig::class, 'lottery_config_id', 'id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -4,14 +4,14 @@
|
|||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
// | Author: your name
|
// | Author: your name
|
||||||
// +----------------------------------------------------------------------
|
// +----------------------------------------------------------------------
|
||||||
namespace app\dice\validate\lottery_config;
|
namespace app\dice\validate\lottery_pool_config;
|
||||||
|
|
||||||
use plugin\saiadmin\basic\BaseValidate;
|
use plugin\saiadmin\basic\BaseValidate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 色子奖池配置验证器
|
* 色子奖池配置验证器
|
||||||
*/
|
*/
|
||||||
class DiceLotteryConfigValidate extends BaseValidate
|
class DiceLotteryPoolConfigValidate extends BaseValidate
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 定义验证规则
|
* 定义验证规则
|
||||||
@@ -18,7 +18,7 @@ abstract class AbstractLogic implements LogicInterface
|
|||||||
* 模型注入
|
* 模型注入
|
||||||
* @var object
|
* @var object
|
||||||
*/
|
*/
|
||||||
protected $model;
|
public $model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理员信息
|
* 管理员信息
|
||||||
|
|||||||
@@ -103,8 +103,12 @@ 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);
|
||||||
fastRoute('dice/lottery_config/DiceLotteryConfig', \app\dice\controller\lottery_config\DiceLotteryConfigController::class);
|
Route::get('/dice/reward_config/DiceRewardConfig/weightRatioList', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'weightRatioList']);
|
||||||
Route::get('/dice/lottery_config/DiceLotteryConfig/getOptions', [\app\dice\controller\lottery_config\DiceLotteryConfigController::class, 'getOptions']);
|
Route::post('/dice/reward_config/DiceRewardConfig/batchUpdateWeights', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdateWeights']);
|
||||||
|
fastRoute('dice/lottery_pool_config/DiceLotteryPoolConfig', \app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class);
|
||||||
|
Route::get('/dice/lottery_pool_config/DiceLotteryPoolConfig/getOptions', [\app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class, 'getOptions']);
|
||||||
|
Route::get('/dice/lottery_pool_config/DiceLotteryPoolConfig/getCurrentPool', [\app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class, 'getCurrentPool']);
|
||||||
|
Route::post('/dice/lottery_pool_config/DiceLotteryPoolConfig/updateCurrentPool', [\app\dice\controller\lottery_pool_config\DiceLotteryPoolConfigController::class, 'updateCurrentPool']);
|
||||||
|
|
||||||
// 数据表维护
|
// 数据表维护
|
||||||
Route::get("/database/index", [\plugin\saiadmin\app\controller\system\DataBaseController::class, 'index']);
|
Route::get("/database/index", [\plugin\saiadmin\app\controller\system\DataBaseController::class, 'index']);
|
||||||
|
|||||||
Reference in New Issue
Block a user