GM 工具:点券实时刷新 + 界面排版与文案优化
服务端: - gm_tool_wallet.go: GMAdjustAccountCera 落库后遍历在线会话,推送账号级 点券钱包投影(op53),在线角色点券余额即时刷新,无需离线/重登 启动器前端: - app.js: 副本控制改名「魔改中心」;删除角色详情页疲劳值编辑块 (saveFatigue/maxFatigue 及相关输入框);黑钻提示改为「在线回城即时生效」 - index.html/shell.css/shell.js/style.css: 精简 GM 全屏标题栏(仅保留 窗口按钮)、移除 D 字图标及死样式、修正侧边栏底部间距
This commit is contained in:
1 parent
645b64fe21
commit
d31a34e9aa
6 files changed
+29
-67
No files matched your search
+3
-38
@@ -276,7 +276,7 @@ const GM_PAGES = {
|
|||||||
inventory: ['背包装备', '/ 查看背包'],
|
inventory: ['背包装备', '/ 查看背包'],
|
||||||
auction: ['拍卖行', '/ 挂单上下架'],
|
auction: ['拍卖行', '/ 挂单上下架'],
|
||||||
quests: ['任务管理', '/ 任务查询'],
|
quests: ['任务管理', '/ 任务查询'],
|
||||||
dungeons: ['副本控制', '/ 副本状态'],
|
dungeons: ['魔改中心', '/ 副本状态'],
|
||||||
wallet: ['钱包点券', '/ 钱包与黑钻'],
|
wallet: ['钱包点券', '/ 钱包与黑钻'],
|
||||||
wipe: ['删档管理', '/ 删除角色与账号'],
|
wipe: ['删档管理', '/ 删除角色与账号'],
|
||||||
settings: ['连接设置', '/ 数据源切换'],
|
settings: ['连接设置', '/ 数据源切换'],
|
||||||
@@ -620,8 +620,6 @@ async function openCharDetail(charId) {
|
|||||||
try {
|
try {
|
||||||
const r = await api('/api/character?id=' + encodeURIComponent(charId));
|
const r = await api('/api/character?id=' + encodeURIComponent(charId));
|
||||||
const ch = r.data || {};
|
const ch = r.data || {};
|
||||||
const fat = await api('/api/character/fatigue?id=' + encodeURIComponent(charId) + '&mode=query');
|
|
||||||
const fatInfo = fat.data || {};
|
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div class="gm-card">
|
<div class="gm-card">
|
||||||
<div class="gm-card-title"><span class="dot"></span>角色:${escapeHtml(ch.name || charId)}</div>
|
<div class="gm-card-title"><span class="dot"></span>角色:${escapeHtml(ch.name || charId)}</div>
|
||||||
@@ -651,23 +649,6 @@ async function openCharDetail(charId) {
|
|||||||
|
|
||||||
<div class="gm-card-title" style="margin-top:8px"><span class="dot"></span>职业 / 转职</div>
|
<div class="gm-card-title" style="margin-top:8px"><span class="dot"></span>职业 / 转职</div>
|
||||||
<div id="careerPanel" style="margin-bottom:10px"><div class="gm-hint">正在加载职业信息…</div></div>
|
<div id="careerPanel" style="margin-bottom:10px"><div class="gm-hint">正在加载职业信息…</div></div>
|
||||||
|
|
||||||
<div class="gm-card-title" style="margin-top:8px"><span class="dot"></span>疲劳值(无限疲劳)</div>
|
|
||||||
<div class="gm-form-row" style="margin-bottom:10px">
|
|
||||||
<div class="gm-field">
|
|
||||||
<label>当前疲劳</label>
|
|
||||||
<input class="gm-input" id="editFatigue" type="number" value="${fmt(fatInfo.fatigue)}">
|
|
||||||
</div>
|
|
||||||
<div class="gm-field">
|
|
||||||
<label>疲劳上限</label>
|
|
||||||
<input class="gm-input" id="editFatigueLimit" type="number" value="${fmt(fatInfo.fatigue_limit)}">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="gm-toolbar">
|
|
||||||
<button class="gm-btn gm-btn-sm" onclick="saveFatigue()">保存疲劳</button>
|
|
||||||
<button class="gm-btn gm-btn-sm gm-btn-primary" onclick="maxFatigue()">⚡ 一键无限疲劳</button>
|
|
||||||
<span class="gm-hint">疲劳每日 6 点重置回上限;无限疲劳 = 把疲劳和上限都设成超大值</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gm-card gm-danger-zone">
|
<div class="gm-card gm-danger-zone">
|
||||||
@@ -779,20 +760,6 @@ async function saveGold() {
|
|||||||
if (r.ok) gmToast('金币已更新', 'ok'); else gmToast('失败:' + (r.error || ''), 'err');
|
if (r.ok) gmToast('金币已更新', 'ok'); else gmToast('失败:' + (r.error || ''), 'err');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveFatigue() {
|
|
||||||
const fatigue = $('editFatigue').value;
|
|
||||||
const limit = $('editFatigueLimit').value;
|
|
||||||
const r = await post('/api/character/fatigue', { id: currentCharId, fatigue: parseInt(fatigue), fatigue_limit: parseInt(limit) });
|
|
||||||
if (r.ok) { gmToast('疲劳值已更新', 'ok'); openCharDetail(currentCharId); }
|
|
||||||
else gmToast('失败:' + (r.error || ''), 'err');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function maxFatigue() {
|
|
||||||
const r = await post('/api/character/fatigue', { id: currentCharId, fatigue: 999999, fatigue_limit: 999999 });
|
|
||||||
if (r.ok) { gmToast('已设置无限疲劳(999999)', 'ok'); openCharDetail(currentCharId); }
|
|
||||||
else gmToast('失败:' + (r.error || ''), 'err');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteCurrentChar() {
|
async function deleteCurrentChar() {
|
||||||
gmConfirm('删除角色', '确定要删除该角色的全部数据吗?<br>此操作<b style="color:#ff5d6c">不可恢复</b>,且需先停止服务端。', 'DELETE', async () => {
|
gmConfirm('删除角色', '确定要删除该角色的全部数据吗?<br>此操作<b style="color:#ff5d6c">不可恢复</b>,且需先停止服务端。', 'DELETE', async () => {
|
||||||
const r = await post('/api/character/delete', { id: currentCharId, confirm: 'DELETE' });
|
const r = await post('/api/character/delete', { id: currentCharId, confirm: 'DELETE' });
|
||||||
@@ -1988,7 +1955,7 @@ async function renderWallet(content) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="gm-btn gm-btn-primary" onclick="activateBlackDiamond()">💎 开启黑钻</button>
|
<button class="gm-btn gm-btn-primary" onclick="activateBlackDiamond()">💎 开启黑钻</button>
|
||||||
<div class="gm-hint" style="margin-top:8px">黑钻提升疲劳上限、额外翻牌等权益;角色需离线(下次登录生效)。</div>
|
<div class="gm-hint" style="margin-top:8px">黑钻提升疲劳上限、额外翻牌等权益;在线角色回城后即时生效,无需离线。</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
fillCharSelect($('wallet-char'));
|
fillCharSelect($('wallet-char'));
|
||||||
fillAccountSelect($('cera-account'));
|
fillAccountSelect($('cera-account'));
|
||||||
@@ -2066,7 +2033,7 @@ async function activateBlackDiamond() {
|
|||||||
const id = $('bd-char').value;
|
const id = $('bd-char').value;
|
||||||
const days = parseInt($('bd-days').value) || 30;
|
const days = parseInt($('bd-days').value) || 30;
|
||||||
if (!id) { gmToast('请选择角色', 'err'); return; }
|
if (!id) { gmToast('请选择角色', 'err'); return; }
|
||||||
gmConfirm('开启黑钻', '确定要为所选角色开启黑钻 ' + days + ' 天吗?<br>角色需处于<b style="color:#ff5d6c">离线状态</b>,下次登录生效。', '开启黑钻', async () => {
|
gmConfirm('开启黑钻', '确定要为所选角色开启黑钻 ' + days + ' 天吗?<br>在线角色回城后即时生效。', '开启黑钻', async () => {
|
||||||
const r = await post('/api/black-diamond/activate', { id, days });
|
const r = await post('/api/black-diamond/activate', { id, days });
|
||||||
if (r.ok) { gmToast('黑钻已开启 ' + days + ' 天', 'ok'); queryBlackDiamond(); }
|
if (r.ok) { gmToast('黑钻已开启 ' + days + ' 天', 'ok'); queryBlackDiamond(); }
|
||||||
else gmToast('开启失败:' + (r.error || ''), 'err');
|
else gmToast('开启失败:' + (r.error || ''), 'err');
|
||||||
@@ -2290,8 +2257,6 @@ function init() {
|
|||||||
window.openCharDetail = openCharDetail;
|
window.openCharDetail = openCharDetail;
|
||||||
window.saveLevel = saveLevel;
|
window.saveLevel = saveLevel;
|
||||||
window.saveGold = saveGold;
|
window.saveGold = saveGold;
|
||||||
window.saveFatigue = saveFatigue;
|
|
||||||
window.maxFatigue = maxFatigue;
|
|
||||||
window.deleteCurrentChar = deleteCurrentChar;
|
window.deleteCurrentChar = deleteCurrentChar;
|
||||||
window.doGrant = doGrant;
|
window.doGrant = doGrant;
|
||||||
window.loadInventory = loadInventory;
|
window.loadInventory = loadInventory;
|
||||||
|
|||||||
+3
-3
@@ -208,7 +208,7 @@
|
|||||||
<section class="shell-page" id="page-gm">
|
<section class="shell-page" id="page-gm">
|
||||||
<div id="gmView" class="gm-app">
|
<div id="gmView" class="gm-app">
|
||||||
<aside class="gm-sidebar">
|
<aside class="gm-sidebar">
|
||||||
<div class="gm-logo"><div class="gm-logo-mark">D</div><div><div class="gm-logo-title">DNF90 GM</div><div class="gm-logo-sub">九零轮回 · 管理工具</div></div></div>
|
<div class="gm-logo"><div><div class="gm-logo-title">DNF90 GM</div><div class="gm-logo-sub">九零轮回 · 管理工具</div></div></div>
|
||||||
<div class="gm-nav-group">总览</div>
|
<div class="gm-nav-group">总览</div>
|
||||||
<div class="gm-nav-item active" data-page="dashboard"><span class="ico">📊</span>控制台</div>
|
<div class="gm-nav-item active" data-page="dashboard"><span class="ico">📊</span>控制台</div>
|
||||||
<div class="gm-nav-item" data-page="characters"><span class="ico">👥</span>角色管理</div>
|
<div class="gm-nav-item" data-page="characters"><span class="ico">👥</span>角色管理</div>
|
||||||
@@ -219,14 +219,14 @@
|
|||||||
<div class="gm-nav-item" data-page="auction"><span class="ico">🏷️</span>拍卖行</div>
|
<div class="gm-nav-item" data-page="auction"><span class="ico">🏷️</span>拍卖行</div>
|
||||||
<div class="gm-nav-group">进度</div>
|
<div class="gm-nav-group">进度</div>
|
||||||
<div class="gm-nav-item" data-page="quests"><span class="ico">📜</span>任务管理</div>
|
<div class="gm-nav-item" data-page="quests"><span class="ico">📜</span>任务管理</div>
|
||||||
<div class="gm-nav-item" data-page="dungeons"><span class="ico">⚔️</span>副本控制</div>
|
<div class="gm-nav-item" data-page="dungeons"><span class="ico">⚔️</span>魔改中心</div>
|
||||||
<div class="gm-nav-group">系统</div>
|
<div class="gm-nav-group">系统</div>
|
||||||
<div class="gm-nav-item" data-page="wallet"><span class="ico">💰</span>钱包点券</div>
|
<div class="gm-nav-item" data-page="wallet"><span class="ico">💰</span>钱包点券</div>
|
||||||
<div class="gm-nav-item" data-page="wipe"><span class="ico">🗑️</span>删档管理</div>
|
<div class="gm-nav-item" data-page="wipe"><span class="ico">🗑️</span>删档管理</div>
|
||||||
<div class="gm-nav-item" data-page="settings"><span class="ico">⚙️</span>连接设置</div>
|
<div class="gm-nav-item" data-page="settings"><span class="ico">⚙️</span>连接设置</div>
|
||||||
<div class="gm-sidebar-foot"><div><span class="conn-dot off" id="gmConnDot"></span><span class="conn-text" id="gmConnText">数据源:SQLite</span></div><button class="gm-back" id="btnBackLauncher">← 返回启动器</button></div>
|
<div class="gm-sidebar-foot"><div><span class="conn-dot off" id="gmConnDot"></span><span class="conn-text" id="gmConnText">数据源:SQLite</span></div><button class="gm-back" id="btnBackLauncher">← 返回启动器</button></div>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="gm-main"><header class="gm-topbar"><h1 id="gmPageTitle">控制台</h1><span class="gm-crumb" id="gmPageCrumb">/ 服务端总览</span><div class="topbar-spacer"></div><button class="gm-btn gm-btn-sm gm-btn-ghost" id="gmBtnRefresh">↻ 刷新</button><button class="gm-win-btn gm-win-min" id="gmWinMin" title="最小化">—</button><button class="gm-win-btn gm-win-close" id="gmWinClose" title="关闭">×</button></header><div class="gm-content" id="gmContent"></div></div>
|
<div class="gm-main"><header class="gm-topbar"><h1 id="gmPageTitle">控制台</h1><span class="gm-crumb" id="gmPageCrumb">/ 服务端总览</span><div class="topbar-spacer"></div><button class="gm-btn gm-btn-sm gm-btn-ghost" id="gmBtnRefresh">↻ 刷新</button></header><div class="gm-content" id="gmContent"></div></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
+7
-11
@@ -181,7 +181,7 @@ button,input,select{font:inherit}
|
|||||||
/* GM embedded */
|
/* GM embedded */
|
||||||
#page-gm{padding:0}
|
#page-gm{padding:0}
|
||||||
#page-gm.active{height:100%}
|
#page-gm.active{height:100%}
|
||||||
#page-gm #gmView{height:100%;display:flex;background:#0b0b14;border:0}
|
#page-gm #gmView{height:100%;margin-top:0;display:flex;background:#0b0b14;border:0}
|
||||||
#page-gm .gm-sidebar{width:180px}
|
#page-gm .gm-sidebar{width:180px}
|
||||||
#page-gm .gm-main{flex:1;min-width:0}
|
#page-gm .gm-main{flex:1;min-width:0}
|
||||||
#page-gm .gm-content{height:calc(100% - 58px);overflow:auto}
|
#page-gm .gm-content{height:calc(100% - 58px);overflow:auto}
|
||||||
@@ -409,22 +409,18 @@ body{font-size:16px}
|
|||||||
|
|
||||||
/* ================= GM 全屏模式(点击 GM 标签覆盖整个窗口) ================= */
|
/* ================= GM 全屏模式(点击 GM 标签覆盖整个窗口) ================= */
|
||||||
body.gm-fullscreen #appShell{height:100vh}
|
body.gm-fullscreen #appShell{height:100vh}
|
||||||
body.gm-fullscreen .shell-topnav{display:none}
|
/* 全屏时保留主标题栏作为极简标题栏:隐藏品牌与导航,仅右上角窗口控制按钮贴顶 */
|
||||||
|
body.gm-fullscreen .shell-topnav{height:34px;background:transparent;border-bottom:0;padding:0 4px 0 0}
|
||||||
|
body.gm-fullscreen .shell-brand{display:none}
|
||||||
|
body.gm-fullscreen .shell-nav{display:none}
|
||||||
|
body.gm-fullscreen .shell-win-controls{height:100%}
|
||||||
body.gm-fullscreen .shell-topbar{display:none}
|
body.gm-fullscreen .shell-topbar{display:none}
|
||||||
body.gm-fullscreen .shell-statusbar{display:none}
|
body.gm-fullscreen .shell-statusbar{display:none}
|
||||||
body.gm-fullscreen #page-gm #gmView{height:100%}
|
body.gm-fullscreen #page-gm #gmView{height:100%;margin-top:0}
|
||||||
body.gm-fullscreen .shell-main{overflow:hidden}
|
body.gm-fullscreen .shell-main{overflow:hidden}
|
||||||
/* GM 全屏下隐藏窗口缩放热区(铺满工作区时不可再拖边) */
|
/* GM 全屏下隐藏窗口缩放热区(铺满工作区时不可再拖边) */
|
||||||
body.gm-fullscreen .win-resize{display:none}
|
body.gm-fullscreen .win-resize{display:none}
|
||||||
|
|
||||||
/* GM 顶部栏内的窗口按钮:仅在全屏模式(顶部导航隐藏)时显示 */
|
|
||||||
.gm-win-btn{display:none;width:28px;height:24px;border:1px solid var(--border,#25483a);
|
|
||||||
background:transparent;color:#a8c7b6;cursor:pointer;font-size:14px;line-height:1;
|
|
||||||
border-radius:3px;flex-shrink:0}
|
|
||||||
.gm-win-btn:hover{background:#1a3a2c;color:#fff}
|
|
||||||
.gm-win-close:hover{background:#7a2a33;border-color:#a33a44;color:#fff}
|
|
||||||
body.gm-fullscreen .gm-win-btn{display:inline-block}
|
|
||||||
|
|
||||||
/* ================= 标题栏右侧窗口控制按钮(最小化/关闭,无边框线性图标) ================= */
|
/* ================= 标题栏右侧窗口控制按钮(最小化/关闭,无边框线性图标) ================= */
|
||||||
.shell-topnav{cursor:move;user-select:none;padding:0 6px 0 16px}
|
.shell-topnav{cursor:move;user-select:none;padding:0 6px 0 16px}
|
||||||
.shell-topnav button,.shell-topnav .shell-side-foot{cursor:default}
|
.shell-topnav button,.shell-topnav .shell-side-foot{cursor:default}
|
||||||
|
|||||||
@@ -66,11 +66,6 @@
|
|||||||
const bMin = $('shellWinMin'); if (bMin) bMin.addEventListener('click', () => { const w = win(); if (w && w.minimize) w.minimize(); });
|
const bMin = $('shellWinMin'); if (bMin) bMin.addEventListener('click', () => { const w = win(); if (w && w.minimize) w.minimize(); });
|
||||||
const bClose = $('shellWinClose'); if (bClose) bClose.addEventListener('click', () => { const w = win(); if (w && w.close) w.close(); });
|
const bClose = $('shellWinClose'); if (bClose) bClose.addEventListener('click', () => { const w = win(); if (w && w.close) w.close(); });
|
||||||
|
|
||||||
// GM 全屏模式下的窗口按钮(最小化 / 关闭)
|
|
||||||
const gmWinMin = $('gmWinMin');
|
|
||||||
const gmWinClose = $('gmWinClose');
|
|
||||||
if (gmWinMin) gmWinMin.addEventListener('click', () => { const w = win(); if (w && w.minimize) w.minimize(); });
|
|
||||||
if (gmWinClose) gmWinClose.addEventListener('click', () => { const w = win(); if (w && w.close) w.close(); });
|
|
||||||
|
|
||||||
// 账号登录:打开原账号管理弹窗,可选择默认账号
|
// 账号登录:打开原账号管理弹窗,可选择默认账号
|
||||||
$('shellAccount').addEventListener('click', () => {
|
$('shellAccount').addEventListener('click', () => {
|
||||||
|
|||||||
+1
-10
@@ -471,17 +471,8 @@ html, body {
|
|||||||
.gm-logo {
|
.gm-logo {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
|
||||||
padding: 4px 10px 20px;
|
padding: 4px 10px 20px;
|
||||||
}
|
}
|
||||||
.gm-logo-mark {
|
|
||||||
width: 38px; height: 38px;
|
|
||||||
border-radius: 11px;
|
|
||||||
background: linear-gradient(135deg, var(--primary), var(--primary-2));
|
|
||||||
display: flex; align-items: center; justify-content: center;
|
|
||||||
font-weight: 800; font-size: 18px; color: #fff;
|
|
||||||
box-shadow: 0 0 22px rgba(124, 92, 255, 0.35);
|
|
||||||
}
|
|
||||||
.gm-logo-title { font-size: 17px; font-weight: 700; letter-spacing: 0.5px; }
|
.gm-logo-title { font-size: 17px; font-weight: 700; letter-spacing: 0.5px; }
|
||||||
.gm-logo-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
.gm-logo-sub { font-size: 11px; color: var(--text-muted); margin-top: 2px; }
|
||||||
|
|
||||||
@@ -518,7 +509,7 @@ html, body {
|
|||||||
|
|
||||||
.gm-sidebar-foot {
|
.gm-sidebar-foot {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding: 12px;
|
padding: 12px 12px 20px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -247,5 +247,20 @@ func (s *Service) GMAdjustAccountCera(ctx context.Context, request GMAccountCera
|
|||||||
}
|
}
|
||||||
after, _ := currency.BalanceOf(account)
|
after, _ := currency.BalanceOf(account)
|
||||||
result.After = after
|
result.After = after
|
||||||
|
|
||||||
|
// 落库后推送给该账号下所有在线角色:点券是账号级钱包,需刷新每个已选角色的
|
||||||
|
// 钱包投影(class-0 op53),让在线角色的点券余额即时刷新,不必离线/重登。
|
||||||
|
for _, session := range s.currentOnlineChatSessions() {
|
||||||
|
if session == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(s.accountIDForSession(session)) != accountID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, pushErr := s.sendCurrentGoldConsignWalletRefresh(session); pushErr != nil {
|
||||||
|
s.logWarn("dnf90 GM cera live refresh failed",
|
||||||
|
"account_id", accountID, "currency", currencyName, "error", pushErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user