GM 页改版:加回满疲劳/赞助页,去掉背包与邮件页和重复标题

- 角色页新增「回满疲劳」,走 POST /dnf/gm/characters/{id}/fatigue/refill(需角色下线)
- 新增「赞助」页:支付宝 / 微信收款码占位,图放 gmweb/qr/ 下即显示
- 删除背包页与邮件页(发到邮箱按钮保留在发物品页)
- 去掉页头「GM 控制台」文字(窗口标题栏已画标题,重复),页头只留连接状态
- serve.py 补 jpg/jpeg/gif/webp/bmp/svg 的 MIME
- 控制台宽度 1040 -> 920
This commit is contained in:
hanfuit committed 2026-11-05 14:15:41 +08:00
1 parent 7f41f9797a
commit 06558415da
6 files changed
+91 -103

No files matched your search

+1 -1
View File
@@ -15,7 +15,7 @@ minimize_game=0
; (推荐,原面板不会残留、也不会再露面);0 = 把面板放回去(老行为,会看到旧 IE 页面) ; (推荐,原面板不会残留、也不会再露面);0 = 把面板放回去(老行为,会看到旧 IE 页面)
close_action=1 close_action=1
; 我们自己的窗口尺寸 ; 我们自己的窗口尺寸
width=1040 width=920
height=720 height=720
; 目标页面。必须是本地 http,避免浏览器弹证书提示。 ; 目标页面。必须是本地 http,避免浏览器弹证书提示。
url=http://127.0.0.1:18112/ url=http://127.0.0.1:18112/
+31 -79
View File
@@ -4,7 +4,7 @@
'use strict'; 'use strict';
var S = { var S = {
charId: '', charName: '', chars: [], bagRows: [], charId: '', charName: '', chars: [],
itemId: 0, itemName: '', picked: null, items: [], itemId: 0, itemName: '', picked: null, items: [],
page: 1, pageSize: 20, total: 0 page: 1, pageSize: 20, total: 0
}; };
@@ -66,7 +66,6 @@
for (var j = 0; j < panes.length; j++) { for (var j = 0; j < panes.length; j++) {
panes[j].className = 'pane' + (panes[j].id === 'pane-' + name ? ' on' : ''); panes[j].className = 'pane' + (panes[j].id === 'pane-' + name ? ' on' : '');
} }
if (name === 'bag') { loadBag(0); }
} }
function bindTabs() { function bindTabs() {
@@ -156,6 +155,17 @@
return true; return true;
} }
/* 一键回满疲劳:直接把库里 fatigue 写回上限。
服务端要求角色已下线(在线时写库会被下一个存档覆盖),所以在线角色会报错。 */
function refillFatigue() {
if (!requireChar()) { return; }
api('/dnf/gm/characters/' + S.charId + '/fatigue/refill', 'POST', {}, function (d) {
if (!d) { return; }
log('已回满 ' + S.charName + ' 的疲劳值', 'ok');
loadChars();
});
}
/* ---------------------------------------------------------------- 物品 */ /* ---------------------------------------------------------------- 物品 */
/* 装备类型标签:PVF 里是 "[weapon]" 这种,去掉括号后映射成中文。 */ /* 装备类型标签:PVF 里是 "[weapon]" 这种,去掉括号后映射成中文。 */
@@ -384,10 +394,7 @@
var data = d.data || {}; var data = d.data || {};
log('已发 ' + S.itemName + ' ×' + n + optText(o) + log('已发 ' + S.itemName + ' ×' + n + optText(o) +
(data.slot != null ? ',格位 ' + data.list_type + ':' + data.slot : ''), 'ok'); (data.slot != null ? ',格位 ' + data.list_type + ':' + data.slot : ''), 'ok');
// 发完立刻切到背包页并高亮这一行:到底有没有进包,玩家要看得见 // 回游戏里看背包即可(客户端会自动刷新),这里不再切页打断操作
switchTab('bag');
loadBag(S.itemId);
setTimeout(function () { loadBag(S.itemId); }, 600);
}); });
} }
@@ -406,78 +413,22 @@
}); });
} }
/* ---------------------------------------------------------------- 背包 */ /* ---------------------------------------------------------------- 赞助 */
/* 背包 / 穿戴的实时快照。GM 发完东西,玩家第一件事就是看它到底进没进包, /* 收款码图片是可选的:放了就显示,没放(404)就把占位提示顶上来。 */
所以这里按服务端实际存的行来列,不做任何本地猜测。 */ function bindQr(id) {
function loadBag(highlightItemId) { var img = $(id);
if (!S.charId) { if (!img) { return; }
$('bag-list').innerHTML = '<div class="empty">先在「角色」页选一个角色</div>'; img.onerror = function () {
$('bag-info').innerHTML = '-'; var box = this.parentNode;
return; if (box) { box.className = 'qr-box miss'; }
} this.style.display = 'none';
api('/local/dnf-gm/inventory/list?character_id=' + encodeURIComponent(S.charId), };
'GET', null, function (d) { img.onload = function () {
if (!d) { return; } var box = this.parentNode;
var data = d.data || {}; if (box) { box.className = 'qr-box'; }
S.bagRows = data.rows || []; this.style.display = '';
renderBag(highlightItemId || 0); };
});
}
function renderBag(highlightItemId) {
var box = $('bag-list');
var rows = S.bagRows || [];
var bagCount = 0, wearCount = 0, i;
for (i = 0; i < rows.length; i++) {
if (rows[i].collection === 'wear') { wearCount++; } else { bagCount++; }
}
$('bag-info').innerHTML = '背包 ' + bagCount + ' 件 · 穿戴 ' + wearCount + ' 件';
if (!rows.length) {
box.innerHTML = '<div class="empty">背包是空的</div>';
return;
}
// 刚发进去的排在最前面,其余保持服务端顺序
var ordered = [];
if (highlightItemId) {
for (i = 0; i < rows.length; i++) {
if (rows[i].item_id === highlightItemId) { ordered.push(rows[i]); }
}
}
for (i = 0; i < rows.length; i++) {
if (!highlightItemId || rows[i].item_id !== highlightItemId) { ordered.push(rows[i]); }
}
var h = [];
for (i = 0; i < ordered.length; i++) {
var r = ordered[i];
var isNew = highlightItemId && r.item_id === highlightItemId;
h.push('<div class="li' + (isNew ? ' on' : '') + '">' +
'<span class="nm">' + esc(r.item_name || ('物品 ' + r.item_id)) +
(isNew ? ' <em class="new">新</em>' : '') + '</span>' +
'<span class="meta">' + (r.collection === 'wear' ? '穿戴' : '背包') +
' · ' + esc(r.entry_key) + ' · id ' + esc(r.item_id) +
(r.count > 1 ? ' ×' + esc(r.count) : '') + '</span></div>');
}
box.innerHTML = h.join('');
}
/* ---------------------------------------------------------------- 邮件 */
function sendMail() {
if (!requireChar()) { return; }
var gold = parseInt($('in-goldmail').value, 10) || 0;
api('/local/dnf-gm/mail', 'POST', {
character_name: S.charName,
title: $('in-title').value || 'GM 邮件',
body: $('in-body').value || '',
gold: gold,
attachments: []
}, function (d) {
if (d) { log('邮件已发送给 ' + S.charName, 'ok'); }
});
} }
/* ---------------------------------------------------------------- 启动 */ /* ---------------------------------------------------------------- 启动 */
@@ -515,9 +466,8 @@
bind('btn-prev', function () { if (S.page > 1) { loadItems(S.page - 1); } }); bind('btn-prev', function () { if (S.page > 1) { loadItems(S.page - 1); } });
bind('btn-next', function () { loadItems(S.page + 1); }); bind('btn-next', function () { loadItems(S.page + 1); });
bind('btn-grant', grantItem); bind('btn-grant', grantItem);
bind('btn-bag', function () { loadBag(0); });
bind('btn-mailitem', mailItem); bind('btn-mailitem', mailItem);
bind('btn-mail', sendMail); bind('btn-fatigue', refillFatigue);
$('in-page-size').onchange = function () { loadItems(1); }; $('in-page-size').onchange = function () { loadItems(1); };
$('in-group').onchange = function () { fillTypeOptions(); loadItems(1); }; $('in-group').onchange = function () { fillTypeOptions(); loadItems(1); };
@@ -549,6 +499,8 @@
}; };
fillTypeOptions(); fillTypeOptions();
bindQr('qr-alipay');
bindQr('qr-wechat');
renderPicked(); renderPicked();
forceIME(); forceIME();
+23 -19
View File
@@ -8,7 +8,6 @@
</head> </head>
<body> <body>
<div id="hd"> <div id="hd">
<div id="hd-t">GM 控制台</div>
<div id="hd-s"><span id="dot" class="dot off"></span><span id="stat">连接中…</span></div> <div id="hd-s"><span id="dot" class="dot off"></span><span id="stat">连接中…</span></div>
</div> </div>
@@ -16,8 +15,7 @@
<a class="tab on" data-tab="home" href="javascript:void(0)">状态</a> <a class="tab on" data-tab="home" href="javascript:void(0)">状态</a>
<a class="tab" data-tab="char" href="javascript:void(0)">角色</a> <a class="tab" data-tab="char" href="javascript:void(0)">角色</a>
<a class="tab" data-tab="item" href="javascript:void(0)">发物品</a> <a class="tab" data-tab="item" href="javascript:void(0)">发物品</a>
<a class="tab" data-tab="bag" href="javascript:void(0)">背包</a> <a class="tab" data-tab="donate" href="javascript:void(0)">赞助</a>
<a class="tab" data-tab="mail" href="javascript:void(0)">邮件</a>
</div> </div>
<div id="body"> <div id="body">
@@ -53,6 +51,11 @@
<label class="lb">等级</label><input id="in-level" class="inp sm" value=""> <label class="lb">等级</label><input id="in-level" class="inp sm" value="">
<button id="btn-level" class="btn">写入</button> <button id="btn-level" class="btn">写入</button>
</div> </div>
<div class="sec">疲劳</div>
<div class="row">
<button id="btn-fatigue" class="btn pri">回满疲劳</button>
<span class="dim">角色需先在游戏里下线(回到角色选择)才能写库</span>
</div>
</div> </div>
<div class="pane" id="pane-item"> <div class="pane" id="pane-item">
@@ -127,23 +130,24 @@
<div class="hint">强化 0-31、增幅等级 0-31、锻造 0-8。增幅填了就直接覆盖强化(红字装不能同时白字强化),非装备行忽略这些值。</div> <div class="hint">强化 0-31、增幅等级 0-31、锻造 0-8。增幅填了就直接覆盖强化(红字装不能同时白字强化),非装备行忽略这些值。</div>
</div> </div>
<div class="pane" id="pane-bag"> <div class="pane" id="pane-donate">
<div class="row"> <div class="sec">赞助支持</div>
<button id="btn-bag" class="btn">刷新背包</button> <div class="hint">服务器长期免费维护。如果玩得开心,可以扫码赞助一点,金额随意,感谢支持。</div>
<span id="bag-info" class="pg">-</span> <div class="qr-wrap">
<div class="qr-card">
<div class="qr-title">支付宝</div>
<div class="qr-box"><img id="qr-alipay" src="/qr/alipay.png" alt="支付宝收款码">
<div class="qr-miss">二维码未配置<div class="qr-path">gmweb\qr\alipay.png</div></div>
</div>
</div>
<div class="qr-card">
<div class="qr-title">微信</div>
<div class="qr-box"><img id="qr-wechat" src="/qr/wechat.png" alt="微信收款码">
<div class="qr-miss">二维码未配置<div class="qr-path">gmweb\qr\wechat.png</div></div>
</div>
</div>
</div> </div>
<div id="bag-list" class="list fixed"></div> <div class="hint">把收款码图片按上面的文件名放进 gmweb\qr\ 目录,刷新这一页就会显示;换成 jpg 也可以,改一下上面的文件名后缀即可。</div>
<div class="hint">发到背包的物品会自动跳到这一页并高亮(标「新」)。列表里没有,就说明没进背包:可能是格位满了,或者这个物品类型不能直接进背包(该走邮箱)。</div>
</div>
<div class="pane" id="pane-mail">
<div class="row"><label class="lb">标题</label><input id="in-title" class="inp" value="GM 邮件"></div>
<div class="row"><label class="lb">内容</label><input id="in-body" class="inp" value="来自 GM 的问候"></div>
<div class="row"><label class="lb">附金币</label><input id="in-goldmail" class="inp sm" value="0"></div>
<div class="row">
<button id="btn-mail" class="btn pri">发送到当前角色</button>
</div>
<div class="hint">收件人取「角色」页选中的角色;未选中会提示。</div>
</div> </div>
</div> </div>
+11
View File
@@ -0,0 +1,11 @@
把收款码图片放到这个目录,文件名固定为:
alipay.png 支付宝收款码
wechat.png 微信收款码
说明:
- 建议正方形图片(页面按 194x194 显示,非正方形会被拉伸)
- 支持 png / jpg / jpeg / gif / bmp / webp / svg
- 用 jpg 的话,把 gmweb\index.html 里对应 <img src="/qr/alipay.png"> 的后缀改成 .jpg 即可
- 换图后刷新控制台页面(标题栏的刷新按钮)就能看到,不用重启服务
- 图片没放时,赞助页显示「二维码未配置」占位,不影响其他功能
+6
View File
@@ -32,6 +32,12 @@ MIME = {
".js": "application/javascript; charset=utf-8", ".js": "application/javascript; charset=utf-8",
".css": "text/css; charset=utf-8", ".css": "text/css; charset=utf-8",
".png": "image/png", ".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".bmp": "image/bmp",
".svg": "image/svg+xml",
".ico": "image/x-icon", ".ico": "image/x-icon",
} }
+19 -4
View File
@@ -6,11 +6,10 @@ html, body {
overflow: hidden; overflow: hidden;
} }
#hd { #hd {
height: 30px; padding: 0 10px; height: 26px; padding: 0 10px;
background: #1d2029; border-bottom: 1px solid #2b3040; background: #1d2029; border-bottom: 1px solid #2b3040;
line-height: 30px; line-height: 26px;
} }
#hd-t { float: left; font-weight: bold; color: #e8c979; letter-spacing: 1px; }
#hd-s { float: right; color: #8d94a6; } #hd-s { float: right; color: #8d94a6; }
.dot { display: inline-block; width: 7px; height: 7px; border-radius: 4px; margin-right: 6px; } .dot { display: inline-block; width: 7px; height: 7px; border-radius: 4px; margin-right: 6px; }
.dot.on { background: #4ec46a; } .dot.on { background: #4ec46a; }
@@ -24,7 +23,7 @@ html, body {
.tab:hover { color: #d8dbe2; } .tab:hover { color: #d8dbe2; }
.tab.on { color: #e8c979; border-bottom-color: #e8c979; background: #20242e; } .tab.on { color: #e8c979; border-bottom-color: #e8c979; background: #20242e; }
#body { position: absolute; top: 59px; bottom: 62px; left: 0; right: 0; overflow: auto; } #body { position: absolute; top: 55px; bottom: 62px; left: 0; right: 0; overflow: auto; }
.pane { display: none; padding: 10px; } .pane { display: none; padding: 10px; }
.pane.on { display: block; } .pane.on { display: block; }
@@ -111,3 +110,19 @@ select.inp { padding: 3px 2px; }
font-style: normal; font-size: 11px; padding: 0 4px; margin-left: 4px; font-style: normal; font-size: 11px; padding: 0 4px; margin-left: 4px;
background: #e8c979; color: #1a1e27; border-radius: 2px; background: #e8c979; color: #1a1e27; border-radius: 2px;
} }
/* 赞助页:收款码(IE11 不支持 flex,用 float 排两列) */
.qr-wrap { overflow: hidden; margin-top: 10px; }
.qr-card {
float: left; width: 216px; margin: 0 14px 10px 0;
background: #1a1e27; border: 1px solid #2b3040; padding: 10px;
}
.qr-title { color: #e8c979; letter-spacing: 1px; margin-bottom: 8px; }
.qr-box {
width: 194px; height: 194px; position: relative; overflow: hidden;
background: #0f1116; border: 1px dashed #3a4154; text-align: center;
}
.qr-box img { display: block; width: 194px; height: 194px; }
.qr-miss { display: none; padding-top: 72px; color: #666e80; line-height: 1.8; }
.qr-box.miss .qr-miss { display: block; }
.qr-path { color: #4c5365; font-size: 11px; margin-top: 2px; }