1.优化奖励配置设置
This commit is contained in:
@@ -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)",
|
||||
|
||||
@@ -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 固定条数(顺/逆)",
|
||||
|
||||
@@ -147,5 +147,15 @@ export default {
|
||||
url: '/core/dice/reward_config/DiceRewardConfig/createRewardReference',
|
||||
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>
|
||||
<ElInputNumber
|
||||
v-model="tierRecommend[tk]"
|
||||
:disabled="tk === 'T5'"
|
||||
:disabled="tk === 'T4'"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
controls-position="right"
|
||||
@@ -131,7 +131,8 @@
|
||||
@change="handleRealEvChange(row)"
|
||||
controls-position="right"
|
||||
size="small"
|
||||
:step="1"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
class="full-width"
|
||||
/>
|
||||
</template>
|
||||
@@ -147,19 +148,7 @@
|
||||
</ElTableColumn>
|
||||
<ElTableColumn :label="$t('page.configPage.colTier')" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<ElSelect
|
||||
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>
|
||||
<span class="tier-readonly">{{ displayRowTier(row) }}</span>
|
||||
</template>
|
||||
</ElTableColumn>
|
||||
<ElTableColumn
|
||||
@@ -246,7 +235,8 @@
|
||||
@change="handleRealEvChange(row)"
|
||||
controls-position="right"
|
||||
size="small"
|
||||
:step="1"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
class="full-width"
|
||||
/>
|
||||
</template>
|
||||
@@ -423,6 +413,7 @@
|
||||
<ElInputNumber
|
||||
v-model="ruleGenT4RealEv"
|
||||
class="rule-gen-input-num"
|
||||
:disabled="true"
|
||||
:step="1"
|
||||
:controls="ruleGenInputControls"
|
||||
:size="ruleGenInputSize"
|
||||
@@ -451,8 +442,8 @@
|
||||
<ElInputNumber
|
||||
v-model="ruleGenT5RealEv"
|
||||
class="rule-gen-input-num"
|
||||
:disabled="true"
|
||||
:step="1"
|
||||
:step="0.1"
|
||||
:precision="2"
|
||||
:controls="ruleGenInputControls"
|
||||
:size="ruleGenInputSize"
|
||||
controls-position="right"
|
||||
@@ -472,6 +463,12 @@
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<CreateRewardReferencePreviewDialog
|
||||
v-model="createRewardPreviewVisible"
|
||||
:dept-id="filterDeptId"
|
||||
@success="loadIndexList"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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<string, unknown>[] {
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user