- 90CN.dll:新增 90CN_gm_window.cpp,自绘标题栏/边框的置顶窗口,内嵌 WebView2 承载 gmweb 页面 - 点小井盖时隐藏游戏内嵌 IE 面板、弹出控制台;关掉后还原,独占全屏压不住时可选 minimize_game - 窗口每轮确认置顶,避免游戏抢窗口栈后消失 - gmweb:新增背包页,发放后自动跳转并高亮新入包的行(GET /local/dnf-gm/inventory/list) - 启动器:新增 /console-status,控制台运行时置灰 GM 工具入口并阻止进入 - 服务端指纹回绑新 DLL(1258496 / 7A179B1B…)
564 lines
21 KiB
JavaScript
564 lines
21 KiB
JavaScript
/* GM 控制台前端 —— 严格 ES5 + XMLHttpRequest,兼容 IE11 内嵌浏览器。
|
||
所有请求走同源 /api/*,由本地 serve.py 代理到服务端 admin 口并注入 token。 */
|
||
(function () {
|
||
'use strict';
|
||
|
||
var S = {
|
||
charId: '', charName: '', chars: [], bagRows: [],
|
||
itemId: 0, itemName: '', picked: null, items: [],
|
||
page: 1, pageSize: 20, total: 0
|
||
};
|
||
|
||
/* ---------------------------------------------------------------- 基础 */
|
||
|
||
function $(id) { return document.getElementById(id); }
|
||
|
||
function log(msg, cls) {
|
||
var box = $('log');
|
||
var d = document.createElement('div');
|
||
if (cls) { d.className = cls; }
|
||
var t = new Date();
|
||
var p = function (n) { return (n < 10 ? '0' : '') + n; };
|
||
d.innerHTML = '[' + p(t.getHours()) + ':' + p(t.getMinutes()) + ':' + p(t.getSeconds()) + '] ' + esc(msg);
|
||
box.appendChild(d);
|
||
box.scrollTop = box.scrollHeight;
|
||
while (box.childNodes.length > 120) { box.removeChild(box.firstChild); }
|
||
}
|
||
|
||
function esc(s) {
|
||
return String(s == null ? '' : s)
|
||
.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
||
}
|
||
|
||
function api(path, method, body, cb) {
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open(method || 'GET', '/api' + path, true);
|
||
xhr.setRequestHeader('Content-Type', 'application/json');
|
||
xhr.onreadystatechange = function () {
|
||
if (xhr.readyState !== 4) { return; }
|
||
if (xhr.status === 0) { log('连不上本地代理(serve.py 没启动?)', 'err'); cb(null); return; }
|
||
var data = null;
|
||
try { data = JSON.parse(xhr.responseText); } catch (e) { data = null; }
|
||
if (!data) { log('响应不是 JSON:' + xhr.responseText.substring(0, 120), 'err'); cb(null); return; }
|
||
if (data.success === false || data.ok === false) {
|
||
log('失败:' + (data.message || data.error || '未知错误'), 'err');
|
||
cb(null);
|
||
return;
|
||
}
|
||
cb(data);
|
||
};
|
||
xhr.send(body ? JSON.stringify(body) : null);
|
||
}
|
||
|
||
function setOnline(on, text) {
|
||
$('dot').className = 'dot ' + (on ? 'on' : 'off');
|
||
$('stat').innerHTML = esc(text);
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 标签页 */
|
||
|
||
function switchTab(name) {
|
||
var tabs = document.getElementsByClassName('tab');
|
||
for (var i = 0; i < tabs.length; i++) {
|
||
tabs[i].className = 'tab' + (tabs[i].getAttribute('data-tab') === name ? ' on' : '');
|
||
}
|
||
var panes = document.getElementsByClassName('pane');
|
||
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() {
|
||
var tabs = document.getElementsByClassName('tab');
|
||
for (var i = 0; i < tabs.length; i++) {
|
||
tabs[i].onclick = function () { switchTab(this.getAttribute('data-tab')); };
|
||
}
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 状态页 */
|
||
|
||
function loadStatus() {
|
||
api('/dnf/gm/status', 'GET', null, function (d) {
|
||
if (!d) { setOnline(false, '服务端未响应'); return; }
|
||
var v = d.data || {};
|
||
$('v-online').innerHTML = esc(v.online_characters == null ? '-' : v.online_characters);
|
||
$('v-repo').innerHTML = v.repository_ready ? '就绪' : '未就绪';
|
||
$('v-items').innerHTML = v.item_catalog_ready ? '就绪' : '未就绪';
|
||
$('v-cap').innerHTML = esc(v.level_cap == null ? '-' : v.level_cap);
|
||
setOnline(true, '已连接 127.0.0.1:18111');
|
||
});
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 角色 */
|
||
|
||
function renderChars() {
|
||
var box = $('char-list');
|
||
if (!S.chars.length) { box.innerHTML = '<div class="empty">没有角色</div>'; return; }
|
||
var h = [];
|
||
for (var i = 0; i < S.chars.length; i++) {
|
||
var c = S.chars[i];
|
||
h.push('<div class="li' + (String(c.character_id) === String(S.charId) ? ' on' : '') +
|
||
'" data-id="' + esc(c.character_id) + '" data-name="' + esc(c.name) + '" data-gold="' + esc(c.gold) +
|
||
'" data-level="' + esc(c.level) + '"><span class="nm">' + esc(c.name) +
|
||
'</span><span class="meta">Lv.' + esc(c.level) + ' · 金币 ' + esc(c.gold) +
|
||
(c.online ? ' · 在线' : '') + '</span></div>');
|
||
}
|
||
box.innerHTML = h.join('');
|
||
var lis = box.getElementsByClassName('li');
|
||
for (var k = 0; k < lis.length; k++) {
|
||
lis[k].onclick = function () { pickChar(this); };
|
||
}
|
||
}
|
||
|
||
function pickChar(el) {
|
||
S.charId = el.getAttribute('data-id');
|
||
S.charName = el.getAttribute('data-name');
|
||
$('in-gold').value = el.getAttribute('data-gold');
|
||
$('in-level').value = el.getAttribute('data-level');
|
||
$('cur').innerHTML = esc(S.charName) + ' <span style="color:#7c8496">(id=' + esc(S.charId) + ')</span>';
|
||
renderChars();
|
||
log('已选角色:' + S.charName, 'tip');
|
||
}
|
||
|
||
function loadChars() {
|
||
var q = $('q-char').value;
|
||
var path = '/local/dnf-gm/characters' + (q ? '?character_name=' + encodeURIComponent(q) : '');
|
||
api(path, 'GET', null, function (d) {
|
||
if (!d) { return; }
|
||
S.chars = d.data || [];
|
||
renderChars();
|
||
log('角色 ' + S.chars.length + ' 个');
|
||
if (S.chars.length === 1) { pickChar($('char-list').getElementsByClassName('li')[0]); }
|
||
});
|
||
}
|
||
|
||
function setGold() {
|
||
if (!requireChar()) { return; }
|
||
var v = parseInt($('in-gold').value, 10);
|
||
if (isNaN(v) || v < 0) { log('金币要填非负整数', 'err'); return; }
|
||
api('/dnf/gm/characters/' + S.charId + '/gold', 'POST', { value: v }, function (d) {
|
||
if (d) { log('金币已改为 ' + v, 'ok'); loadChars(); }
|
||
});
|
||
}
|
||
|
||
function setLevel() {
|
||
if (!requireChar()) { return; }
|
||
var v = parseInt($('in-level').value, 10);
|
||
if (isNaN(v) || v < 1) { log('等级要填正整数', 'err'); return; }
|
||
api('/dnf/gm/characters/' + S.charId + '/level', 'POST', { level: v }, function (d) {
|
||
if (d) { log('等级已改为 ' + v, 'ok'); loadChars(); }
|
||
});
|
||
}
|
||
|
||
function requireChar() {
|
||
if (!S.charId) { log('先在「角色」页选一个角色', 'err'); return false; }
|
||
return true;
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 物品 */
|
||
|
||
/* 装备类型标签:PVF 里是 "[weapon]" 这种,去掉括号后映射成中文。 */
|
||
var EQUIP_LABEL = {
|
||
'weapon': '武器', 'coat': '上衣', 'shoulder': '头肩', 'pants': '下装',
|
||
'shoes': '鞋', 'waist': '腰带', 'amulet': '项链', 'wrist': '手镯',
|
||
'ring': '戒指', 'support': '辅助装备', 'magic stone': '魔法石',
|
||
'earring': '耳环', 'support weapon': '辅助武器', 'avatar': '时装',
|
||
'creature': '宠物', 'artifact': '神器'
|
||
};
|
||
|
||
/* 与服务器 gmItemSupportsEquipmentAttributes 保持一致:只有这些类型吃得下强化/增幅。 */
|
||
var UPGRADABLE = {
|
||
'weapon': 1, 'coat': 1, 'shoulder': 1, 'pants': 1, 'shoes': 1, 'waist': 1,
|
||
'amulet': 1, 'wrist': 1, 'ring': 1, 'support': 1, 'magic stone': 1,
|
||
'earring': 1, 'support weapon': 1
|
||
};
|
||
|
||
function equipTag(value) {
|
||
return String(value == null ? '' : value)
|
||
.replace(/[\[\]`]/g, '').replace(/^\s+|\s+$/g, '').toLowerCase();
|
||
}
|
||
|
||
function kindText(it) {
|
||
if (!it) { return ''; }
|
||
if (it.kind !== 'equipment') { return '道具'; }
|
||
var tag = equipTag(it.equipment_type);
|
||
return '装备 · ' + (EQUIP_LABEL[tag] || tag || '未知');
|
||
}
|
||
|
||
function canUpgrade(it) {
|
||
if (!it || it.kind !== 'equipment') { return false; }
|
||
return UPGRADABLE[equipTag(it.equipment_type)] === 1;
|
||
}
|
||
|
||
function clamp(v, lo, hi) {
|
||
if (isNaN(v)) { return lo; }
|
||
return v < lo ? lo : (v > hi ? hi : v);
|
||
}
|
||
|
||
function renderItems() {
|
||
var box = $('item-list');
|
||
var pages = S.total > 0 ? Math.ceil(S.total / S.pageSize) : 1;
|
||
$('page-info').innerHTML = '第 ' + S.page + ' / ' + pages + ' 页 · 共 ' + S.total + ' 条';
|
||
$('btn-prev').disabled = S.page <= 1;
|
||
$('btn-next').disabled = S.page >= pages;
|
||
|
||
if (!S.items.length) {
|
||
box.innerHTML = '<div class="empty">没有匹配的物品</div>';
|
||
return;
|
||
}
|
||
var h = [];
|
||
for (var i = 0; i < S.items.length; i++) {
|
||
var it = S.items[i];
|
||
h.push('<div class="li' + (it.item_id === S.itemId ? ' on' : '') +
|
||
'" data-i="' + i + '">' +
|
||
'<span class="nm">' + esc(it.name) + '</span>' +
|
||
'<span class="meta">id ' + esc(it.item_id) + ' · ' + esc(kindText(it)) +
|
||
(canUpgrade(it) ? '' : '') + '</span></div>');
|
||
}
|
||
box.innerHTML = h.join('');
|
||
var lis = box.getElementsByClassName('li');
|
||
for (var k = 0; k < lis.length; k++) {
|
||
lis[k].onclick = function () {
|
||
var index = parseInt(this.getAttribute('data-i'), 10);
|
||
pickItem(S.items[index]);
|
||
};
|
||
}
|
||
}
|
||
|
||
function renderPicked() {
|
||
var box = $('picked');
|
||
if (!S.itemId) {
|
||
box.innerHTML = '未选择物品 —— 先搜索,再点一行';
|
||
} else {
|
||
var it = S.picked;
|
||
var hint = '';
|
||
if (it && it.kind === 'equipment') {
|
||
hint = canUpgrade(it) ? '可强化 / 增幅' : '该类型不支持强化(时装类)';
|
||
} else {
|
||
hint = '普通道具,属性值会被忽略';
|
||
}
|
||
box.innerHTML = esc(S.itemName) + ' <span class="dim">(id ' + esc(S.itemId) + ')</span> · ' + esc(hint);
|
||
}
|
||
var off = !canUpgrade(S.picked);
|
||
$('in-reinforce').disabled = off;
|
||
$('in-amplify').disabled = off;
|
||
$('in-amplify-level').disabled = off;
|
||
$('in-forging').disabled = off;
|
||
$('in-quality').disabled = off;
|
||
}
|
||
|
||
function pickItem(it) {
|
||
if (!it) { return; }
|
||
S.itemId = it.item_id;
|
||
S.itemName = it.name;
|
||
S.picked = it;
|
||
renderItems();
|
||
renderPicked();
|
||
log('已选物品:' + it.name + '(id ' + it.item_id + ')', 'tip');
|
||
}
|
||
|
||
/* 分组 -> 类型选项。装备/时装/宠物走 tag(PVF 的 equipment type),
|
||
道具走 segment(服务端按 stackable type 归类成中文段)。
|
||
有了这两级下拉,不敲键盘也能把范围缩到十几页。 */
|
||
var TYPE_OPTIONS = {
|
||
equipment: [
|
||
['tag:weapon', '武器'], ['tag:coat', '上衣'], ['tag:shoulder', '头肩'],
|
||
['tag:pants', '下装'], ['tag:shoes', '鞋'], ['tag:waist', '腰带'],
|
||
['tag:amulet', '项链'], ['tag:wrist', '手镯'], ['tag:ring', '戒指'],
|
||
['tag:support', '辅助装备'], ['tag:magic stone', '魔法石'],
|
||
['tag:earring', '耳环'], ['tag:support weapon', '辅助武器'],
|
||
['tag:title name', '称号'], ['tag:name tag', '名牌']
|
||
],
|
||
avatar: [
|
||
['tag:hat avatar', '帽子'], ['tag:hair avatar', '头发'], ['tag:face avatar', '脸部'],
|
||
['tag:coat avatar', '上衣'], ['tag:breast avatar', '胸部'], ['tag:waist avatar', '腰部'],
|
||
['tag:pants avatar', '下装'], ['tag:shoes avatar', '鞋'], ['tag:skin avatar', '皮肤'],
|
||
['tag:aurora avatar', '光环'], ['tag:weapon avatar', '武器装扮']
|
||
],
|
||
pet: [
|
||
['tag:creature', '宠物'], ['tag:artifact red', '神器·红'],
|
||
['tag:artifact blue', '神器·蓝'], ['tag:artifact green', '神器·绿']
|
||
],
|
||
stackable: [
|
||
['seg:消耗品', '消耗品'], ['seg:材料', '材料'], ['seg:特殊材料', '特殊材料'],
|
||
['seg:任务品', '任务品'], ['seg:徽章', '徽章'], ['seg:副职业材料', '副职业材料']
|
||
]
|
||
};
|
||
|
||
function fillTypeOptions() {
|
||
var opts = TYPE_OPTIONS[$('in-group').value] || [];
|
||
var h = ['<option value="">不限</option>'];
|
||
for (var i = 0; i < opts.length; i++) {
|
||
h.push('<option value="' + esc(opts[i][0]) + '">' + esc(opts[i][1]) + '</option>');
|
||
}
|
||
$('in-tag').innerHTML = h.join('');
|
||
}
|
||
|
||
function loadItems(page) {
|
||
var q = $('q-item').value;
|
||
var size = parseInt($('in-page-size').value, 10) || 20;
|
||
var p = page || 1;
|
||
if (p < 1) { p = 1; }
|
||
S.pageSize = size;
|
||
|
||
var group = $('in-group').value;
|
||
var picked = $('in-tag').value;
|
||
var minLv = parseInt($('in-lv-min').value, 10);
|
||
var maxLv = parseInt($('in-lv-max').value, 10);
|
||
if (isNaN(minLv)) { minLv = 0; }
|
||
if (isNaN(maxLv)) { maxLv = 0; }
|
||
|
||
var qs = 'limit=' + size + '&offset=' + ((p - 1) * size) +
|
||
'&q=' + encodeURIComponent(q || '');
|
||
if (group) { qs += '&group=' + encodeURIComponent(group); }
|
||
if (picked) {
|
||
if (picked.indexOf('tag:') === 0) {
|
||
qs += '&tag=' + encodeURIComponent(picked.substring(4));
|
||
} else if (picked.indexOf('seg:') === 0) {
|
||
qs += '&segment=' + encodeURIComponent(picked.substring(4));
|
||
}
|
||
}
|
||
if (minLv > 0) { qs += '&min_level=' + minLv; }
|
||
if (maxLv > 0) { qs += '&max_level=' + maxLv; }
|
||
|
||
api('/dnf/gm/items/page?' + qs, 'GET', null, function (d) {
|
||
if (!d) { return; }
|
||
var pageData = d.data || {};
|
||
S.items = pageData.items || [];
|
||
S.total = pageData.total || 0;
|
||
S.page = p;
|
||
renderItems();
|
||
log('物品 ' + S.items.length + ' 条 / 共 ' + S.total + ' 条');
|
||
});
|
||
}
|
||
|
||
/* 组装强化/增幅/锻造/品质:增幅优先,红字装不再叠加白字强化。 */
|
||
function readOptions() {
|
||
var out = { reinforce: 0, amplify_type: 0, amplify_level: 0, forging: 0, equipment_quality: '' };
|
||
if (!canUpgrade(S.picked)) { return out; }
|
||
var amp = parseInt($('in-amplify').value, 10) || 0;
|
||
var ampLevel = parseInt($('in-amplify-level').value, 10) || 0;
|
||
var rein = parseInt($('in-reinforce').value, 10) || 0;
|
||
var forge = parseInt($('in-forging').value, 10) || 0;
|
||
if (amp > 0) {
|
||
out.amplify_type = clamp(amp, 1, 4);
|
||
out.amplify_level = clamp(ampLevel, 0, 31);
|
||
out.forging = 0;
|
||
} else {
|
||
out.reinforce = clamp(rein, 0, 31);
|
||
out.forging = clamp(forge, 0, 8);
|
||
}
|
||
out.equipment_quality = $('in-quality').value || '';
|
||
return out;
|
||
}
|
||
|
||
/* 增幅属性编号取自 etc/amplifyitem.etc 的 [option mapping table]。 */
|
||
var AMP_LABEL = { 1: '物防', 2: '魔防', 3: '物攻', 4: '魔攻' };
|
||
|
||
function optText(o) {
|
||
var parts = [];
|
||
if (o.amplify_type > 0) {
|
||
parts.push('增幅' + (AMP_LABEL[o.amplify_type] || o.amplify_type) + ' +' + o.amplify_level);
|
||
} else if (o.reinforce > 0) {
|
||
parts.push('强化 +' + o.reinforce);
|
||
}
|
||
if (o.forging > 0) { parts.push('锻造 +' + o.forging); }
|
||
if (o.equipment_quality) {
|
||
parts.push('品质' + (o.equipment_quality === 'top' ? '最上级' : '随机'));
|
||
}
|
||
return parts.length ? '(' + parts.join(',') + ')' : '';
|
||
}
|
||
|
||
function grantItem() {
|
||
if (!requireChar()) { return; }
|
||
if (!S.itemId) { log('先搜一个物品并选中', 'err'); return; }
|
||
var n = parseInt($('in-count').value, 10) || 1;
|
||
var o = readOptions();
|
||
api('/local/dnf-gm/item/grant-options', 'POST', {
|
||
character_id: String(S.charId), item_id: S.itemId, count: n,
|
||
reinforce: o.reinforce, amplify_type: o.amplify_type, amplify_level: o.amplify_level,
|
||
forging: o.forging, equipment_quality: o.equipment_quality
|
||
}, function (d) {
|
||
if (!d) { return; }
|
||
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);
|
||
});
|
||
}
|
||
|
||
function mailItem() {
|
||
if (!requireChar()) { return; }
|
||
if (!S.itemId) { log('先搜一个物品并选中', 'err'); return; }
|
||
var n = parseInt($('in-count').value, 10) || 1;
|
||
var o = readOptions();
|
||
api('/dnf/gm/characters/' + S.charId + '/mail', 'POST', {
|
||
item_id: S.itemId, count: n, title: 'GM 物品', body: 'GM 发放',
|
||
expire_days: 7, reinforce: o.reinforce, amplify_type: o.amplify_type,
|
||
amplify_level: o.amplify_level, equipment_quality: o.equipment_quality,
|
||
forging: o.forging, emblem_item_ids: [], enchant_bead_item_id: 0
|
||
}, function (d) {
|
||
if (d) { log('已邮寄 ' + S.itemName + ' ×' + n + optText(o), 'ok'); }
|
||
});
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 背包 */
|
||
|
||
/* 背包 / 穿戴的实时快照。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() {
|
||
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'); }
|
||
});
|
||
}
|
||
|
||
/* ---------------------------------------------------------------- 启动 */
|
||
|
||
function bind(id, fn) {
|
||
var el = $(id);
|
||
if (el) { el.onclick = fn; }
|
||
}
|
||
|
||
/* 内嵌 IE 控件的输入法常被游戏主窗口占着,focus 时再主动要一次。
|
||
ime-mode:active 是 IE 私有属性,别的浏览器会忽略,不影响兼容。 */
|
||
function forceIME() {
|
||
var els = document.getElementsByTagName('input');
|
||
for (var i = 0; i < els.length; i++) {
|
||
els[i].onfocus = function () {
|
||
try { this.style.imeMode = 'active'; } catch (e) { }
|
||
};
|
||
}
|
||
}
|
||
|
||
function init() {
|
||
bindTabs();
|
||
|
||
bind('btn-refresh', function () { loadStatus(); log('刷新状态'); });
|
||
bind('btn-resync', function () {
|
||
if (!requireChar()) { return; }
|
||
api('/local/quest-resync', 'POST', { character_id: parseInt(S.charId, 10) }, function (d) {
|
||
if (d) { log('角色数据已同步', 'ok'); }
|
||
});
|
||
});
|
||
bind('btn-search-char', loadChars);
|
||
bind('btn-gold', setGold);
|
||
bind('btn-level', setLevel);
|
||
bind('btn-search-item', function () { loadItems(1); });
|
||
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);
|
||
|
||
$('in-page-size').onchange = function () { loadItems(1); };
|
||
$('in-group').onchange = function () { fillTypeOptions(); loadItems(1); };
|
||
$('in-tag').onchange = function () { loadItems(1); };
|
||
|
||
/* 等级快捷档:点一下就把区间填好并重新查,省得手敲。 */
|
||
var lvBtns = document.getElementsByTagName('button');
|
||
for (var b = 0; b < lvBtns.length; b++) {
|
||
var spec = lvBtns[b].getAttribute('data-lv');
|
||
if (!spec) { continue; }
|
||
lvBtns[b].onclick = function () {
|
||
var pair = this.getAttribute('data-lv').split('-');
|
||
$('in-lv-min').value = pair[0];
|
||
$('in-lv-max').value = pair[1];
|
||
loadItems(1);
|
||
};
|
||
}
|
||
|
||
$('q-char').onkeydown = function (e) { if ((e || window.event).keyCode === 13) { loadChars(); } };
|
||
$('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();
|
||
|
||
renderPicked();
|
||
forceIME();
|
||
log('GM 控制台已加载', 'tip');
|
||
loadStatus();
|
||
loadChars();
|
||
setInterval(loadStatus, 15000);
|
||
}
|
||
|
||
if (window.addEventListener) { window.addEventListener('load', init, false); }
|
||
else { window.attachEvent('onload', init); }
|
||
})();
|