修改河南项目的管理后台ui
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 当前水位快照 + 波段表策略 + 本轮生效的随机数区间
|
||||
export function getWatermarkInfo() {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存水位波段表策略(后端带校验:区间合法 + 水位越高随机数越小)
|
||||
export function saveWatermarkSetting(data) {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/setting',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 注水 / 抽水(type 1注水 2抽水;调整原因必填)
|
||||
export function adjustWatermark(data) {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/adjust',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 重置人工调整(插入抵消日志,SUM(delta) 归零,历史保留)
|
||||
export function resetWatermarkAdjust(data) {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/adjustReset',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 调整历史(分页)
|
||||
export function getWatermarkLogs(params) {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/adjustLog',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
// 按历史记录全量重算累计下注/返奖(纠正 Redis 计数漂移)
|
||||
export function recalcWatermark() {
|
||||
return request({
|
||||
url: '/admin/upgradeWatermark/recalc',
|
||||
method: 'post'
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,530 @@
|
||||
<template>
|
||||
<div class="app-container reward-pool-page">
|
||||
<div class="page-head">
|
||||
<div class="page-head-row">
|
||||
<el-button type="text" size="mini" :icon="showGuide ? 'el-icon-arrow-up' : 'el-icon-question'" @click="showGuide = !showGuide">
|
||||
{{ showGuide ? '收起说明' : '配置说明' }}
|
||||
</el-button>
|
||||
</div>
|
||||
<p class="head-tip">
|
||||
水位是<b>整个幸运升级</b>的全局指标:水位 = (Σ下注 − Σ返奖 + 人工调整) / Σ下注,<b>排除白号</b>(白号的量单独展示,不进水位口径)。
|
||||
只需设一个<b>目标水位</b>:水位高于它 → 随机数中心下移(放水);低于它 → 上移(收紧),长期会自动收敛回这个值。
|
||||
</p>
|
||||
|
||||
<div v-show="showGuide" class="guide-panel">
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-info" /> 随机数是什么</h5>
|
||||
<ul>
|
||||
<li>随机数的单位是「饰品价的百分比」,等于本轮出货所需的累计进度。</li>
|
||||
<li>理论 RTP = 100 / 随机数。随机数 125 → 玩家平均要投到 1.25 倍原价才出。</li>
|
||||
<li>实测 RTP 会比名义值低 15%~20%(判定最后一拉整段计入、溢出丢弃),所以<b>以页面上的实测 RTP 为准</b>。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-setting" /> 目标水位怎么定</h5>
|
||||
<ul>
|
||||
<li>目标水位 = 平台希望长期维持的净收益比例。定 30% ⇒ 长期平均 RTP 约 70%。</li>
|
||||
<li>系统会自动把水位拉回这个值(水位高就放水、低就收紧),<b>不用手配随机数</b>。</li>
|
||||
<li>水位从 0 起步,离目标值较远时前几轮会明显偏放水或偏收紧,属正常的收敛过程。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-s-grid" /> 饰品的随机数区间</h5>
|
||||
<ul>
|
||||
<li>随机数的取值范围由<b>每个饰品自己的区间</b>限定(在「幸运升级 → 饰品列表」里配)。</li>
|
||||
<li><b>整个区间始终都可能出</b>,水位只改变概率重心:水位高 → 靠下限的数出现得更频繁;
|
||||
水位正好等于目标时是均匀分布(区间内等概率)。</li>
|
||||
<li>⚠️ <b>区间还限定了水位能调到的范围</b>(硬约束):区间越窄、下限越高,能开的水位上限越低。
|
||||
目标水位必须落在所有在售饰品区间都能覆盖的范围内,否则水位会停在附近而不是目标值。</li>
|
||||
<li>区间越宽,单轮波动越大、越不容易被玩家归纳出规律。</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="guide-col">
|
||||
<h5><i class="el-icon-s-operation" /> 注水 / 抽水</h5>
|
||||
<ul>
|
||||
<li>水位从 0 起步,上线初期想立刻把水位拉到目标值就注水。</li>
|
||||
<li>调整原因必填,每次调整都会写入历史(可审计)。</li>
|
||||
<li>「重置调整」只把人工调整归零,不影响真实流水;历史记录全部保留。</li>
|
||||
<li>累计量走 Redis 增量计数,怀疑漂移时点「按历史重算」从流水全量重算。</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-row :gutter="16" class="stat-row">
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-terra"><i class="el-icon-odometer" /></div>
|
||||
<div class="sc-body">
|
||||
<small>当前水位</small>
|
||||
<h3 :style="{ color: info.watermark < 0 ? '#a4485a' : '#9a5c35' }">{{ pct(info.watermark) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-grass"><i class="el-icon-data-line" /></div>
|
||||
<div class="sc-body">
|
||||
<small>实测 RTP(排除白号)</small>
|
||||
<h3 :style="{ color: info.rtp > 100 ? '#a4485a' : '#457450' }">{{ pct(info.rtp) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-daisy"><i class="el-icon-coin" /></div>
|
||||
<div class="sc-body">
|
||||
<small>累计下注(排除白号)</small>
|
||||
<h3>{{ fmt(info.totalBet) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="6">
|
||||
<div class="stat-card">
|
||||
<div class="sc-icon sc-rose"><i class="el-icon-present" /></div>
|
||||
<div class="sc-body">
|
||||
<small>累计返奖(排除白号)</small>
|
||||
<h3>{{ fmt(info.totalWin) }}</h3>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div class="band-live">
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">目标水位</span>
|
||||
<b class="bl-value">{{ info.targetWatermark == null ? '—' : Number(info.targetWatermark).toFixed(1) + '%' }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">离目标</span>
|
||||
<b class="bl-value" :style="{ color: gapColor }">{{ gapText }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">下一轮随机数</span>
|
||||
<b class="bl-value" style="color:#9a5c35">{{ info.centerNow == null ? '—' : Number(info.centerNow).toFixed(1) }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">人工调整</span>
|
||||
<b class="bl-value" :style="{ color: Number(info.adjust) < 0 ? '#a4485a' : (Number(info.adjust) > 0 ? '#457450' : '#4f4130') }">{{ fmt(info.adjust) }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">实测标定系数</span>
|
||||
<b class="bl-value">{{ info.calibration == null ? '样本不足' : info.calibration }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">白号(不进水位口径)</span>
|
||||
<b class="bl-value" style="color:#8f6418">下注 {{ fmt(info.blankBet) }} / 返奖 {{ fmt(info.blankWin) }}</b>
|
||||
</span>
|
||||
<span class="bl-item">
|
||||
<span class="bl-label">策略开关</span>
|
||||
<el-tag :type="info.enabled ? 'success' : 'info'" size="mini">{{ info.enabled ? '水位驱动已启用' : '已关闭(回退固定区间)' }}</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-card shadow="never" class="op-card">
|
||||
<div slot="header">
|
||||
<b>水位策略</b>
|
||||
<span class="sub-tip">改完<b>立即生效</b>,不需要点保存</span>
|
||||
</div>
|
||||
<el-form :inline="true" @submit.native.prevent>
|
||||
<el-form-item>
|
||||
<span class="fld-label">目标水位</span>
|
||||
<el-input-number
|
||||
v-model="form.targetWatermark"
|
||||
:precision="1"
|
||||
:step="1"
|
||||
:min="-50"
|
||||
:max="95"
|
||||
:controls="false"
|
||||
style="width:140px"
|
||||
placeholder="如 30"
|
||||
@change="autoSaveSetting"
|
||||
/>
|
||||
<span class="fld-unit">%</span>
|
||||
<span class="fld-note" style="margin-left:10px">平台希望长期维持的净收益比例;定 30% ⇒ 长期平均 RTP ≈ 70%</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div class="setting-flags">
|
||||
<span class="fld-label">策略开关</span>
|
||||
<el-switch v-model="form.enabled" active-color="#457450" inactive-color="#a4485a" @change="autoSaveSetting" />
|
||||
<span class="fld-note" style="margin-left:8px">
|
||||
{{ form.enabled ? '按水位动态出随机数' : '关闭后回退到每个饰品的固定区间(旧行为)' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="range-calc">
|
||||
<span class="fld-label">随机数区间</span>
|
||||
<el-input-number v-model="form.rangeMin" :precision="0" :min="1" :controls="false" style="width:84px" placeholder="下限" />
|
||||
<span class="rc-sep">~</span>
|
||||
<el-input-number v-model="form.rangeMax" :precision="0" :min="2" :controls="false" style="width:84px" placeholder="上限" />
|
||||
<span class="rc-arrow">⇒</span>
|
||||
<span class="fld-label">可维持水位</span>
|
||||
<b class="rc-result" :style="{ color: rangeResult.color }">{{ rangeResult.text }}</b>
|
||||
<el-popover placement="top" width="440" trigger="hover" popper-class="wm-help">
|
||||
<div slot="reference" class="help-ref">?</div>
|
||||
<div class="wm-help-body">
|
||||
<p><b>随机数区间</b>:该饰品每轮随机数的允许取值范围。整个区间<b>始终都可能出</b>,
|
||||
水位只改变概率重心 —— 水位高于目标时,靠下限的数出现得更频繁。</p>
|
||||
<p><b>为什么它决定了水位范围</b>:随机数的中心由目标水位自动推算,
|
||||
但推算出的中心只能落在「下限 + 10% 区间宽度」到「上限 − 10% 区间宽度」之间(超出会被夹住)。
|
||||
把这两个端点代回 <code>水位 = 1 − 100 ÷ (标定系数 × 随机数)</code>,就是左边显示的可维持范围。</p>
|
||||
<p><b>怎么用</b>:目标水位要落在<b>各在售饰品区间都能覆盖</b>的范围内,否则水位会停在附近而不是目标值。
|
||||
想把水位开得更高,把区间上限抬高即可 —— 上限 160 时水位最多约 45%,想到 60% 上限要到 210 以上。
|
||||
(区间在「幸运升级 → 饰品列表」里按身份分别配置。)</p>
|
||||
</div>
|
||||
</el-popover>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" class="op-card">
|
||||
<div slot="header">
|
||||
<b>人工注水 / 抽水</b>
|
||||
<span class="sub-tip">注水 = 抬高水位(更容易出货);抽水 = 降低水位(收紧)</span>
|
||||
</div>
|
||||
<el-form :inline="true" @submit.native.prevent>
|
||||
<el-form-item>
|
||||
<span class="fld-label">金额</span>
|
||||
<el-input-number v-model="form.amount" :min="0.01" :precision="2" :step="100" :controls="false" style="width:140px" placeholder="如 1000" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<span class="fld-label">调整原因<span class="req">*</span></span>
|
||||
<el-input v-model="form.remark" maxlength="100" placeholder="必填,如:上线初期补水" style="width:220px" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-popconfirm :title="'确认注水 ' + fmt(form.amount) + '?水位将上升。'" @confirm="submitAdjust(1)">
|
||||
<el-button slot="reference" type="success" plain icon="el-icon-top" :loading="adjusting" :disabled="!form.amount">注水</el-button>
|
||||
</el-popconfirm>
|
||||
<el-popconfirm :title="'确认抽水 ' + fmt(form.amount) + '?水位将下降。'" @confirm="submitAdjust(2)">
|
||||
<el-button slot="reference" type="danger" plain icon="el-icon-bottom" :loading="adjusting" :disabled="!form.amount" style="margin-left:8px">抽水</el-button>
|
||||
</el-popconfirm>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-popconfirm title="把人工调整累计值归零(只清人工部分,不动真实流水;历史记录保留)?" @confirm="submitReset">
|
||||
<el-button slot="reference" type="warning" plain icon="el-icon-refresh-left" :loading="resetting">重置调整</el-button>
|
||||
</el-popconfirm>
|
||||
<el-popconfirm title="按 tt_upgrade_record 全量重算累计下注/返奖,覆盖 Redis 计数(用于纠正漂移)?" @confirm="submitRecalc">
|
||||
<el-button slot="reference" plain icon="el-icon-refresh" :loading="recalcing" style="margin-left:8px">按历史重算</el-button>
|
||||
</el-popconfirm>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<div class="section-head">
|
||||
<h4 style="margin:0">调整历史</h4>
|
||||
<el-button size="mini" type="text" icon="el-icon-refresh" @click="loadLogs">刷新</el-button>
|
||||
</div>
|
||||
<el-table v-loading="loading" :data="logs" border size="small">
|
||||
<el-table-column prop="createTime" label="时间" min-width="150" align="center" />
|
||||
<el-table-column label="类型" width="80" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-tag :type="scope.row.type === 1 ? 'success' : (scope.row.type === 2 ? 'danger' : 'warning')" size="mini">
|
||||
{{ scope.row.type === 1 ? '注水' : (scope.row.type === 2 ? '抽水' : '重置') }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="金额" min-width="110" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.type === 1 ? '#457450' : (scope.row.type === 2 ? '#a4485a' : '#8f6418') }">
|
||||
{{ scope.row.type === 1 ? '+' : (scope.row.type === 2 ? '-' : '') }}{{ fmt(scope.row.amount) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整前水位" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.watermarkBefore == null ? '—' : scope.row.watermarkBefore + '%' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="调整后水位" min-width="110" align="center">
|
||||
<template slot-scope="scope">{{ scope.row.watermarkAfter == null ? '—' : scope.row.watermarkAfter + '%' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="operator" label="操作人" min-width="90" align="center" />
|
||||
<el-table-column label="原因" min-width="160" align="center" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span :style="{ color: scope.row.remark ? '#4f4130' : '#8b755a' }">{{ scope.row.remark || '—' }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
style="margin-top:16px;text-align:right"
|
||||
background
|
||||
layout="total, prev, pager, next, sizes"
|
||||
:total="total"
|
||||
:page-size.sync="query.size"
|
||||
:current-page.sync="query.page"
|
||||
:page-sizes="[10,20,50]"
|
||||
@size-change="loadLogs"
|
||||
@current-change="loadLogs"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getWatermarkInfo,
|
||||
saveWatermarkSetting,
|
||||
adjustWatermark,
|
||||
resetWatermarkAdjust,
|
||||
getWatermarkLogs,
|
||||
recalcWatermark
|
||||
} from '@/api/skins/luckyUpgrade/rewardPool/api'
|
||||
|
||||
/** 样本不足时的兜底标定系数,与后端 UpgradeWatermarkService.DEFAULT_CALIBRATION 保持一致 */
|
||||
const FALLBACK_CALIBRATION = 1.19
|
||||
|
||||
export default {
|
||||
name: 'LuckyUpgradeRewardPool',
|
||||
data() {
|
||||
return {
|
||||
showGuide: false,
|
||||
loading: false,
|
||||
saving: false,
|
||||
adjusting: false,
|
||||
resetting: false,
|
||||
recalcing: false,
|
||||
info: {
|
||||
enabled: true,
|
||||
watermark: 0,
|
||||
rtp: 0,
|
||||
totalBet: 0,
|
||||
totalWin: 0,
|
||||
blankBet: 0,
|
||||
blankWin: 0,
|
||||
adjust: 0,
|
||||
targetWatermark: 30,
|
||||
gain: 2,
|
||||
targetGap: 0,
|
||||
targetCenter: null,
|
||||
centerNow: null,
|
||||
calibration: null,
|
||||
effectiveCalibration: FALLBACK_CALIBRATION
|
||||
},
|
||||
logs: [],
|
||||
total: 0,
|
||||
form: {
|
||||
targetWatermark: 30,
|
||||
enabled: true,
|
||||
gain: 2,
|
||||
luckyValueMode: 'raw',
|
||||
rangeMin: undefined,
|
||||
rangeMax: undefined,
|
||||
amount: undefined,
|
||||
remark: ''
|
||||
},
|
||||
query: { page: 1, size: 10 }
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
gapText() {
|
||||
const g = Number(this.info.targetGap)
|
||||
if (isNaN(g)) return '—'
|
||||
const sign = g > 0 ? '+' : ''
|
||||
return sign + g.toFixed(2) + '%' + (g > 0 ? '(下一轮放水)' : (g < 0 ? '(下一轮收紧)' : ''))
|
||||
},
|
||||
gapColor() {
|
||||
const g = Number(this.info.targetGap)
|
||||
if (isNaN(g) || g === 0) return '#4f4130'
|
||||
return g > 0 ? '#457450' : '#a4485a'
|
||||
},
|
||||
/**
|
||||
* 按照输入的随机数区间,反推这个区间能维持的水位范围。
|
||||
* 随机数中心的可表达范围 = 下限 + [10%, 90%] × 区间宽度(超出会被夹取),
|
||||
* 再代回 水位 = 1 − 100 / (标定系数 × 随机数) 即得。
|
||||
*/
|
||||
rangeWatermark() {
|
||||
const lo = Number(this.form.rangeMin)
|
||||
const hi = Number(this.form.rangeMax)
|
||||
if (isNaN(lo) || isNaN(hi) || lo <= 0 || hi <= lo) return null
|
||||
const k = Number(this.info.effectiveCalibration) || FALLBACK_CALIBRATION
|
||||
const w = hi - lo
|
||||
const watermarkAt = (center) => (1 - 100 / (k * center)) * 100
|
||||
return { min: watermarkAt(lo + 0.1 * w), max: watermarkAt(lo + 0.9 * w) }
|
||||
},
|
||||
rangeResult() {
|
||||
if (this.form.rangeMin == null || this.form.rangeMax == null) {
|
||||
return { text: '—(填入区间后自动换算)', color: '#7a664e' }
|
||||
}
|
||||
const r = this.rangeWatermark
|
||||
if (!r) return { text: '—(需满足 0 < 下限 < 上限)', color: '#7a664e' }
|
||||
const text = r.min.toFixed(1) + '% ~ ' + r.max.toFixed(1) + '%'
|
||||
const t = Number(this.form.targetWatermark)
|
||||
if (isNaN(t)) return { text, color: '#4f4130' }
|
||||
const ok = t >= r.min && t <= r.max
|
||||
return {
|
||||
text: ok ? text : text + ' ⚠ 目标水位 ' + t.toFixed(1) + '% 不在范围内',
|
||||
color: ok ? '#457450' : '#a4485a'
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadInfo()
|
||||
this.loadLogs()
|
||||
},
|
||||
methods: {
|
||||
fmt(v) {
|
||||
const n = Number(v)
|
||||
return isNaN(n) ? '0.00' : n.toFixed(2)
|
||||
},
|
||||
pct(v) {
|
||||
const n = Number(v)
|
||||
return isNaN(n) ? '0.00%' : n.toFixed(2) + '%'
|
||||
},
|
||||
applyInfo(data) {
|
||||
if (!data) return
|
||||
this.info = data
|
||||
this.form.targetWatermark = data.targetWatermark == null ? 30 : Number(data.targetWatermark)
|
||||
this.form.enabled = data.enabled !== false
|
||||
this.form.gain = data.gain == null ? 2 : Number(data.gain)
|
||||
this.form.luckyValueMode = data.luckyValueMode || 'raw'
|
||||
},
|
||||
loadInfo() {
|
||||
getWatermarkInfo().then(res => this.applyInfo(res.data))
|
||||
},
|
||||
loadLogs() {
|
||||
this.loading = true
|
||||
getWatermarkLogs(this.query)
|
||||
.then(res => {
|
||||
this.logs = (res.data && res.data.rows) || []
|
||||
this.total = (res.data && res.data.total) || 0
|
||||
this.loading = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
requireRemark() {
|
||||
if (this.form.remark && this.form.remark.trim()) return true
|
||||
this.$message.warning('请填写调整原因')
|
||||
return false
|
||||
},
|
||||
/** 目标水位 / 策略开关改动即保存(本页没有保存按钮) */
|
||||
autoSaveSetting() {
|
||||
if (this.saving) return
|
||||
const t = Number(this.form.targetWatermark)
|
||||
if (isNaN(t) || t <= -50 || t >= 95) {
|
||||
this.$message.error('目标水位需在 -50% ~ 95% 之间')
|
||||
this.loadInfo()
|
||||
return
|
||||
}
|
||||
if (t === Number(this.info.targetWatermark) && this.form.enabled === (this.info.enabled !== false)) {
|
||||
return
|
||||
}
|
||||
this.saving = true
|
||||
saveWatermarkSetting({
|
||||
enabled: this.form.enabled,
|
||||
luckyValueMode: this.form.luckyValueMode,
|
||||
targetWatermark: t,
|
||||
gain: Number(this.form.gain) || 2
|
||||
})
|
||||
.then(res => {
|
||||
this.applyInfo(res.data)
|
||||
this.$message.success('已生效')
|
||||
this.saving = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.saving = false
|
||||
this.loadInfo()
|
||||
})
|
||||
},
|
||||
submitAdjust(type) {
|
||||
if (!this.form.amount || this.form.amount <= 0) {
|
||||
this.$message.warning('请输入大于 0 的金额')
|
||||
return
|
||||
}
|
||||
if (!this.requireRemark()) return
|
||||
this.adjusting = true
|
||||
adjustWatermark({ type, amount: this.form.amount, remark: this.form.remark.trim() })
|
||||
.then(res => {
|
||||
this.applyInfo(res.data)
|
||||
this.$message.success((type === 1 ? '注水' : '抽水') + '成功,当前水位 ' + this.pct(res.data.watermark))
|
||||
this.form.amount = undefined
|
||||
this.form.remark = ''
|
||||
this.query.page = 1
|
||||
this.loadLogs()
|
||||
this.adjusting = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.adjusting = false
|
||||
})
|
||||
},
|
||||
submitReset() {
|
||||
if (!this.requireRemark()) return
|
||||
this.resetting = true
|
||||
resetWatermarkAdjust({ remark: this.form.remark.trim() })
|
||||
.then(res => {
|
||||
this.applyInfo(res.data)
|
||||
this.$message.success('重置完成,清除净调整 ' + this.fmt(res.data.cleared))
|
||||
this.form.remark = ''
|
||||
this.query.page = 1
|
||||
this.loadLogs()
|
||||
this.resetting = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.resetting = false
|
||||
})
|
||||
},
|
||||
submitRecalc() {
|
||||
this.recalcing = true
|
||||
recalcWatermark()
|
||||
.then(res => {
|
||||
this.applyInfo(res.data)
|
||||
this.$message.success('已按历史流水重算累计量')
|
||||
this.recalcing = false
|
||||
})
|
||||
.catch(() => {
|
||||
this.recalcing = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.page-head { margin-bottom: 14px; }
|
||||
.page-head-row { display: flex; align-items: center; justify-content: space-between; }
|
||||
.head-tip { color: #7a664e; font-size: 12px; margin: 6px 0 0; line-height: 1.8; }
|
||||
.guide-panel { display: flex; gap: 16px; background: #f6efe3; border: 1px solid #eadfd0; border-radius: 8px; padding: 14px 16px; margin-top: 10px; }
|
||||
.guide-col { flex: 1; min-width: 0; }
|
||||
.guide-col h5 { margin: 0 0 8px; font-size: 13px; color: #4f4130; }
|
||||
.guide-col h5 i { color: #9a5c35; margin-right: 4px; }
|
||||
.guide-col ul { margin: 0; padding-left: 16px; }
|
||||
.guide-col li { font-size: 12px; color: #5f4f3d; line-height: 1.9; }
|
||||
.stat-row { margin-bottom: 12px; }
|
||||
.stat-card { display: flex; align-items: center; gap: 12px; background: #fffdfa; border: 1px solid #eadfd0; border-radius: 8px; padding: 14px 16px; }
|
||||
.sc-icon { width: 42px; height: 42px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
|
||||
.sc-terra { background: rgba(154, 92, 53, .22); color: #9a5c35; }
|
||||
.sc-grass { background: rgba(69, 116, 80, .22); color: #457450; }
|
||||
.sc-daisy { background: rgba(168, 121, 31, .24); color: #a8791f; }
|
||||
.sc-rose { background: rgba(181, 116, 116, .24); color: #b57474; }
|
||||
.sc-body small { color: #7a664e; display: block; }
|
||||
.sc-body h3 { margin: 2px 0 0; font-size: 18px; }
|
||||
.band-live { display: flex; flex-wrap: wrap; gap: 10px 26px; align-items: center; background: #f6efe3; border: 1px solid #eadfd0; border-radius: 8px; padding: 10px 16px; margin-bottom: 14px; }
|
||||
.bl-item { display: flex; align-items: center; gap: 6px; }
|
||||
.bl-label { color: #7a664e; font-size: 12px; }
|
||||
.bl-value { color: #4f4130; font-size: 14px; }
|
||||
.op-card { margin-bottom: 16px; border-radius: 8px; }
|
||||
.op-card .sub-tip { color: #7a664e; font-size: 12px; margin-left: 10px; }
|
||||
.fld-label { color: #5f4f3d; font-size: 13px; margin-right: 6px; }
|
||||
.fld-label .req { color: #a4485a; margin-left: 2px; }
|
||||
.fld-unit { color: #7a664e; font-size: 13px; margin-left: 6px; }
|
||||
.fld-note { color: #7a664e; font-size: 12px; }
|
||||
.fld-note b { color: #9a5c35; font-size: 14px; }
|
||||
.op-card >>> .el-form-item { margin-bottom: 0; }
|
||||
.setting-flags { display: flex; align-items: center; margin-top: 14px; }
|
||||
.range-calc { display: flex; align-items: center; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
|
||||
.rc-sep { color: #7a664e; font-size: 13px; }
|
||||
.rc-arrow { color: #9a5c35; font-size: 13px; margin: 0 4px; }
|
||||
.rc-result { font-size: 14px; }
|
||||
.help-ref {
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
width: 18px; height: 18px; border-radius: 50%;
|
||||
border: 1px solid #eadfd0; color: #9a5c35;
|
||||
font-size: 12px; cursor: help; user-select: none;
|
||||
}
|
||||
.help-ref:hover { border-color: #9a5c35; background: #f6efe3; }
|
||||
.section-head { display: flex; align-items: center; justify-content: space-between; margin: 16px 0 10px; }
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.wm-help .wm-help-body { font-size: 12px; line-height: 1.9; color: #5f4f3d; }
|
||||
.wm-help .wm-help-body p { margin: 0 0 8px; }
|
||||
.wm-help .wm-help-body p:last-child { margin-bottom: 0; }
|
||||
.wm-help .wm-help-body code { background: #f6efe3; padding: 1px 4px; border-radius: 3px; color: #9a5c35; }
|
||||
</style>
|
||||
@@ -44,6 +44,31 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 全局水位条:随机数中心由「目标水位」自动推算;下面各饰品自己的区间只是允许的取值范围内 -->
|
||||
<div class="wm-bar">
|
||||
<span class="wm-item">
|
||||
<span class="wm-label">全局水位</span>
|
||||
<b :style="{ color: Number(wm.watermark) < 0 ? '#a4485a' : '#9a5c35' }">{{ wm.watermark == null ? '—' : Number(wm.watermark).toFixed(2) + '%' }}</b>
|
||||
</span>
|
||||
<span class="wm-item">
|
||||
<span class="wm-label">目标水位</span>
|
||||
<b style="color:#457450">{{ wm.targetWatermark == null ? '—' : Number(wm.targetWatermark).toFixed(1) + '%' }}</b>
|
||||
</span>
|
||||
<span class="wm-item">
|
||||
<span class="wm-label">实测RTP</span>
|
||||
<b :style="{ color: Number(wm.rtp) > 100 ? '#a4485a' : '#457450' }">{{ wm.rtp == null ? '—' : Number(wm.rtp).toFixed(2) + '%' }}</b>
|
||||
</span>
|
||||
<span class="wm-item">
|
||||
<span class="wm-label">推算随机数中心</span>
|
||||
<b style="color:#9a5c35">{{ wm.targetCenter == null ? '—' : Number(wm.targetCenter).toFixed(1) }}</b>
|
||||
</span>
|
||||
<span class="wm-item">
|
||||
<el-tag :type="wm.enabled ? 'success' : 'info'" size="mini">{{ wm.enabled ? '水位驱动中' : '已关闭(回退固定区间)' }}</el-tag>
|
||||
</span>
|
||||
<span class="wm-hint">随机数在其下方每个饰品自己的区间内、围绕按目标水位推算的中心波动:水位高于目标 → 落点靠近区间下限。</span>
|
||||
<el-button type="text" size="mini" icon="el-icon-setting" class="wm-link" @click="goRewardPool">奖池水位 / 注水抽水</el-button>
|
||||
</div>
|
||||
|
||||
<el-table v-loading="loading" :data="ornamentsList">
|
||||
<el-table-column label="ID" align="center" prop="id" />
|
||||
<el-table-column label="名称" align="center" prop="itemName" />
|
||||
@@ -89,7 +114,7 @@
|
||||
</el-table-column>
|
||||
<el-table-column label="类型" align="center" prop="typeName" />
|
||||
<el-table-column label="普通用户" align="center">
|
||||
<el-table-column label="幸运区间(单位:%)" align="center" prop="luckSection">
|
||||
<el-table-column label="随机数区间(单位:% )" align="center" prop="luckSection">
|
||||
<template slot-scope="scope">
|
||||
<el-popover placement="top" popper-class="layout">
|
||||
<div style="text-align: center;">
|
||||
@@ -132,7 +157,7 @@
|
||||
<el-table-column label="本轮已投金额" align="center" prop="amountInvested"></el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="主播用户" align="center">
|
||||
<el-table-column label="幸运区间(单位:%)" align="center" prop="anchorLuckSection">
|
||||
<el-table-column label="随机数区间(单位:% )" align="center" prop="anchorLuckSection">
|
||||
<template slot-scope="scope">
|
||||
<el-popover placement="top" popper-class="layout">
|
||||
<div style="text-align: center;">
|
||||
@@ -175,7 +200,7 @@
|
||||
<el-table-column label="本轮已投金额" align="center" prop="anchorAmountInvested"></el-table-column>
|
||||
</el-table-column>
|
||||
<el-table-column label="白号用户" align="center">
|
||||
<el-table-column label="幸运区间(单位:%)" align="center" prop="blankLuckSection">
|
||||
<el-table-column label="随机数区间(单位:% )" align="center" prop="blankLuckSection">
|
||||
<template slot-scope="scope">
|
||||
<el-popover placement="top" popper-class="layout">
|
||||
<div style="text-align: center;">
|
||||
@@ -405,6 +430,7 @@ import {
|
||||
} from "@/api/skins/ttUpgrade/api";
|
||||
import { listOrnaments, getWebsiteProperty } from "@/api/skins/ttOrnaments/api";
|
||||
import { listLevel } from "@/api/skins/ttOrnamentsLevel/api";
|
||||
import { getWatermarkInfo } from "@/api/skins/luckyUpgrade/rewardPool/api";
|
||||
import * as echarts from "echarts";
|
||||
|
||||
export default {
|
||||
@@ -416,6 +442,8 @@ export default {
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
// 全局水位快照(随机数中心由「目标水位」推算,本页只读展示;区间在本页编辑)
|
||||
wm: { enabled: true, watermark: null, rtp: null, targetWatermark: null, targetCenter: null },
|
||||
anchorLuckSection: {
|
||||
maxanchorLuckSection: null,
|
||||
minanchorLuckSection: null
|
||||
@@ -499,8 +527,20 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadWatermark();
|
||||
},
|
||||
methods: {
|
||||
/** 全局水位快照:随机数由水位决定,本页只读展示 */
|
||||
loadWatermark() {
|
||||
getWatermarkInfo()
|
||||
.then(res => {
|
||||
if (res.data) this.wm = res.data
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
goRewardPool() {
|
||||
this.$router.push('/luckyUpgrade/rewardPool')
|
||||
},
|
||||
// 数据库 rarity_color 字段为空,前端根据 rarity_name 映射 CS2 标准稀有度颜色(颜色按稀有度由低到高递进)
|
||||
getRarityColor(rarityName) {
|
||||
const map = {
|
||||
@@ -846,6 +886,24 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
/* 全局水位条:暖褐系浅底 + 陶土强调,与侧栏/统计卡同一套色 */
|
||||
.wm-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 8px 22px;
|
||||
background: #f6efe3;
|
||||
border: 1px solid #eadfd0;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.wm-item { display: flex; align-items: center; gap: 6px; }
|
||||
.wm-label { color: #7a664e; font-size: 12px; }
|
||||
.wm-item b { color: #4f4130; font-size: 14px; font-weight: 500; }
|
||||
.wm-hint { color: #7a664e; font-size: 12px; }
|
||||
.wm-link { margin-left: auto; color: #9a5c35; }
|
||||
|
||||
::v-deep .el-switch {
|
||||
border-radius: 0px !important;
|
||||
height: 24px !important;
|
||||
|
||||
Reference in New Issue
Block a user