修改河南项目的管理后台ui

This commit is contained in:
2026-09-24 11:18:30 +08:00
parent 23fe1f3995
commit 4be9ce4e85
19 changed files with 2513 additions and 178 deletions
@@ -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>
+61 -3
View File
@@ -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;