小井盖改为自绘 GM 窗口(WebView2)+ 背包可见 + 启动器互斥

- 90CN.dll:新增 90CN_gm_window.cpp,自绘标题栏/边框的置顶窗口,内嵌 WebView2 承载 gmweb 页面
- 点小井盖时隐藏游戏内嵌 IE 面板、弹出控制台;关掉后还原,独占全屏压不住时可选 minimize_game
- 窗口每轮确认置顶,避免游戏抢窗口栈后消失
- gmweb:新增背包页,发放后自动跳转并高亮新入包的行(GET /local/dnf-gm/inventory/list)
- 启动器:新增 /console-status,控制台运行时置灰 GM 工具入口并阻止进入
- 服务端指纹回绑新 DLL(1258496 / 7A179B1B…)
This commit is contained in:
hanfuit committed 2026-11-05 13:16:52 +08:00
1 parent e9a3952a9d
commit b22579658e
10 files changed
+1246 -97

No files matched your search

+75 -2
View File
@@ -4,7 +4,7 @@
'use strict';
var S = {
charId: '', charName: '', chars: [],
charId: '', charName: '', chars: [], bagRows: [],
itemId: 0, itemName: '', picked: null, items: [],
page: 1, pageSize: 20, total: 0
};
@@ -66,6 +66,7 @@
for (var j = 0; j < panes.length; j++) {
panes[j].className = 'pane' + (panes[j].id === 'pane-' + name ? ' on' : '');
}
if (name === 'bag') { loadBag(0); }
}
function bindTabs() {
@@ -383,6 +384,10 @@
var data = d.data || {};
log('已发 ' + S.itemName + ' ×' + n + optText(o) +
(data.slot != null ? ',格位 ' + data.list_type + ':' + data.slot : ''), 'ok');
// 发完立刻切到背包页并高亮这一行:到底有没有进包,玩家要看得见
switchTab('bag');
loadBag(S.itemId);
setTimeout(function () { loadBag(S.itemId); }, 600);
});
}
@@ -401,6 +406,64 @@
});
}
/* ---------------------------------------------------------------- 背包 */
/* 背包 / 穿戴的实时快照。GM 发完东西,玩家第一件事就是看它到底进没进包,
所以这里按服务端实际存的行来列,不做任何本地猜测。 */
function loadBag(highlightItemId) {
if (!S.charId) {
$('bag-list').innerHTML = '<div class="empty">先在「角色」页选一个角色</div>';
$('bag-info').innerHTML = '-';
return;
}
api('/local/dnf-gm/inventory/list?character_id=' + encodeURIComponent(S.charId),
'GET', null, function (d) {
if (!d) { return; }
var data = d.data || {};
S.bagRows = data.rows || [];
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() {
@@ -452,6 +515,7 @@
bind('btn-prev', function () { if (S.page > 1) { loadItems(S.page - 1); } });
bind('btn-next', function () { loadItems(S.page + 1); });
bind('btn-grant', grantItem);
bind('btn-bag', function () { loadBag(0); });
bind('btn-mailitem', mailItem);
bind('btn-mail', sendMail);
@@ -473,7 +537,16 @@
}
$('q-char').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadChars(); } };
$('q-item').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadItems(1); } };
$('q-item').onkeydown = function (e) {
e = e || window.event;
// 229 是输入法组合中的键码,这时候的回车是上屏,不能抢。
if (e.keyCode === 229) { return; }
if (e.keyCode === 13) {
if (e.preventDefault) { e.preventDefault(); }
loadItems(1);
return false;
}
};
fillTypeOptions();
+11 -1
View File
@@ -16,6 +16,7 @@
<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="item" href="javascript:void(0)">发物品</a>
<a class="tab" data-tab="bag" href="javascript:void(0)">背包</a>
<a class="tab" data-tab="mail" href="javascript:void(0)">邮件</a>
</div>
@@ -79,7 +80,7 @@
<button class="btn sm" data-lv="61-90">61-90</button>
</div>
<div class="row">
<input id="q-item" class="inp" placeholder="物品名关键字(可留空,用上面分类浏览)">
<textarea id="q-item" class="inp ta" rows="1" placeholder="物品名关键字(可留空,用上面分类浏览)"></textarea>
<select id="in-page-size" class="inp sm">
<option value="10">每页 10 行</option>
<option value="20" selected>每页 20 行</option>
@@ -126,6 +127,15 @@
<div class="hint">强化 0-31、增幅等级 0-31、锻造 0-8。增幅填了就直接覆盖强化(红字装不能同时白字强化),非装备行忽略这些值。</div>
</div>
<div class="pane" id="pane-bag">
<div class="row">
<button id="btn-bag" class="btn">刷新背包</button>
<span id="bag-info" class="pg">-</span>
</div>
<div id="bag-list" class="list fixed"></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>
+12
View File
@@ -53,6 +53,12 @@ table.kv td { padding: 4px 0; border-bottom: 1px solid #232734; color: #dfe3ea;
}
.inp:focus { border-color: #e8c979; ime-mode: active; }
input[type="text"], textarea { ime-mode: active; }
/* 搜索框用 textarea:MSHTML 里它的 IME 走编辑引擎,比 input 更容易触发候选框。
单行用法,所以要自己压高度、禁掉拉伸和平滚动条。 */
.inp.ta {
height: 26px; line-height: 18px; resize: none; overflow: hidden;
padding: 3px 6px; vertical-align: middle;
}
.inp.sm { width: 110px; }
.inp.xs { width: 60px; }
.inp.xs2 { width: 84px; }
@@ -99,3 +105,9 @@ select.inp { padding: 3px 2px; }
#log .err { color: #e07070; }
#log .tip { color: #e8c979; }
.hint { color: #666e80; font-size: 11px; margin-top: 6px; }
/* 背包页:刚发进去那一行的「新」标记 */
.li .new {
font-style: normal; font-size: 11px; padding: 0 4px; margin-left: 4px;
background: #e8c979; color: #1a1e27; border-radius: 2px;
}