优化一键测试权重
This commit is contained in:
@@ -57,6 +57,10 @@
|
|||||||
"title": "One-Click Weight Test",
|
"title": "One-Click Weight Test",
|
||||||
"alertTitle": "Bonus pool logic",
|
"alertTitle": "Bonus pool logic",
|
||||||
"alertBody": "Same as playStart draw: uses name=default safety line and kill switch; when profit is below the line, paid tickets use player tier weights (custom below), free tickets use killScore; when profit reaches the line and kill is on, both use killScore.",
|
"alertBody": "Same as playStart draw: uses name=default safety line and kill switch; when profit is below the line, paid tickets use player tier weights (custom below), free tickets use killScore; when profit reaches the line and kill is on, both use killScore.",
|
||||||
|
"chainModeHint": "Simulation: set paid spin counts only (CW/CCW). If a paid draw hits “play again” (or T5), the next draw is free with the same ante, lottery type free, paid amount 0. Free-draw tier odds are configured below (including chained free plays).",
|
||||||
|
"sectionPaid": "Paid draws",
|
||||||
|
"sectionFreeAfterPlayAgain": "Free draw tier odds (after play-again)",
|
||||||
|
"tierProbHintFreeChain": "When using custom tier odds: T1–T5 below apply when a free draw runs (tier roll; combined with dice_reward row weights).",
|
||||||
"stepPaid": "Paid ticket",
|
"stepPaid": "Paid ticket",
|
||||||
"stepFree": "Free ticket",
|
"stepFree": "Free ticket",
|
||||||
"labelLotteryTypePaid": "Test pool type",
|
"labelLotteryTypePaid": "Test pool type",
|
||||||
@@ -75,6 +79,7 @@
|
|||||||
"btnStart": "Start test",
|
"btnStart": "Start test",
|
||||||
"btnCancel": "Cancel",
|
"btnCancel": "Cancel",
|
||||||
"warnAnte": "Ante must be greater than 0",
|
"warnAnte": "Ante must be greater than 0",
|
||||||
|
"warnPaidSpins": "Paid clockwise + counter-clockwise spin counts must be greater than 0",
|
||||||
"warnTotalSpins": "At least one of paid/free direction spin counts must be greater than 0",
|
"warnTotalSpins": "At least one of paid/free direction spin counts must be greater than 0",
|
||||||
"warnPaidTierSumPositive": "When no paid pool is selected, T1–T5 odds sum must be greater than 0",
|
"warnPaidTierSumPositive": "When no paid pool is selected, T1–T5 odds sum must be greater than 0",
|
||||||
"warnPaidTierSumMax": "Paid T1–T5 odds sum cannot exceed 100%",
|
"warnPaidTierSumMax": "Paid T1–T5 odds sum cannot exceed 100%",
|
||||||
|
|||||||
@@ -8,9 +8,15 @@
|
|||||||
"counterclockwiseAbbr": "CCW",
|
"counterclockwiseAbbr": "CCW",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"paidDraw": "Paid Draw",
|
"paidDraw": "Paid Draw",
|
||||||
"freeDraw": "Free Draw",
|
"chainMode": "Chain play-again",
|
||||||
|
"chainModeYes": "Yes",
|
||||||
|
"chainModeNo": "No",
|
||||||
|
"paidPlannedSpins": "Planned paid spins",
|
||||||
|
"playAgainCount": "Play-again count",
|
||||||
|
"progressDraws": "{over} done",
|
||||||
|
"progressFailed": "{over} before fail",
|
||||||
"platformProfit": "Platform Profit",
|
"platformProfit": "Platform Profit",
|
||||||
"totalDrawCount": "Total Draw Count",
|
"totalDrawCount": "Total draws",
|
||||||
"createdBy": "Created By",
|
"createdBy": "Created By",
|
||||||
"remark": "Remark",
|
"remark": "Remark",
|
||||||
"createTime": "Create Time",
|
"createTime": "Create Time",
|
||||||
@@ -37,6 +43,10 @@
|
|||||||
"recordId": "Record ID",
|
"recordId": "Record ID",
|
||||||
"testCount": "Test count",
|
"testCount": "Test count",
|
||||||
"testCountSuffix": " runs",
|
"testCountSuffix": " runs",
|
||||||
|
"testCountProgress": "In progress: {over} done",
|
||||||
|
"testCountFailed": "{over} before failure",
|
||||||
|
"chainModeLabel": "Chain play-again",
|
||||||
|
"paidPlannedSpins": "Planned paid spins",
|
||||||
"createTime": "Created at",
|
"createTime": "Created at",
|
||||||
"admin": "Operator",
|
"admin": "Operator",
|
||||||
"paidPoolId": "Paid lottery pool config ID",
|
"paidPoolId": "Paid lottery pool config ID",
|
||||||
|
|||||||
@@ -57,6 +57,10 @@
|
|||||||
"title": "一键测试权重",
|
"title": "一键测试权重",
|
||||||
"alertTitle": "彩金池逻辑说明",
|
"alertTitle": "彩金池逻辑说明",
|
||||||
"alertBody": "与 playStart 抽奖逻辑一致:使用 name=default 的安全线、杀分开关;盈利未达安全线时,付费抽奖券使用玩家自身权重(下方自定义档位),免费抽奖券使用 killScore 配置;盈利达到安全线且杀分开启时,付费/免费均使用 killScore 配置。",
|
"alertBody": "与 playStart 抽奖逻辑一致:使用 name=default 的安全线、杀分开关;盈利未达安全线时,付费抽奖券使用玩家自身权重(下方自定义档位),免费抽奖券使用 killScore 配置;盈利达到安全线且杀分开启时,付费/免费均使用 killScore 配置。",
|
||||||
|
"chainModeHint": "模拟方式:只配置付费抽奖次数(顺/逆时针)。付费抽到「再来一次」或 T5 时,下一局自动为免费抽奖,底注与触发局相同,抽奖类型记为免费、付费金额记为 0。免费抽奖的档位概率由下方「免费抽奖」配置决定(含通过再来一次触发的后续免费局)。",
|
||||||
|
"sectionPaid": "付费抽奖",
|
||||||
|
"sectionFreeAfterPlayAgain": "免费抽奖(再来一次后的档位概率)",
|
||||||
|
"tierProbHintFreeChain": "当使用自定义档位时:以下为「免费抽奖」时 T1~T5 的档位概率(仅在有免费局时参与摇档,与 dice_reward 格子权重共同决定结果)。",
|
||||||
"stepPaid": "付费抽奖券",
|
"stepPaid": "付费抽奖券",
|
||||||
"stepFree": "免费抽奖券",
|
"stepFree": "免费抽奖券",
|
||||||
"labelLotteryTypePaid": "测试数据档位类型",
|
"labelLotteryTypePaid": "测试数据档位类型",
|
||||||
@@ -75,6 +79,7 @@
|
|||||||
"btnStart": "开始测试",
|
"btnStart": "开始测试",
|
||||||
"btnCancel": "取消",
|
"btnCancel": "取消",
|
||||||
"warnAnte": "底注 ante 必须大于 0",
|
"warnAnte": "底注 ante 必须大于 0",
|
||||||
|
"warnPaidSpins": "付费抽奖顺时针与逆时针次数之和须大于 0",
|
||||||
"warnTotalSpins": "付费或免费至少一种方向次数之和大于 0",
|
"warnTotalSpins": "付费或免费至少一种方向次数之和大于 0",
|
||||||
"warnPaidTierSumPositive": "付费未选奖池时,T1~T5 档位概率之和需大于 0",
|
"warnPaidTierSumPositive": "付费未选奖池时,T1~T5 档位概率之和需大于 0",
|
||||||
"warnPaidTierSumMax": "付费档位概率 T1~T5 之和不能超过 100%",
|
"warnPaidTierSumMax": "付费档位概率 T1~T5 之和不能超过 100%",
|
||||||
|
|||||||
@@ -8,7 +8,13 @@
|
|||||||
"counterclockwiseAbbr": "逆",
|
"counterclockwiseAbbr": "逆",
|
||||||
"status": "状态",
|
"status": "状态",
|
||||||
"paidDraw": "付费抽取",
|
"paidDraw": "付费抽取",
|
||||||
"freeDraw": "免费抽取",
|
"chainMode": "链式再来一次",
|
||||||
|
"chainModeYes": "是",
|
||||||
|
"chainModeNo": "否",
|
||||||
|
"paidPlannedSpins": "计划付费次数",
|
||||||
|
"playAgainCount": "再来一次次数",
|
||||||
|
"progressDraws": "已完成 {over} 次",
|
||||||
|
"progressFailed": "失败前 {over} 次",
|
||||||
"platformProfit": "平台赚取金额",
|
"platformProfit": "平台赚取金额",
|
||||||
"totalDrawCount": "总抽奖次数",
|
"totalDrawCount": "总抽奖次数",
|
||||||
"createdBy": "创建管理员",
|
"createdBy": "创建管理员",
|
||||||
@@ -37,6 +43,10 @@
|
|||||||
"recordId": "记录ID",
|
"recordId": "记录ID",
|
||||||
"testCount": "测试次数",
|
"testCount": "测试次数",
|
||||||
"testCountSuffix": "次",
|
"testCountSuffix": "次",
|
||||||
|
"testCountProgress": "进行中:已完成 {over} 次",
|
||||||
|
"testCountFailed": "失败前 {over} 次",
|
||||||
|
"chainModeLabel": "链式再来一次",
|
||||||
|
"paidPlannedSpins": "计划付费次数",
|
||||||
"createTime": "创建时间",
|
"createTime": "创建时间",
|
||||||
"admin": "执行管理员",
|
"admin": "执行管理员",
|
||||||
"paidPoolId": "付费奖池配置ID",
|
"paidPoolId": "付费奖池配置ID",
|
||||||
|
|||||||
@@ -57,14 +57,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一键测试权重:创建测试记录并启动后台执行,按付费/免费、顺逆方向交替抽奖
|
* 一键测试权重:创建测试记录并启动后台执行
|
||||||
* 可选 lottery_config_id;不选则传 paid_tier_weights / free_tier_weights(T1-T5)
|
* chain_free_mode=true:仅模拟付费次数;付费抽到再来一次则插入免费抽奖(同底注、付费金额 0)
|
||||||
*/
|
*/
|
||||||
startWeightTest(params: {
|
startWeightTest(params: {
|
||||||
ante?: number
|
ante?: number
|
||||||
lottery_config_id?: number
|
lottery_config_id?: number
|
||||||
paid_lottery_config_id?: number
|
paid_lottery_config_id?: number
|
||||||
free_lottery_config_id?: number
|
free_lottery_config_id?: number
|
||||||
|
chain_free_mode?: boolean
|
||||||
s_count?: number
|
s_count?: number
|
||||||
n_count?: number
|
n_count?: number
|
||||||
paid_s_count?: number
|
paid_s_count?: number
|
||||||
|
|||||||
@@ -11,155 +11,125 @@
|
|||||||
<template #title>{{ $t('page.weightTest.alertTitle') }}</template>
|
<template #title>{{ $t('page.weightTest.alertTitle') }}</template>
|
||||||
{{ $t('page.weightTest.alertBody') }}
|
{{ $t('page.weightTest.alertBody') }}
|
||||||
</ElAlert>
|
</ElAlert>
|
||||||
<ElForm ref="formRef" :model="form" label-width="140px">
|
<ElAlert type="warning" :closable="false" show-icon class="weight-test-tip chain-tip">
|
||||||
|
{{ $t('page.weightTest.chainModeHint') }}
|
||||||
|
</ElAlert>
|
||||||
|
<ElForm :model="form" label-width="140px">
|
||||||
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante" required>
|
<ElFormItem :label="$t('page.weightTest.labelAnte')" prop="ante" required>
|
||||||
<ElInputNumber v-model="form.ante" :min="1" :step="1" style="width: 100%" />
|
<ElInputNumber v-model="form.ante" :min="1" :step="1" style="width: 100%" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElSteps :active="currentStep" finish-status="success" simple class="steps-wrap">
|
|
||||||
<ElStep :title="$t('page.weightTest.stepPaid')" />
|
|
||||||
<ElStep :title="$t('page.weightTest.stepFree')" />
|
|
||||||
</ElSteps>
|
|
||||||
|
|
||||||
<!-- 第一页:付费抽奖券 -->
|
<div class="section-title">{{ $t('page.weightTest.sectionPaid') }}</div>
|
||||||
<div v-show="currentStep === 0" class="step-panel">
|
<ElFormItem
|
||||||
<ElFormItem
|
:label="$t('page.weightTest.labelLotteryTypePaid')"
|
||||||
:label="$t('page.weightTest.labelLotteryTypePaid')"
|
prop="paid_lottery_config_id"
|
||||||
prop="paid_lottery_config_id"
|
>
|
||||||
|
<ElSelect
|
||||||
|
v-model="form.paid_lottery_config_id"
|
||||||
|
:placeholder="$t('page.weightTest.placeholderPaidPool')"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<ElSelect
|
<ElOption
|
||||||
v-model="form.paid_lottery_config_id"
|
v-for="item in paidLotteryOptions"
|
||||||
:placeholder="$t('page.weightTest.placeholderPaidPool')"
|
:key="item.id"
|
||||||
clearable
|
:label="item.name"
|
||||||
filterable
|
:value="item.id"
|
||||||
style="width: 100%"
|
/>
|
||||||
>
|
</ElSelect>
|
||||||
<ElOption
|
</ElFormItem>
|
||||||
v-for="item in paidLotteryOptions"
|
<template v-if="form.paid_lottery_config_id == null">
|
||||||
:key="item.id"
|
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
|
||||||
:label="item.name"
|
<ElRow :gutter="12" class="tier-row">
|
||||||
:value="item.id"
|
<ElCol v-for="t in tierKeys" :key="'paid-' + t" :span="8">
|
||||||
/>
|
<div class="tier-field">
|
||||||
</ElSelect>
|
<label class="tier-field-label">{{
|
||||||
</ElFormItem>
|
$t('page.weightTest.tierFieldLabel', { tier: t })
|
||||||
<template v-if="form.paid_lottery_config_id == null">
|
}}</label>
|
||||||
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
|
<input
|
||||||
<ElRow :gutter="12" class="tier-row">
|
type="number"
|
||||||
<ElCol v-for="t in tierKeys" :key="'paid-' + t" :span="8">
|
:value="getPaidTier(t)"
|
||||||
<div class="tier-field">
|
min="0"
|
||||||
<label class="tier-field-label">{{
|
max="100"
|
||||||
$t('page.weightTest.tierFieldLabel', { tier: t })
|
placeholder="0"
|
||||||
}}</label>
|
class="tier-input"
|
||||||
<input
|
@input="setPaidTier(t, $event)"
|
||||||
type="number"
|
/>
|
||||||
:value="getPaidTier(t)"
|
</div>
|
||||||
min="0"
|
</ElCol>
|
||||||
max="100"
|
</ElRow>
|
||||||
placeholder="0"
|
<div v-if="paidTierSum > 100" class="tier-error">{{
|
||||||
class="tier-input"
|
$t('page.weightTest.tierSumError', { sum: paidTierSum })
|
||||||
@input="setPaidTier(t, $event)"
|
}}</div>
|
||||||
/>
|
</template>
|
||||||
</div>
|
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="paid_s_count" required>
|
||||||
</ElCol>
|
<ElSelect
|
||||||
</ElRow>
|
v-model="form.paid_s_count"
|
||||||
<div v-if="paidTierSum > 100" class="tier-error">{{
|
:placeholder="$t('page.weightTest.placeholderSelect')"
|
||||||
$t('page.weightTest.tierSumError', { sum: paidTierSum })
|
style="width: 100%"
|
||||||
}}</div>
|
>
|
||||||
</template>
|
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
||||||
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="paid_s_count" required>
|
</ElSelect>
|
||||||
<ElSelect
|
</ElFormItem>
|
||||||
v-model="form.paid_s_count"
|
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="paid_n_count" required>
|
||||||
:placeholder="$t('page.weightTest.placeholderSelect')"
|
<ElSelect
|
||||||
style="width: 100%"
|
v-model="form.paid_n_count"
|
||||||
>
|
:placeholder="$t('page.weightTest.placeholderSelect')"
|
||||||
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
style="width: 100%"
|
||||||
</ElSelect>
|
>
|
||||||
</ElFormItem>
|
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
||||||
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="paid_n_count" required>
|
</ElSelect>
|
||||||
<ElSelect
|
</ElFormItem>
|
||||||
v-model="form.paid_n_count"
|
|
||||||
:placeholder="$t('page.weightTest.placeholderSelect')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第二页:免费抽奖券 -->
|
<div class="section-title">{{ $t('page.weightTest.sectionFreeAfterPlayAgain') }}</div>
|
||||||
<div v-show="currentStep === 1" class="step-panel">
|
<ElFormItem
|
||||||
<ElFormItem
|
:label="$t('page.weightTest.labelLotteryTypeFree')"
|
||||||
:label="$t('page.weightTest.labelLotteryTypeFree')"
|
prop="free_lottery_config_id"
|
||||||
prop="free_lottery_config_id"
|
>
|
||||||
|
<ElSelect
|
||||||
|
v-model="form.free_lottery_config_id"
|
||||||
|
:placeholder="$t('page.weightTest.placeholderFreePool')"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<ElSelect
|
<ElOption
|
||||||
v-model="form.free_lottery_config_id"
|
v-for="item in freeLotteryOptions"
|
||||||
:placeholder="$t('page.weightTest.placeholderFreePool')"
|
:key="item.id"
|
||||||
clearable
|
:label="item.name"
|
||||||
filterable
|
:value="item.id"
|
||||||
style="width: 100%"
|
/>
|
||||||
>
|
</ElSelect>
|
||||||
<ElOption
|
</ElFormItem>
|
||||||
v-for="item in freeLotteryOptions"
|
<template v-if="form.free_lottery_config_id == null">
|
||||||
:key="item.id"
|
<div class="tier-label">{{ $t('page.weightTest.tierProbHintFreeChain') }}</div>
|
||||||
:label="item.name"
|
<ElRow :gutter="12" class="tier-row">
|
||||||
:value="item.id"
|
<ElCol v-for="t in tierKeys" :key="'free-' + t" :span="8">
|
||||||
/>
|
<div class="tier-field">
|
||||||
</ElSelect>
|
<label class="tier-field-label">{{
|
||||||
</ElFormItem>
|
$t('page.weightTest.tierFieldLabel', { tier: t })
|
||||||
<template v-if="form.free_lottery_config_id == null">
|
}}</label>
|
||||||
<div class="tier-label">{{ $t('page.weightTest.tierProbHint') }}</div>
|
<input
|
||||||
<ElRow :gutter="12" class="tier-row">
|
type="number"
|
||||||
<ElCol v-for="t in tierKeys" :key="'free-' + t" :span="8">
|
:value="getFreeTier(t)"
|
||||||
<div class="tier-field">
|
min="0"
|
||||||
<label class="tier-field-label">{{
|
max="100"
|
||||||
$t('page.weightTest.tierFieldLabel', { tier: t })
|
placeholder="0"
|
||||||
}}</label>
|
class="tier-input"
|
||||||
<input
|
@input="setFreeTier(t, $event)"
|
||||||
type="number"
|
/>
|
||||||
:value="getFreeTier(t)"
|
</div>
|
||||||
min="0"
|
</ElCol>
|
||||||
max="100"
|
</ElRow>
|
||||||
placeholder="0"
|
<div v-if="freeTierSum > 100" class="tier-error">{{
|
||||||
class="tier-input"
|
$t('page.weightTest.tierSumError', { sum: freeTierSum })
|
||||||
@input="setFreeTier(t, $event)"
|
}}</div>
|
||||||
/>
|
</template>
|
||||||
</div>
|
|
||||||
</ElCol>
|
|
||||||
</ElRow>
|
|
||||||
<div v-if="freeTierSum > 100" class="tier-error">{{
|
|
||||||
$t('page.weightTest.tierSumError', { sum: freeTierSum })
|
|
||||||
}}</div>
|
|
||||||
</template>
|
|
||||||
<ElFormItem :label="$t('page.weightTest.labelCwCount')" prop="free_s_count" required>
|
|
||||||
<ElSelect
|
|
||||||
v-model="form.free_s_count"
|
|
||||||
:placeholder="$t('page.weightTest.placeholderSelect')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
<ElFormItem :label="$t('page.weightTest.labelCcwCount')" prop="free_n_count" required>
|
|
||||||
<ElSelect
|
|
||||||
v-model="form.free_n_count"
|
|
||||||
:placeholder="$t('page.weightTest.placeholderSelect')"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<ElOption v-for="c in countOptions" :key="c" :label="String(c)" :value="c" />
|
|
||||||
</ElSelect>
|
|
||||||
</ElFormItem>
|
|
||||||
</div>
|
|
||||||
</ElForm>
|
</ElForm>
|
||||||
|
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<ElButton v-if="currentStep > 0" :disabled="running" @click="currentStep--">{{
|
|
||||||
$t('page.weightTest.btnPrev')
|
|
||||||
}}</ElButton>
|
|
||||||
<ElButton v-if="currentStep < 1" type="primary" :disabled="running" @click="currentStep++">{{
|
|
||||||
$t('page.weightTest.btnNext')
|
|
||||||
}}</ElButton>
|
|
||||||
<ElButton
|
<ElButton
|
||||||
v-if="currentStep === 1"
|
|
||||||
v-permission="'dice:reward:index:startWeightTest'"
|
v-permission="'dice:reward:index:startWeightTest'"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="running"
|
:loading="running"
|
||||||
@@ -187,8 +157,6 @@
|
|||||||
const visible = defineModel<boolean>({ default: false })
|
const visible = defineModel<boolean>({ default: false })
|
||||||
const emit = defineEmits<{ (e: 'success'): void }>()
|
const emit = defineEmits<{ (e: 'success'): void }>()
|
||||||
|
|
||||||
const formRef = ref()
|
|
||||||
const currentStep = ref(0)
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
ante: 1,
|
ante: 1,
|
||||||
paid_lottery_config_id: undefined as number | undefined,
|
paid_lottery_config_id: undefined as number | undefined,
|
||||||
@@ -196,9 +164,7 @@
|
|||||||
paid_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
paid_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
||||||
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
free_tier_weights: { T1: 20, T2: 20, T3: 20, T4: 20, T5: 20 } as Record<string, number>,
|
||||||
paid_s_count: 100,
|
paid_s_count: 100,
|
||||||
paid_n_count: 100,
|
paid_n_count: 100
|
||||||
free_s_count: 100,
|
|
||||||
free_n_count: 100
|
|
||||||
})
|
})
|
||||||
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
|
const lotteryOptions = ref<Array<{ id: number; name: string }>>([])
|
||||||
/** 付费抽奖券可选档位:name=default */
|
/** 付费抽奖券可选档位:name=default */
|
||||||
@@ -214,7 +180,6 @@
|
|||||||
|
|
||||||
function onClose() {
|
function onClose() {
|
||||||
running.value = false
|
running.value = false
|
||||||
currentStep.value = 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPaidTier(t: string): string {
|
function getPaidTier(t: string): string {
|
||||||
@@ -255,12 +220,10 @@
|
|||||||
id: r.id,
|
id: r.id,
|
||||||
name: r.name
|
name: r.name
|
||||||
}))
|
}))
|
||||||
// 付费抽奖券默认使用 name=default
|
|
||||||
const normal = list.find((r: { name?: string }) => r.name === 'default')
|
const normal = list.find((r: { name?: string }) => r.name === 'default')
|
||||||
if (normal) {
|
if (normal) {
|
||||||
form.paid_lottery_config_id = normal.id
|
form.paid_lottery_config_id = normal.id
|
||||||
}
|
}
|
||||||
// 免费抽奖券默认使用 name=killScore;若无则默认选第一项
|
|
||||||
const kill = list.find((r: { name?: string }) => r.name === 'killScore')
|
const kill = list.find((r: { name?: string }) => r.name === 'killScore')
|
||||||
if (kill) {
|
if (kill) {
|
||||||
form.free_lottery_config_id = kill.id
|
form.free_lottery_config_id = kill.id
|
||||||
@@ -277,8 +240,9 @@
|
|||||||
ante: form.ante,
|
ante: form.ante,
|
||||||
paid_s_count: form.paid_s_count,
|
paid_s_count: form.paid_s_count,
|
||||||
paid_n_count: form.paid_n_count,
|
paid_n_count: form.paid_n_count,
|
||||||
free_s_count: form.free_s_count,
|
free_s_count: 0,
|
||||||
free_n_count: form.free_n_count
|
free_n_count: 0,
|
||||||
|
chain_free_mode: true
|
||||||
}
|
}
|
||||||
if (form.paid_lottery_config_id != null) {
|
if (form.paid_lottery_config_id != null) {
|
||||||
payload.paid_lottery_config_id = form.paid_lottery_config_id
|
payload.paid_lottery_config_id = form.paid_lottery_config_id
|
||||||
@@ -298,8 +262,8 @@
|
|||||||
ElMessage.warning(t('page.weightTest.warnAnte'))
|
ElMessage.warning(t('page.weightTest.warnAnte'))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (form.paid_s_count + form.paid_n_count + form.free_s_count + form.free_n_count <= 0) {
|
if (form.paid_s_count + form.paid_n_count <= 0) {
|
||||||
ElMessage.warning(t('page.weightTest.warnTotalSpins'))
|
ElMessage.warning(t('page.weightTest.warnPaidSpins'))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const needPaidTier = form.paid_lottery_config_id == null
|
const needPaidTier = form.paid_lottery_config_id == null
|
||||||
@@ -351,28 +315,22 @@
|
|||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 切换到免费步骤时,若当前选中 id 不在免费档位列表中,则重置为第一个 killScore 的选项,避免显示错误
|
|
||||||
watch(currentStep, (step) => {
|
|
||||||
if (step === 1) {
|
|
||||||
const freeOpts = freeLotteryOptions.value
|
|
||||||
const id = form.free_lottery_config_id
|
|
||||||
if (freeOpts.length && (id == null || !freeOpts.some((o) => o.id === id))) {
|
|
||||||
form.free_lottery_config_id = freeOpts[0].id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.weight-test-tip {
|
.weight-test-tip {
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
.steps-wrap {
|
.chain-tip {
|
||||||
margin-bottom: 16px;
|
margin-top: -8px;
|
||||||
}
|
}
|
||||||
.step-panel {
|
.section-title {
|
||||||
min-height: 200px;
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||||
}
|
}
|
||||||
.tier-label {
|
.tier-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
|||||||
@@ -40,19 +40,18 @@
|
|||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<span>{{ formatStatus(row.status) }}</span>
|
<span>{{ formatStatus(row.status) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 付费抽取:顺时针、逆时针抽取次数(兼容旧数据用 s_count/n_count) -->
|
<!-- 付费抽取:顺时针、逆时针抽取次数 -->
|
||||||
<template #paid_draw="{ row }">
|
<template #paid_draw="{ row }">
|
||||||
<span
|
<span
|
||||||
>{{ $t('page.table.clockwiseAbbr') }} {{ getPaidS(row) }} /
|
>{{ $t('page.table.clockwiseAbbr') }} {{ getPaidS(row) }} /
|
||||||
{{ $t('page.table.counterclockwiseAbbr') }} {{ getPaidN(row) }}</span
|
{{ $t('page.table.counterclockwiseAbbr') }} {{ getPaidN(row) }}</span
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
<!-- 免费抽取:顺时针、逆时针抽取次数 -->
|
<template #chain_mode="{ row }">
|
||||||
<template #free_draw="{ row }">
|
<span>{{ formatChainMode(row) }}</span>
|
||||||
<span
|
</template>
|
||||||
>{{ $t('page.table.clockwiseAbbr') }} {{ row.free_s_count ?? 0 }} /
|
<template #total_draw="{ row }">
|
||||||
{{ $t('page.table.counterclockwiseAbbr') }} {{ row.free_n_count ?? 0 }}</span
|
<span>{{ formatTotalDraw(row) }}</span>
|
||||||
>
|
|
||||||
</template>
|
</template>
|
||||||
<!-- 平台赚取金额 -->
|
<!-- 平台赚取金额 -->
|
||||||
<template #platform_profit="{ row }">
|
<template #platform_profit="{ row }">
|
||||||
@@ -136,16 +135,12 @@
|
|||||||
return t('page.detail.dash')
|
return t('page.detail.dash')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 付费抽取次数(兼容旧数据:无 paid_s_count 时用 s_count)
|
// 付费抽取次数
|
||||||
function getPaidS(row: Record<string, any>): number {
|
function getPaidS(row: Record<string, any>): number {
|
||||||
const v = row.paid_s_count
|
return Number(row.paid_s_count ?? 0)
|
||||||
if (v !== undefined && v !== null && (Number(v) || 0) > 0) return Number(v)
|
|
||||||
return Number(row.s_count ?? 0)
|
|
||||||
}
|
}
|
||||||
function getPaidN(row: Record<string, any>): number {
|
function getPaidN(row: Record<string, any>): number {
|
||||||
const v = row.paid_n_count
|
return Number(row.paid_n_count ?? 0)
|
||||||
if (v !== undefined && v !== null && (Number(v) || 0) > 0) return Number(v)
|
|
||||||
return Number(row.n_count ?? 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 平台赚取金额展示(未完成或空显示 —)
|
// 平台赚取金额展示(未完成或空显示 —)
|
||||||
@@ -157,6 +152,31 @@
|
|||||||
return String(n)
|
return String(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 链式再来一次:1=是(新库字段),JSON 旧数据用 tier_weights_snapshot.chain_free_mode */
|
||||||
|
function formatChainMode(row: Record<string, any>): string {
|
||||||
|
const v = row.chain_free_mode
|
||||||
|
if (v === 1 || v === '1' || v === true) return t('page.table.chainModeYes')
|
||||||
|
const snap = row.tier_weights_snapshot
|
||||||
|
if (snap && typeof snap === 'object' && (snap as { chain_free_mode?: boolean }).chain_free_mode) {
|
||||||
|
return t('page.table.chainModeYes')
|
||||||
|
}
|
||||||
|
return t('page.table.chainModeNo')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 总抽奖次数:仅完成态写最终值;测试中显示已完成次数 */
|
||||||
|
function formatTotalDraw(row: Record<string, any>): string {
|
||||||
|
const status = Number(row.status)
|
||||||
|
const done = Number(row.total_play_count ?? 0)
|
||||||
|
const over = Number(row.over_play_count ?? 0)
|
||||||
|
if (status === 1) {
|
||||||
|
return String(done)
|
||||||
|
}
|
||||||
|
if (status === -1) {
|
||||||
|
return over > 0 ? t('page.table.progressFailed', { over }) : t('page.detail.dash')
|
||||||
|
}
|
||||||
|
return t('page.table.progressDraws', { over })
|
||||||
|
}
|
||||||
|
|
||||||
// 表格配置
|
// 表格配置
|
||||||
const {
|
const {
|
||||||
columns,
|
columns,
|
||||||
@@ -193,12 +213,24 @@
|
|||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
prop: 'free_draw',
|
prop: 'chain_mode',
|
||||||
label: 'page.table.freeDraw',
|
label: 'page.table.chainMode',
|
||||||
width: 160,
|
width: 110,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
prop: 'paid_planned_spins',
|
||||||
|
label: 'page.table.paidPlannedSpins',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'play_again_count',
|
||||||
|
label: 'page.table.playAgainCount',
|
||||||
|
width: 120,
|
||||||
|
align: 'center'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'platform_profit',
|
prop: 'platform_profit',
|
||||||
label: 'page.table.platformProfit',
|
label: 'page.table.platformProfit',
|
||||||
@@ -206,7 +238,13 @@
|
|||||||
align: 'center',
|
align: 'center',
|
||||||
useSlot: true
|
useSlot: true
|
||||||
},
|
},
|
||||||
{ prop: 'total_play_count', label: 'page.table.totalDrawCount', width: 110, align: 'center' },
|
{
|
||||||
|
prop: 'total_draw',
|
||||||
|
label: 'page.table.totalDrawCount',
|
||||||
|
width: 140,
|
||||||
|
align: 'center',
|
||||||
|
useSlot: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
prop: 'admin_name',
|
prop: 'admin_name',
|
||||||
label: 'page.table.createdBy',
|
label: 'page.table.createdBy',
|
||||||
|
|||||||
@@ -14,9 +14,15 @@
|
|||||||
<el-descriptions-item :label="$t('page.detail.recordId')">
|
<el-descriptions-item :label="$t('page.detail.recordId')">
|
||||||
{{ record.id }}
|
{{ record.id }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item :label="$t('page.detail.testCount')"
|
<el-descriptions-item :label="$t('page.detail.chainModeLabel')">
|
||||||
>{{ record.test_count }}{{ $t('page.detail.testCountSuffix') }}</el-descriptions-item
|
{{ formatChainModeDetail(record) }}
|
||||||
>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('page.detail.paidPlannedSpins')">
|
||||||
|
{{ record.paid_planned_spins ?? $t('page.detail.dash') }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item :label="$t('page.detail.testCount')">
|
||||||
|
{{ formatTestCountDisplay(record) }}
|
||||||
|
</el-descriptions-item>
|
||||||
<el-descriptions-item :label="$t('page.detail.createTime')">
|
<el-descriptions-item :label="$t('page.detail.createTime')">
|
||||||
{{ record.create_time || $t('page.detail.dash') }}
|
{{ record.create_time || $t('page.detail.dash') }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
@@ -231,6 +237,11 @@
|
|||||||
interface RecordRow {
|
interface RecordRow {
|
||||||
id?: number
|
id?: number
|
||||||
test_count?: number
|
test_count?: number
|
||||||
|
total_play_count?: number
|
||||||
|
status?: number
|
||||||
|
over_play_count?: number
|
||||||
|
chain_free_mode?: number | boolean
|
||||||
|
paid_planned_spins?: number
|
||||||
create_time?: string
|
create_time?: string
|
||||||
admin_id?: number | null
|
admin_id?: number | null
|
||||||
admin_name?: string
|
admin_name?: string
|
||||||
@@ -238,7 +249,6 @@
|
|||||||
paid_lottery_config_id?: number | null
|
paid_lottery_config_id?: number | null
|
||||||
free_lottery_config_id?: number | null
|
free_lottery_config_id?: number | null
|
||||||
bigwin_weight?: Record<string, number> | Array<[number, number]> | null
|
bigwin_weight?: Record<string, number> | Array<[number, number]> | null
|
||||||
// 新结构:{ paid: {T1..T5}, free: {T1..T5} },兼容旧结构直接是 {T1..T5}
|
|
||||||
tier_weights_snapshot?:
|
tier_weights_snapshot?:
|
||||||
| {
|
| {
|
||||||
paid?: Record<string, number>
|
paid?: Record<string, number>
|
||||||
@@ -257,6 +267,32 @@
|
|||||||
result_counts?: Record<string, number>
|
result_counts?: Record<string, number>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatChainModeDetail(record: RecordRow | null): string {
|
||||||
|
if (!record) return t('page.detail.dash')
|
||||||
|
const v = record.chain_free_mode
|
||||||
|
if (v === 1 || v === '1' || v === true) return t('page.table.chainModeYes')
|
||||||
|
const snap = record.tier_weights_snapshot
|
||||||
|
if (snap && typeof snap === 'object' && (snap as { chain_free_mode?: boolean }).chain_free_mode) {
|
||||||
|
return t('page.table.chainModeYes')
|
||||||
|
}
|
||||||
|
return t('page.table.chainModeNo')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTestCountDisplay(record: RecordRow | null): string {
|
||||||
|
if (!record) return t('page.detail.dash')
|
||||||
|
const status = Number(record.status)
|
||||||
|
if (status === 1) {
|
||||||
|
const n = record.test_count ?? record.total_play_count
|
||||||
|
return `${n ?? 0}${t('page.detail.testCountSuffix')}`
|
||||||
|
}
|
||||||
|
if (status === -1) {
|
||||||
|
const over = Number(record.over_play_count ?? 0)
|
||||||
|
return over > 0 ? t('page.detail.testCountFailed', { over }) : t('page.detail.dash')
|
||||||
|
}
|
||||||
|
const over = Number(record.over_play_count ?? 0)
|
||||||
|
return t('page.detail.testCountProgress', { over })
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
record: RecordRow | null
|
record: RecordRow | null
|
||||||
|
|||||||
@@ -183,7 +183,11 @@ class PlayStartLogic
|
|||||||
$targetIndex = (int) ($chosen['end_index'] ?? 0);
|
$targetIndex = (int) ($chosen['end_index'] ?? 0);
|
||||||
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
$rollNumber = (int) ($chosen['grid_number'] ?? 0);
|
||||||
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
$realEv = (float) ($chosen['real_ev'] ?? 0);
|
||||||
|
// T5/再来一次:以奖励行 tier 为准,并以摇奖档位 $tier 兜底(与 reward_tier 展示一致,避免 dice_reward 行缺 tier 时不发券)
|
||||||
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
$isTierT5 = (string) ($chosen['tier'] ?? '') === 'T5';
|
||||||
|
if ($isTierT5 === false && (string) ($tier ?? '') === 'T5') {
|
||||||
|
$isTierT5 = true;
|
||||||
|
}
|
||||||
// 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante);不再叠加票价 100
|
// 摇色子中奖:按 dice_reward_config.real_ev 直接结算(已乘 ante);不再叠加票价 100
|
||||||
$rewardWinCoin = $realEv * $ante;
|
$rewardWinCoin = $realEv * $ante;
|
||||||
|
|
||||||
@@ -711,6 +715,8 @@ class PlayStartLogic
|
|||||||
$costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);
|
$costRealEv = $realEv + ($isWin === 1 ? $bigWinRealEv : 0.0);
|
||||||
$paidAmount = $lotteryType === 0 ? ($ante * self::UNIT_COST) : 0;
|
$paidAmount = $lotteryType === 0 ? ($ante * self::UNIT_COST) : 0;
|
||||||
$rewardTier = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
$rewardTier = ($isWin === 1 && $superWinCoin > 0) ? 'BIGWIN' : (string) ($tier ?? '');
|
||||||
|
// 与写入记录的 reward_tier 完全一致:仅当展示档位为 T5(非豹子大奖)时触发「再来一次」链式免费局
|
||||||
|
$grantsFreeTicket = ($rewardTier === 'T5');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'player_id' => 0,
|
'player_id' => 0,
|
||||||
@@ -735,6 +741,7 @@ class PlayStartLogic
|
|||||||
'real_ev' => $realEv,
|
'real_ev' => $realEv,
|
||||||
'bigwin_real_ev' => $isWin === 1 ? $bigWinRealEv : 0.0,
|
'bigwin_real_ev' => $isWin === 1 ? $bigWinRealEv : 0.0,
|
||||||
'cost_ev' => $costRealEv,
|
'cost_ev' => $costRealEv,
|
||||||
|
'grants_free_ticket' => $grantsFreeTicket,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,9 +81,10 @@ class DiceRewardController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一键测试权重:创建测试记录并启动单进程后台执行,按付费/免费、顺逆方向交替写入 dice_play_record_test
|
* 一键测试权重:创建测试记录并启动单进程后台执行,写入 dice_play_record_test
|
||||||
* 参数:lottery_config_id 可选,不选则传 paid_tier_weights / free_tier_weights 自定义档位;
|
* 参数:lottery_config_id 可选;paid_tier_weights / free_tier_weights 自定义档位;
|
||||||
* paid_s_count, paid_n_count, free_s_count, free_n_count;或兼容旧版 s_count, n_count
|
* paid_s_count, paid_n_count
|
||||||
|
* chain_free_mode=1:仅按付费次数模拟;付费抽到再来一次/T5 则在队列中插入免费局(同底注、lottery_type=免费、paid_amount=0)
|
||||||
*/
|
*/
|
||||||
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]
|
#[Permission('一键测试权重', 'dice:reward:index:startWeightTest')]
|
||||||
public function startWeightTest(Request $request): Response
|
public function startWeightTest(Request $request): Response
|
||||||
@@ -94,14 +95,11 @@ class DiceRewardController extends BaseController
|
|||||||
'lottery_config_id' => $post['lottery_config_id'] ?? null,
|
'lottery_config_id' => $post['lottery_config_id'] ?? null,
|
||||||
'paid_lottery_config_id' => $post['paid_lottery_config_id'] ?? null,
|
'paid_lottery_config_id' => $post['paid_lottery_config_id'] ?? null,
|
||||||
'free_lottery_config_id' => $post['free_lottery_config_id'] ?? null,
|
'free_lottery_config_id' => $post['free_lottery_config_id'] ?? null,
|
||||||
's_count' => $post['s_count'] ?? null,
|
|
||||||
'n_count' => $post['n_count'] ?? null,
|
|
||||||
'paid_s_count' => $post['paid_s_count'] ?? null,
|
'paid_s_count' => $post['paid_s_count'] ?? null,
|
||||||
'paid_n_count' => $post['paid_n_count'] ?? null,
|
'paid_n_count' => $post['paid_n_count'] ?? null,
|
||||||
'free_s_count' => $post['free_s_count'] ?? null,
|
|
||||||
'free_n_count' => $post['free_n_count'] ?? null,
|
|
||||||
'paid_tier_weights' => $post['paid_tier_weights'] ?? null,
|
'paid_tier_weights' => $post['paid_tier_weights'] ?? null,
|
||||||
'free_tier_weights' => $post['free_tier_weights'] ?? null,
|
'free_tier_weights' => $post['free_tier_weights'] ?? null,
|
||||||
|
'chain_free_mode' => $post['chain_free_mode'] ?? null,
|
||||||
];
|
];
|
||||||
$adminId = isset($this->adminInfo['id']) ? (int) $this->adminInfo['id'] : null;
|
$adminId = isset($this->adminInfo['id']) ? (int) $this->adminInfo['id'] : null;
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -229,10 +229,10 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建一键测试权重记录并返回 ID,供后台执行器按付费/免费、顺逆方向交替写入 dice_play_record_test
|
* 创建一键测试权重记录并返回 ID,供后台执行器写入 dice_play_record_test
|
||||||
* 支持两种模式:1)选择奖池配置 lottery_config_id,档位概率取自配置;2)不选配置,使用自定义 paid_tier_weights / free_tier_weights
|
* 支持两种模式:1)选择奖池配置 lottery_config_id,档位概率取自配置;2)不选配置,使用自定义 paid_tier_weights / free_tier_weights
|
||||||
* @param array|int $params 数组:lottery_config_id(可选), paid_s_count, paid_n_count, free_s_count, free_n_count;或兼容旧版传 4 个 int 时视为 (paid_s_count, paid_n_count, free_s_count, free_n_count)
|
* @param array|int $params 数组:lottery_config_id(可选), paid_s_count, paid_n_count
|
||||||
* @param int|null $adminId 执行人(旧版 4 参调用时第二参为 paid_n_count,此处不传 adminId)
|
* @param int|null $adminId 执行人
|
||||||
* @return int 记录 ID
|
* @return int 记录 ID
|
||||||
* @throws ApiException
|
* @throws ApiException
|
||||||
*/
|
*/
|
||||||
@@ -240,12 +240,10 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
|||||||
{
|
{
|
||||||
$adminId = null;
|
$adminId = null;
|
||||||
if (!is_array($params)) {
|
if (!is_array($params)) {
|
||||||
// 兼容旧版调用:createWeightTestRecord(paid_s_count, paid_n_count, free_s_count, free_n_count)
|
// 兼容旧版调用:createWeightTestRecord(paid_s_count, paid_n_count)
|
||||||
$params = [
|
$params = [
|
||||||
'paid_s_count' => (int) $params,
|
'paid_s_count' => (int) $params,
|
||||||
'paid_n_count' => (int) $adminIdOrFreeS,
|
'paid_n_count' => (int) $adminIdOrFreeS,
|
||||||
'free_s_count' => (int) $freeSOrFreeN,
|
|
||||||
'free_n_count' => (int) $freeN,
|
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
$adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
|
$adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
|
||||||
@@ -269,19 +267,18 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
|||||||
if ($freeConfigId <= 0 && $lotteryConfigId > 0) {
|
if ($freeConfigId <= 0 && $lotteryConfigId > 0) {
|
||||||
$freeConfigId = $lotteryConfigId;
|
$freeConfigId = $lotteryConfigId;
|
||||||
}
|
}
|
||||||
$paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : (int) ($params['s_count'] ?? 0);
|
$paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : 0;
|
||||||
$paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : (int) ($params['n_count'] ?? 0);
|
$paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : 0;
|
||||||
$freeS = (int) ($params['free_s_count'] ?? 0);
|
$chainFreeMode = !empty($params['chain_free_mode']);
|
||||||
$freeN = (int) ($params['free_n_count'] ?? 0);
|
|
||||||
|
|
||||||
foreach ([$paidS, $paidN, $freeS, $freeN] as $c) {
|
foreach ([$paidS, $paidN] as $c) {
|
||||||
if ($c !== 0 && !in_array($c, $allowed, true)) {
|
if ($c !== 0 && !in_array($c, $allowed, true)) {
|
||||||
throw new ApiException('Counts only support 0, 100, 500, 1000, 5000');
|
throw new ApiException('Counts only support 0, 100, 500, 1000, 5000');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$total = $paidS + $paidN + $freeS + $freeN;
|
$total = $paidS + $paidN;
|
||||||
if ($total <= 0) {
|
if ($total <= 0) {
|
||||||
throw new ApiException('Sum of paid/free direction counts must be greater than 0');
|
throw new ApiException('Sum of paid direction counts must be greater than 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
$snapshot = [];
|
$snapshot = [];
|
||||||
@@ -394,24 +391,28 @@ class DiceRewardConfigRecordLogic extends BaseLogic
|
|||||||
if (!is_array($tierWeightsSnapshot['free'])) {
|
if (!is_array($tierWeightsSnapshot['free'])) {
|
||||||
$tierWeightsSnapshot['free'] = [];
|
$tierWeightsSnapshot['free'] = [];
|
||||||
}
|
}
|
||||||
|
if ($chainFreeMode) {
|
||||||
|
$tierWeightsSnapshot['chain_free_mode'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
$record = new DiceRewardConfigRecord();
|
$record = new DiceRewardConfigRecord();
|
||||||
$record->test_count = $total;
|
$plannedPaidSpins = $paidS + $paidN;
|
||||||
|
$record->chain_free_mode = $chainFreeMode ? 1 : 0;
|
||||||
|
$record->paid_planned_spins = $plannedPaidSpins;
|
||||||
|
// 总抽奖次数与 test_count 仅在任务成功结束时写入(见 WeightTestRunner::markSuccess)
|
||||||
|
$record->test_count = 0;
|
||||||
|
$record->total_play_count = 0;
|
||||||
$record->weight_config_snapshot = $snapshot;
|
$record->weight_config_snapshot = $snapshot;
|
||||||
$record->tier_weights_snapshot = $tierWeightsSnapshot;
|
$record->tier_weights_snapshot = $tierWeightsSnapshot;
|
||||||
$record->lottery_config_id = $lotteryConfigId > 0 ? $lotteryConfigId : null;
|
$record->lottery_config_id = $lotteryConfigId > 0 ? $lotteryConfigId : null;
|
||||||
$record->paid_lottery_config_id = $paidConfigId > 0 ? $paidConfigId : null;
|
$record->paid_lottery_config_id = $paidConfigId > 0 ? $paidConfigId : null;
|
||||||
$record->free_lottery_config_id = $freeConfigId > 0 ? $freeConfigId : null;
|
$record->free_lottery_config_id = $freeConfigId > 0 ? $freeConfigId : null;
|
||||||
$record->total_play_count = $total;
|
|
||||||
$record->over_play_count = 0;
|
$record->over_play_count = 0;
|
||||||
$record->status = DiceRewardConfigRecord::STATUS_RUNNING;
|
$record->status = DiceRewardConfigRecord::STATUS_RUNNING;
|
||||||
$record->remark = null;
|
$record->remark = null;
|
||||||
$record->s_count = $paidS + $paidN;
|
|
||||||
$record->n_count = $freeS + $freeN;
|
|
||||||
$record->paid_s_count = $paidS;
|
$record->paid_s_count = $paidS;
|
||||||
$record->paid_n_count = $paidN;
|
$record->paid_n_count = $paidN;
|
||||||
$record->free_s_count = $freeS;
|
$record->play_again_count = 0;
|
||||||
$record->free_n_count = $freeN;
|
|
||||||
$record->paid_tier_weights = $paidTierWeights;
|
$record->paid_tier_weights = $paidTierWeights;
|
||||||
$record->free_tier_weights = $freeTierWeights;
|
$record->free_tier_weights = $freeTierWeights;
|
||||||
$record->result_counts = [];
|
$record->result_counts = [];
|
||||||
|
|||||||
@@ -56,20 +56,10 @@ class WeightTestRunner
|
|||||||
$ante = is_numeric($record->ante ?? null) ? intval($record->ante) : 1;
|
$ante = is_numeric($record->ante ?? null) ? intval($record->ante) : 1;
|
||||||
$paidS = (int) ($record->paid_s_count ?? 0);
|
$paidS = (int) ($record->paid_s_count ?? 0);
|
||||||
$paidN = (int) ($record->paid_n_count ?? 0);
|
$paidN = (int) ($record->paid_n_count ?? 0);
|
||||||
$freeS = (int) ($record->free_s_count ?? 0);
|
$total = $paidS + $paidN;
|
||||||
$freeN = (int) ($record->free_n_count ?? 0);
|
if ($total <= 0) {
|
||||||
if ($paidS + $paidN + $freeS + $freeN <= 0) {
|
$this->markFailed($recordId, '抽奖次数必须大于 0');
|
||||||
$sCount = (int) ($record->s_count ?? 0);
|
return;
|
||||||
$nCount = (int) ($record->n_count ?? 0);
|
|
||||||
$total = $sCount + $nCount;
|
|
||||||
if ($total <= 0) {
|
|
||||||
$this->markFailed($recordId, '抽奖次数必须大于 0');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$paidS = $sCount;
|
|
||||||
$paidN = $nCount;
|
|
||||||
} else {
|
|
||||||
$total = $paidS + $paidN + $freeS + $freeN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->find();
|
$configType0 = DiceLotteryPoolConfig::where('name', 'default')->find();
|
||||||
@@ -123,53 +113,30 @@ class WeightTestRunner
|
|||||||
$done = 0;
|
$done = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for ($i = 0; $i < $paidS; $i++) {
|
$this->runChainFreeMode(
|
||||||
$usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
$recordId,
|
||||||
$paidConfig = $usePoolWeights ? $configType1 : $paidPoolConfig;
|
$playLogic,
|
||||||
$customWeights = $usePoolWeights ? null : $paidTierWeightsCustom;
|
$paidS,
|
||||||
$row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $ante, $customWeights);
|
$paidN,
|
||||||
$this->accumulateProfitForDefault($row, 0, $paidConfig, $configType0, $poolProfitTotal);
|
$ante,
|
||||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
$paidPoolConfig,
|
||||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
$freePoolConfig,
|
||||||
$done++;
|
$paidTierWeightsCustom,
|
||||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
$freeTierWeightsCustom,
|
||||||
}
|
$configType0,
|
||||||
for ($i = 0; $i < $paidN; $i++) {
|
$configType1,
|
||||||
$usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
$safetyLine,
|
||||||
$paidConfig = $usePoolWeights ? $configType1 : $paidPoolConfig;
|
$killEnabled,
|
||||||
$customWeights = $usePoolWeights ? null : $paidTierWeightsCustom;
|
$poolProfitTotal,
|
||||||
$row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $ante, $customWeights);
|
$resultCounts,
|
||||||
$this->accumulateProfitForDefault($row, 0, $paidConfig, $configType0, $poolProfitTotal);
|
$tierCounts,
|
||||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
$buffer,
|
||||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
$done
|
||||||
$done++;
|
);
|
||||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
|
||||||
}
|
|
||||||
for ($i = 0; $i < $freeS; $i++) {
|
|
||||||
$useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
|
||||||
$freeConfig = $useKillMode ? $configType1 : $freePoolConfig;
|
|
||||||
$customWeights = $useKillMode ? null : $freeTierWeightsCustom;
|
|
||||||
$row = $playLogic->simulateOnePlay($freeConfig, 0, 1, $ante, $customWeights);
|
|
||||||
$this->accumulateProfitForDefault($row, 1, $freeConfig, $configType0, $poolProfitTotal);
|
|
||||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
|
||||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
|
||||||
$done++;
|
|
||||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
|
||||||
}
|
|
||||||
for ($i = 0; $i < $freeN; $i++) {
|
|
||||||
$useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
|
||||||
$freeConfig = $useKillMode ? $configType1 : $freePoolConfig;
|
|
||||||
$customWeights = $useKillMode ? null : $freeTierWeightsCustom;
|
|
||||||
$row = $playLogic->simulateOnePlay($freeConfig, 1, 1, $ante, $customWeights);
|
|
||||||
$this->accumulateProfitForDefault($row, 1, $freeConfig, $configType0, $poolProfitTotal);
|
|
||||||
$this->aggregate($row, $resultCounts, $tierCounts);
|
|
||||||
$buffer[] = $this->rowForInsert($row, $recordId);
|
|
||||||
$done++;
|
|
||||||
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
|
|
||||||
}
|
|
||||||
if (!empty($buffer)) {
|
if (!empty($buffer)) {
|
||||||
$this->insertBuffer($buffer);
|
$this->insertBuffer($buffer);
|
||||||
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
|
// 链式/非链式:运行中均不写入 total_play_count,仅在 markSuccess 落库实际总次数
|
||||||
|
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts, null);
|
||||||
}
|
}
|
||||||
// 平台赚取金额:通过关联 DicePlayRecordTest(reward_config_record_id)统计
|
// 平台赚取金额:通过关联 DicePlayRecordTest(reward_config_record_id)统计
|
||||||
$this->markSuccess($recordId, $resultCounts, $tierCounts);
|
$this->markSuccess($recordId, $resultCounts, $tierCounts);
|
||||||
@@ -179,6 +146,70 @@ class WeightTestRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 付费次数仅由配置决定;付费抽到「再来一次」则在队列末尾插入一条免费抽奖(同方向、同底注),可链式触发
|
||||||
|
*/
|
||||||
|
private function runChainFreeMode(
|
||||||
|
int $recordId,
|
||||||
|
PlayStartLogic $playLogic,
|
||||||
|
int $paidS,
|
||||||
|
int $paidN,
|
||||||
|
int $ante,
|
||||||
|
$paidPoolConfig,
|
||||||
|
$freePoolConfig,
|
||||||
|
?array $paidTierWeightsCustom,
|
||||||
|
?array $freeTierWeightsCustom,
|
||||||
|
$configType0,
|
||||||
|
$configType1,
|
||||||
|
int $safetyLine,
|
||||||
|
bool $killEnabled,
|
||||||
|
float &$poolProfitTotal,
|
||||||
|
array &$resultCounts,
|
||||||
|
array &$tierCounts,
|
||||||
|
array &$buffer,
|
||||||
|
int &$done
|
||||||
|
): void {
|
||||||
|
$queue = [];
|
||||||
|
for ($i = 0; $i < $paidS; $i++) {
|
||||||
|
$queue[] = ['paid', 0, $ante];
|
||||||
|
}
|
||||||
|
for ($i = 0; $i < $paidN; $i++) {
|
||||||
|
$queue[] = ['paid', 1, $ante];
|
||||||
|
}
|
||||||
|
$qi = 0;
|
||||||
|
while ($qi < count($queue)) {
|
||||||
|
$item = $queue[$qi];
|
||||||
|
$isPaid = $item[0] === 'paid';
|
||||||
|
$dir = $item[1];
|
||||||
|
$playAnte = $item[2];
|
||||||
|
$lotteryType = $isPaid ? 0 : 1;
|
||||||
|
|
||||||
|
if ($isPaid) {
|
||||||
|
$usePoolWeights = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||||
|
$cfg = $usePoolWeights ? $configType1 : $paidPoolConfig;
|
||||||
|
$customWeights = $usePoolWeights ? null : $paidTierWeightsCustom;
|
||||||
|
} else {
|
||||||
|
$useKillMode = $killEnabled && $poolProfitTotal >= $safetyLine && $configType1 !== null;
|
||||||
|
$cfg = $useKillMode ? $configType1 : $freePoolConfig;
|
||||||
|
$customWeights = $useKillMode ? null : $freeTierWeightsCustom;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $playLogic->simulateOnePlay($cfg, $dir, $lotteryType, $playAnte, $customWeights);
|
||||||
|
$this->accumulateProfitForDefault($row, $lotteryType, $cfg, $configType0, $poolProfitTotal);
|
||||||
|
$this->aggregate($row, $resultCounts, $tierCounts);
|
||||||
|
$buffer[] = $this->rowForInsert($row, $recordId);
|
||||||
|
$done++;
|
||||||
|
|
||||||
|
if (!empty($row['grants_free_ticket'])) {
|
||||||
|
$queue[] = ['free', $dir, $playAnte];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->flushIfNeeded($buffer, $recordId, $done, count($queue), $resultCounts, $tierCounts, null);
|
||||||
|
|
||||||
|
$qi++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 累加彩金池累计盈利,用于触发杀分,与 PlayStartLogic 一致
|
* 累加彩金池累计盈利,用于触发杀分,与 PlayStartLogic 一致
|
||||||
* @param int $lotteryType 0=付费券,1=免费券
|
* @param int $lotteryType 0=付费券,1=免费券
|
||||||
@@ -227,14 +258,14 @@ class WeightTestRunner
|
|||||||
return $out;
|
return $out;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function flushIfNeeded(array &$buffer, int $recordId, int $done, int $total, array $resultCounts, array $tierCounts): void
|
private function flushIfNeeded(array &$buffer, int $recordId, int $done, int $total, array $resultCounts, array $tierCounts, ?int $recordTotalPlayCount = null): void
|
||||||
{
|
{
|
||||||
if (count($buffer) < self::BATCH_SIZE) {
|
if (count($buffer) < self::BATCH_SIZE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->insertBuffer($buffer);
|
$this->insertBuffer($buffer);
|
||||||
$buffer = [];
|
$buffer = [];
|
||||||
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
|
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts, $recordTotalPlayCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function insertBuffer(array $rows): void
|
private function insertBuffer(array $rows): void
|
||||||
@@ -259,11 +290,14 @@ class WeightTestRunner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private function updateProgress(int $recordId, int $overPlayCount, array $resultCounts, array $tierCounts): void
|
private function updateProgress(int $recordId, int $overPlayCount, array $resultCounts, array $tierCounts, ?int $totalPlayCount = null): void
|
||||||
{
|
{
|
||||||
$record = DiceRewardConfigRecord::find($recordId);
|
$record = DiceRewardConfigRecord::find($recordId);
|
||||||
if ($record) {
|
if ($record) {
|
||||||
$record->over_play_count = $overPlayCount;
|
$record->over_play_count = $overPlayCount;
|
||||||
|
if ($totalPlayCount !== null) {
|
||||||
|
$record->total_play_count = $totalPlayCount;
|
||||||
|
}
|
||||||
$record->result_counts = $resultCounts;
|
$record->result_counts = $resultCounts;
|
||||||
$record->tier_counts = $tierCounts;
|
$record->tier_counts = $tierCounts;
|
||||||
$record->save();
|
$record->save();
|
||||||
@@ -288,6 +322,10 @@ class WeightTestRunner
|
|||||||
$record->tier_counts = $tierCounts;
|
$record->tier_counts = $tierCounts;
|
||||||
$record->remark = null;
|
$record->remark = null;
|
||||||
$record->platform_profit = $platformProfit;
|
$record->platform_profit = $platformProfit;
|
||||||
|
$record->play_again_count = DiceRewardConfigRecord::computePlayAgainCountFromRelated($recordId);
|
||||||
|
$actualCount = (int) DicePlayRecordTest::where('reward_config_record_id', $recordId)->count();
|
||||||
|
$record->total_play_count = $actualCount;
|
||||||
|
$record->test_count = $actualCount;
|
||||||
$record->save();
|
$record->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,17 +22,16 @@ use think\model\relation\HasMany;
|
|||||||
* @property int|null $lottery_config_id 测试时使用的奖池配置 ID(兼容旧:付费+免费共用)
|
* @property int|null $lottery_config_id 测试时使用的奖池配置 ID(兼容旧:付费+免费共用)
|
||||||
* @property int|null $paid_lottery_config_id 付费抽奖奖池配置 ID,默认 type=0
|
* @property int|null $paid_lottery_config_id 付费抽奖奖池配置 ID,默认 type=0
|
||||||
* @property int|null $free_lottery_config_id 免费抽奖奖池配置 ID,默认 type=1
|
* @property int|null $free_lottery_config_id 免费抽奖奖池配置 ID,默认 type=1
|
||||||
* @property int $total_play_count 总模拟次数(s_count+n_count)
|
* @property int $total_play_count 总模拟次数
|
||||||
* @property int $over_play_count 已完成次数
|
* @property int $over_play_count 已完成次数
|
||||||
* @property int $status 状态 -1失败 0进行中 1成功
|
* @property int $status 状态 -1失败 0进行中 1成功
|
||||||
* @property string|null $remark 失败时记录原因
|
* @property string|null $remark 失败时记录原因
|
||||||
* @property int|null $ante 底注/注数(dice_ante_config.mult)
|
* @property int|null $ante 底注/注数(dice_ante_config.mult)
|
||||||
* @property int $s_count 顺时针模拟次数(兼容旧数据)
|
|
||||||
* @property int $n_count 逆时针模拟次数(兼容旧数据)
|
|
||||||
* @property int $paid_s_count 付费抽奖顺时针次数
|
* @property int $paid_s_count 付费抽奖顺时针次数
|
||||||
* @property int $paid_n_count 付费抽奖逆时针次数
|
* @property int $paid_n_count 付费抽奖逆时针次数
|
||||||
* @property int $free_s_count 免费抽奖顺时针次数
|
* @property int $chain_free_mode 1=链式再来一次免费抽奖
|
||||||
* @property int $free_n_count 免费抽奖逆时针次数
|
* @property int $paid_planned_spins 计划付费抽奖次数(顺+逆)
|
||||||
|
* @property int $play_again_count 再来一次次数(T5触发次数)
|
||||||
* @property array|null $paid_tier_weights 付费自定义档位权重 T1-T5
|
* @property array|null $paid_tier_weights 付费自定义档位权重 T1-T5
|
||||||
* @property array|null $free_tier_weights 免费自定义档位权重 T1-T5
|
* @property array|null $free_tier_weights 免费自定义档位权重 T1-T5
|
||||||
* @property array $result_counts 落点统计 grid_number=>出现次数
|
* @property array $result_counts 落点统计 grid_number=>出现次数
|
||||||
@@ -85,6 +84,18 @@ class DiceRewardConfigRecord extends BaseModel
|
|||||||
return round($paidAmount - $sumWinCoin, 2);
|
return round($paidAmount - $sumWinCoin, 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据关联的 DicePlayRecordTest 统计再来一次次数(reward_tier=T5)
|
||||||
|
* @param int $recordId
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public static function computePlayAgainCountFromRelated(int $recordId): int
|
||||||
|
{
|
||||||
|
return (int) DicePlayRecordTest::where('reward_config_record_id', $recordId)
|
||||||
|
->where('reward_tier', 'T5')
|
||||||
|
->count();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据关联的 DicePlayRecordTest 统计落点次数
|
* 根据关联的 DicePlayRecordTest 统计落点次数
|
||||||
* result_counts = [grid_number => 出现次数],只统计 roll_number 在 5-30 之间的记录
|
* result_counts = [grid_number => 出现次数],只统计 roll_number 在 5-30 之间的记录
|
||||||
|
|||||||
Reference in New Issue
Block a user