From 79c84c198a0b0e283e4c1603e76725b5586a51cc Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Tue, 2 Jun 2026 14:51:10 +0800 Subject: [PATCH] =?UTF-8?q?1.=E4=BC=98=E5=8C=96=E5=A5=96=E5=8A=B1=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../locales/langs/en/dice/reward_config.json | 37 +- .../locales/langs/zh/dice/reward_config.json | 37 +- .../plugin/dice/api/reward_config/index.ts | 10 + .../plugin/dice/reward_config/index/index.vue | 140 ++++--- ...create-reward-reference-preview-dialog.vue | 243 ++++++++++++ .../utils/generateIndexByRules.ts | 53 ++- .../DiceRewardConfigController.php | 17 + .../app/dice/logic/reward/DiceRewardLogic.php | 353 +++++++++++++----- server/docs/ADMIN_GUIDE.md | 2 +- server/plugin/saiadmin/config/route.php | 1 + 10 files changed, 662 insertions(+), 231 deletions(-) create mode 100644 saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/create-reward-reference-preview-dialog.vue diff --git a/saiadmin-artd/src/locales/langs/en/dice/reward_config.json b/saiadmin-artd/src/locales/langs/en/dice/reward_config.json index a50a51e..e813f63 100644 --- a/saiadmin-artd/src/locales/langs/en/dice/reward_config.json +++ b/saiadmin-artd/src/locales/langs/en/dice/reward_config.json @@ -8,17 +8,18 @@ "tabIndex": "Reward Index", "tabBigwin": "Big Win Weights", "tipIndex": "Dice points must be between 5 and 30 and unique in this table.", - "tierRecommendRules": "[Settlement vs tier] Settlement < 0 → T4; 0 < Settlement < 1 → T3; 1 < Settlement < 2 → T2; Settlement > 2 → T1; T5 “try again” settlement = 0. Set a unified settlement standard per tier below; values are written on generation and can be edited row by row in the table.", + "tierRecommendRules": "[Settlement vs tier] T1 (big prize): >2; T2 (small win): 2≥amount>1; T3 (rake): 1≥amount>0; T4 (try again): =0; T5 (penalty): 0>amount. Set recommended settlement per tier below. The Tier column is auto-calculated from settlement and cannot be edited manually.", "tierRecommendRealEv": "Recommended settlement", "tierRecommendAutoMatch": "Auto-match tier when settlement changes", "tierRecommendApplyAmount": "Fill recommended amount for rows with tier set", "tierRecommendApplyAmountOk": "Filled recommended settlement for {n} row(s)", - "tierRecommendNoTierRows": "Set tier T1–T5 on table rows first", + "tierRecommendNoTierRows": "No rows with a tier inferable from settlement", "tierRecommendMatchTier": "Match all tiers from settlement", "tierRecommendMatchTierOk": "Matched tier for {n} row(s) from settlement", "tierRecommendMatchTierNone": "No rows to match", - "tierRecommendT5UiText": "再来一次", - "tierRecommendT5UiTextEn": "Once again", + "tierRecommendT4UiText": "再来一次", + "tierRecommendT4UiTextEn": "Once again", + "colTierAutoHint": "Auto-matched from settlement", "tipBigwin": "Left to right: big-win points (read-only), display text, real EV, remark, weight (0~10000). Points 5 and 30 are fixed at 100%. This tab saves big-win weights only.", "colId": "Index (id)", "colDicePoints": "Dice Points", @@ -47,6 +48,18 @@ "confirmCreateRefMsg": "Create reward reference by rule: start_index is the id of the cell for grid_number in reward config; clockwise end_index=(start_index+roll)%26; counter-clockwise end_index=start_index-roll if >=0 else 26+start_index-roll. Existing data will be cleared, then 26 points (5–30) for both directions will be generated. Continue?", "confirmCreateRefOk": "Create", "confirmCreateRefCancel": "Cancel", + "createRefPreviewTitle": "Create Reward Reference Preview", + "createRefPreviewClockwise": "Clockwise", + "createRefPreviewCounterclockwise": "Counter-clockwise", + "createRefPreviewTipUnchanged": "Dice points mapping is unchanged: weights in the preview are reused from current dice_reward; importing will not override existing weights.", + "createRefPreviewTipChanged": "Dice points mapping has changed: preview weights use the default value (1). After importing, adjust weights in the Dice Reward page if needed.", + "createRefPreviewSkipped": "{n} dice point(s) are missing in the reward index and were skipped (please complete all 26 points from 5 to 30).", + "createRefPreviewRefresh": "Refresh preview", + "createRefPreviewImport": "Import", + "createRefPreviewImportOk": "Imported reward reference", + "createRefPreviewImportNoop": "Mapping unchanged, nothing to import (existing weights kept)", + "createRefPreviewDiff": "Diff (old → new)", + "createRefPreviewNoDiff": "No change", "createRefSuccess": "Created for 26 dice points (5–30), clockwise + counter-clockwise: clockwise added {cwNew}, counter-clockwise added {ccwNew}; clockwise updated {cwUp}, counter-clockwise updated {ccwUp}{skippedPart}", "createRefSuccessSkipped": "; {n} point(s) used fallback start index", "createRefSuccessSimple": "Created successfully", @@ -65,21 +78,21 @@ "infoNoBigwin": "No BIGWIN rows. Set tier to BIGWIN in the Reward Index tab first.", "btnRuleGenerate": "Generate by rules", "ruleGenerateTitle": "Generate reward index by rules", - "ruleGenerateRules": "[Generation logic (same as Create Reward Reference)]\n• 26 cells ordered by id ascending are positions 0–25; each row’s grid_number is 5–30 and unique.\n• Roll D (5–30): start at the cell whose grid_number equals D (start_index); clockwise landing = (start position + D) mod 26; counter-clockwise = start − D (if negative, +26).\n• Each reference row’s “dice points” column is the roll D; tier / real_ev / display text come from the config at the landing id.\n\n[Leopard rolls]\nFor rolls 5, 10, 15, 20, 25, 30, clockwise and counter-clockwise landing tiers must NOT be T4 or T5 (avoid leopard roll + penalty / once again).\n\n[Settlement amount vs tier]\nSettlement < 0 → T4; 0 < Settlement < 1 → T3; 1 < Settlement < 2 → T2; Settlement > 2 → T1; T5 “once again” settlement = 0. You can set a unified settlement standard for each tier below; generated rows write those values into the config, and details can be edited later in the table.\n\n[Inputs in this dialog]\nCount: T1/T4/T5 are fixed; T2 is minimum. Clockwise and counter-clockwise weighted counts (each roll result counts once) must each satisfy the entered values; T1, T4, and T5 are entered separately.\nSettlement standard: all cells in the same tier use the same value. On generation, T1–T4 use ui_text / ui_text_en = settlement real_ev; T5 is fixed to \"再来一次\" / \"Once again\". Remarks still distinguish break-even / small win, etc.", + "ruleGenerateRules": "[Generation logic (same as Create Reward Reference)]\n• 26 cells ordered by id ascending are positions 0–25; each row’s grid_number is 5–30 and unique.\n• Roll D (5–30): start at the cell whose grid_number equals D (start_index); clockwise landing = (start position + D) mod 26; counter-clockwise = start − D (if negative, +26).\n• Each reference row’s “dice points” column is the roll D; tier / real_ev / display text come from the config at the landing id.\n\n[Leopard rolls]\nFor rolls 5, 10, 15, 20, 25, 30, clockwise and counter-clockwise landing tiers must NOT be T4 or T5 (avoid leopard roll + try again / penalty).\n\n[Settlement vs tier]\nT1: >2; T2: 2≥amount>1; T3: 1≥amount>0; T4: =0; T5: 0>amount. Set recommended settlement per tier below.\n\n[Inputs in this dialog]\nCount: T1/T4/T5 are fixed; T2 is minimum. Clockwise and counter-clockwise weighted counts must each satisfy the entered values.\nSettlement standard: same tier uses the same value. T1–T3 and T5 use ui_text = settlement; T4 is fixed to \"再来一次\" / \"Once again\".", "ruleGenT1Row": "T1 (big prize)", "ruleGenT2Row": "T2 (small win / break-even)", "ruleGenT3RealEvOnly": "T3 (rake)", - "ruleGenT4Row": "T4 (penalty)", - "ruleGenT5Row": "T5 (try again)", + "ruleGenT4Row": "T4 (try again)", + "ruleGenT5Row": "T5 (penalty)", "ruleGenMinCount": "Min count", "ruleGenFixedCount": "Fixed count (CW & CCW)", "ruleGenRealEvStd": "real_ev standard", "ruleGenRealEvEditHint": "After saving, you can still edit display text, EN, real_ev and remarks per row in the table above.", - "ruleGenInvalidT1RealEv": "T1 settlement amount must satisfy: value > 2", - "ruleGenInvalidT2RealEv": "T2 settlement amount must satisfy: 1 < value < 2", - "ruleGenInvalidT3RealEv": "T3 settlement amount must satisfy: 0 < value < 1", - "ruleGenInvalidT4RealEv": "T4 settlement amount must satisfy: value < 0", - "ruleGenInvalidT5RealEv": "T5 “try again” real_ev must be 0", + "ruleGenInvalidT1RealEv": "T1 (big prize) settlement must satisfy: value > 2", + "ruleGenInvalidT2RealEv": "T2 (small win) settlement must satisfy: 1 < value ≤ 2", + "ruleGenInvalidT3RealEv": "T3 (rake) settlement must satisfy: 0 < value ≤ 1", + "ruleGenInvalidT4RealEv": "T4 (try again) settlement must be 0", + "ruleGenInvalidT5RealEv": "T5 (penalty) settlement must satisfy: value < 0", "ruleGenT1Min": "T1 fixed count (CW & CCW)", "ruleGenT2Min": "T2 min (CW & CCW)", "ruleGenT4Max": "T4 fixed count (CW & CCW)", diff --git a/saiadmin-artd/src/locales/langs/zh/dice/reward_config.json b/saiadmin-artd/src/locales/langs/zh/dice/reward_config.json index 9fbeac5..68bd5bb 100644 --- a/saiadmin-artd/src/locales/langs/zh/dice/reward_config.json +++ b/saiadmin-artd/src/locales/langs/zh/dice/reward_config.json @@ -8,17 +8,18 @@ "tabIndex": "奖励索引", "tabBigwin": "大奖权重", "tipIndex": "色子点数须在 5~30 之间且本表内不重复。", - "tierRecommendRules": "【结算金额与档位】结算金额 < 0 → T4;0 < 结算金额 < 1 → T3;1 < 结算金额 < 2 → T2;2 < 结算金额 → T1;T5「再来一次」结算金额 = 0。下方可为各档位填写统一的结算金额标准,生成时写入配置;细则可稍后在表格中再改。", + "tierRecommendRules": "【结算金额与档位】【大奖】T1:结算金额>2;【小赚】T2:2>=结算金额>1;【抽水】T3:1>=结算金额>0;【再来一次】T4:结算金额=0;【惩罚】T5:0>结算金额。下方可为各档位填写推荐结算金额;表格中「所属档位」随结算金额自动计算,不可手动修改。", "tierRecommendRealEv": "推荐结算金额", "tierRecommendAutoMatch": "修改结算金额时自动匹配档位", "tierRecommendApplyAmount": "将推荐金额填入已选档位的行", "tierRecommendApplyAmountOk": "已为 {n} 行填入推荐结算金额", - "tierRecommendNoTierRows": "请先在表格中为行选择 T1~T5 档位", + "tierRecommendNoTierRows": "没有可根据结算金额推断档位的行", "tierRecommendMatchTier": "按结算金额匹配全部档位", "tierRecommendMatchTierOk": "已根据结算金额为 {n} 行匹配档位", "tierRecommendMatchTierNone": "没有可匹配档位的行", - "tierRecommendT5UiText": "再来一次", - "tierRecommendT5UiTextEn": "Once again", + "tierRecommendT4UiText": "再来一次", + "tierRecommendT4UiTextEn": "Once again", + "colTierAutoHint": "根据结算金额自动匹配", "tipBigwin": "从左至右:中大奖点数(不可改)、显示信息、实际中奖、备注、权重(0~10000)。点数 5、30 权重固定 100%。本表单独立提交,仅提交大奖权重。", "colId": "索引(id)", "colDicePoints": "色子点数", @@ -47,6 +48,18 @@ "confirmCreateRefMsg": "按规则创建奖励对照:起始索引 start_index=奖励配置中 grid_number 对应格位的 id;顺时针 end_index=(start_index+摇取点数)%26;逆时针 end_index=start_index-摇取点数≥0 则取该值,否则 26+start_index-摇取点数。先清空现有数据再为 5-30 共 26 个点数、顺/逆时针分别生成。是否继续?", "confirmCreateRefOk": "确定创建", "confirmCreateRefCancel": "取消", + "createRefPreviewTitle": "创建奖励对照预览", + "createRefPreviewClockwise": "顺时针", + "createRefPreviewCounterclockwise": "逆时针", + "createRefPreviewTipUnchanged": "检测到色子点数映射未变化:预览中权重将复用当前奖励对照表(dice_reward)的权重;导入时不会覆盖现有权重。", + "createRefPreviewTipChanged": "检测到色子点数映射已变化:预览中权重将使用默认值(1);确认导入后可再到「奖励对照」页面调整权重。", + "createRefPreviewSkipped": "有 {n} 个点数在当前奖励索引中缺失,已跳过生成(请先补齐 5~30 共 26 个点数)。", + "createRefPreviewRefresh": "刷新预览", + "createRefPreviewImport": "确认导入", + "createRefPreviewImportOk": "已导入奖励对照表", + "createRefPreviewImportNoop": "色子点数映射未变化,无需导入(已保留现有权重)", + "createRefPreviewDiff": "差异(旧 → 新)", + "createRefPreviewNoDiff": "无变化", "createRefSuccess": "已按 5-30 共 26 个点数、顺时针+逆时针创建:顺时针新增 {cwNew} 条、逆时针新增 {ccwNew} 条;顺时针更新 {cwUp} 条、逆时针更新 {ccwUp} 条{skippedPart}", "createRefSuccessSkipped": ";{n} 个点数使用兜底起始索引", "createRefSuccessSimple": "创建成功", @@ -65,21 +78,21 @@ "infoNoBigwin": "暂无 BIGWIN 档位配置,请先在「奖励索引」中设置 tier 为 BIGWIN", "btnRuleGenerate": "按规则生成", "ruleGenerateTitle": "按规则生成奖励索引", - "ruleGenerateRules": "【生成逻辑(与创建奖励对照一致)】\n• 盘面 26 格按 id 升序为位置 0~25;每条配置的 grid_number 为 5~30 且不重复。\n• 摇取点数 D(5~30):起点为「grid_number=D」所在格位的 id(即 start_index),顺时针落点位置 = (起点位置 + D) mod 26,逆时针落点 = 起点位置 − D(若小于 0 则 +26)。\n• 对照表每条记录的「色子点数」列为摇取点数 D;档位、真实结算、显示文案取自落点格位对应 id 的配置。\n\n【豹子摇取点数】\n摇取点数为 5、10、15、20、25、30 时,其顺/逆时针落点档位不能为 T4、T5(避免对照表上出现豹子点数 + 惩罚/再来一次)。\n\n【结算金额 与 档位】\n结算金额 < 0 → T4;0 < 结算金额 < 1 → T3;1 < 结算金额 < 2 → T2;2 < 结算金额 → T1;T5「再来一次」结算金额=0。下方可为各档位填写统一的 结算金额 标准,生成时写入配置;细则可稍后在表格中再改。\n\n【本弹窗输入】\n条数:T1/T4/T5「固定」;T2「不少于」——顺时针与逆时针的加权条数(每条摇取结果计一次)须分别满足所填数值;T1、T4 与 T5 分开填写。\n结算金额 标准:同档位各格使用同一数值。生成时 T1~T4 的 显示文本 / 显示文本(英文) = 结算金额;T5 固定为「再来一次」/「Once again」。备注仍区分完美回本/小赚等。", + "ruleGenerateRules": "【生成逻辑(与创建奖励对照一致)】\n• 盘面 26 格按 id 升序为位置 0~25;每条配置的 grid_number 为 5~30 且不重复。\n• 摇取点数 D(5~30):起点为「grid_number=D」所在格位的 id(即 start_index),顺时针落点位置 = (起点位置 + D) mod 26,逆时针落点 = 起点位置 − D(若小于 0 则 +26)。\n• 对照表每条记录的「色子点数」列为摇取点数 D;档位、真实结算、显示文案取自落点格位对应 id 的配置。\n\n【豹子摇取点数】\n摇取点数为 5、10、15、20、25、30 时,其顺/逆时针落点档位不能为 T4、T5(避免对照表上出现豹子点数 + 再来一次/惩罚)。\n\n【结算金额 与 档位】\n【大奖】T1:>2;【小赚】T2:2>=金额>1;【抽水】T3:1>=金额>0;【再来一次】T4:=0;【惩罚】T5:0>金额。下方可为各档位填写推荐结算金额标准,生成时写入配置。\n\n【本弹窗输入】\n条数:T1/T4/T5「固定」;T2「不少于」——顺时针与逆时针的加权条数(每条摇取结果计一次)须分别满足所填数值;T1、T4 与 T5 分开填写。\n结算金额 标准:同档位各格使用同一数值。生成时 T1~T3、T5 的显示文本 = 结算金额;T4 固定为「再来一次」/「Once again」。备注仍区分完美回本/小赚等。", "ruleGenT1Row": "T1 大奖", "ruleGenT2Row": "T2 小赚/回本", "ruleGenT3RealEvOnly": "T3 抽水", - "ruleGenT4Row": "T4 惩罚", - "ruleGenT5Row": "T5 再来一次", + "ruleGenT4Row": "T4 再来一次", + "ruleGenT5Row": "T5 惩罚", "ruleGenMinCount": "最少条数", "ruleGenFixedCount": "固定条数(顺/逆)", "ruleGenRealEvStd": "结算金额", "ruleGenRealEvEditHint": "生成并保存后,仍可在本页表格中逐条修改显示文案、英文、真实结算与备注。", - "ruleGenInvalidT1RealEv": "T1 的 结算金额 满足:2 < 值", - "ruleGenInvalidT2RealEv": "T2 的 结算金额 满足:1 < 值 < 2", - "ruleGenInvalidT3RealEv": "T3 的 结算金额 满足:0 < 值 < 1", - "ruleGenInvalidT4RealEv": "T4 的 结算金额 满足:值 < 0", - "ruleGenInvalidT5RealEv": "T5「再来一次」的 结算金额 须为 0", + "ruleGenInvalidT1RealEv": "T1(大奖)结算金额须满足:值 > 2", + "ruleGenInvalidT2RealEv": "T2(小赚)结算金额须满足:1 < 值 ≤ 2", + "ruleGenInvalidT3RealEv": "T3(抽水)结算金额须满足:0 < 值 ≤ 1", + "ruleGenInvalidT4RealEv": "T4(再来一次)结算金额须为 0", + "ruleGenInvalidT5RealEv": "T5(惩罚)结算金额须满足:值 < 0", "ruleGenT1Min": "T1 固定条数(顺/逆)", "ruleGenT2Min": "T2 最少条数(顺/逆)", "ruleGenT4Max": "T4 固定条数(顺/逆)", diff --git a/saiadmin-artd/src/views/plugin/dice/api/reward_config/index.ts b/saiadmin-artd/src/views/plugin/dice/api/reward_config/index.ts index 003577c..54dd1e8 100644 --- a/saiadmin-artd/src/views/plugin/dice/api/reward_config/index.ts +++ b/saiadmin-artd/src/views/plugin/dice/api/reward_config/index.ts @@ -147,5 +147,15 @@ export default { url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference', data: params || {} }) + }, + + /** + * 创建奖励对照(预览):不写库,返回将要生成的对照与权重预览 + */ + createRewardReferencePreview(params?: Record) { + return request.post({ + url: '/core/dice/reward_config/DiceRewardConfig/createRewardReferencePreview', + data: params || {} + }) } } diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue b/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue index a46b954..058fb55 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/index/index.vue @@ -29,7 +29,7 @@ {{ $t('page.configPage.tierRecommendRealEv') }} @@ -147,19 +148,7 @@ @@ -423,6 +413,7 @@ + + @@ -483,10 +480,11 @@ useInjectedChannelDept } from '@/composables/useChannelDeptScope' import { useWindowSize } from '@vueuse/core' - import { ElMessage, ElMessageBox } from 'element-plus' + import { ElMessage } from 'element-plus' import { useI18n } from 'vue-i18n' import api from '../../api/reward_config/index' import { checkAuth } from '@/utils/tool' + import CreateRewardReferencePreviewDialog from './modules/create-reward-reference-preview-dialog.vue' import { buildRowsFromTiers, computeBoardFrequencies, @@ -547,6 +545,7 @@ const savingIndex = ref(false) const savingBigwin = ref(false) const createRewardLoading = ref(false) + const createRewardPreviewVisible = ref(false) const ruleGenerateDialogVisible = ref(false) const ruleGenSubmitting = ref(false) const ruleGenT1Fixed = ref(3) @@ -593,15 +592,34 @@ row.ui_text_en = text } + function rowRealEvNumber(row: IndexRow): number { + return typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev) + ? row.real_ev + : Number(row.real_ev) + } + + function syncRowTierFromRealEv(row: IndexRow) { + const tier = inferTierFromRealEv(rowRealEvNumber(row)) + if (tier !== '') { + row.tier = tier + } + } + + function displayRowTier(row: IndexRow): string { + const tier = inferTierFromRealEv(rowRealEvNumber(row)) + return tier !== '' ? tier : row.tier || '-' + } + function applyRecommendRealEvToRow(row: IndexRow, tier: TierRecommendKey) { const ev = tierRecommend[tier] row.real_ev = ev - if (tier === 'T5') { - row.ui_text = t('page.configPage.tierRecommendT5UiText') - row.ui_text_en = t('page.configPage.tierRecommendT5UiTextEn') + if (tier === 'T4') { + row.ui_text = t('page.configPage.tierRecommendT4UiText') + row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn') } else { applyRealEvDisplay(row, ev) } + syncRowTierFromRealEv(row) } /** 奖励索引 id 与后端 DiceRewardConfigLogic 一致:0~25 */ @@ -655,16 +673,13 @@ } function handleRealEvChange(row: IndexRow) { - const n = - typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev) - ? row.real_ev - : Number(row.real_ev) - applyRealEvDisplay(row, n) - if (canTierRecommend.value && autoMatchTierOnRealEv.value) { - const tier = inferTierFromRealEv(n) - if (tier !== '') { - row.tier = tier - } + const n = rowRealEvNumber(row) + syncRowTierFromRealEv(row) + if (row.tier === 'T4') { + row.ui_text = t('page.configPage.tierRecommendT4UiText') + row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn') + } else { + applyRealEvDisplay(row, n) } } @@ -674,7 +689,7 @@ } let count = 0 for (const row of indexRowsExcludeBigwin.value) { - const tier = row.tier + const tier = inferTierFromRealEv(rowRealEvNumber(row)) if (tier === 'T1' || tier === 'T2' || tier === 'T3' || tier === 'T4' || tier === 'T5') { applyRecommendRealEvToRow(row, tier) count++ @@ -693,13 +708,8 @@ } let count = 0 for (const row of indexRowsExcludeBigwin.value) { - const n = - typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev) - ? row.real_ev - : Number(row.real_ev) - const tier = inferTierFromRealEv(n) - if (tier !== '') { - row.tier = tier + syncRowTierFromRealEv(row) + if (row.tier !== '') { count++ } } @@ -718,43 +728,7 @@ } async function handleCreateRewardReference() { - try { - await ElMessageBox.confirm( - t('page.configPage.confirmCreateRefMsg'), - t('page.configPage.confirmCreateRefTitle'), - { - confirmButtonText: t('page.configPage.confirmCreateRefOk'), - cancelButtonText: t('page.configPage.confirmCreateRefCancel'), - type: 'warning' - } - ) - } catch { - return - } - createRewardLoading.value = true - try { - const res: any = await api.createRewardReference({ dept_id: filterDeptId.value as number }) - const data = res?.data ?? res - let msg = t('page.configPage.createRefSuccessSimple') - if (typeof data === 'object' && data !== null) { - const skipped = Number(data.skipped ?? 0) - const skippedPart = - skipped > 0 ? t('page.configPage.createRefSuccessSkipped', { n: skipped }) : '' - msg = t('page.configPage.createRefSuccess', { - cwNew: data.created_clockwise ?? 0, - ccwNew: data.created_counterclockwise ?? 0, - cwUp: data.updated_clockwise ?? 0, - ccwUp: data.updated_counterclockwise ?? 0, - skippedPart - }) - } - ElMessage.success(msg) - loadIndexList() - } catch (e: any) { - ElMessage.error(e?.message ?? t('page.configPage.createRefFail')) - } finally { - createRewardLoading.value = false - } + createRewardPreviewVisible.value = true } function extractIndexList(res: unknown): Record[] { @@ -779,6 +753,11 @@ .list({ saiType: 'all', limit: 200, dept_id: filterDeptId.value }) .then((res: unknown) => { const rows = extractIndexList(res).map((r) => normalizeIndexRow(r)) + for (const row of rows) { + if (row.tier !== 'BIGWIN') { + syncRowTierFromRealEv(row) + } + } indexRows.value = rows indexRowsSnapshot = rows.map((r) => ({ ...r })) }) @@ -1024,6 +1003,9 @@ return } const toSave = indexRows.value.filter((r) => r.tier !== 'BIGWIN') + for (const row of toSave) { + syncRowTierFromRealEv(row) + } savingIndex.value = true try { const indexPayload = toSave.map((r) => ({ @@ -1239,6 +1221,10 @@ min-width: 100px; max-width: 160px; } + .tier-readonly { + font-weight: 500; + color: var(--el-text-color-primary); + } .tier-recommend-actions { display: flex; flex-wrap: wrap; diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/create-reward-reference-preview-dialog.vue b/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/create-reward-reference-preview-dialog.vue new file mode 100644 index 0000000..1063912 --- /dev/null +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/index/modules/create-reward-reference-preview-dialog.vue @@ -0,0 +1,243 @@ + + + + + + diff --git a/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts b/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts index 8c865f7..33d0f78 100644 --- a/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts +++ b/saiadmin-artd/src/views/plugin/dice/reward_config/utils/generateIndexByRules.ts @@ -58,38 +58,36 @@ export const DEFAULT_TIER_REAL_EV_STANDARDS: TierRealEvStandards = { T1: 3, T2: 1.5, T3: 0.5, - T4: -0.4, - T5: 0 + T4: 0, + T5: -0.4 } /** * 按结算金额推断档位(与奖励配置页规则说明一致) - * 结算金额 < 0 → T4;0 < 结算金额 < 1 → T3;1 < 结算金额 < 2 → T2;2 < 结算金额 → T1;T5 结算金额 = 0 + * T1 大奖:>2 + * T2 小赚:2>=金额>1 + * T3 抽水:1>=金额>0 + * T4 再来一次:=0 + * T5 惩罚:0>金额 */ export function inferTierFromRealEv(realEv: number): IndexTier | '' { if (!Number.isFinite(realEv)) { return '' } - if (realEv === 0) { - return 'T5' - } - if (realEv < 0) { - return 'T4' - } if (realEv > 2) { return 'T1' } - if (realEv > 1 && realEv < 2) { + if (realEv > 1 && realEv <= 2) { return 'T2' } - if (realEv > 0 && realEv < 1) { + if (realEv > 0 && realEv <= 1) { return 'T3' } - if (realEv === 1) { - return 'T2' + if (realEv === 0) { + return 'T4' } - if (realEv === 2) { - return 'T1' + if (realEv < 0) { + return 'T5' } return '' } @@ -101,16 +99,16 @@ export function validateTierRealEvStandards(s: TierRealEvStandards): string | nu if (!Number.isFinite(s.T1) || !(s.T1 > 2)) { return 'ruleGenInvalidT1RealEv' } - if (!Number.isFinite(s.T2) || !(s.T2 > 1 && s.T2 < 2)) { + if (!Number.isFinite(s.T2) || !(s.T2 > 1 && s.T2 <= 2)) { return 'ruleGenInvalidT2RealEv' } - if (!Number.isFinite(s.T3) || !(s.T3 > 0 && s.T3 < 1)) { + if (!Number.isFinite(s.T3) || !(s.T3 > 0 && s.T3 <= 1)) { return 'ruleGenInvalidT3RealEv' } - if (!Number.isFinite(s.T4) || !(s.T4 < 0)) { + if (!Number.isFinite(s.T4) || s.T4 !== 0) { return 'ruleGenInvalidT4RealEv' } - if (!Number.isFinite(s.T5) || s.T5 !== 0) { + if (!Number.isFinite(s.T5) || s.T5 >= 0) { return 'ruleGenInvalidT5RealEv' } return null @@ -307,7 +305,7 @@ function uiTextByTierWhenStandards( tier: IndexTier, realEv: number ): { ui_text: string; ui_text_en: string } { - if (tier === 'T5') { + if (tier === 'T4') { return { ui_text: '再来一次', ui_text_en: 'Once again' } } const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv) @@ -379,13 +377,13 @@ export function buildRowsFromTiers( const f = uiTextByTierWhenStandards(tier, real_ev) ui_text = f.ui_text ui_text_en = f.ui_text_en - remark = '惩罚' + remark = '再来一次' } else { real_ev = standards.T5 const f = uiTextByTierWhenStandards(tier, real_ev) ui_text = f.ui_text ui_text_en = f.ui_text_en - remark = '前端需要在播放一次动画(特殊)' + remark = '惩罚' } } else if (tier === 'T1') { real_ev = 101 + ((id * 17 + grid_number * 3) % 398) @@ -417,18 +415,17 @@ export function buildRowsFromTiers( ui_text_en = f.ui_text_en remark = '抽水' } else if (tier === 'T4') { + real_ev = 0 + ui_text = '再来一次' + ui_text_en = 'Once again' + remark = '再来一次' + } else { t4Seq++ real_ev = -101 - t4Seq * 15 const f = uiTextFromRealEv(real_ev) ui_text = f.ui_text ui_text_en = f.ui_text_en remark = '惩罚' - } else { - real_ev = 0 - const f = uiTextFromRealEv(real_ev) - ui_text = f.ui_text - ui_text_en = f.ui_text_en - remark = '前端需要在播放一次动画(特殊)' } rows.push({ id, diff --git a/server/app/dice/controller/reward_config/DiceRewardConfigController.php b/server/app/dice/controller/reward_config/DiceRewardConfigController.php index b702cab..9ecb306 100644 --- a/server/app/dice/controller/reward_config/DiceRewardConfigController.php +++ b/server/app/dice/controller/reward_config/DiceRewardConfigController.php @@ -263,6 +263,23 @@ class DiceRewardConfigController extends BaseController } } + /** + * 创建奖励对照(预览):不写入 dice_reward,仅计算并返回预览分组数据。 + * 若当前 dice_reward 与计算结果一致,则 unchanged=true,并在预览中复用现有权重(导入时仍沿用旧权重)。 + */ + #[Permission('创建奖励对照', 'dice:reward_config:index:createRewardReference')] + public function createRewardReferencePreview(Request $request): Response + { + try { + $rewardLogic = new DiceRewardLogic(); + $deptId = AdminScopeHelper::resolveConfigDeptId($this->adminInfo ?? null, $request->input('dept_id')); + $result = $rewardLogic->createRewardReferencePreviewFromConfig($deptId); + return $this->success($result, 'preview reward mapping success'); + } catch (\plugin\saiadmin\exception\ApiException $e) { + return $this->fail($e->getMessage()); + } + } + /** * 权重配比测试:仅模拟落点统计,不创建游玩记录。按当前配置在内存中模拟 N 次抽奖,返回各 grid_number 落点次数,可选保存到 dice_reward_config_record。 * @param Request $request test_count: 100|500|1000, save_record: bool, lottery_config_id: int|null 奖池配置ID,用于设定 T1-T5 概率 diff --git a/server/app/dice/logic/reward/DiceRewardLogic.php b/server/app/dice/logic/reward/DiceRewardLogic.php index b1af329..4f3ca7c 100644 --- a/server/app/dice/logic/reward/DiceRewardLogic.php +++ b/server/app/dice/logic/reward/DiceRewardLogic.php @@ -327,6 +327,79 @@ class DiceRewardLogic private const GRID_NUMBER_MIN = 5; private const GRID_NUMBER_MAX = 30; + /** + * 预览:按当前 dice_reward_config 计算将要生成的 dice_reward(不写库) + * 若当前 dice_reward 与计算结果完全一致,则标记 unchanged=true,并返回现有权重(导入时将复用旧权重) + * + * @return array{unchanged: bool, skipped: int, preview: array} + * @throws ApiException + */ + public function createRewardReferencePreviewFromConfig(?int $deptId = null): array + { + $normalizedDeptId = $deptId; + $list = $this->loadConfigListForReference($normalizedDeptId); + $computed = $this->computeReferenceRowsFromConfigList($list, $normalizedDeptId); + + $existing = $this->loadExistingRewardRowsForReference($normalizedDeptId); + $compare = $this->compareReferenceRows($computed['rows'], $existing); + $unchanged = $compare['unchanged']; + + $previewRows = []; + foreach ($computed['rows'] as $row) { + $key = $row['direction'] . ':' . $row['grid_number']; + $weight = self::WEIGHT_MIN; + $oldStart = null; + $oldEnd = null; + $oldTier = null; + $oldWeight = null; + if (isset($existing[$key])) { + $oldStart = isset($existing[$key]['start_index']) ? (int) $existing[$key]['start_index'] : null; + $oldEnd = isset($existing[$key]['end_index']) ? (int) $existing[$key]['end_index'] : null; + $oldTier = isset($existing[$key]['tier']) ? (string) $existing[$key]['tier'] : null; + $oldWeight = isset($existing[$key]['weight']) ? (int) $existing[$key]['weight'] : null; + } + if ($unchanged && $oldWeight !== null) { + $weight = max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $oldWeight)); + } + + $diffChanged = false; + $diffFields = []; + if ($oldStart === null || $oldEnd === null || $oldTier === null) { + $diffChanged = true; + $diffFields[] = 'new'; + } else { + if ((int) $oldStart !== (int) ($row['start_index'] ?? 0)) { + $diffChanged = true; + $diffFields[] = 'start_index'; + } + if ((int) $oldEnd !== (int) ($row['end_index'] ?? 0)) { + $diffChanged = true; + $diffFields[] = 'end_index'; + } + if (trim((string) $oldTier) !== trim((string) ($row['tier'] ?? ''))) { + $diffChanged = true; + $diffFields[] = 'tier'; + } + } + + $previewRows[] = array_merge($row, [ + 'weight' => $weight, + 'old_start_index' => $oldStart, + 'old_end_index' => $oldEnd, + 'old_tier' => $oldTier, + 'old_weight' => $oldWeight, + 'diff_changed' => $diffChanged, + 'diff_fields' => $diffFields, + ]); + } + + return [ + 'unchanged' => $unchanged, + 'skipped' => $computed['skipped'], + 'preview' => $this->groupReferenceRowsByTierWithDirection($previewRows), + ]; + } + /** * 创建奖励对照:先清空 dice_reward 表,再按两种方向为点数 5-30 生成记录。 * @@ -352,10 +425,76 @@ class DiceRewardLogic * @throws ApiException */ public function createRewardReferenceFromConfig(?int $deptId = null): array + { + $normalizedDeptId = $deptId; + $list = $this->loadConfigListForReference($normalizedDeptId); + $computed = $this->computeReferenceRowsFromConfigList($list, $normalizedDeptId); + + $existing = $this->loadExistingRewardRowsForReference($normalizedDeptId); + $compare = $this->compareReferenceRows($computed['rows'], $existing); + if ($compare['unchanged']) { + return [ + 'created_clockwise' => 0, + 'created_counterclockwise' => 0, + 'updated_clockwise' => 0, + 'updated_counterclockwise' => 0, + 'skipped' => $computed['skipped'], + 'unchanged' => true, + ]; + } + + $table = (new DiceReward())->getTable(); + if ($normalizedDeptId === null) { + Db::table($table)->whereNull('dept_id')->delete(); + } else { + Db::table($table)->where('dept_id', $normalizedDeptId)->delete(); + } + + $createdCw = 0; + $createdCcw = 0; + foreach ($computed['rows'] as $row) { + $m = new DiceReward(); + $m->tier = $row['tier']; + $m->direction = (int) $row['direction']; + $m->end_index = (int) $row['end_index']; + $m->weight = self::WEIGHT_MIN; + $m->grid_number = (int) $row['grid_number']; + $m->start_index = (int) $row['start_index']; + $m->ui_text = (string) ($row['ui_text'] ?? ''); + $m->real_ev = $row['real_ev'] ?? null; + $m->remark = (string) ($row['remark'] ?? ''); + $m->type = isset($row['type']) ? (int) $row['type'] : 0; + if ($normalizedDeptId !== null) { + $m->dept_id = $normalizedDeptId; + } + $m->save(); + if ((int) $row['direction'] === DiceReward::DIRECTION_CLOCKWISE) { + $createdCw++; + } else { + $createdCcw++; + } + } + + DiceReward::refreshCache($normalizedDeptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT); + return [ + 'created_clockwise' => $createdCw, + 'created_counterclockwise' => $createdCcw, + 'updated_clockwise' => 0, + 'updated_counterclockwise' => 0, + 'skipped' => $computed['skipped'], + 'unchanged' => false, + ]; + } + + /** + * 读取奖励配置(按 id asc),并把模板 dept 转为 null + * @return array> + */ + private function loadConfigListForReference(?int &$deptId): array { $configQuery = DiceRewardConfig::order('id', 'asc'); - if ($deptId === null || $deptId === \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) { - $templateId = \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT; + if ($deptId === null || $deptId === AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) { + $templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT; $configQuery->where(function ($q) use ($templateId) { $q->where('dept_id', $templateId)->whereOr('dept_id', 'null'); }); @@ -367,25 +506,25 @@ class DiceRewardLogic if (empty($list)) { throw new ApiException('Reward config is empty, please maintain dice_reward_config first'); } - $configCount = count($list); - if ($configCount < self::BOARD_SIZE) { + if (count($list) < self::BOARD_SIZE) { throw new ApiException( \app\api\util\ApiLang::translateParams( '奖励配置需覆盖 26 个格位(id 0-25 或 1-26),当前仅 %s 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照', - [$configCount] + [count($list)] ) ); } + return $list; + } - $table = (new DiceReward())->getTable(); - if ($deptId === null) { - Db::table($table)->whereNull('dept_id')->delete(); - } else { - Db::table($table)->where('dept_id', $deptId)->delete(); - } - DiceReward::refreshCache($deptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT); - - // 按 id 排序后,盘面位置 0..25 对应 $list[$pos],避免 config.id 非 0-25/1-26 时取模结果找不到 + /** + * 计算 5-30 两个方向的对照行(不含权重) + * @param array> $list + * @return array{rows: array>, skipped: int} + */ + private function computeReferenceRowsFromConfigList(array $list, ?int $deptId): array + { + // 按 id 排序后,盘面位置 0..25 对应 $list[$pos] $gridToPosition = []; foreach ($list as $pos => $row) { $gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0; @@ -394,12 +533,8 @@ class DiceRewardLogic } } - $createdCw = 0; - $createdCcw = 0; - $updatedCw = 0; - $updatedCcw = 0; + $rows = []; $skipped = 0; - for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) { if (!isset($gridToPosition[$gridNumber])) { $skipped++; @@ -414,115 +549,131 @@ class DiceRewardLogic $configCw = $list[$endPosCw] ?? null; $configCcw = $list[$endPosCcw] ?? null; - $endIdCw = $configCw !== null && isset($configCw['id']) ? (int) $configCw['id'] : 0; - $endIdCcw = $configCcw !== null && isset($configCcw['id']) ? (int) $configCcw['id'] : 0; - if ($configCw !== null) { $tier = isset($configCw['tier']) ? trim((string) $configCw['tier']) : ''; if ($tier !== '') { - // 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重) - $weightCw = isset($configCw['weight']) && $configCw['weight'] !== null - ? $configCw['weight'] - : self::WEIGHT_MIN; - $payloadCw = [ + $rows[] = [ 'tier' => $tier, - 'weight' => $weightCw, + 'direction' => DiceReward::DIRECTION_CLOCKWISE, + 'weight' => self::WEIGHT_MIN, 'grid_number' => $gridNumber, 'start_index' => $startId, - 'end_index' => $endIdCw, + 'end_index' => isset($configCw['id']) ? (int) $configCw['id'] : 0, 'ui_text' => $configCw['ui_text'] ?? '', 'real_ev' => $configCw['real_ev'] ?? null, 'remark' => $configCw['remark'] ?? '', 'type' => isset($configCw['type']) ? (int) $configCw['type'] : 0, ]; - $existingQuery = DiceReward::where('direction', DiceReward::DIRECTION_CLOCKWISE)->where('grid_number', $gridNumber); - if ($deptId === null) { - $existingQuery->whereNull('dept_id'); - } else { - $existingQuery->where('dept_id', $deptId); - } - $existing = $existingQuery->find(); - if ($existing) { - DiceReward::where('id', $existing->id)->update($payloadCw); - $updatedCw++; - } else { - $m = new DiceReward(); - $m->tier = $tier; - $m->direction = DiceReward::DIRECTION_CLOCKWISE; - $m->end_index = $endIdCw; - $m->weight = $weightCw; - $m->grid_number = $gridNumber; - $m->start_index = $startId; - $m->ui_text = $configCw['ui_text'] ?? ''; - $m->real_ev = $configCw['real_ev'] ?? null; - $m->remark = $configCw['remark'] ?? ''; - $m->type = isset($configCw['type']) ? (int) $configCw['type'] : 0; - if ($deptId !== null) { - $m->dept_id = $deptId; - } - $m->save(); - $createdCw++; - } } } - if ($configCcw !== null) { $tier = isset($configCcw['tier']) ? trim((string) $configCcw['tier']) : ''; if ($tier !== '') { - // 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重) - $weightCcw = isset($configCcw['weight']) && $configCcw['weight'] !== null - ? $configCcw['weight'] - : self::WEIGHT_MIN; - $payloadCcw = [ + $rows[] = [ 'tier' => $tier, - 'weight' => $weightCcw, + 'direction' => DiceReward::DIRECTION_COUNTERCLOCKWISE, + 'weight' => self::WEIGHT_MIN, 'grid_number' => $gridNumber, 'start_index' => $startId, - 'end_index' => $endIdCcw, + 'end_index' => isset($configCcw['id']) ? (int) $configCcw['id'] : 0, 'ui_text' => $configCcw['ui_text'] ?? '', 'real_ev' => $configCcw['real_ev'] ?? null, 'remark' => $configCcw['remark'] ?? '', 'type' => isset($configCcw['type']) ? (int) $configCcw['type'] : 0, ]; - $existingQuery = DiceReward::where('direction', DiceReward::DIRECTION_COUNTERCLOCKWISE)->where('grid_number', $gridNumber); - if ($deptId === null) { - $existingQuery->whereNull('dept_id'); - } else { - $existingQuery->where('dept_id', $deptId); - } - $existing = $existingQuery->find(); - if ($existing) { - DiceReward::where('id', $existing->id)->update($payloadCcw); - $updatedCcw++; - } else { - $m = new DiceReward(); - $m->tier = $tier; - $m->direction = DiceReward::DIRECTION_COUNTERCLOCKWISE; - $m->end_index = $endIdCcw; - $m->weight = $weightCcw; - $m->grid_number = $gridNumber; - $m->start_index = $startId; - $m->ui_text = $configCcw['ui_text'] ?? ''; - $m->real_ev = $configCcw['real_ev'] ?? null; - $m->remark = $configCcw['remark'] ?? ''; - $m->type = isset($configCcw['type']) ? (int) $configCcw['type'] : 0; - if ($deptId !== null) { - $m->dept_id = $deptId; - } - $m->save(); - $createdCcw++; - } } } } + return ['rows' => $rows, 'skipped' => $skipped]; + } - DiceReward::refreshCache($deptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT); - return [ - 'created_clockwise' => $createdCw, - 'created_counterclockwise' => $createdCcw, - 'updated_clockwise' => $updatedCw, - 'updated_counterclockwise' => $updatedCcw, - 'skipped' => $skipped, - ]; + /** + * 读出当前 dice_reward(用于对比/复用权重)。key = "direction:grid_number" + * @return array> + */ + private function loadExistingRewardRowsForReference(?int $deptId): array + { + $query = DiceReward::whereIn('grid_number', range(self::GRID_NUMBER_MIN, self::GRID_NUMBER_MAX)) + ->whereIn('direction', [DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE]); + if ($deptId === null) { + $query->whereNull('dept_id'); + } else { + $query->where('dept_id', $deptId); + } + $rows = $query->select()->toArray(); + $map = []; + foreach ($rows as $r) { + $dir = isset($r['direction']) ? (int) $r['direction'] : 0; + $gn = isset($r['grid_number']) ? (int) $r['grid_number'] : 0; + $map[$dir . ':' . $gn] = $r; + } + return $map; + } + + /** + * 对比 computed 与 existing 是否完全一致(忽略权重) + * @param array> $computedRows + * @param array> $existingMap + * @return array{unchanged: bool} + */ + private function compareReferenceRows(array $computedRows, array $existingMap): array + { + if (empty($computedRows)) { + return ['unchanged' => false]; + } + foreach ($computedRows as $row) { + $key = $row['direction'] . ':' . $row['grid_number']; + if (!isset($existingMap[$key])) { + return ['unchanged' => false]; + } + $ex = $existingMap[$key]; + $same = + ((int) ($ex['start_index'] ?? 0) === (int) ($row['start_index'] ?? 0)) && + ((int) ($ex['end_index'] ?? 0) === (int) ($row['end_index'] ?? 0)) && + (trim((string) ($ex['tier'] ?? '')) === trim((string) ($row['tier'] ?? ''))); + if (!$same) { + return ['unchanged' => false]; + } + } + return ['unchanged' => true]; + } + + /** + * 将行按 tier -> {0:[],1:[]} 组织,便于前端展示(与 weightRatioList 输出结构一致) + * @param array> $rows + * @return array + */ + private function groupReferenceRowsByTierWithDirection(array $rows): array + { + $result = []; + foreach (self::TIER_KEYS as $tier) { + $result[$tier] = [0 => [], 1 => []]; + } + foreach ($rows as $r) { + $tier = isset($r['tier']) ? trim((string) $r['tier']) : ''; + if ($tier === '' || !isset($result[$tier])) { + continue; + } + $dir = isset($r['direction']) ? (int) $r['direction'] : 0; + $dir = $dir === 1 ? 1 : 0; + $result[$tier][$dir][] = [ + 'reward_id' => 0, + 'id' => isset($r['end_index']) ? (int) $r['end_index'] : 0, + 'grid_number' => isset($r['grid_number']) ? (int) $r['grid_number'] : 0, + 'ui_text' => (string) ($r['ui_text'] ?? ''), + 'real_ev' => $r['real_ev'] ?? 0, + 'remark' => (string) ($r['remark'] ?? ''), + 'weight' => isset($r['weight']) ? max(self::WEIGHT_MIN, min(self::WEIGHT_MAX, (int) $r['weight'])) : self::WEIGHT_MIN, + 'start_index' => isset($r['start_index']) ? (int) $r['start_index'] : 0, + 'tier' => (string) ($r['tier'] ?? ''), + 'old_start_index' => $r['old_start_index'] ?? null, + 'old_end_index' => $r['old_end_index'] ?? null, + 'old_tier' => $r['old_tier'] ?? null, + 'old_weight' => $r['old_weight'] ?? null, + 'diff_changed' => (bool) ($r['diff_changed'] ?? false), + 'diff_fields' => $r['diff_fields'] ?? [], + ]; + } + return $result; } } diff --git a/server/docs/ADMIN_GUIDE.md b/server/docs/ADMIN_GUIDE.md index 75988b5..138a071 100644 --- a/server/docs/ADMIN_GUIDE.md +++ b/server/docs/ADMIN_GUIDE.md @@ -83,7 +83,7 @@ 其中地图的索引可以按照需求点击图中的按规则生成 -并且规则尽可能符合:结算金额 < 0 → T4;0 < 结算金额 < 100 → T3;100 < 结算金额 < 200 → T2;200 < 结算金额 → T1;T5「再来一次」结算金额=0 +并且规则尽可能符合:结算金额>2 → T1;2>=结算金额>1 → T2;1>=结算金额>0 → T3;结算金额=0 → T4(再来一次);0>结算金额 → T5(惩罚) ![image.png](/docs/picture/guide_16.png) diff --git a/server/plugin/saiadmin/config/route.php b/server/plugin/saiadmin/config/route.php index 661c629..cba8e83 100644 --- a/server/plugin/saiadmin/config/route.php +++ b/server/plugin/saiadmin/config/route.php @@ -121,6 +121,7 @@ Route::group('/core', function () { Route::post('/dice/reward_config/DiceRewardConfig/batchUpdate', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'batchUpdate']); Route::post('/dice/reward_config/DiceRewardConfig/generateIndexByRules', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'generateIndexByRules']); Route::post('/dice/reward_config/DiceRewardConfig/createRewardReference', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'createRewardReference']); + Route::post('/dice/reward_config/DiceRewardConfig/createRewardReferencePreview', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'createRewardReferencePreview']); Route::post('/dice/reward_config/DiceRewardConfig/runWeightTest', [\app\dice\controller\reward_config\DiceRewardConfigController::class, 'runWeightTest']); fastRoute('dice/game/DiceGame', \app\dice\controller\game\DiceGameController::class); fastRoute('dice/ante_config/DiceAnteConfig', \app\dice\controller\ante_config\DiceAnteConfigController::class);