1.优化奖励配置设置
This commit is contained in:
@@ -8,17 +8,18 @@
|
|||||||
"tabIndex": "Reward Index",
|
"tabIndex": "Reward Index",
|
||||||
"tabBigwin": "Big Win Weights",
|
"tabBigwin": "Big Win Weights",
|
||||||
"tipIndex": "Dice points must be between 5 and 30 and unique in this table.",
|
"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",
|
"tierRecommendRealEv": "Recommended settlement",
|
||||||
"tierRecommendAutoMatch": "Auto-match tier when settlement changes",
|
"tierRecommendAutoMatch": "Auto-match tier when settlement changes",
|
||||||
"tierRecommendApplyAmount": "Fill recommended amount for rows with tier set",
|
"tierRecommendApplyAmount": "Fill recommended amount for rows with tier set",
|
||||||
"tierRecommendApplyAmountOk": "Filled recommended settlement for {n} row(s)",
|
"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",
|
"tierRecommendMatchTier": "Match all tiers from settlement",
|
||||||
"tierRecommendMatchTierOk": "Matched tier for {n} row(s) from settlement",
|
"tierRecommendMatchTierOk": "Matched tier for {n} row(s) from settlement",
|
||||||
"tierRecommendMatchTierNone": "No rows to match",
|
"tierRecommendMatchTierNone": "No rows to match",
|
||||||
"tierRecommendT5UiText": "再来一次",
|
"tierRecommendT4UiText": "再来一次",
|
||||||
"tierRecommendT5UiTextEn": "Once again",
|
"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.",
|
"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)",
|
"colId": "Index (id)",
|
||||||
"colDicePoints": "Dice Points",
|
"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?",
|
"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",
|
"confirmCreateRefOk": "Create",
|
||||||
"confirmCreateRefCancel": "Cancel",
|
"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}",
|
"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",
|
"createRefSuccessSkipped": "; {n} point(s) used fallback start index",
|
||||||
"createRefSuccessSimple": "Created successfully",
|
"createRefSuccessSimple": "Created successfully",
|
||||||
@@ -65,21 +78,21 @@
|
|||||||
"infoNoBigwin": "No BIGWIN rows. Set tier to BIGWIN in the Reward Index tab first.",
|
"infoNoBigwin": "No BIGWIN rows. Set tier to BIGWIN in the Reward Index tab first.",
|
||||||
"btnRuleGenerate": "Generate by rules",
|
"btnRuleGenerate": "Generate by rules",
|
||||||
"ruleGenerateTitle": "Generate reward index 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)",
|
"ruleGenT1Row": "T1 (big prize)",
|
||||||
"ruleGenT2Row": "T2 (small win / break-even)",
|
"ruleGenT2Row": "T2 (small win / break-even)",
|
||||||
"ruleGenT3RealEvOnly": "T3 (rake)",
|
"ruleGenT3RealEvOnly": "T3 (rake)",
|
||||||
"ruleGenT4Row": "T4 (penalty)",
|
"ruleGenT4Row": "T4 (try again)",
|
||||||
"ruleGenT5Row": "T5 (try again)",
|
"ruleGenT5Row": "T5 (penalty)",
|
||||||
"ruleGenMinCount": "Min count",
|
"ruleGenMinCount": "Min count",
|
||||||
"ruleGenFixedCount": "Fixed count (CW & CCW)",
|
"ruleGenFixedCount": "Fixed count (CW & CCW)",
|
||||||
"ruleGenRealEvStd": "real_ev standard",
|
"ruleGenRealEvStd": "real_ev standard",
|
||||||
"ruleGenRealEvEditHint": "After saving, you can still edit display text, EN, real_ev and remarks per row in the table above.",
|
"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",
|
"ruleGenInvalidT1RealEv": "T1 (big prize) settlement must satisfy: value > 2",
|
||||||
"ruleGenInvalidT2RealEv": "T2 settlement amount must satisfy: 1 < value < 2",
|
"ruleGenInvalidT2RealEv": "T2 (small win) settlement must satisfy: 1 < value ≤ 2",
|
||||||
"ruleGenInvalidT3RealEv": "T3 settlement amount must satisfy: 0 < value < 1",
|
"ruleGenInvalidT3RealEv": "T3 (rake) settlement must satisfy: 0 < value ≤ 1",
|
||||||
"ruleGenInvalidT4RealEv": "T4 settlement amount must satisfy: value < 0",
|
"ruleGenInvalidT4RealEv": "T4 (try again) settlement must be 0",
|
||||||
"ruleGenInvalidT5RealEv": "T5 “try again” real_ev must be 0",
|
"ruleGenInvalidT5RealEv": "T5 (penalty) settlement must satisfy: value < 0",
|
||||||
"ruleGenT1Min": "T1 fixed count (CW & CCW)",
|
"ruleGenT1Min": "T1 fixed count (CW & CCW)",
|
||||||
"ruleGenT2Min": "T2 min (CW & CCW)",
|
"ruleGenT2Min": "T2 min (CW & CCW)",
|
||||||
"ruleGenT4Max": "T4 fixed count (CW & CCW)",
|
"ruleGenT4Max": "T4 fixed count (CW & CCW)",
|
||||||
|
|||||||
@@ -8,17 +8,18 @@
|
|||||||
"tabIndex": "奖励索引",
|
"tabIndex": "奖励索引",
|
||||||
"tabBigwin": "大奖权重",
|
"tabBigwin": "大奖权重",
|
||||||
"tipIndex": "色子点数须在 5~30 之间且本表内不重复。",
|
"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": "推荐结算金额",
|
"tierRecommendRealEv": "推荐结算金额",
|
||||||
"tierRecommendAutoMatch": "修改结算金额时自动匹配档位",
|
"tierRecommendAutoMatch": "修改结算金额时自动匹配档位",
|
||||||
"tierRecommendApplyAmount": "将推荐金额填入已选档位的行",
|
"tierRecommendApplyAmount": "将推荐金额填入已选档位的行",
|
||||||
"tierRecommendApplyAmountOk": "已为 {n} 行填入推荐结算金额",
|
"tierRecommendApplyAmountOk": "已为 {n} 行填入推荐结算金额",
|
||||||
"tierRecommendNoTierRows": "请先在表格中为行选择 T1~T5 档位",
|
"tierRecommendNoTierRows": "没有可根据结算金额推断档位的行",
|
||||||
"tierRecommendMatchTier": "按结算金额匹配全部档位",
|
"tierRecommendMatchTier": "按结算金额匹配全部档位",
|
||||||
"tierRecommendMatchTierOk": "已根据结算金额为 {n} 行匹配档位",
|
"tierRecommendMatchTierOk": "已根据结算金额为 {n} 行匹配档位",
|
||||||
"tierRecommendMatchTierNone": "没有可匹配档位的行",
|
"tierRecommendMatchTierNone": "没有可匹配档位的行",
|
||||||
"tierRecommendT5UiText": "再来一次",
|
"tierRecommendT4UiText": "再来一次",
|
||||||
"tierRecommendT5UiTextEn": "Once again",
|
"tierRecommendT4UiTextEn": "Once again",
|
||||||
|
"colTierAutoHint": "根据结算金额自动匹配",
|
||||||
"tipBigwin": "从左至右:中大奖点数(不可改)、显示信息、实际中奖、备注、权重(0~10000)。点数 5、30 权重固定 100%。本表单独立提交,仅提交大奖权重。",
|
"tipBigwin": "从左至右:中大奖点数(不可改)、显示信息、实际中奖、备注、权重(0~10000)。点数 5、30 权重固定 100%。本表单独立提交,仅提交大奖权重。",
|
||||||
"colId": "索引(id)",
|
"colId": "索引(id)",
|
||||||
"colDicePoints": "色子点数",
|
"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 个点数、顺/逆时针分别生成。是否继续?",
|
"confirmCreateRefMsg": "按规则创建奖励对照:起始索引 start_index=奖励配置中 grid_number 对应格位的 id;顺时针 end_index=(start_index+摇取点数)%26;逆时针 end_index=start_index-摇取点数≥0 则取该值,否则 26+start_index-摇取点数。先清空现有数据再为 5-30 共 26 个点数、顺/逆时针分别生成。是否继续?",
|
||||||
"confirmCreateRefOk": "确定创建",
|
"confirmCreateRefOk": "确定创建",
|
||||||
"confirmCreateRefCancel": "取消",
|
"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}",
|
"createRefSuccess": "已按 5-30 共 26 个点数、顺时针+逆时针创建:顺时针新增 {cwNew} 条、逆时针新增 {ccwNew} 条;顺时针更新 {cwUp} 条、逆时针更新 {ccwUp} 条{skippedPart}",
|
||||||
"createRefSuccessSkipped": ";{n} 个点数使用兜底起始索引",
|
"createRefSuccessSkipped": ";{n} 个点数使用兜底起始索引",
|
||||||
"createRefSuccessSimple": "创建成功",
|
"createRefSuccessSimple": "创建成功",
|
||||||
@@ -65,21 +78,21 @@
|
|||||||
"infoNoBigwin": "暂无 BIGWIN 档位配置,请先在「奖励索引」中设置 tier 为 BIGWIN",
|
"infoNoBigwin": "暂无 BIGWIN 档位配置,请先在「奖励索引」中设置 tier 为 BIGWIN",
|
||||||
"btnRuleGenerate": "按规则生成",
|
"btnRuleGenerate": "按规则生成",
|
||||||
"ruleGenerateTitle": "按规则生成奖励索引",
|
"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 大奖",
|
"ruleGenT1Row": "T1 大奖",
|
||||||
"ruleGenT2Row": "T2 小赚/回本",
|
"ruleGenT2Row": "T2 小赚/回本",
|
||||||
"ruleGenT3RealEvOnly": "T3 抽水",
|
"ruleGenT3RealEvOnly": "T3 抽水",
|
||||||
"ruleGenT4Row": "T4 惩罚",
|
"ruleGenT4Row": "T4 再来一次",
|
||||||
"ruleGenT5Row": "T5 再来一次",
|
"ruleGenT5Row": "T5 惩罚",
|
||||||
"ruleGenMinCount": "最少条数",
|
"ruleGenMinCount": "最少条数",
|
||||||
"ruleGenFixedCount": "固定条数(顺/逆)",
|
"ruleGenFixedCount": "固定条数(顺/逆)",
|
||||||
"ruleGenRealEvStd": "结算金额",
|
"ruleGenRealEvStd": "结算金额",
|
||||||
"ruleGenRealEvEditHint": "生成并保存后,仍可在本页表格中逐条修改显示文案、英文、真实结算与备注。",
|
"ruleGenRealEvEditHint": "生成并保存后,仍可在本页表格中逐条修改显示文案、英文、真实结算与备注。",
|
||||||
"ruleGenInvalidT1RealEv": "T1 的 结算金额 满足:2 < 值",
|
"ruleGenInvalidT1RealEv": "T1(大奖)结算金额须满足:值 > 2",
|
||||||
"ruleGenInvalidT2RealEv": "T2 的 结算金额 满足:1 < 值 < 2",
|
"ruleGenInvalidT2RealEv": "T2(小赚)结算金额须满足:1 < 值 ≤ 2",
|
||||||
"ruleGenInvalidT3RealEv": "T3 的 结算金额 满足:0 < 值 < 1",
|
"ruleGenInvalidT3RealEv": "T3(抽水)结算金额须满足:0 < 值 ≤ 1",
|
||||||
"ruleGenInvalidT4RealEv": "T4 的 结算金额 满足:值 < 0",
|
"ruleGenInvalidT4RealEv": "T4(再来一次)结算金额须为 0",
|
||||||
"ruleGenInvalidT5RealEv": "T5「再来一次」的 结算金额 须为 0",
|
"ruleGenInvalidT5RealEv": "T5(惩罚)结算金额须满足:值 < 0",
|
||||||
"ruleGenT1Min": "T1 固定条数(顺/逆)",
|
"ruleGenT1Min": "T1 固定条数(顺/逆)",
|
||||||
"ruleGenT2Min": "T2 最少条数(顺/逆)",
|
"ruleGenT2Min": "T2 最少条数(顺/逆)",
|
||||||
"ruleGenT4Max": "T4 固定条数(顺/逆)",
|
"ruleGenT4Max": "T4 固定条数(顺/逆)",
|
||||||
|
|||||||
@@ -147,5 +147,15 @@ export default {
|
|||||||
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference',
|
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference',
|
||||||
data: params || {}
|
data: params || {}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建奖励对照(预览):不写库,返回将要生成的对照与权重预览
|
||||||
|
*/
|
||||||
|
createRewardReferencePreview(params?: Record<string, any>) {
|
||||||
|
return request.post<any>({
|
||||||
|
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReferencePreview',
|
||||||
|
data: params || {}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
<span class="tier-recommend-hint">{{ $t('page.configPage.tierRecommendRealEv') }}</span>
|
<span class="tier-recommend-hint">{{ $t('page.configPage.tierRecommendRealEv') }}</span>
|
||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="tierRecommend[tk]"
|
v-model="tierRecommend[tk]"
|
||||||
:disabled="tk === 'T5'"
|
:disabled="tk === 'T4'"
|
||||||
:step="0.1"
|
:step="0.1"
|
||||||
:precision="2"
|
:precision="2"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
@@ -131,7 +131,8 @@
|
|||||||
@change="handleRealEvChange(row)"
|
@change="handleRealEvChange(row)"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
:step="1"
|
:step="0.1"
|
||||||
|
:precision="2"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -147,19 +148,7 @@
|
|||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn :label="$t('page.configPage.colTier')" width="100" align="center">
|
<ElTableColumn :label="$t('page.configPage.colTier')" width="100" align="center">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<ElSelect
|
<span class="tier-readonly">{{ displayRowTier(row) }}</span>
|
||||||
v-model="row.tier"
|
|
||||||
:placeholder="$t('page.configPage.placeholderTierSelect')"
|
|
||||||
clearable
|
|
||||||
size="small"
|
|
||||||
class="full-width"
|
|
||||||
>
|
|
||||||
<ElOption label="T1" value="T1" />
|
|
||||||
<ElOption label="T2" value="T2" />
|
|
||||||
<ElOption label="T3" value="T3" />
|
|
||||||
<ElOption label="T4" value="T4" />
|
|
||||||
<ElOption label="T5" value="T5" />
|
|
||||||
</ElSelect>
|
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
<ElTableColumn
|
<ElTableColumn
|
||||||
@@ -246,7 +235,8 @@
|
|||||||
@change="handleRealEvChange(row)"
|
@change="handleRealEvChange(row)"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
size="small"
|
size="small"
|
||||||
:step="1"
|
:step="0.1"
|
||||||
|
:precision="2"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
@@ -423,6 +413,7 @@
|
|||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="ruleGenT4RealEv"
|
v-model="ruleGenT4RealEv"
|
||||||
class="rule-gen-input-num"
|
class="rule-gen-input-num"
|
||||||
|
:disabled="true"
|
||||||
:step="1"
|
:step="1"
|
||||||
:controls="ruleGenInputControls"
|
:controls="ruleGenInputControls"
|
||||||
:size="ruleGenInputSize"
|
:size="ruleGenInputSize"
|
||||||
@@ -451,8 +442,8 @@
|
|||||||
<ElInputNumber
|
<ElInputNumber
|
||||||
v-model="ruleGenT5RealEv"
|
v-model="ruleGenT5RealEv"
|
||||||
class="rule-gen-input-num"
|
class="rule-gen-input-num"
|
||||||
:disabled="true"
|
:step="0.1"
|
||||||
:step="1"
|
:precision="2"
|
||||||
:controls="ruleGenInputControls"
|
:controls="ruleGenInputControls"
|
||||||
:size="ruleGenInputSize"
|
:size="ruleGenInputSize"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
@@ -472,6 +463,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElDialog>
|
</ElDialog>
|
||||||
|
|
||||||
|
<CreateRewardReferencePreviewDialog
|
||||||
|
v-model="createRewardPreviewVisible"
|
||||||
|
:dept-id="filterDeptId"
|
||||||
|
@success="loadIndexList"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -483,10 +480,11 @@
|
|||||||
useInjectedChannelDept
|
useInjectedChannelDept
|
||||||
} from '@/composables/useChannelDeptScope'
|
} from '@/composables/useChannelDeptScope'
|
||||||
import { useWindowSize } from '@vueuse/core'
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import api from '../../api/reward_config/index'
|
import api from '../../api/reward_config/index'
|
||||||
import { checkAuth } from '@/utils/tool'
|
import { checkAuth } from '@/utils/tool'
|
||||||
|
import CreateRewardReferencePreviewDialog from './modules/create-reward-reference-preview-dialog.vue'
|
||||||
import {
|
import {
|
||||||
buildRowsFromTiers,
|
buildRowsFromTiers,
|
||||||
computeBoardFrequencies,
|
computeBoardFrequencies,
|
||||||
@@ -547,6 +545,7 @@
|
|||||||
const savingIndex = ref(false)
|
const savingIndex = ref(false)
|
||||||
const savingBigwin = ref(false)
|
const savingBigwin = ref(false)
|
||||||
const createRewardLoading = ref(false)
|
const createRewardLoading = ref(false)
|
||||||
|
const createRewardPreviewVisible = ref(false)
|
||||||
const ruleGenerateDialogVisible = ref(false)
|
const ruleGenerateDialogVisible = ref(false)
|
||||||
const ruleGenSubmitting = ref(false)
|
const ruleGenSubmitting = ref(false)
|
||||||
const ruleGenT1Fixed = ref(3)
|
const ruleGenT1Fixed = ref(3)
|
||||||
@@ -593,15 +592,34 @@
|
|||||||
row.ui_text_en = text
|
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) {
|
function applyRecommendRealEvToRow(row: IndexRow, tier: TierRecommendKey) {
|
||||||
const ev = tierRecommend[tier]
|
const ev = tierRecommend[tier]
|
||||||
row.real_ev = ev
|
row.real_ev = ev
|
||||||
if (tier === 'T5') {
|
if (tier === 'T4') {
|
||||||
row.ui_text = t('page.configPage.tierRecommendT5UiText')
|
row.ui_text = t('page.configPage.tierRecommendT4UiText')
|
||||||
row.ui_text_en = t('page.configPage.tierRecommendT5UiTextEn')
|
row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn')
|
||||||
} else {
|
} else {
|
||||||
applyRealEvDisplay(row, ev)
|
applyRealEvDisplay(row, ev)
|
||||||
}
|
}
|
||||||
|
syncRowTierFromRealEv(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 奖励索引 id 与后端 DiceRewardConfigLogic 一致:0~25 */
|
/** 奖励索引 id 与后端 DiceRewardConfigLogic 一致:0~25 */
|
||||||
@@ -655,16 +673,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleRealEvChange(row: IndexRow) {
|
function handleRealEvChange(row: IndexRow) {
|
||||||
const n =
|
const n = rowRealEvNumber(row)
|
||||||
typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev)
|
syncRowTierFromRealEv(row)
|
||||||
? row.real_ev
|
if (row.tier === 'T4') {
|
||||||
: Number(row.real_ev)
|
row.ui_text = t('page.configPage.tierRecommendT4UiText')
|
||||||
applyRealEvDisplay(row, n)
|
row.ui_text_en = t('page.configPage.tierRecommendT4UiTextEn')
|
||||||
if (canTierRecommend.value && autoMatchTierOnRealEv.value) {
|
} else {
|
||||||
const tier = inferTierFromRealEv(n)
|
applyRealEvDisplay(row, n)
|
||||||
if (tier !== '') {
|
|
||||||
row.tier = tier
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -674,7 +689,7 @@
|
|||||||
}
|
}
|
||||||
let count = 0
|
let count = 0
|
||||||
for (const row of indexRowsExcludeBigwin.value) {
|
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') {
|
if (tier === 'T1' || tier === 'T2' || tier === 'T3' || tier === 'T4' || tier === 'T5') {
|
||||||
applyRecommendRealEvToRow(row, tier)
|
applyRecommendRealEvToRow(row, tier)
|
||||||
count++
|
count++
|
||||||
@@ -693,13 +708,8 @@
|
|||||||
}
|
}
|
||||||
let count = 0
|
let count = 0
|
||||||
for (const row of indexRowsExcludeBigwin.value) {
|
for (const row of indexRowsExcludeBigwin.value) {
|
||||||
const n =
|
syncRowTierFromRealEv(row)
|
||||||
typeof row.real_ev === 'number' && !Number.isNaN(row.real_ev)
|
if (row.tier !== '') {
|
||||||
? row.real_ev
|
|
||||||
: Number(row.real_ev)
|
|
||||||
const tier = inferTierFromRealEv(n)
|
|
||||||
if (tier !== '') {
|
|
||||||
row.tier = tier
|
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -718,43 +728,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleCreateRewardReference() {
|
async function handleCreateRewardReference() {
|
||||||
try {
|
createRewardPreviewVisible.value = true
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractIndexList(res: unknown): Record<string, unknown>[] {
|
function extractIndexList(res: unknown): Record<string, unknown>[] {
|
||||||
@@ -779,6 +753,11 @@
|
|||||||
.list({ saiType: 'all', limit: 200, dept_id: filterDeptId.value })
|
.list({ saiType: 'all', limit: 200, dept_id: filterDeptId.value })
|
||||||
.then((res: unknown) => {
|
.then((res: unknown) => {
|
||||||
const rows = extractIndexList(res).map((r) => normalizeIndexRow(r))
|
const rows = extractIndexList(res).map((r) => normalizeIndexRow(r))
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.tier !== 'BIGWIN') {
|
||||||
|
syncRowTierFromRealEv(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
indexRows.value = rows
|
indexRows.value = rows
|
||||||
indexRowsSnapshot = rows.map((r) => ({ ...r }))
|
indexRowsSnapshot = rows.map((r) => ({ ...r }))
|
||||||
})
|
})
|
||||||
@@ -1024,6 +1003,9 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const toSave = indexRows.value.filter((r) => r.tier !== 'BIGWIN')
|
const toSave = indexRows.value.filter((r) => r.tier !== 'BIGWIN')
|
||||||
|
for (const row of toSave) {
|
||||||
|
syncRowTierFromRealEv(row)
|
||||||
|
}
|
||||||
savingIndex.value = true
|
savingIndex.value = true
|
||||||
try {
|
try {
|
||||||
const indexPayload = toSave.map((r) => ({
|
const indexPayload = toSave.map((r) => ({
|
||||||
@@ -1239,6 +1221,10 @@
|
|||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
max-width: 160px;
|
max-width: 160px;
|
||||||
}
|
}
|
||||||
|
.tier-readonly {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--el-text-color-primary);
|
||||||
|
}
|
||||||
.tier-recommend-actions {
|
.tier-recommend-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
<template>
|
||||||
|
<ElDialog
|
||||||
|
v-model="visible"
|
||||||
|
:title="$t('page.configPage.createRefPreviewTitle')"
|
||||||
|
width="980px"
|
||||||
|
align-center
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<div v-loading="loading" class="dialog-body">
|
||||||
|
<div class="preview-tip">
|
||||||
|
<div v-if="meta.unchanged" class="tip-line">
|
||||||
|
{{ $t('page.configPage.createRefPreviewTipUnchanged') }}
|
||||||
|
</div>
|
||||||
|
<div v-else class="tip-line">
|
||||||
|
{{ $t('page.configPage.createRefPreviewTipChanged') }}
|
||||||
|
</div>
|
||||||
|
<div class="tip-line" v-if="meta.skipped > 0">
|
||||||
|
{{ $t('page.configPage.createRefPreviewSkipped', { n: meta.skipped }) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ElTabs v-model="activeDirection" type="card" class="direction-tabs">
|
||||||
|
<ElTabPane :label="$t('page.configPage.createRefPreviewClockwise')" name="0">
|
||||||
|
<ElTabs v-model="activeTier" type="card" class="tier-tabs">
|
||||||
|
<ElTabPane v-for="t in tierKeys" :key="'cw-' + t" :label="t" :name="t">
|
||||||
|
<ElTable
|
||||||
|
:data="getTierItems(t, 0)"
|
||||||
|
:row-class-name="rowClassName"
|
||||||
|
border
|
||||||
|
size="small"
|
||||||
|
class="preview-table"
|
||||||
|
>
|
||||||
|
<ElTableColumn :label="$t('page.table.dicePoints')" prop="grid_number" width="120" align="center" />
|
||||||
|
<ElTableColumn label="start_index" prop="start_index" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.endIndex')" prop="id" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.tier')" prop="tier" width="90" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.displayText')" prop="ui_text" min-width="110" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.realEv')" prop="real_ev" width="110" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.remark')" prop="remark" min-width="140" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.weight')" prop="weight" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.configPage.createRefPreviewDiff')" min-width="220" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row?.diff_changed" class="diff-text">
|
||||||
|
{{ formatDiff(row) }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="diff-text diff-text-ok">{{ $t('page.configPage.createRefPreviewNoDiff') }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</ElTabPane>
|
||||||
|
</ElTabs>
|
||||||
|
</ElTabPane>
|
||||||
|
|
||||||
|
<ElTabPane :label="$t('page.configPage.createRefPreviewCounterclockwise')" name="1">
|
||||||
|
<ElTabs v-model="activeTier" type="card" class="tier-tabs">
|
||||||
|
<ElTabPane v-for="t in tierKeys" :key="'ccw-' + t" :label="t" :name="t">
|
||||||
|
<ElTable
|
||||||
|
:data="getTierItems(t, 1)"
|
||||||
|
:row-class-name="rowClassName"
|
||||||
|
border
|
||||||
|
size="small"
|
||||||
|
class="preview-table"
|
||||||
|
>
|
||||||
|
<ElTableColumn :label="$t('page.table.dicePoints')" prop="grid_number" width="120" align="center" />
|
||||||
|
<ElTableColumn label="start_index" prop="start_index" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.endIndex')" prop="id" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.tier')" prop="tier" width="90" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.table.displayText')" prop="ui_text" min-width="110" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.realEv')" prop="real_ev" width="110" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.remark')" prop="remark" min-width="140" align="center" show-overflow-tooltip />
|
||||||
|
<ElTableColumn :label="$t('page.table.weight')" prop="weight" width="110" align="center" />
|
||||||
|
<ElTableColumn :label="$t('page.configPage.createRefPreviewDiff')" min-width="220" align="center">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<span v-if="row?.diff_changed" class="diff-text">
|
||||||
|
{{ formatDiff(row) }}
|
||||||
|
</span>
|
||||||
|
<span v-else class="diff-text diff-text-ok">{{ $t('page.configPage.createRefPreviewNoDiff') }}</span>
|
||||||
|
</template>
|
||||||
|
</ElTableColumn>
|
||||||
|
</ElTable>
|
||||||
|
</ElTabPane>
|
||||||
|
</ElTabs>
|
||||||
|
</ElTabPane>
|
||||||
|
</ElTabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<ElButton @click="refreshPreview" :loading="loading" v-ripple>{{
|
||||||
|
$t('page.configPage.createRefPreviewRefresh')
|
||||||
|
}}</ElButton>
|
||||||
|
<ElButton @click="handleClose" v-ripple>{{ $t('common.cancel') }}</ElButton>
|
||||||
|
<ElButton type="primary" :loading="submitting" @click="handleImport" v-ripple>{{
|
||||||
|
$t('page.configPage.createRefPreviewImport')
|
||||||
|
}}</ElButton>
|
||||||
|
</template>
|
||||||
|
</ElDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import api from '../../../api/reward_config/index'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const TIER_KEYS = ['T1', 'T2', 'T3', 'T4', 'T5'] as const
|
||||||
|
const tierKeys = TIER_KEYS
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
modelValue: boolean
|
||||||
|
deptId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: false,
|
||||||
|
deptId: 0
|
||||||
|
})
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const visible = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (v) => emit('update:modelValue', v)
|
||||||
|
})
|
||||||
|
|
||||||
|
const activeDirection = ref<'0' | '1'>('0')
|
||||||
|
const activeTier = ref('T1')
|
||||||
|
const loading = ref(false)
|
||||||
|
const submitting = ref(false)
|
||||||
|
|
||||||
|
const meta = reactive<{ unchanged: boolean; skipped: number }>({ unchanged: false, skipped: 0 })
|
||||||
|
const preview = ref<Record<string, { 0: any[]; 1: any[] }>>({})
|
||||||
|
|
||||||
|
function rowClassName({ row }: { row: any }): string {
|
||||||
|
return row?.diff_changed ? 'row-diff' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDiff(row: any): string {
|
||||||
|
if (!row) return ''
|
||||||
|
const oldStart = row.old_start_index != null ? String(row.old_start_index) : '-'
|
||||||
|
const oldEnd = row.old_end_index != null ? String(row.old_end_index) : '-'
|
||||||
|
const oldTier = row.old_tier != null ? String(row.old_tier) : '-'
|
||||||
|
const newStart = row.start_index != null ? String(row.start_index) : '-'
|
||||||
|
const newEnd = row.id != null ? String(row.id) : '-'
|
||||||
|
const newTier = row.tier != null ? String(row.tier) : '-'
|
||||||
|
return `${oldStart}/${oldEnd}/${oldTier} → ${newStart}/${newEnd}/${newTier}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTierItems(tier: string, direction: 0 | 1): any[] {
|
||||||
|
const tierData = preview.value?.[tier]
|
||||||
|
if (!tierData) return []
|
||||||
|
const rows = direction === 0 ? tierData[0] : tierData[1]
|
||||||
|
return Array.isArray(rows) ? rows : []
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshPreview() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await api.createRewardReferencePreview({ dept_id: props.deptId })
|
||||||
|
const data = res?.data ?? res
|
||||||
|
meta.unchanged = Boolean(data?.unchanged)
|
||||||
|
meta.skipped = Number(data?.skipped ?? 0)
|
||||||
|
preview.value = (data?.preview ?? {}) as any
|
||||||
|
activeDirection.value = '0'
|
||||||
|
activeTier.value = 'T1'
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message ?? 'preview failed')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleImport() {
|
||||||
|
submitting.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await api.createRewardReference({ dept_id: props.deptId })
|
||||||
|
const data = res?.data ?? res
|
||||||
|
if (data?.unchanged) {
|
||||||
|
ElMessage.success(t('page.configPage.createRefPreviewImportNoop'))
|
||||||
|
} else {
|
||||||
|
ElMessage.success(t('page.configPage.createRefPreviewImportOk'))
|
||||||
|
}
|
||||||
|
emit('success')
|
||||||
|
handleClose()
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.message ?? 'import failed')
|
||||||
|
} finally {
|
||||||
|
submitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClose() {
|
||||||
|
visible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(open) => {
|
||||||
|
if (open) {
|
||||||
|
refreshPreview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dialog-body {
|
||||||
|
min-height: 240px;
|
||||||
|
}
|
||||||
|
.preview-tip {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
background: var(--el-fill-color-light);
|
||||||
|
border-radius: 6px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.tip-line + .tip-line {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
.preview-table {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
:deep(.row-diff td) {
|
||||||
|
background: var(--el-color-warning-light-9);
|
||||||
|
}
|
||||||
|
.diff-text {
|
||||||
|
color: var(--el-text-color-regular);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.2;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.diff-text-ok {
|
||||||
|
color: var(--el-text-color-secondary);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
@@ -58,38 +58,36 @@ export const DEFAULT_TIER_REAL_EV_STANDARDS: TierRealEvStandards = {
|
|||||||
T1: 3,
|
T1: 3,
|
||||||
T2: 1.5,
|
T2: 1.5,
|
||||||
T3: 0.5,
|
T3: 0.5,
|
||||||
T4: -0.4,
|
T4: 0,
|
||||||
T5: 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 | '' {
|
export function inferTierFromRealEv(realEv: number): IndexTier | '' {
|
||||||
if (!Number.isFinite(realEv)) {
|
if (!Number.isFinite(realEv)) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
if (realEv === 0) {
|
|
||||||
return 'T5'
|
|
||||||
}
|
|
||||||
if (realEv < 0) {
|
|
||||||
return 'T4'
|
|
||||||
}
|
|
||||||
if (realEv > 2) {
|
if (realEv > 2) {
|
||||||
return 'T1'
|
return 'T1'
|
||||||
}
|
}
|
||||||
if (realEv > 1 && realEv < 2) {
|
if (realEv > 1 && realEv <= 2) {
|
||||||
return 'T2'
|
return 'T2'
|
||||||
}
|
}
|
||||||
if (realEv > 0 && realEv < 1) {
|
if (realEv > 0 && realEv <= 1) {
|
||||||
return 'T3'
|
return 'T3'
|
||||||
}
|
}
|
||||||
if (realEv === 1) {
|
if (realEv === 0) {
|
||||||
return 'T2'
|
return 'T4'
|
||||||
}
|
}
|
||||||
if (realEv === 2) {
|
if (realEv < 0) {
|
||||||
return 'T1'
|
return 'T5'
|
||||||
}
|
}
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
@@ -101,16 +99,16 @@ export function validateTierRealEvStandards(s: TierRealEvStandards): string | nu
|
|||||||
if (!Number.isFinite(s.T1) || !(s.T1 > 2)) {
|
if (!Number.isFinite(s.T1) || !(s.T1 > 2)) {
|
||||||
return 'ruleGenInvalidT1RealEv'
|
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'
|
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'
|
return 'ruleGenInvalidT3RealEv'
|
||||||
}
|
}
|
||||||
if (!Number.isFinite(s.T4) || !(s.T4 < 0)) {
|
if (!Number.isFinite(s.T4) || s.T4 !== 0) {
|
||||||
return 'ruleGenInvalidT4RealEv'
|
return 'ruleGenInvalidT4RealEv'
|
||||||
}
|
}
|
||||||
if (!Number.isFinite(s.T5) || s.T5 !== 0) {
|
if (!Number.isFinite(s.T5) || s.T5 >= 0) {
|
||||||
return 'ruleGenInvalidT5RealEv'
|
return 'ruleGenInvalidT5RealEv'
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@@ -307,7 +305,7 @@ function uiTextByTierWhenStandards(
|
|||||||
tier: IndexTier,
|
tier: IndexTier,
|
||||||
realEv: number
|
realEv: number
|
||||||
): { ui_text: string; ui_text_en: string } {
|
): { ui_text: string; ui_text_en: string } {
|
||||||
if (tier === 'T5') {
|
if (tier === 'T4') {
|
||||||
return { ui_text: '再来一次', ui_text_en: 'Once again' }
|
return { ui_text: '再来一次', ui_text_en: 'Once again' }
|
||||||
}
|
}
|
||||||
const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv)
|
const value = Number.isFinite(realEv) ? realEv.toFixed(2) : String(realEv)
|
||||||
@@ -379,13 +377,13 @@ export function buildRowsFromTiers(
|
|||||||
const f = uiTextByTierWhenStandards(tier, real_ev)
|
const f = uiTextByTierWhenStandards(tier, real_ev)
|
||||||
ui_text = f.ui_text
|
ui_text = f.ui_text
|
||||||
ui_text_en = f.ui_text_en
|
ui_text_en = f.ui_text_en
|
||||||
remark = '惩罚'
|
remark = '再来一次'
|
||||||
} else {
|
} else {
|
||||||
real_ev = standards.T5
|
real_ev = standards.T5
|
||||||
const f = uiTextByTierWhenStandards(tier, real_ev)
|
const f = uiTextByTierWhenStandards(tier, real_ev)
|
||||||
ui_text = f.ui_text
|
ui_text = f.ui_text
|
||||||
ui_text_en = f.ui_text_en
|
ui_text_en = f.ui_text_en
|
||||||
remark = '前端需要在播放一次动画(特殊)'
|
remark = '惩罚'
|
||||||
}
|
}
|
||||||
} else if (tier === 'T1') {
|
} else if (tier === 'T1') {
|
||||||
real_ev = 101 + ((id * 17 + grid_number * 3) % 398)
|
real_ev = 101 + ((id * 17 + grid_number * 3) % 398)
|
||||||
@@ -417,18 +415,17 @@ export function buildRowsFromTiers(
|
|||||||
ui_text_en = f.ui_text_en
|
ui_text_en = f.ui_text_en
|
||||||
remark = '抽水'
|
remark = '抽水'
|
||||||
} else if (tier === 'T4') {
|
} else if (tier === 'T4') {
|
||||||
|
real_ev = 0
|
||||||
|
ui_text = '再来一次'
|
||||||
|
ui_text_en = 'Once again'
|
||||||
|
remark = '再来一次'
|
||||||
|
} else {
|
||||||
t4Seq++
|
t4Seq++
|
||||||
real_ev = -101 - t4Seq * 15
|
real_ev = -101 - t4Seq * 15
|
||||||
const f = uiTextFromRealEv(real_ev)
|
const f = uiTextFromRealEv(real_ev)
|
||||||
ui_text = f.ui_text
|
ui_text = f.ui_text
|
||||||
ui_text_en = f.ui_text_en
|
ui_text_en = f.ui_text_en
|
||||||
remark = '惩罚'
|
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({
|
rows.push({
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -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。
|
* 权重配比测试:仅模拟落点统计,不创建游玩记录。按当前配置在内存中模拟 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 概率
|
* @param Request $request test_count: 100|500|1000, save_record: bool, lottery_config_id: int|null 奖池配置ID,用于设定 T1-T5 概率
|
||||||
|
|||||||
@@ -327,6 +327,79 @@ class DiceRewardLogic
|
|||||||
private const GRID_NUMBER_MIN = 5;
|
private const GRID_NUMBER_MIN = 5;
|
||||||
private const GRID_NUMBER_MAX = 30;
|
private const GRID_NUMBER_MAX = 30;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预览:按当前 dice_reward_config 计算将要生成的 dice_reward(不写库)
|
||||||
|
* 若当前 dice_reward 与计算结果完全一致,则标记 unchanged=true,并返回现有权重(导入时将复用旧权重)
|
||||||
|
*
|
||||||
|
* @return array{unchanged: bool, skipped: int, preview: array<string, array{0: array, 1: 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 生成记录。
|
* 创建奖励对照:先清空 dice_reward 表,再按两种方向为点数 5-30 生成记录。
|
||||||
*
|
*
|
||||||
@@ -352,10 +425,76 @@ class DiceRewardLogic
|
|||||||
* @throws ApiException
|
* @throws ApiException
|
||||||
*/
|
*/
|
||||||
public function createRewardReferenceFromConfig(?int $deptId = null): array
|
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<int, array<string, mixed>>
|
||||||
|
*/
|
||||||
|
private function loadConfigListForReference(?int &$deptId): array
|
||||||
{
|
{
|
||||||
$configQuery = DiceRewardConfig::order('id', 'asc');
|
$configQuery = DiceRewardConfig::order('id', 'asc');
|
||||||
if ($deptId === null || $deptId === \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
|
if ($deptId === null || $deptId === AdminScopeHelper::DEFAULT_TEMPLATE_DEPT) {
|
||||||
$templateId = \app\dice\helper\AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
|
$templateId = AdminScopeHelper::DEFAULT_TEMPLATE_DEPT;
|
||||||
$configQuery->where(function ($q) use ($templateId) {
|
$configQuery->where(function ($q) use ($templateId) {
|
||||||
$q->where('dept_id', $templateId)->whereOr('dept_id', 'null');
|
$q->where('dept_id', $templateId)->whereOr('dept_id', 'null');
|
||||||
});
|
});
|
||||||
@@ -367,25 +506,25 @@ class DiceRewardLogic
|
|||||||
if (empty($list)) {
|
if (empty($list)) {
|
||||||
throw new ApiException('Reward config is empty, please maintain dice_reward_config first');
|
throw new ApiException('Reward config is empty, please maintain dice_reward_config first');
|
||||||
}
|
}
|
||||||
$configCount = count($list);
|
if (count($list) < self::BOARD_SIZE) {
|
||||||
if ($configCount < self::BOARD_SIZE) {
|
|
||||||
throw new ApiException(
|
throw new ApiException(
|
||||||
\app\api\util\ApiLang::translateParams(
|
\app\api\util\ApiLang::translateParams(
|
||||||
'奖励配置需覆盖 26 个格位(id 0-25 或 1-26),当前仅 %s 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照',
|
'奖励配置需覆盖 26 个格位(id 0-25 或 1-26),当前仅 %s 条,无法完整生成 5-30 共26个点数、顺时针与逆时针的奖励对照',
|
||||||
[$configCount]
|
[count($list)]
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
$table = (new DiceReward())->getTable();
|
/**
|
||||||
if ($deptId === null) {
|
* 计算 5-30 两个方向的对照行(不含权重)
|
||||||
Db::table($table)->whereNull('dept_id')->delete();
|
* @param array<int, array<string, mixed>> $list
|
||||||
} else {
|
* @return array{rows: array<int, array<string, mixed>>, skipped: int}
|
||||||
Db::table($table)->where('dept_id', $deptId)->delete();
|
*/
|
||||||
}
|
private function computeReferenceRowsFromConfigList(array $list, ?int $deptId): array
|
||||||
DiceReward::refreshCache($deptId ?? AdminScopeHelper::DEFAULT_TEMPLATE_DEPT);
|
{
|
||||||
|
// 按 id 排序后,盘面位置 0..25 对应 $list[$pos]
|
||||||
// 按 id 排序后,盘面位置 0..25 对应 $list[$pos],避免 config.id 非 0-25/1-26 时取模结果找不到
|
|
||||||
$gridToPosition = [];
|
$gridToPosition = [];
|
||||||
foreach ($list as $pos => $row) {
|
foreach ($list as $pos => $row) {
|
||||||
$gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
$gn = isset($row['grid_number']) ? (int) $row['grid_number'] : 0;
|
||||||
@@ -394,12 +533,8 @@ class DiceRewardLogic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$createdCw = 0;
|
$rows = [];
|
||||||
$createdCcw = 0;
|
|
||||||
$updatedCw = 0;
|
|
||||||
$updatedCcw = 0;
|
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
|
|
||||||
for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) {
|
for ($gridNumber = self::GRID_NUMBER_MIN; $gridNumber <= self::GRID_NUMBER_MAX; $gridNumber++) {
|
||||||
if (!isset($gridToPosition[$gridNumber])) {
|
if (!isset($gridToPosition[$gridNumber])) {
|
||||||
$skipped++;
|
$skipped++;
|
||||||
@@ -414,115 +549,131 @@ class DiceRewardLogic
|
|||||||
|
|
||||||
$configCw = $list[$endPosCw] ?? null;
|
$configCw = $list[$endPosCw] ?? null;
|
||||||
$configCcw = $list[$endPosCcw] ?? 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) {
|
if ($configCw !== null) {
|
||||||
$tier = isset($configCw['tier']) ? trim((string) $configCw['tier']) : '';
|
$tier = isset($configCw['tier']) ? trim((string) $configCw['tier']) : '';
|
||||||
if ($tier !== '') {
|
if ($tier !== '') {
|
||||||
// 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重)
|
$rows[] = [
|
||||||
$weightCw = isset($configCw['weight']) && $configCw['weight'] !== null
|
|
||||||
? $configCw['weight']
|
|
||||||
: self::WEIGHT_MIN;
|
|
||||||
$payloadCw = [
|
|
||||||
'tier' => $tier,
|
'tier' => $tier,
|
||||||
'weight' => $weightCw,
|
'direction' => DiceReward::DIRECTION_CLOCKWISE,
|
||||||
|
'weight' => self::WEIGHT_MIN,
|
||||||
'grid_number' => $gridNumber,
|
'grid_number' => $gridNumber,
|
||||||
'start_index' => $startId,
|
'start_index' => $startId,
|
||||||
'end_index' => $endIdCw,
|
'end_index' => isset($configCw['id']) ? (int) $configCw['id'] : 0,
|
||||||
'ui_text' => $configCw['ui_text'] ?? '',
|
'ui_text' => $configCw['ui_text'] ?? '',
|
||||||
'real_ev' => $configCw['real_ev'] ?? null,
|
'real_ev' => $configCw['real_ev'] ?? null,
|
||||||
'remark' => $configCw['remark'] ?? '',
|
'remark' => $configCw['remark'] ?? '',
|
||||||
'type' => isset($configCw['type']) ? (int) $configCw['type'] : 0,
|
'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) {
|
if ($configCcw !== null) {
|
||||||
$tier = isset($configCcw['tier']) ? trim((string) $configCcw['tier']) : '';
|
$tier = isset($configCcw['tier']) ? trim((string) $configCcw['tier']) : '';
|
||||||
if ($tier !== '') {
|
if ($tier !== '') {
|
||||||
// 使用对应奖励配置的 weight 作为格子权重(若未配置则退回最小权重)
|
$rows[] = [
|
||||||
$weightCcw = isset($configCcw['weight']) && $configCcw['weight'] !== null
|
|
||||||
? $configCcw['weight']
|
|
||||||
: self::WEIGHT_MIN;
|
|
||||||
$payloadCcw = [
|
|
||||||
'tier' => $tier,
|
'tier' => $tier,
|
||||||
'weight' => $weightCcw,
|
'direction' => DiceReward::DIRECTION_COUNTERCLOCKWISE,
|
||||||
|
'weight' => self::WEIGHT_MIN,
|
||||||
'grid_number' => $gridNumber,
|
'grid_number' => $gridNumber,
|
||||||
'start_index' => $startId,
|
'start_index' => $startId,
|
||||||
'end_index' => $endIdCcw,
|
'end_index' => isset($configCcw['id']) ? (int) $configCcw['id'] : 0,
|
||||||
'ui_text' => $configCcw['ui_text'] ?? '',
|
'ui_text' => $configCcw['ui_text'] ?? '',
|
||||||
'real_ev' => $configCcw['real_ev'] ?? null,
|
'real_ev' => $configCcw['real_ev'] ?? null,
|
||||||
'remark' => $configCcw['remark'] ?? '',
|
'remark' => $configCcw['remark'] ?? '',
|
||||||
'type' => isset($configCcw['type']) ? (int) $configCcw['type'] : 0,
|
'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 [
|
* 读出当前 dice_reward(用于对比/复用权重)。key = "direction:grid_number"
|
||||||
'created_clockwise' => $createdCw,
|
* @return array<string, array<string, mixed>>
|
||||||
'created_counterclockwise' => $createdCcw,
|
*/
|
||||||
'updated_clockwise' => $updatedCw,
|
private function loadExistingRewardRowsForReference(?int $deptId): array
|
||||||
'updated_counterclockwise' => $updatedCcw,
|
{
|
||||||
'skipped' => $skipped,
|
$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<int, array<string, mixed>> $computedRows
|
||||||
|
* @param array<string, array<string, mixed>> $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<int, array<string, mixed>> $rows
|
||||||
|
* @return array<string, array{0: array, 1: 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(惩罚)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
@@ -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/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/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/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']);
|
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/game/DiceGame', \app\dice\controller\game\DiceGameController::class);
|
||||||
fastRoute('dice/ante_config/DiceAnteConfig', \app\dice\controller\ante_config\DiceAnteConfigController::class);
|
fastRoute('dice/ante_config/DiceAnteConfig', \app\dice\controller\ante_config\DiceAnteConfigController::class);
|
||||||
|
|||||||
Reference in New Issue
Block a user