重新优化中奖权重计算方式
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
// 数据配置
|
// 数据配置
|
||||||
data: () => [0, 0, 0, 0, 0, 0, 0],
|
data: () => [0, 0, 0, 0, 0, 0, 0],
|
||||||
xAxisData: () => [],
|
xAxisData: () => [],
|
||||||
|
xAxisName: '',
|
||||||
barWidth: '40%',
|
barWidth: '40%',
|
||||||
stack: false,
|
stack: false,
|
||||||
|
|
||||||
@@ -124,24 +125,30 @@
|
|||||||
} = useChartComponent({
|
} = useChartComponent({
|
||||||
props,
|
props,
|
||||||
checkEmpty: () => {
|
checkEmpty: () => {
|
||||||
// 检查单数据情况
|
if (!Array.isArray(props.data) || !props.data.length) return true
|
||||||
if (Array.isArray(props.data) && typeof props.data[0] === 'number') {
|
|
||||||
|
const first = props.data[0]
|
||||||
|
// 单数据情况:number[] 或可转为数字的数组(兼容后端 int 返回为 string)
|
||||||
|
if (typeof first === 'number' && !Number.isNaN(first)) {
|
||||||
const singleData = props.data as number[]
|
const singleData = props.data as number[]
|
||||||
return !singleData.length || singleData.every((val) => val === 0)
|
return singleData.every((val) => val === 0 || Number.isNaN(Number(val)))
|
||||||
|
}
|
||||||
|
if (typeof first === 'string' && !Number.isNaN(Number(first))) {
|
||||||
|
const singleData = props.data.map((v) => Number(v))
|
||||||
|
return singleData.every((val) => val === 0 || Number.isNaN(val))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查多数据情况
|
// 多数据情况
|
||||||
if (Array.isArray(props.data) && typeof props.data[0] === 'object') {
|
if (typeof first === 'object' && first !== null && 'name' in first) {
|
||||||
const multiData = props.data as BarDataItem[]
|
const multiData = props.data as BarDataItem[]
|
||||||
return (
|
return multiData.every(
|
||||||
!multiData.length ||
|
(item) => !item.data?.length || item.data.every((val) => val === 0 || Number.isNaN(Number(val)))
|
||||||
multiData.every((item) => !item.data?.length || item.data.every((val) => val === 0))
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
watchSources: [() => props.data, () => props.xAxisData, () => props.colors],
|
watchSources: [() => props.data, () => props.xAxisData, () => props.xAxisName, () => props.colors],
|
||||||
generateOptions: (): EChartsOption => {
|
generateOptions: (): EChartsOption => {
|
||||||
const options: EChartsOption = {
|
const options: EChartsOption = {
|
||||||
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
|
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
|
||||||
@@ -152,6 +159,9 @@
|
|||||||
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
name: props.xAxisName || undefined,
|
||||||
|
nameLocation: 'middle',
|
||||||
|
nameGap: 25,
|
||||||
data: props.xAxisData,
|
data: props.xAxisData,
|
||||||
axisTick: getAxisTickStyle(),
|
axisTick: getAxisTickStyle(),
|
||||||
axisLine: getAxisLineStyle(props.showAxisLine),
|
axisLine: getAxisLineStyle(props.showAxisLine),
|
||||||
|
|||||||
@@ -117,6 +117,8 @@ export interface BarChartProps extends BaseChartProps, AxisDisplayProps, Interac
|
|||||||
data: number[] | BarDataItem[]
|
data: number[] | BarDataItem[]
|
||||||
/** X轴标签数据 */
|
/** X轴标签数据 */
|
||||||
xAxisData?: string[]
|
xAxisData?: string[]
|
||||||
|
/** X轴名称(如:色子点数) */
|
||||||
|
xAxisName?: string
|
||||||
/** 柱状图宽度 */
|
/** 柱状图宽度 */
|
||||||
barWidth?: string | number
|
barWidth?: string | number
|
||||||
/** 是否堆叠显示 */
|
/** 是否堆叠显示 */
|
||||||
|
|||||||
@@ -336,6 +336,13 @@
|
|||||||
if (formData.roll_number == null && formData.rollArrayItems.length === 5) {
|
if (formData.roll_number == null && formData.rollArrayItems.length === 5) {
|
||||||
formData.roll_number = formData.rollArrayItems.reduce((s, n) => (s ?? 0) + (n ?? 0), 0) || null
|
formData.roll_number = formData.rollArrayItems.reduce((s, n) => (s ?? 0) + (n ?? 0), 0) || null
|
||||||
}
|
}
|
||||||
|
// 点数和有值但五个点数为空或无效时,根据 roll_number 补全显示(兼容历史错误数据)
|
||||||
|
const sum = formData.roll_number != null ? Number(formData.roll_number) : 0
|
||||||
|
const hasNull = formData.rollArrayItems.some((n) => n == null)
|
||||||
|
const itemsSum = formData.rollArrayItems.reduce((s, n) => (s ?? 0) + (n ?? 0), 0) ?? 0
|
||||||
|
if (sum >= 5 && sum <= 30 && (hasNull || itemsSum !== sum)) {
|
||||||
|
formData.rollArrayItems = defaultRollArrayItems(sum)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 将接口的 roll_array 转为固定 5 项数组,不足补 null */
|
/** 将接口的 roll_array 转为固定 5 项数组,不足补 null */
|
||||||
@@ -360,6 +367,16 @@
|
|||||||
return items.slice(0, 5)
|
return items.slice(0, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 点数和有值但五个点数缺失时,根据点数和生成默认 5 个数(与后端 defaultRollArrayForSum 一致) */
|
||||||
|
function defaultRollArrayItems(sum: number): (number | null)[] {
|
||||||
|
const s = Math.max(5, Math.min(30, Math.floor(Number(sum))))
|
||||||
|
const base = Math.floor(s / 5)
|
||||||
|
const rem = s - 5 * base
|
||||||
|
const arr: number[] = Array(5).fill(base)
|
||||||
|
for (let i = 0; i < rem; i++) arr[i]++
|
||||||
|
return arr.map((v) => Math.max(1, Math.min(6, v)))
|
||||||
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
{ prop: 'ui_text', label: '前端显示文本', align: 'center' },
|
{ prop: 'ui_text', label: '前端显示文本', align: 'center' },
|
||||||
{ prop: 'real_ev', label: '真实资金结算', align: 'center' },
|
{ prop: 'real_ev', label: '真实资金结算', align: 'center' },
|
||||||
{ prop: 'tier', label: '所属档位', sortable: true, align: 'center' },
|
{ prop: 'tier', label: '所属档位', sortable: true, align: 'center' },
|
||||||
{ prop: 'weight', label: '权重(%)', width: 100, align: 'center' },
|
{ prop: 'weight', label: '权重(1-10000)', width: 110, align: 'center' },
|
||||||
// { prop: 'create_time', label: '创建时间', sortable: true, align: 'center' },
|
// { prop: 'create_time', label: '创建时间', sortable: true, align: 'center' },
|
||||||
{
|
{
|
||||||
prop: 'operation',
|
prop: 'operation',
|
||||||
|
|||||||
@@ -37,19 +37,25 @@
|
|||||||
<el-option label="BIGWIN(超级大奖)" value="BIGWIN" />
|
<el-option label="BIGWIN(超级大奖)" value="BIGWIN" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="formData.tier === 'BIGWIN'" label="权重(%)" prop="weight">
|
<el-form-item label="权重(1-10000)" prop="weight">
|
||||||
<el-slider
|
<el-input-number
|
||||||
v-model="formData.weight"
|
v-model="formData.weight"
|
||||||
:min="0"
|
:min="1"
|
||||||
:max="100"
|
:max="10000"
|
||||||
:step="0.01"
|
:step="1"
|
||||||
:disabled="isWeightFixed100"
|
:disabled="isWeightFixed10000"
|
||||||
show-input
|
placeholder="1-10000"
|
||||||
/>
|
/>
|
||||||
<div v-if="isWeightFixed100" class="weight-fixed-hint">
|
<div v-if="isWeightFixed10000" class="weight-fixed-hint">
|
||||||
色子点数 5、30 固定 100% 豹子,不可修改权重
|
色子点数 5、30 固定 10000,不可修改权重
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<el-form-item label="顺时针起始索引" prop="s_start_index">
|
||||||
|
<el-input-number v-model="formData.s_start_index" :min="0" :step="1" placeholder="s_start_index" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="逆时针起始索引" prop="n_start_index">
|
||||||
|
<el-input-number v-model="formData.n_start_index" :min="0" :step="1" placeholder="n_start_index" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="formData.remark"
|
v-model="formData.remark"
|
||||||
@@ -102,8 +108,8 @@
|
|||||||
set: (value) => emit('update:modelValue', value)
|
set: (value) => emit('update:modelValue', value)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** BIGWIN 且 grid_number 为 5 或 30 时豹子概率固定为 100%(禁止手动调整) */
|
/** BIGWIN 且 grid_number 为 5 或 30 时权重固定为 10000(禁止手动调整) */
|
||||||
const isWeightFixed100 = computed(
|
const isWeightFixed10000 = computed(
|
||||||
() =>
|
() =>
|
||||||
formData.tier === 'BIGWIN' &&
|
formData.tier === 'BIGWIN' &&
|
||||||
(formData.grid_number === 5 || formData.grid_number === 30)
|
(formData.grid_number === 5 || formData.grid_number === 30)
|
||||||
@@ -120,20 +126,18 @@
|
|||||||
weight: [
|
weight: [
|
||||||
{
|
{
|
||||||
validator: (_rule: unknown, value: number | null, callback: (e?: Error) => void) => {
|
validator: (_rule: unknown, value: number | null, callback: (e?: Error) => void) => {
|
||||||
if (formData.tier !== 'BIGWIN') {
|
|
||||||
callback()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const n = value != null ? Number(value) : NaN
|
const n = value != null ? Number(value) : NaN
|
||||||
if (Number.isNaN(n) || n < 0 || n > 100) {
|
if (Number.isNaN(n) || n < 1 || n > 10000) {
|
||||||
callback(new Error('权重仅 BIGWIN 可设定,且必须为 0-100%'))
|
callback(new Error('权重必须为 1-10000'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
callback()
|
callback()
|
||||||
},
|
},
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
n_start_index: [],
|
||||||
|
s_start_index: []
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -145,7 +149,9 @@
|
|||||||
ui_text: '',
|
ui_text: '',
|
||||||
real_ev: '',
|
real_ev: '',
|
||||||
tier: '',
|
tier: '',
|
||||||
weight: 0 as number,
|
weight: 1 as number,
|
||||||
|
n_start_index: 0 as number,
|
||||||
|
s_start_index: 0 as number,
|
||||||
remark: ''
|
remark: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +172,7 @@
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 当 BIGWIN 且 grid_number 为 5 或 30 时,权重固定为 100 便于展示 */
|
/** 当 BIGWIN 且 grid_number 为 5 或 30 时,权重固定为 10000 便于展示 */
|
||||||
watch(
|
watch(
|
||||||
() => [formData.tier, formData.grid_number],
|
() => [formData.tier, formData.grid_number],
|
||||||
() => {
|
() => {
|
||||||
@@ -174,7 +180,7 @@
|
|||||||
formData.tier === 'BIGWIN' &&
|
formData.tier === 'BIGWIN' &&
|
||||||
(formData.grid_number === 5 || formData.grid_number === 30)
|
(formData.grid_number === 5 || formData.grid_number === 30)
|
||||||
) {
|
) {
|
||||||
formData.weight = 100
|
formData.weight = 10000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -197,7 +203,7 @@
|
|||||||
*/
|
*/
|
||||||
const initForm = () => {
|
const initForm = () => {
|
||||||
if (!props.data) return
|
if (!props.data) return
|
||||||
const numKeys = ['id', 'grid_number', 'real_ev', 'weight']
|
const numKeys = ['id', 'grid_number', 'real_ev', 'weight', 'n_start_index', 's_start_index']
|
||||||
for (const key of Object.keys(formData)) {
|
for (const key of Object.keys(formData)) {
|
||||||
if (!(key in props.data)) continue
|
if (!(key in props.data)) continue
|
||||||
const val = props.data[key]
|
const val = props.data[key]
|
||||||
@@ -228,18 +234,13 @@
|
|||||||
try {
|
try {
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
const payload = { ...formData }
|
const payload = { ...formData }
|
||||||
if (payload.tier !== 'BIGWIN') {
|
const w = Number(payload.weight)
|
||||||
payload.weight = 0
|
payload.weight = Number.isNaN(w) ? 1 : Math.max(1, Math.min(10000, w))
|
||||||
} else if (payload.grid_number === 5 || payload.grid_number === 30) {
|
if (payload.tier === 'BIGWIN' && (payload.grid_number === 5 || payload.grid_number === 30)) {
|
||||||
payload.weight = 100
|
payload.weight = 10000
|
||||||
} else {
|
|
||||||
if (payload.grid_number === 5 || payload.grid_number === 30) {
|
|
||||||
payload.weight = 100
|
|
||||||
} else {
|
|
||||||
const w = Number(payload.weight)
|
|
||||||
payload.weight = Number.isNaN(w) ? 0 : Math.max(0, Math.min(100, w))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
payload.n_start_index = Number(payload.n_start_index) || 0
|
||||||
|
payload.s_start_index = Number(payload.s_start_index) || 0
|
||||||
if (props.dialogType === 'add') {
|
if (props.dialogType === 'add') {
|
||||||
await api.save(payload)
|
await api.save(payload)
|
||||||
ElMessage.success('新增成功')
|
ElMessage.success('新增成功')
|
||||||
|
|||||||
@@ -13,19 +13,18 @@
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="chart-wrap">
|
<div class="chart-wrap">
|
||||||
<ArtBarChart
|
<ArtBarChart
|
||||||
|
x-axis-name="色子点数"
|
||||||
:x-axis-data="getTierChartLabels(t)"
|
:x-axis-data="getTierChartLabels(t)"
|
||||||
:data="getTierChartData(t)"
|
:data="getTierChartData(t)"
|
||||||
height="220px"
|
height="220px"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-sum" v-if="t !== 'BIGWIN'">
|
<div class="weight-sum" v-if="t !== 'BIGWIN'">
|
||||||
当前档位权重合计:<strong :class="{ over: getTierSumForValidation(t) !== 100 }">{{
|
当前档位权重合计:<strong>{{ getTierSumForValidation(t) }}</strong>
|
||||||
getTierSumForValidation(t).toFixed(1)
|
(各条权重 1-10000,档位内按权重比抽取 grid_number,和不限制)
|
||||||
}}</strong>
|
|
||||||
/ 100(须等于 100%)
|
|
||||||
</div>
|
</div>
|
||||||
<div class="weight-sum weight-sum-bigwin" v-else>
|
<div class="weight-sum weight-sum-bigwin" v-else>
|
||||||
BIGWIN 为豹子权重,单独设定每条 0–100%,无合计要求
|
BIGWIN 为豹子权重,每条权重 1-10000
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="getTierItems(t)" border size="small" class="weight-table">
|
<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="grid_number" width="50" align="center" />
|
||||||
@@ -50,20 +49,20 @@
|
|||||||
align="center"
|
align="center"
|
||||||
show-overflow-tooltip
|
show-overflow-tooltip
|
||||||
/>
|
/>
|
||||||
<el-table-column label="权重(%)" min-width="200" align="center">
|
<el-table-column label="权重(1-10000)" min-width="200" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="weight-cell-vertical">
|
<div class="weight-cell-vertical">
|
||||||
<div class="weight-slider-wrap">
|
<div class="weight-slider-wrap">
|
||||||
<el-slider
|
<el-slider
|
||||||
:model-value="getItemWeight(row)"
|
:model-value="getItemWeight(row)"
|
||||||
:min="0"
|
:min="1"
|
||||||
:max="100"
|
:max="10000"
|
||||||
:step="1"
|
:step="1"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="isWeightDisabled(row, t)"
|
:disabled="isWeightDisabled(row, t)"
|
||||||
class="weight-slider"
|
class="weight-slider"
|
||||||
@update:model-value="
|
@update:model-value="
|
||||||
(v: number | number[]) => setItemWeight(row, normalizeSliderValue(v))
|
(v: number | number[]) => setItemWeightByRow(t, row, normalizeSliderValue(v))
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -71,28 +70,29 @@
|
|||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row) <= 0"
|
native-type="button"
|
||||||
@click="adjustWeight(row, -1)"
|
:disabled="isWeightDisabled(row, t) || getItemWeight(row) <= 1"
|
||||||
|
@click.prevent="adjustWeightByRow(t, row, -1)"
|
||||||
>
|
>
|
||||||
-
|
-
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-input-number
|
<el-input-number
|
||||||
:model-value="getItemWeight(row)"
|
:model-value="getItemWeight(row)"
|
||||||
:min="0"
|
:min="1"
|
||||||
:max="100"
|
:max="10000"
|
||||||
:step="1"
|
:step="1"
|
||||||
:precision="1"
|
|
||||||
:disabled="isWeightDisabled(row, t)"
|
:disabled="isWeightDisabled(row, t)"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
class="weight-input"
|
class="weight-input"
|
||||||
@update:model-value="(v: number | undefined) => setItemWeight(row, v ?? 0)"
|
@update:model-value="(v: number | string | undefined) => setItemWeightByRow(t, row, typeof v === 'number' && !Number.isNaN(v) ? v : Number(v) || 1)"
|
||||||
/>
|
/>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
link
|
link
|
||||||
:disabled="isWeightDisabled(row, t) || getItemWeight(row) >= 100"
|
native-type="button"
|
||||||
@click="adjustWeight(row, 1)"
|
:disabled="isWeightDisabled(row, t) || getItemWeight(row) >= 10000"
|
||||||
|
@click.prevent="adjustWeightByRow(t, row, 1)"
|
||||||
>
|
>
|
||||||
+
|
+
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -156,30 +156,59 @@
|
|||||||
return grouped.value[tier] ?? []
|
return grouped.value[tier] ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 图表横坐标为色子点数(grid_number) */
|
||||||
function getTierChartLabels(tier: string): string[] {
|
function getTierChartLabels(tier: string): string[] {
|
||||||
const items = getTierItems(tier)
|
const items = getTierItems(tier)
|
||||||
return items.map((r) => (r.ui_text ? String(r.ui_text) : `点数 ${r.grid_number ?? ''}`))
|
return items.map((r) => String(r.grid_number ?? ''))
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTierChartData(tier: string): number[] {
|
function getTierChartData(tier: string): number[] {
|
||||||
const items = getTierItems(tier)
|
const items = getTierItems(tier)
|
||||||
return items.map((r) => getItemWeight(r))
|
return items.map((r) => getItemWeight(r)).map((n) => Number(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 用于 T1–T5 校验与展示的档位权重和(仅 T1–T5 使用;BIGWIN 不要求合计) */
|
/** 档位权重和(仅展示用,不限制) */
|
||||||
function getTierSumForValidation(tier: string): number {
|
function getTierSumForValidation(tier: string): number {
|
||||||
const items = getTierItems(tier)
|
const items = getTierItems(tier)
|
||||||
return items.reduce((s, r) => s + getItemWeight(r), 0)
|
return items.reduce((s, r) => s + getItemWeight(r), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemWeight(row: Record<string, unknown> & { weight?: number }): number {
|
/** 读取权重,1-10000 整数(兼容后端 int 返回为 number 或 string) */
|
||||||
|
function getItemWeight(row: Record<string, unknown> & { weight?: number | string }): number {
|
||||||
const w = row.weight
|
const w = row.weight
|
||||||
if (typeof w === 'number' && !Number.isNaN(w)) return Math.max(0, Math.min(100, w))
|
const num = typeof w === 'number' && !Number.isNaN(w) ? w : Number(w)
|
||||||
return 0
|
if (Number.isNaN(num)) return 1
|
||||||
|
const n = Math.max(1, Math.min(10000, Math.floor(num)))
|
||||||
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
function setItemWeight(row: Record<string, unknown> & { weight: number }, value: number) {
|
function setItemWeight(row: Record<string, unknown> & { weight: number }, value: number) {
|
||||||
row.weight = Math.max(0, Math.min(100, value))
|
const v = toWeightPrecision(value)
|
||||||
|
row.weight = v
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 权重 1-10000 整数 */
|
||||||
|
function toWeightPrecision(value: number): number {
|
||||||
|
const n = Math.max(1, Math.min(10000, Math.floor(value)))
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按档位与行更新权重:原地修改 list 中对应项 */
|
||||||
|
function setItemWeightByRow(tier: string, row: Record<string, unknown> & { weight: number }, value: number) {
|
||||||
|
const v = toWeightPrecision(value)
|
||||||
|
const list = grouped.value[tier]
|
||||||
|
if (!list) return
|
||||||
|
const idx = list.findIndex((r) => r === row || (r.id != null && row.id != null && r.id === row.id))
|
||||||
|
if (idx >= 0) {
|
||||||
|
list[idx].weight = v
|
||||||
|
} else {
|
||||||
|
row.weight = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function adjustWeightByRow(tier: string, row: Record<string, unknown> & { weight: number }, delta: number) {
|
||||||
|
const cur = getItemWeight(row)
|
||||||
|
setItemWeightByRow(tier, row, cur + delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustWeight(row: Record<string, unknown> & { weight: number }, delta: number) {
|
function adjustWeight(row: Record<string, unknown> & { weight: number }, delta: number) {
|
||||||
@@ -187,9 +216,8 @@
|
|||||||
setItemWeight(row, cur + delta)
|
setItemWeight(row, cur + delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** T4、T5 及 BIGWIN 的 grid_number=5、30 不可修改权重(固定 100%) */
|
/** BIGWIN 的 grid_number=5、30 不可修改权重(固定 10000) */
|
||||||
function isWeightDisabled(row: Record<string, unknown>, tier: string): boolean {
|
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
|
if (tier === 'BIGWIN' && (row.grid_number === 5 || row.grid_number === 30)) return true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -242,13 +270,11 @@
|
|||||||
}
|
}
|
||||||
for (const t of TIER_KEYS) {
|
for (const t of TIER_KEYS) {
|
||||||
const list = Array.isArray(data[t]) ? data[t] : []
|
const list = Array.isArray(data[t]) ? data[t] : []
|
||||||
next[t] = list.map((r) => ({
|
next[t] = list.map((r) => {
|
||||||
...r,
|
const raw =
|
||||||
weight:
|
typeof r.weight === 'number' && !Number.isNaN(r.weight) ? r.weight : Number(r.weight) || 1
|
||||||
typeof r.weight === 'number' && !Number.isNaN(r.weight)
|
return { ...r, weight: Math.max(1, Math.min(10000, Math.floor(raw))) }
|
||||||
? r.weight
|
})
|
||||||
: Number(r.weight) || 0
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
grouped.value = next
|
grouped.value = next
|
||||||
})
|
})
|
||||||
@@ -258,23 +284,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function validateAll(): boolean {
|
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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 收集所有档位的 id + weight,同一 id 出现多次时合并权重(避免后端只保留最后一次导致档位合计不足 100%)。
|
* 收集所有档位的 id + weight(1-10000),BIGWIN 的 5/30 提交时固定为 10000。
|
||||||
* T4、T5、BIGWIN 的 5/30 不可修改,提交时固定为 100。
|
|
||||||
*/
|
*/
|
||||||
function collectItems(): Array<{ id: number; weight: number }> {
|
function collectItems(): Array<{ id: number; weight: number }> {
|
||||||
const byId = new Map<number, number>()
|
const byId = new Map<number, number>()
|
||||||
@@ -282,8 +296,8 @@
|
|||||||
for (const row of getTierItems(t)) {
|
for (const row of getTierItems(t)) {
|
||||||
const id = row.id != null ? Number(row.id) : 0
|
const id = row.id != null ? Number(row.id) : 0
|
||||||
if (id >= 0 && !Number.isNaN(id)) {
|
if (id >= 0 && !Number.isNaN(id)) {
|
||||||
const w = isWeightDisabled(row, t) ? 100 : getItemWeight(row)
|
const w = isWeightDisabled(row, t) ? 10000 : getItemWeight(row)
|
||||||
byId.set(id, Math.min(100, (byId.get(id) ?? 0) + w))
|
byId.set(id, w)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@ class PlayStartLogic
|
|||||||
private const MIN_COIN_EXTRA = 100;
|
private const MIN_COIN_EXTRA = 100;
|
||||||
/** 豹子号中大奖额外平台币(无 BIGWIN 配置时兜底) */
|
/** 豹子号中大奖额外平台币(无 BIGWIN 配置时兜底) */
|
||||||
private const SUPER_WIN_BONUS = 500;
|
private const SUPER_WIN_BONUS = 500;
|
||||||
/** 可触发超级大奖的 grid_number(5=全1 10=全2 15=全3 20=全4 25=全5 30=全6);其中 5 和 30 固定 100% 出豹子 */
|
/** 可触发超级大奖的 grid_number(5=全1 10=全2 15=全3 20=全4 25=全5 30=全6) */
|
||||||
private const SUPER_WIN_GRID_NUMBERS = [5, 10, 15, 20, 25, 30];
|
private const SUPER_WIN_GRID_NUMBERS = [5, 10, 15, 20, 25, 30];
|
||||||
/** grid_number 为 5 或 30 时豹子概率固定 100%(DiceRewardConfig tier=BIGWIN 约定) */
|
/** 5 和 30 抽到即豹子,不参与 BIGWIN 权重判定;10/15/20/25 按 BIGWIN weight 判定是否豹子 */
|
||||||
private const SUPER_WIN_ALWAYS_GRID_NUMBERS = [5, 30];
|
private const SUPER_WIN_ALWAYS_GRID_NUMBERS = [5, 30];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,10 +85,9 @@ class PlayStartLogic
|
|||||||
$safetyLine = (int) ($config->safety_line ?? 0);
|
$safetyLine = (int) ($config->safety_line ?? 0);
|
||||||
$usePoolWeights = $poolProfit >= $safetyLine;
|
$usePoolWeights = $poolProfit >= $safetyLine;
|
||||||
|
|
||||||
// 按上述规则抽取档位;若该档位无奖励或该方向下均无可用路径则重新摇取档位
|
// 按档位 T1-T5 抽取后,直接按该档位内 weight 抽取一条 DiceRewardConfig,得到 grid_number
|
||||||
$maxTierRetry = 10;
|
$maxTierRetry = 10;
|
||||||
$chosen = null;
|
$chosen = null;
|
||||||
$startCandidates = [];
|
|
||||||
$tier = null;
|
$tier = null;
|
||||||
for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
|
for ($tierAttempt = 0; $tierAttempt < $maxTierRetry; $tierAttempt++) {
|
||||||
$tier = $usePoolWeights
|
$tier = $usePoolWeights
|
||||||
@@ -99,58 +98,55 @@ class PlayStartLogic
|
|||||||
Log::warning("档位 {$tier} 无任何奖励配置,重新摇取档位");
|
Log::warning("档位 {$tier} 无任何奖励配置,重新摇取档位");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$maxRewardRetry = count($tierRewards);
|
try {
|
||||||
for ($attempt = 0; $attempt < $maxRewardRetry; $attempt++) {
|
|
||||||
$chosen = self::drawRewardByWeight($tierRewards);
|
$chosen = self::drawRewardByWeight($tierRewards);
|
||||||
$chosenId = (int) ($chosen['id'] ?? 0);
|
} catch (\RuntimeException $e) {
|
||||||
if ($direction === 0) {
|
if ($e->getMessage() === self::EXCEPTION_WEIGHT_ALL_ZERO) {
|
||||||
$startCandidates = DiceRewardConfig::getCachedBySEndIndex($chosenId);
|
Log::warning("档位 {$tier} 下所有奖励权重均为 0,重新摇取档位");
|
||||||
} else {
|
continue;
|
||||||
$startCandidates = DiceRewardConfig::getCachedByNEndIndex($chosenId);
|
|
||||||
}
|
}
|
||||||
if (!empty($startCandidates)) {
|
throw $e;
|
||||||
break 2;
|
|
||||||
}
|
|
||||||
Log::warning("方向 {$direction} 下无 s_end_index/n_end_index={$chosenId} 的配置,重新摇取");
|
|
||||||
}
|
}
|
||||||
Log::warning("方向 {$direction} 下档位 {$tier} 所有奖励均无可用路径配置,重新摇取档位");
|
break;
|
||||||
}
|
}
|
||||||
if (empty($startCandidates)) {
|
if ($chosen === null) {
|
||||||
Log::error("方向 {$direction} 下多次摇取档位后仍无可用路径配置");
|
Log::error("多次摇取档位后仍无有效权重配置");
|
||||||
throw new ApiException('该方向下暂无可用路径配置');
|
throw new ApiException('暂无可用奖励配置');
|
||||||
}
|
}
|
||||||
$chosenId = (int) ($chosen['id'] ?? 0);
|
$chosenId = (int) ($chosen['id'] ?? 0);
|
||||||
$startRecord = $startCandidates[array_rand($startCandidates)];
|
|
||||||
|
|
||||||
$startIndex = (int) ($startRecord['id'] ?? 0);
|
// 起始索引:根据 direction 取 s_start_index(顺时针)或 n_start_index(逆时针);结束索引:当前中奖配置 id
|
||||||
$targetIndex = $direction === 0
|
$startIndex = $direction === 0
|
||||||
? (int) ($startRecord['s_end_index'] ?? 0)
|
? (int) ($chosen['s_start_index'] ?? 0)
|
||||||
: (int) ($startRecord['n_end_index'] ?? 0);
|
: (int) ($chosen['n_start_index'] ?? 0);
|
||||||
$rollNumber = (int) ($startRecord['grid_number'] ?? 0);
|
$targetIndex = $chosenId;
|
||||||
|
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
||||||
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
||||||
$rewardWinCoin = 100 + $realEv; // 摇色子中奖平台币 = 100 + DiceRewardConfig.real_ev
|
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
||||||
|
// T5(再来一次):摇色子中奖平台币 = 配置的 real_ev;其他档位 = 100 + real_ev
|
||||||
|
$rewardWinCoin = $isTierT5 ? $realEv : (100 + $realEv);
|
||||||
|
|
||||||
// 当抽到的 grid_number 为 5/10/15/20/25/30 时,可出豹子;其中 grid_number=5 与 30 固定 100% 豹子(BIGWIN 约定)
|
// 流程:先抽档位 T1-T5,再按档位内权重抽色子点数;5 和 30 抽到即豹子,10/15/20/25 按 BIGWIN weight 判定是否中大奖(豹子如 [4,4,4,4,4])
|
||||||
$superWinCoin = 0;
|
$superWinCoin = 0;
|
||||||
$isWin = 0;
|
$isWin = 0;
|
||||||
$bigWinRealEv = 0.0; // BIGWIN 档位的真实资金结算,用于从彩金池盈利中一并扣除
|
$bigWinRealEv = 0.0;
|
||||||
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);
|
||||||
$doSuperWin = $alwaysSuperWin;
|
$doSuperWin = $alwaysSuperWin;
|
||||||
if (!$doSuperWin) {
|
if (!$doSuperWin) {
|
||||||
$weight = $bigWinConfig !== null
|
$weight = $bigWinConfig !== null
|
||||||
? max(0.0, min(100.0, (float) ($bigWinConfig['weight'] ?? 0)))
|
? max(1, min(10000, (int) ($bigWinConfig['weight'] ?? 1)))
|
||||||
: 100.0;
|
: 10000;
|
||||||
$roll = mt_rand(1, 10000) / 10000;
|
$roll = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX);
|
||||||
$doSuperWin = $roll <= $weight / 100;
|
$doSuperWin = $roll < ($weight / 10000.0);
|
||||||
}
|
}
|
||||||
if ($doSuperWin) {
|
if ($doSuperWin) {
|
||||||
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
$rollArray = $this->getSuperWinRollArray($rollNumber);
|
||||||
$isWin = 1;
|
$isWin = 1;
|
||||||
if ($bigWinConfig !== null) {
|
if ($bigWinConfig !== null) {
|
||||||
$bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
|
$bigWinRealEv = (float) ($bigWinConfig['real_ev'] ?? 0);
|
||||||
$superWinCoin = 100 + $bigWinRealEv;
|
$superWinCoin = $bigWinRealEv;
|
||||||
} else {
|
} else {
|
||||||
$superWinCoin = self::SUPER_WIN_BONUS;
|
$superWinCoin = self::SUPER_WIN_BONUS;
|
||||||
}
|
}
|
||||||
@@ -174,7 +170,6 @@ class PlayStartLogic
|
|||||||
$configId = (int) $config->id;
|
$configId = (int) $config->id;
|
||||||
$rewardId = $chosenId;
|
$rewardId = $chosenId;
|
||||||
$configName = (string) ($config->name ?? '');
|
$configName = (string) ($config->name ?? '');
|
||||||
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
|
||||||
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
|
$adminId = ($player->admin_id ?? null) ? (int) $player->admin_id : null;
|
||||||
try {
|
try {
|
||||||
Db::transaction(function () use (
|
Db::transaction(function () use (
|
||||||
@@ -322,34 +317,39 @@ class PlayStartLogic
|
|||||||
return $arr;
|
return $arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 该组配置权重均为 0 时抛出,供调用方重试 */
|
||||||
|
private const EXCEPTION_WEIGHT_ALL_ZERO = 'REWARD_WEIGHT_ALL_ZERO';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* T1-T5 档位内按 DiceRewardConfig.weight 抽取一条配置;权重均为 0 或未设时等权随机
|
* 按权重抽取一条配置:仅 weight>0 参与抽取(weight=0 不会被摇到)
|
||||||
* @param array<int, array> $rewards 该档位配置列表(每条含 weight、id、real_ev 等)
|
* 使用 [0, total) 浮点随机,支持最小权重 0.1%(如 weight=0.1),避免整数随机导致小权重失真
|
||||||
* @return array 抽中的一条配置
|
* 全部 weight 为 0 时抛出 RuntimeException(EXCEPTION_WEIGHT_ALL_ZERO)
|
||||||
*/
|
*/
|
||||||
private static function drawRewardByWeight(array $rewards): array
|
private static function drawRewardByWeight(array $rewards): array
|
||||||
{
|
{
|
||||||
if (empty($rewards)) {
|
if (empty($rewards)) {
|
||||||
throw new \InvalidArgumentException('rewards 不能为空');
|
throw new \InvalidArgumentException('rewards 不能为空');
|
||||||
}
|
}
|
||||||
$weights = [];
|
$candidateWeights = [];
|
||||||
foreach ($rewards as $i => $row) {
|
foreach ($rewards as $i => $row) {
|
||||||
$w = isset($row['weight']) ? max(0, (float) $row['weight']) : 0;
|
$w = isset($row['weight']) ? (float) $row['weight'] : 0.0;
|
||||||
$weights[$i] = $w;
|
if ($w > 0) {
|
||||||
}
|
$candidateWeights[$i] = $w;
|
||||||
$total = array_sum($weights);
|
|
||||||
if ($total <= 0) {
|
|
||||||
return $rewards[array_rand($rewards)];
|
|
||||||
}
|
|
||||||
$r = mt_rand(1, (int) $total);
|
|
||||||
$acc = 0;
|
|
||||||
foreach ($weights as $i => $w) {
|
|
||||||
$acc += $w;
|
|
||||||
if ($r <= $acc) {
|
|
||||||
return $rewards[$i];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return $rewards[array_key_last($rewards)];
|
$total = (float) array_sum($candidateWeights);
|
||||||
|
if ($total > 0) {
|
||||||
|
$r = (random_int(0, PHP_INT_MAX - 1) / (float) PHP_INT_MAX) * $total;
|
||||||
|
$acc = 0.0;
|
||||||
|
foreach ($candidateWeights as $i => $w) {
|
||||||
|
$acc += $w;
|
||||||
|
if ($r < $acc) {
|
||||||
|
return $rewards[$i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $rewards[array_key_last($candidateWeights)];
|
||||||
|
}
|
||||||
|
throw new \RuntimeException(self::EXCEPTION_WEIGHT_ALL_ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -368,7 +368,7 @@ class PlayStartLogic
|
|||||||
if (empty($candidates)) {
|
if (empty($candidates)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$idx = $candidates[array_rand($candidates)];
|
$idx = $candidates[random_int(0, count($candidates) - 1)];
|
||||||
$arr[$idx]++;
|
$arr[$idx]++;
|
||||||
}
|
}
|
||||||
shuffle($arr);
|
shuffle($arr);
|
||||||
@@ -403,8 +403,8 @@ class PlayStartLogic
|
|||||||
$arr = $super;
|
$arr = $super;
|
||||||
$maxAttempts = 20;
|
$maxAttempts = 20;
|
||||||
for ($a = 0; $a < $maxAttempts; $a++) {
|
for ($a = 0; $a < $maxAttempts; $a++) {
|
||||||
$idx = array_rand($arr);
|
$idx = random_int(0, count($arr) - 1);
|
||||||
$j = array_rand($arr);
|
$j = random_int(0, count($arr) - 1);
|
||||||
if ($idx === $j) {
|
if ($idx === $j) {
|
||||||
$j = ($j + 1) % 5;
|
$j = ($j + 1) % 5;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ class LotteryService
|
|||||||
{
|
{
|
||||||
$total = array_sum($weights);
|
$total = array_sum($weights);
|
||||||
if ($total <= 0) {
|
if ($total <= 0) {
|
||||||
return $tiers[array_rand($tiers)];
|
return $tiers[random_int(0, count($tiers) - 1)];
|
||||||
}
|
}
|
||||||
$r = mt_rand(1, $total);
|
$r = random_int(1, $total);
|
||||||
$acc = 0;
|
$acc = 0;
|
||||||
foreach ($weights as $i => $w) {
|
foreach ($weights as $i => $w) {
|
||||||
$acc += $w;
|
$acc += $w;
|
||||||
@@ -145,7 +145,7 @@ class LotteryService
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
$total = $paid + $free;
|
$total = $paid + $free;
|
||||||
$r = mt_rand(1, $total);
|
$r = random_int(1, $total);
|
||||||
return $r <= $paid ? 0 : 1;
|
return $r <= $paid ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ class DiceRewardConfigController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* T1-T5、BIGWIN 权重配比:批量更新权重(T1-T5 同档位权重和须为 100%,BIGWIN 为豹子权重单独设定、无合计要求)
|
* T1-T5、BIGWIN 权重配比:批量更新权重(单条 weight 1-10000,各档位权重和不限制)
|
||||||
|
* 保存后 Logic 会重新实例化奖励配置表缓存(DiceRewardConfig::refreshCache)
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -13,49 +13,61 @@ use app\dice\model\reward_config\DiceRewardConfig;
|
|||||||
use support\Log;
|
use support\Log;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 奖励配置逻辑层
|
* 奖励配置逻辑层(DiceRewardConfig)
|
||||||
* weight 仅 tier=BIGWIN 时可设定,保存时非 BIGWIN 强制 weight=0
|
* weight 1-10000,各档位权重和不限制
|
||||||
*/
|
*/
|
||||||
class DiceRewardConfigLogic extends BaseLogic
|
class DiceRewardConfigLogic extends BaseLogic
|
||||||
{
|
{
|
||||||
/**
|
/** weight 取值范围 */
|
||||||
* 构造函数
|
private const WEIGHT_MIN = 1;
|
||||||
*/
|
private const WEIGHT_MAX = 10000;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->model = new DiceRewardConfig();
|
$this->model = new DiceRewardConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增前:非 BIGWIN 时强制 weight=0
|
* 新增:weight 限制 1-10000;保存后刷新缓存
|
||||||
*/
|
*/
|
||||||
public function add(array $data): mixed
|
public function add(array $data): mixed
|
||||||
{
|
{
|
||||||
$data = $this->normalizeWeightByTier($data);
|
$data = $this->normalizeWeight($data);
|
||||||
return parent::add($data);
|
$result = parent::add($data);
|
||||||
|
DiceRewardConfig::refreshCache();
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改前:非 BIGWIN 时强制 weight=0
|
* 修改:weight 限制 1-10000;保存后刷新缓存
|
||||||
*/
|
*/
|
||||||
public function edit($id, array $data): mixed
|
public function edit($id, array $data): mixed
|
||||||
{
|
{
|
||||||
$data = $this->normalizeWeightByTier($data);
|
$data = $this->normalizeWeight($data);
|
||||||
return parent::edit($id, $data);
|
$result = parent::edit($id, $data);
|
||||||
|
DiceRewardConfig::refreshCache();
|
||||||
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 仅 tier=BIGWIN 时保留 weight(且限制 0-100),否则强制为 0
|
* 删除后刷新缓存
|
||||||
*/
|
*/
|
||||||
private function normalizeWeightByTier(array $data): array
|
public function destroy($ids): bool
|
||||||
{
|
{
|
||||||
$tier = isset($data['tier']) ? (string) $data['tier'] : '';
|
$result = parent::destroy($ids);
|
||||||
if ($tier !== 'BIGWIN') {
|
if ($result) {
|
||||||
$data['weight'] = 0;
|
DiceRewardConfig::refreshCache();
|
||||||
return $data;
|
|
||||||
}
|
}
|
||||||
$w = isset($data['weight']) ? (float) $data['weight'] : 0;
|
return $result;
|
||||||
$data['weight'] = max(0, min(100, $w));
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* weight 限制 1-10000
|
||||||
|
*/
|
||||||
|
private function normalizeWeight(array $data): array
|
||||||
|
{
|
||||||
|
$w = isset($data['weight']) ? (int) $data['weight'] : self::WEIGHT_MIN;
|
||||||
|
$data['weight'] = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, $w));
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,9 +93,9 @@ class DiceRewardConfigLogic extends BaseLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量更新权重:T1-T5 同档位权重之和必须等于 100;BIGWIN 为豹子权重单独设定,不校验合计
|
* 批量更新权重:单条 weight 1-10000,各档位权重和不限制
|
||||||
* @param array<int, array{id: int, weight: float}> $items 元素为 [ id => 配置ID, weight => 0-100 ]
|
* @param array<int, array{id: int, weight: int}> $items 元素为 [ id => 配置ID, weight => 1-10000 ]
|
||||||
* @throws ApiException 当单条 weight 非法或 T1-T5 某档位权重和≠100 时
|
* @throws ApiException 当单条 weight 非法时
|
||||||
*/
|
*/
|
||||||
public function batchUpdateWeights(array $items): void
|
public function batchUpdateWeights(array $items): void
|
||||||
{
|
{
|
||||||
@@ -98,16 +110,15 @@ class DiceRewardConfigLogic extends BaseLogic
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$id = isset($item['id']) ? (int) $item['id'] : 0;
|
$id = isset($item['id']) ? (int) $item['id'] : 0;
|
||||||
$w = isset($item['weight']) ? (float) $item['weight'] : 0;
|
$w = isset($item['weight']) ? (int) $item['weight'] : self::WEIGHT_MIN;
|
||||||
if ($id < 0) {
|
if ($id < 0) {
|
||||||
throw new ApiException('存在无效的配置ID');
|
throw new ApiException('存在无效的配置ID');
|
||||||
}
|
}
|
||||||
if ($w < 0 || $w > 100) {
|
if ($w < self::WEIGHT_MIN || $w > self::WEIGHT_MAX) {
|
||||||
throw new ApiException('权重必须在 0-100 之间');
|
throw new ApiException('权重必须在 ' . self::WEIGHT_MIN . '-' . self::WEIGHT_MAX . ' 之间');
|
||||||
}
|
}
|
||||||
$ids[] = $id;
|
$ids[] = $id;
|
||||||
$w = max(0, min(100, $w));
|
$weightById[$id] = $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();
|
$list = $this->model->whereIn('id', array_unique($ids))->field('id,tier,grid_number')->select()->toArray();
|
||||||
$idToTier = [];
|
$idToTier = [];
|
||||||
@@ -115,26 +126,11 @@ class DiceRewardConfigLogic extends BaseLogic
|
|||||||
$id = isset($r['id']) ? (int) $r['id'] : 0;
|
$id = isset($r['id']) ? (int) $r['id'] : 0;
|
||||||
$idToTier[$id] = isset($r['tier']) ? (string) $r['tier'] : '';
|
$idToTier[$id] = isset($r['tier']) ? (string) $r['tier'] : '';
|
||||||
}
|
}
|
||||||
$sumByTier = [];
|
|
||||||
foreach ($weightById as $id => $w) {
|
foreach ($weightById as $id => $w) {
|
||||||
$tier = $idToTier[$id] ?? '';
|
$tier = $idToTier[$id] ?? '';
|
||||||
if ($tier === '') {
|
if ($tier === '') {
|
||||||
throw new ApiException('配置ID ' . $id . ' 不存在或档位为空');
|
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::where('id', $id)->update(['weight' => $w]);
|
||||||
}
|
}
|
||||||
DiceRewardConfig::refreshCache();
|
DiceRewardConfig::refreshCache();
|
||||||
|
|||||||
@@ -107,6 +107,58 @@ class DicePlayRecord extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取 roll_array 时:若为空且 roll_number 在 5~30,则按点数和生成默认 5 个色子数组,避免“点数和有值但五个点数空”的展示问题
|
||||||
|
* @param mixed $value 库中原始值(JSON 字符串或 null)
|
||||||
|
* @param array $data 当前记录数据
|
||||||
|
* @return array 5 个元素的数组,每项 1~6
|
||||||
|
*/
|
||||||
|
public function getRollArrayAttr($value, $data = []): array
|
||||||
|
{
|
||||||
|
$arr = [];
|
||||||
|
if (is_string($value) && $value !== '') {
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
$arr = is_array($decoded) ? $decoded : [];
|
||||||
|
} elseif (is_array($value)) {
|
||||||
|
$arr = $value;
|
||||||
|
}
|
||||||
|
$sum = isset($data['roll_number']) ? (int) $data['roll_number'] : 0;
|
||||||
|
if (count($arr) === 5 && array_sum($arr) === $sum) {
|
||||||
|
$valid = true;
|
||||||
|
foreach ($arr as $v) {
|
||||||
|
if (!is_numeric($v) || (int) $v < 1 || (int) $v > 6) {
|
||||||
|
$valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($valid) {
|
||||||
|
return array_map('intval', array_slice($arr, 0, 5));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($sum >= 5 && $sum <= 30) {
|
||||||
|
return self::defaultRollArrayForSum($sum);
|
||||||
|
}
|
||||||
|
return array_slice(array_map('intval', $arr), 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据点数和生成默认 5 个色子数组(每项 1~6),用于补全缺失的 roll_array
|
||||||
|
*/
|
||||||
|
public static function defaultRollArrayForSum(int $sum): array
|
||||||
|
{
|
||||||
|
$sum = max(5, min(30, $sum));
|
||||||
|
$base = (int) floor($sum / 5);
|
||||||
|
$rem = $sum - 5 * $base;
|
||||||
|
$arr = array_fill(0, 5, $base);
|
||||||
|
for ($i = 0; $i < $rem; $i++) {
|
||||||
|
$arr[$i]++;
|
||||||
|
}
|
||||||
|
$arr = array_map(function ($v) {
|
||||||
|
return max(1, min(6, (int) $v));
|
||||||
|
}, $arr);
|
||||||
|
return array_values($arr);
|
||||||
|
}
|
||||||
|
|
||||||
/** 抽奖类型 */
|
/** 抽奖类型 */
|
||||||
public function searchLotteryTypeAttr($query, $value)
|
public function searchLotteryTypeAttr($query, $value)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,46 +13,36 @@ use support\think\Cache;
|
|||||||
* 奖励配置模型
|
* 奖励配置模型
|
||||||
*
|
*
|
||||||
* dice_reward_config 奖励配置
|
* dice_reward_config 奖励配置
|
||||||
* 奖励列表为全玩家通用,保存时刷新缓存,游戏时优先读缓存。
|
* 按档位 T1-T5 直接权重抽取 grid_number,weight 1-10000;起始索引 s_start_index / n_start_index
|
||||||
*
|
*
|
||||||
* @property $id ID
|
* @property $id ID
|
||||||
* @property $grid_number 色子点数
|
* @property $grid_number 色子点数
|
||||||
* @property $ui_text 前端显示文本
|
* @property $ui_text 前端显示文本
|
||||||
* @property $real_ev 真实资金结算
|
* @property $real_ev 真实资金结算
|
||||||
* @property $tier 所属档位
|
* @property $tier 所属档位
|
||||||
* @property $weight 权重%(仅 tier=BIGWIN 时可设定,0-100)
|
* @property $weight 权重 1-10000,档位内按权重比抽取
|
||||||
* @property $s_end_index 顺时针结束索引
|
* @property $n_start_index 逆时针起始索引
|
||||||
* @property $n_end_index 逆时针结束索引
|
* @property $s_start_index 顺时针起始索引
|
||||||
* @property $remark 备注
|
* @property $remark 备注
|
||||||
* @property $create_time 创建时间
|
* @property $create_time 创建时间
|
||||||
* @property $update_time 修改时间
|
* @property $update_time 修改时间
|
||||||
*/
|
*/
|
||||||
class DiceRewardConfig extends BaseModel
|
class DiceRewardConfig extends BaseModel
|
||||||
{
|
{
|
||||||
/** 缓存键:彩金池奖励列表实例(含列表与索引) */
|
/** 缓存键:彩金池奖励列表实例 */
|
||||||
private const CACHE_KEY_INSTANCE = 'dice:reward_config:instance';
|
private const CACHE_KEY_INSTANCE = 'dice:reward_config:instance';
|
||||||
|
|
||||||
/** 缓存过期时间(秒),保存时会主动刷新故设较长 */
|
|
||||||
private const CACHE_TTL = 86400 * 30;
|
private const CACHE_TTL = 86400 * 30;
|
||||||
|
|
||||||
/** 当前请求内已加载的实例,避免同请求多次读缓存 */
|
|
||||||
private static ?array $instance = null;
|
private static ?array $instance = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据表主键
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $pk = 'id';
|
protected $pk = 'id';
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库表名称
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $table = 'dice_reward_config';
|
protected $table = 'dice_reward_config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取彩金池实例(含 list / 索引),无则从库加载并写入缓存;同请求内复用
|
* 获取彩金池实例(含 list / by_tier / by_tier_grid),无则从库加载并写入缓存
|
||||||
* @return array{list: array, by_tier: array, by_tier_grid: array, by_s_end_index: array, by_n_end_index: array, min_real_ev: float}
|
* @return array{list: array, by_tier: array, by_tier_grid: array, min_real_ev: float}
|
||||||
*/
|
*/
|
||||||
public static function getCachedInstance(): array
|
public static function getCachedInstance(): array
|
||||||
{
|
{
|
||||||
@@ -70,10 +60,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
return self::$instance;
|
return self::$instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取缓存的奖励列表(无则从库加载并写入缓存)
|
|
||||||
* @return array<int, array>
|
|
||||||
*/
|
|
||||||
public static function getCachedList(): array
|
public static function getCachedList(): array
|
||||||
{
|
{
|
||||||
$inst = self::getCachedInstance();
|
$inst = self::getCachedInstance();
|
||||||
@@ -81,26 +67,20 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新从数据库加载并写入缓存(DiceRewardConfig 新增/修改/删除后调用),构建列表与索引
|
* 重新从数据库加载并写入缓存(按档位+权重抽 grid_number,含 by_tier、by_tier_grid)
|
||||||
* 实例化结果含完整行(含 weight),供 playStart 从缓存中查找 BIGWIN 的 weight 按概率抽奖
|
|
||||||
*/
|
*/
|
||||||
public static function refreshCache(): void
|
public static function refreshCache(): void
|
||||||
{
|
{
|
||||||
$list = (new self())->order('id', 'asc')->select()->toArray();
|
$list = (new self())->order('id', 'asc')->select()->toArray();
|
||||||
$byTier = [];
|
$byTier = [];
|
||||||
$byTierGrid = [];
|
$byTierGrid = [];
|
||||||
$bySEndIndex = [];
|
|
||||||
$byNEndIndex = [];
|
|
||||||
foreach ($list as $row) {
|
foreach ($list as $row) {
|
||||||
$tier = isset($row['tier']) ? (string) $row['tier'] : '';
|
$tier = isset($row['tier']) ? (string) $row['tier'] : '';
|
||||||
if ($tier !== '') {
|
if ($tier !== '') {
|
||||||
// 过滤 tier=BIGWIN:不参与档位抽奖,仅豹子时通过 getCachedByTierAndGridNumber('BIGWIN', ...) 使用
|
if (!isset($byTier[$tier])) {
|
||||||
if ($tier !== 'BIGWIN') {
|
$byTier[$tier] = [];
|
||||||
if (!isset($byTier[$tier])) {
|
|
||||||
$byTier[$tier] = [];
|
|
||||||
}
|
|
||||||
$byTier[$tier][] = $row;
|
|
||||||
}
|
}
|
||||||
|
$byTier[$tier][] = $row;
|
||||||
$gridNum = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
$gridNum = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
||||||
if (!isset($byTierGrid[$tier])) {
|
if (!isset($byTierGrid[$tier])) {
|
||||||
$byTierGrid[$tier] = [];
|
$byTierGrid[$tier] = [];
|
||||||
@@ -109,48 +89,29 @@ class DiceRewardConfig extends BaseModel
|
|||||||
$byTierGrid[$tier][$gridNum] = $row;
|
$byTierGrid[$tier][$gridNum] = $row;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$sEnd = isset($row['s_end_index']) ? (int) $row['s_end_index'] : 0;
|
|
||||||
if (!isset($bySEndIndex[$sEnd])) {
|
|
||||||
$bySEndIndex[$sEnd] = [];
|
|
||||||
}
|
|
||||||
$bySEndIndex[$sEnd][] = $row;
|
|
||||||
$nEnd = isset($row['n_end_index']) ? (int) $row['n_end_index'] : 0;
|
|
||||||
if (!isset($byNEndIndex[$nEnd])) {
|
|
||||||
$byNEndIndex[$nEnd] = [];
|
|
||||||
}
|
|
||||||
$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 = [
|
||||||
'list' => $list,
|
'list' => $list,
|
||||||
'by_tier' => $byTier,
|
'by_tier' => $byTier,
|
||||||
'by_tier_grid' => $byTierGrid,
|
'by_tier_grid' => $byTierGrid,
|
||||||
'by_s_end_index' => $bySEndIndex,
|
'min_real_ev' => $minRealEv,
|
||||||
'by_n_end_index' => $byNEndIndex,
|
|
||||||
'min_real_ev' => $minRealEv,
|
|
||||||
];
|
];
|
||||||
Cache::set(self::CACHE_KEY_INSTANCE, self::$instance, self::CACHE_TTL);
|
Cache::set(self::CACHE_KEY_INSTANCE, self::$instance, self::CACHE_TTL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 空实例结构 */
|
|
||||||
private static function buildEmptyInstance(): array
|
private static function buildEmptyInstance(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'list' => [],
|
'list' => [],
|
||||||
'by_tier' => [],
|
'by_tier' => [],
|
||||||
'by_tier_grid' => [],
|
'by_tier_grid' => [],
|
||||||
'by_s_end_index' => [],
|
'min_real_ev' => 0.0,
|
||||||
'by_n_end_index' => [],
|
|
||||||
'min_real_ev' => 0.0,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从缓存实例按档位 + 色子点数取一条奖励配置(用于超级大奖 tier=BIGWIN + grid_number=roll_number)
|
* 按档位+色子点数取一条(用于 BIGWIN)
|
||||||
* 返回行含 weight(0-100):playStart 据此概率抽奖,weight=100 表示摇到该 roll_number 时 100% 中超级大奖
|
|
||||||
* @param string $tier 档位,如 BIGWIN
|
|
||||||
* @param int $gridNumber 色子点数(摇出总和 roll_number)
|
|
||||||
* @return array|null 配置行(含 weight、real_ev 等)或 null
|
|
||||||
*/
|
*/
|
||||||
public static function getCachedByTierAndGridNumber(string $tier, int $gridNumber): ?array
|
public static function getCachedByTierAndGridNumber(string $tier, int $gridNumber): ?array
|
||||||
{
|
{
|
||||||
@@ -161,9 +122,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
return is_array($row) ? $row : null;
|
return is_array($row) ? $row : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从缓存取最小 real_ev
|
|
||||||
*/
|
|
||||||
public static function getCachedMinRealEv(): float
|
public static function getCachedMinRealEv(): float
|
||||||
{
|
{
|
||||||
$inst = self::getCachedInstance();
|
$inst = self::getCachedInstance();
|
||||||
@@ -171,8 +129,7 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从缓存按档位取奖励列表
|
* 从缓存按档位取奖励列表(含 weight,用于按权重抽 grid_number)
|
||||||
* @return array<int, array>
|
|
||||||
*/
|
*/
|
||||||
public static function getCachedByTier(string $tier): array
|
public static function getCachedByTier(string $tier): array
|
||||||
{
|
{
|
||||||
@@ -181,55 +138,26 @@ class DiceRewardConfig extends BaseModel
|
|||||||
return $byTier[$tier] ?? [];
|
return $byTier[$tier] ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从缓存按顺时针结束索引取列表(s_end_index = id 的配置)
|
|
||||||
* @return array<int, array>
|
|
||||||
*/
|
|
||||||
public static function getCachedBySEndIndex(int $id): array
|
|
||||||
{
|
|
||||||
$inst = self::getCachedInstance();
|
|
||||||
$by = $inst['by_s_end_index'] ?? [];
|
|
||||||
return $by[$id] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 从缓存按逆时针结束索引取列表(n_end_index = id 的配置)
|
|
||||||
* @return array<int, array>
|
|
||||||
*/
|
|
||||||
public static function getCachedByNEndIndex(int $id): array
|
|
||||||
{
|
|
||||||
$inst = self::getCachedInstance();
|
|
||||||
$by = $inst['by_n_end_index'] ?? [];
|
|
||||||
return $by[$id] ?? [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 清除当前请求内实例(如测试或需强制下次读缓存时调用)
|
|
||||||
*/
|
|
||||||
public static function clearRequestInstance(): void
|
public static function clearRequestInstance(): void
|
||||||
{
|
{
|
||||||
self::$instance = null;
|
self::$instance = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 保存后刷新缓存 */
|
|
||||||
public static function onAfterInsert($model): void
|
public static function onAfterInsert($model): void
|
||||||
{
|
{
|
||||||
self::refreshCache();
|
self::refreshCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 更新后刷新缓存 */
|
|
||||||
public static function onAfterUpdate($model): void
|
public static function onAfterUpdate($model): void
|
||||||
{
|
{
|
||||||
self::refreshCache();
|
self::refreshCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除后刷新缓存 */
|
|
||||||
public static function onAfterDelete($model): void
|
public static function onAfterDelete($model): void
|
||||||
{
|
{
|
||||||
self::refreshCache();
|
self::refreshCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 色子点数下限 */
|
|
||||||
public function searchGridNumberMinAttr($query, $value)
|
public function searchGridNumberMinAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -237,7 +165,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 色子点数上限 */
|
|
||||||
public function searchGridNumberMaxAttr($query, $value)
|
public function searchGridNumberMaxAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -245,7 +172,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 前端显示文本模糊 */
|
|
||||||
public function searchUiTextAttr($query, $value)
|
public function searchUiTextAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -253,7 +179,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 真实资金结算下限 */
|
|
||||||
public function searchRealEvMinAttr($query, $value)
|
public function searchRealEvMinAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -261,7 +186,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 真实资金结算上限 */
|
|
||||||
public function searchRealEvMaxAttr($query, $value)
|
public function searchRealEvMaxAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -269,7 +193,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 所属档位 */
|
|
||||||
public function searchTierAttr($query, $value)
|
public function searchTierAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -277,7 +200,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 权重下限(仅 tier=BIGWIN 时有意义) */
|
|
||||||
public function searchWeightMinAttr($query, $value)
|
public function searchWeightMinAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
@@ -285,7 +207,6 @@ class DiceRewardConfig extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 权重上限 */
|
|
||||||
public function searchWeightMaxAttr($query, $value)
|
public function searchWeightMaxAttr($query, $value)
|
||||||
{
|
{
|
||||||
if ($value !== '' && $value !== null) {
|
if ($value !== '' && $value !== null) {
|
||||||
|
|||||||
@@ -9,55 +9,49 @@ namespace app\dice\validate\reward_config;
|
|||||||
use plugin\saiadmin\basic\BaseValidate;
|
use plugin\saiadmin\basic\BaseValidate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 奖励配置验证器
|
* 奖励配置验证器(DiceRewardConfig)
|
||||||
* weight 仅当 tier=BIGWIN 时可设定,且严格限制 0-100(%)
|
* weight 1-10000,各档位权重和不限制
|
||||||
*/
|
*/
|
||||||
class DiceRewardConfigValidate extends BaseValidate
|
class DiceRewardConfigValidate extends BaseValidate
|
||||||
{
|
{
|
||||||
/**
|
private const WEIGHT_MIN = 1;
|
||||||
* 定义验证规则
|
private const WEIGHT_MAX = 10000;
|
||||||
*/
|
|
||||||
protected $rule = [
|
protected $rule = [
|
||||||
'grid_number' => 'require',
|
'grid_number' => 'require',
|
||||||
'ui_text' => 'require',
|
'ui_text' => 'require',
|
||||||
'real_ev' => 'require',
|
'real_ev' => 'require',
|
||||||
'tier' => 'require',
|
'tier' => 'require',
|
||||||
'weight' => 'checkWeight',
|
'weight' => 'checkWeight',
|
||||||
|
'n_start_index' => 'number',
|
||||||
|
's_start_index' => 'number',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* 定义错误信息
|
|
||||||
*/
|
|
||||||
protected $message = [
|
protected $message = [
|
||||||
'grid_number' => '色子点数必须填写',
|
'grid_number' => '色子点数必须填写',
|
||||||
'ui_text' => '前端显示文本必须填写',
|
'ui_text' => '前端显示文本必须填写',
|
||||||
'real_ev' => '真实资金结算必须填写',
|
'real_ev' => '真实资金结算必须填写',
|
||||||
'tier' => '所属档位必须填写',
|
'tier' => '所属档位必须填写',
|
||||||
'weight' => '权重仅 tier=BIGWIN 时可设定,且必须为 0-100',
|
'weight' => '权重必须为 1-10000',
|
||||||
|
'n_start_index' => '逆时针起始索引须为数字',
|
||||||
|
's_start_index' => '顺时针起始索引须为数字',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* 定义场景
|
|
||||||
*/
|
|
||||||
protected $scene = [
|
protected $scene = [
|
||||||
'save' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight'],
|
'save' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight', 'n_start_index', 's_start_index'],
|
||||||
'update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight'],
|
'update' => ['grid_number', 'ui_text', 'real_ev', 'tier', 'weight', 'n_start_index', 's_start_index'],
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* weight:仅 tier=BIGWIN 时可设定,严格限制 0-100(%)
|
* weight:1-10000
|
||||||
*/
|
*/
|
||||||
protected function checkWeight($value, $rule = '', $data = []): bool
|
protected function checkWeight($value, $rule = '', $data = []): bool
|
||||||
{
|
{
|
||||||
$tier = isset($data['tier']) ? (string) $data['tier'] : '';
|
$num = is_numeric($value) ? (int) $value : null;
|
||||||
if ($tier !== 'BIGWIN') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$num = is_numeric($value) ? (float) $value : null;
|
|
||||||
if ($num === null) {
|
if ($num === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if ($num < 0 || $num > 100) {
|
if ($num < self::WEIGHT_MIN || $num > self::WEIGHT_MAX) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user