启动器新增游戏设置页(频道开关+活动开关),窗口标题改名
- 服务端新增活动开关运行时配置与 GM 接口(GET/POST /dnf/gm/activity-switches), 持久化到 Settings,接入月签到/周签到/PC房↔累计在线的实时下发 - 服务端新增频道开关 GM 接口(channel-switches),目录生成时过滤禁用频道 - channelcatalog/channelinfo 支持按禁用频道 ID 过滤目录与客户端脚本 - 启动器新增「游戏设置」页面:频道开关 Tab + 活动开关 Tab - native.py/server.py 新增对应转发 API - 窗口标题/页面 title/侧边栏「90轮回」统一改为「DNF90 灵狐小竹整合单机版」
This commit is contained in:
1 parent
977a4a1196
commit
0ced464168
15 files changed
+1022
-14
No files matched your search
+23
-4
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>90轮回</title>
|
||||
<title>DNF90 灵狐小竹整合单机版</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<link rel="stylesheet" href="/shell.css">
|
||||
</head>
|
||||
@@ -22,12 +22,13 @@
|
||||
<header class="shell-topnav">
|
||||
<div class="shell-brand">
|
||||
<div class="shell-logo">90</div>
|
||||
<div><b>90轮回</b><span>启动器 <em>v2.1.0</em></span></div>
|
||||
<div><b>灵狐小竹</b><span>启动器 <em>v2.1.0</em></span></div>
|
||||
</div>
|
||||
<nav class="shell-nav">
|
||||
<button class="shell-nav-item active" data-shell-page="start"><span class="nav-ico">▶</span>开始</button>
|
||||
<button class="shell-nav-item" data-shell-page="gm"><span class="nav-ico">⚙</span>GM工具</button>
|
||||
<button class="shell-nav-item" data-shell-page="loadout"><span class="nav-ico">🔄</span>一键换装</button>
|
||||
<button class="shell-nav-item" data-shell-page="gamesettings"><span class="nav-ico">🎮</span>游戏设置</button>
|
||||
<div class="shell-nav-gap"></div>
|
||||
<button class="shell-nav-item" data-shell-page="settings"><span class="nav-ico">⚙</span>设置</button>
|
||||
<button class="shell-nav-item" data-shell-page="changelog"><span class="nav-ico">📜</span>游戏更新</button>
|
||||
@@ -48,7 +49,7 @@
|
||||
<div class="hero-bg"></div>
|
||||
<div class="hero-content">
|
||||
<div class="hero-kicker">DNF · 90级 · 本地服务端</div>
|
||||
<h1 id="shellServerName">Server90轮回</h1>
|
||||
<h1 id="shellServerName">DNF90 灵狐小竹整合单机版</h1>
|
||||
<div class="hero-status">
|
||||
<span class="live-dot" id="shellStatusDot"></span>
|
||||
<span id="shellStatusText">检测中…</span>
|
||||
@@ -122,6 +123,24 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell-page" id="page-gamesettings">
|
||||
<div class="page-pad">
|
||||
<div class="page-heading"><div><h2>游戏设置</h2><p class="muted">调整服务端频道与活动;改动实时生效,无需重启。</p></div><button class="shell-btn" id="gsRefresh">刷新</button></div>
|
||||
<div class="st-tabs" id="gsTabs">
|
||||
<button class="st-tab active" data-gs="channels">频道开关</button>
|
||||
<button class="st-tab" data-gs="activities">活动开关</button>
|
||||
</div>
|
||||
<div id="gsChannelsPane">
|
||||
<div class="gs-summary muted" id="gsChannelSummary">正在加载频道…</div>
|
||||
<div class="gs-grid" id="gsChannelGrid"><div class="empty-box">加载中…</div></div>
|
||||
</div>
|
||||
<div id="gsActivitiesPane" style="display:none">
|
||||
<div class="gs-summary muted" id="gsActivitySummary">正在加载活动…</div>
|
||||
<div class="gs-list" id="gsActivityList"><div class="empty-box">加载中…</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell-page" id="page-changelog">
|
||||
<div class="page-pad"><h2>游戏更新</h2><p class="muted">从自托管仓库(Lingbao/90CNServer)的发布记录自动获取。</p><div id="changelogBody"></div></div>
|
||||
</section>
|
||||
@@ -160,7 +179,7 @@
|
||||
<div class="win-resize nw" data-edge="topleft"></div><div class="win-resize ne" data-edge="topright"></div>
|
||||
<div class="win-resize sw" data-edge="bottomleft"></div><div class="win-resize se" data-edge="bottomright"></div>
|
||||
|
||||
<div class="modal-mask hidden" id="sponsorModal"><div class="modal"><div class="modal-head"><span>赞助我们</span><button class="modal-close" id="btnCloseSponsor">×</button></div><div class="modal-body"><p class="sponsor-tip">感谢支持 DNF90 单机模拟器!</p><div class="qrcode-box" id="qrcodeBox"></div></div></div></div>
|
||||
<div class="modal-mask hidden" id="sponsorModal"><div class="modal"><div class="modal-head"><span>赞助我们</span><button class="modal-close" id="btnCloseSponsor">×</button></div><div class="modal-body"><p class="sponsor-tip">感谢支持 DNF90 灵狐小竹整合单机版!</p><div class="qrcode-box" id="qrcodeBox"></div></div></div></div>
|
||||
<div class="modal-mask hidden" id="accountsModal"><div class="modal" style="width:520px"><div class="modal-head"><span>账号管理</span><button class="modal-close" id="btnCloseAccounts" onclick="document.getElementById('accountsModal').classList.add('hidden')">×</button></div><div class="modal-body" style="text-align:left"><div id="accountsList" style="margin-bottom:16px"></div><div class="accounts-add"><div class="accounts-add-title">添加新账号</div><div class="gm-form-row"><div class="gm-field"><label>用户名</label><input class="gm-input" id="newAccUser"></div><div class="gm-field"><label>密码</label><input class="gm-input" id="newAccPass" type="password"></div></div><button class="gm-btn gm-btn-primary" id="btnAddAccount">+ 添加账号</button></div></div></div></div>
|
||||
|
||||
<script src="/app.js"></script>
|
||||
|
||||
@@ -622,3 +622,22 @@ class NativeClient(object):
|
||||
def loadout_apply(self, character_id, slots):
|
||||
return self._unwrap(self._post("/local/dnf-gm/loadout/apply", {
|
||||
"character_id": str(character_id), "slots": slots}))
|
||||
|
||||
# ---- 游戏设置:活动开关 / 频道开关(Native,服务端在线)--------
|
||||
def get_activity_switches(self):
|
||||
"""GET /dnf/gm/activity-switches -> GMActivitySwitches"""
|
||||
return self._unwrap(self._get("/dnf/gm/activity-switches"))
|
||||
|
||||
def set_activity_switch(self, key, enabled):
|
||||
"""POST /dnf/gm/activity-switches {key:str, enabled:bool}"""
|
||||
return self._unwrap(self._post("/dnf/gm/activity-switches", {
|
||||
"key": str(key), "enabled": bool(enabled)}))
|
||||
|
||||
def get_channel_switches(self):
|
||||
"""GET /dnf/gm/channel-switches -> GMChannelSwitches"""
|
||||
return self._unwrap(self._get("/dnf/gm/channel-switches"))
|
||||
|
||||
def set_channel_switch(self, channel_id, enabled):
|
||||
"""POST /dnf/gm/channel-switch {channel_id:int, enabled:bool}"""
|
||||
return self._unwrap(self._post("/dnf/gm/channel-switch", {
|
||||
"channel_id": int(channel_id), "enabled": bool(enabled)}))
|
||||
+64
-1
@@ -578,6 +578,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._api_character_black_diamond(qs)
|
||||
elif path == "/api/gameplay":
|
||||
self._api_gameplay(qs)
|
||||
elif path == "/api/game-settings/activity-switches":
|
||||
self._api_activity_switches()
|
||||
elif path == "/api/game-settings/channel-switches":
|
||||
self._api_channel_switches()
|
||||
elif path == "/api/quests":
|
||||
self._api_quests(qs)
|
||||
elif path == "/api/quests/search":
|
||||
@@ -638,6 +642,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._api_gameplay_fatigue_mode(data)
|
||||
elif path == "/api/gameplay/burning":
|
||||
self._api_gameplay_burning(data)
|
||||
elif path == "/api/game-settings/activity-switch":
|
||||
self._api_activity_switch(data)
|
||||
elif path == "/api/game-settings/channel-switch":
|
||||
self._api_channel_switch(data)
|
||||
elif path == "/api/character/fatigue/refill":
|
||||
self._api_fatigue_refill(data)
|
||||
elif path == "/api/quests/action":
|
||||
@@ -1214,6 +1222,61 @@ class Handler(BaseHTTPRequestHandler):
|
||||
except Exception as e:
|
||||
self._json({"error": str(e)}, 500)
|
||||
|
||||
def _api_activity_switches(self):
|
||||
if not self._require_native():
|
||||
return
|
||||
try:
|
||||
res = NATIVE.get_activity_switches()
|
||||
self._json({"ok": True, "data": res})
|
||||
except NativeError as e:
|
||||
self._json({"error": str(e)}, 502)
|
||||
except Exception as e:
|
||||
self._json({"error": str(e)}, 500)
|
||||
|
||||
def _api_activity_switch(self, data):
|
||||
if not self._require_native():
|
||||
return
|
||||
key = data.get("key", "")
|
||||
enabled = bool(data.get("enabled", False))
|
||||
if not key:
|
||||
self._json({"error": "missing key"}, 400)
|
||||
return
|
||||
try:
|
||||
res = NATIVE.set_activity_switch(key, enabled)
|
||||
self._json({"ok": True, "data": res})
|
||||
except NativeError as e:
|
||||
self._json({"error": str(e)}, 502)
|
||||
except Exception as e:
|
||||
self._json({"error": str(e)}, 500)
|
||||
|
||||
def _api_channel_switches(self):
|
||||
if not self._require_native():
|
||||
return
|
||||
try:
|
||||
res = NATIVE.get_channel_switches()
|
||||
self._json({"ok": True, "data": res})
|
||||
except NativeError as e:
|
||||
self._json({"error": str(e)}, 502)
|
||||
except Exception as e:
|
||||
self._json({"error": str(e)}, 500)
|
||||
|
||||
def _api_channel_switch(self, data):
|
||||
if not self._require_native():
|
||||
return
|
||||
try:
|
||||
channel_id = int(data.get("channel_id"))
|
||||
except (TypeError, ValueError):
|
||||
self._json({"error": "missing or invalid channel_id"}, 400)
|
||||
return
|
||||
enabled = bool(data.get("enabled", False))
|
||||
try:
|
||||
res = NATIVE.set_channel_switch(channel_id, enabled)
|
||||
self._json({"ok": True, "data": res})
|
||||
except NativeError as e:
|
||||
self._json({"error": str(e)}, 502)
|
||||
except Exception as e:
|
||||
self._json({"error": str(e)}, 500)
|
||||
|
||||
def _api_dungeon_unlock(self, data):
|
||||
char_id = str(data.get("id", ""))
|
||||
kind = data.get("kind", "difficulties")
|
||||
@@ -2221,7 +2284,7 @@ def show_window(url):
|
||||
|
||||
# 主窗口:启动器(无边框,屏幕居中)
|
||||
window = webview.create_window(
|
||||
"90轮回",
|
||||
"DNF90 灵狐小竹整合单机版",
|
||||
url,
|
||||
width=1204,
|
||||
height=717,
|
||||
|
||||
@@ -371,3 +371,31 @@ body.gm-fullscreen .win-resize{display:none}
|
||||
.shell-win-controls .tb-btn:hover{background:rgba(255,255,255,.1);color:#fff}
|
||||
.shell-win-controls .tb-close:hover{background:var(--danger);color:#fff}
|
||||
|
||||
/* ================= 游戏设置(频道开关 / 活动开关) ================= */
|
||||
.st-tabs{display:flex;gap:6px;margin:14px 0 16px;border-bottom:1px solid var(--line)}
|
||||
.st-tab{appearance:none;background:transparent;border:0;border-bottom:2px solid transparent;
|
||||
color:var(--muted);padding:8px 16px;cursor:pointer;font-size:14px}
|
||||
.st-tab:hover{color:var(--text)}
|
||||
.st-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:500}
|
||||
.gs-summary{font-size:12px;margin-bottom:12px}
|
||||
.gs-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px}
|
||||
.gs-card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px}
|
||||
.gs-card.gs-off{opacity:.72}
|
||||
.gs-card-head{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-bottom:6px}
|
||||
.gs-card-name{font-weight:600;color:var(--text)}
|
||||
.gs-card-meta{font-size:12px;margin-bottom:12px;min-height:17px}
|
||||
.gs-card-actions{display:flex;gap:8px}
|
||||
.gs-list{display:flex;flex-direction:column;gap:10px}
|
||||
.gs-row{display:flex;align-items:center;justify-content:space-between;gap:16px;
|
||||
background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:14px 16px}
|
||||
.gs-row.gs-off{opacity:.72}
|
||||
.gs-row-main{flex:1;min-width:0}
|
||||
.gs-row-name{font-weight:600;color:var(--text);margin-bottom:3px}
|
||||
.gs-row-desc{font-size:12px}
|
||||
.gs-switch{position:relative;width:44px;height:24px;border-radius:12px;border:0;
|
||||
background:rgba(255,255,255,.12);cursor:pointer;flex-shrink:0;transition:background .2s;padding:0}
|
||||
.gs-switch .gs-knob{position:absolute;top:2px;left:2px;width:20px;height:20px;border-radius:50%;
|
||||
background:#fff;transition:left .2s;box-shadow:0 1px 3px rgba(0,0,0,.3)}
|
||||
.gs-switch.on{background:var(--primary)}
|
||||
.gs-switch.on .gs-knob{left:22px}
|
||||
|
||||
+169
-2
@@ -1,13 +1,13 @@
|
||||
/* 90轮回启动器 · 单窗口页面与工具整合 */
|
||||
(function () {
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const pages = ['start','gm','loadout','settings','changelog'];
|
||||
const pages = ['start','gm','loadout','gamesettings','settings','changelog'];
|
||||
let shellGmInit = false;
|
||||
let loConfig = null, loPage = 0, loScheme = -1;
|
||||
let loCooldownUntil = 0, loCooldownCharacter = '', loCooldownTimer = null;
|
||||
let hotkeyCapture = false;
|
||||
|
||||
const crumb = {start:'开始 / 本地服务端',gm:'GM工具',loadout:'工具 / 一键换装',settings:'设置',changelog:'更新日志'};
|
||||
const crumb = {start:'开始 / 本地服务端',gm:'GM工具',loadout:'工具 / 一键换装',gamesettings:'工具 / 游戏设置',settings:'设置',changelog:'更新日志'};
|
||||
|
||||
function shellGo(page) {
|
||||
if (!pages.includes(page)) page = 'start';
|
||||
@@ -16,6 +16,7 @@
|
||||
$('shellCrumb').textContent = crumb[page] || page;
|
||||
if (page === 'start') loadStart();
|
||||
if (page === 'loadout') loadLoadout();
|
||||
if (page === 'gamesettings') loadGameSettings();
|
||||
if (page === 'settings') loadSettings();
|
||||
if (page === 'changelog') loadChangelog();
|
||||
if (page === 'gm') {
|
||||
@@ -488,6 +489,172 @@
|
||||
}
|
||||
$('setRefresh').addEventListener('click', loadSettings);
|
||||
|
||||
// ==================== 游戏设置(频道开关 / 活动开关) ====================
|
||||
let gsCurrentTab = 'channels';
|
||||
const gsToast = (msg, type = 'info') => {
|
||||
if (typeof gmToast === 'function') gmToast(msg, type);
|
||||
else console.log(msg);
|
||||
};
|
||||
|
||||
function gsSwitchTab(tab) {
|
||||
gsCurrentTab = tab;
|
||||
document.querySelectorAll('#gsTabs .st-tab').forEach(el =>
|
||||
el.classList.toggle('active', el.dataset.gs === tab));
|
||||
$('gsChannelsPane').style.display = tab === 'channels' ? '' : 'none';
|
||||
$('gsActivitiesPane').style.display = tab === 'activities' ? '' : 'none';
|
||||
}
|
||||
|
||||
function gsChannelTypeText(type) {
|
||||
const t = Number(type);
|
||||
if (t === 22) return '特殊区域';
|
||||
if (t === 11) return '普通城镇';
|
||||
if (t === 2 || t === 3) return '引导';
|
||||
return '频道 ' + t;
|
||||
}
|
||||
|
||||
async function gsLoadChannels() {
|
||||
const grid = $('gsChannelGrid');
|
||||
const summary = $('gsChannelSummary');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '<div class="empty-box">加载中…</div>';
|
||||
try {
|
||||
const r = await api('/api/game-settings/channel-switches');
|
||||
if (!r.ok) {
|
||||
grid.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(r.error || '未知错误') + '</div>';
|
||||
summary.textContent = '';
|
||||
return;
|
||||
}
|
||||
const d = r.data || {};
|
||||
const list = d.channels || [];
|
||||
summary.textContent = '共 ' + (d.total_channels ?? list.length) + ' 个频道 · 已启用 ' +
|
||||
(d.enabled_channels ?? '—') + ' · 已停用 ' + (d.disabled_channels ?? '—');
|
||||
if (!list.length) {
|
||||
grid.innerHTML = '<div class="empty-box">暂无频道数据。请确认服务端已启动。</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = list.map(ch => {
|
||||
const on = !!ch.enabled;
|
||||
return `<div class="gs-card ${on ? '' : 'gs-off'}">
|
||||
<div class="gs-card-head">
|
||||
<span class="gs-card-name">${escapeHtml(ch.name || ('频道 ' + ch.id))}</span>
|
||||
<span class="gm-tag ${on ? 'gm-tag-green' : 'gm-tag-gray'}">${on ? '启用' : '停用'}</span>
|
||||
</div>
|
||||
<div class="gs-card-meta muted">ID ${ch.id} · ${gsChannelTypeText(ch.type)}${ch.group ? ' · ' + escapeHtml(ch.group) : ''}${ch.port ? ' · 端口 ' + ch.port : ''}</div>
|
||||
<div class="gs-card-actions">
|
||||
<button class="shell-btn ${on ? '' : 'primary'}" data-gs-channel="${ch.id}" data-gs-enable="${!on}">${on ? '停用' : '启用'}</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
} catch (e) {
|
||||
grid.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(e && e.message ? e.message : e) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function gsLoadActivities() {
|
||||
const list = $('gsActivityList');
|
||||
const summary = $('gsActivitySummary');
|
||||
if (!list) return;
|
||||
list.innerHTML = '<div class="empty-box">加载中…</div>';
|
||||
try {
|
||||
const r = await api('/api/game-settings/activity-switches');
|
||||
if (!r.ok) {
|
||||
list.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(r.error || '未知错误') + '</div>';
|
||||
summary.textContent = '';
|
||||
return;
|
||||
}
|
||||
const d = r.data || {};
|
||||
const items = d.switches || [];
|
||||
const onCount = items.filter(x => x.enabled).length;
|
||||
summary.textContent = '共 ' + items.length + ' 个活动 · 已开启 ' + onCount;
|
||||
if (!items.length) {
|
||||
list.innerHTML = '<div class="empty-box">暂无活动数据。请确认服务端已启动。</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = items.map(it => {
|
||||
const on = !!it.enabled;
|
||||
return `<div class="gs-row ${on ? '' : 'gs-off'}">
|
||||
<div class="gs-row-main">
|
||||
<div class="gs-row-name">${escapeHtml(it.title || it.key)}</div>
|
||||
<div class="gs-row-desc muted">${escapeHtml(it.description || '')}</div>
|
||||
</div>
|
||||
<button class="gs-switch ${on ? 'on' : ''}" data-gs-activity="${escapeHtml(it.key)}" data-gs-enable="${!on}" role="switch" aria-checked="${on}">
|
||||
<span class="gs-knob"></span>
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
} catch (e) {
|
||||
list.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(e && e.message ? e.message : e) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGameSettings() {
|
||||
gsSwitchTab(gsCurrentTab);
|
||||
if (gsCurrentTab === 'channels') await gsLoadChannels();
|
||||
else await gsLoadActivities();
|
||||
}
|
||||
|
||||
// 刷新按钮
|
||||
$('gsRefresh').addEventListener('click', () => {
|
||||
if (gsCurrentTab === 'channels') gsLoadChannels();
|
||||
else gsLoadActivities();
|
||||
});
|
||||
|
||||
// Tab 切换
|
||||
document.querySelectorAll('#gsTabs .st-tab').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
gsSwitchTab(el.dataset.gs);
|
||||
if (gsCurrentTab === 'channels') gsLoadChannels();
|
||||
else gsLoadActivities();
|
||||
});
|
||||
});
|
||||
|
||||
// 频道开关点击(事件委托)
|
||||
$('gsChannelGrid').addEventListener('click', async (e) => {
|
||||
const btn = e.target.closest('button[data-gs-channel]');
|
||||
if (!btn) return;
|
||||
const id = Number(btn.dataset.gsChannel);
|
||||
const enable = btn.dataset.gsEnable === 'true';
|
||||
btn.disabled = true;
|
||||
btn.textContent = '处理中…';
|
||||
try {
|
||||
const r = await post('/api/game-settings/channel-switch', { channel_id: id, enabled: enable });
|
||||
if (r.ok) {
|
||||
gsToast(enable ? '频道已启用' : '频道已停用', 'success');
|
||||
await gsLoadChannels();
|
||||
} else {
|
||||
gsToast('操作失败:' + (r.error || '未知错误'), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = enable ? '启用' : '停用';
|
||||
}
|
||||
} catch (e) {
|
||||
gsToast('操作失败:' + (e && e.message ? e.message : e), 'error');
|
||||
btn.disabled = false;
|
||||
btn.textContent = enable ? '启用' : '停用';
|
||||
}
|
||||
});
|
||||
|
||||
// 活动开关点击(事件委托)
|
||||
$('gsActivityList').addEventListener('click', async (e) => {
|
||||
const sw = e.target.closest('button[data-gs-activity]');
|
||||
if (!sw) return;
|
||||
const key = sw.dataset.gsActivity;
|
||||
const enable = sw.dataset.gsEnable === 'true';
|
||||
sw.disabled = true;
|
||||
try {
|
||||
const r = await post('/api/game-settings/activity-switch', { key: key, enabled: enable });
|
||||
if (r.ok) {
|
||||
gsToast(enable ? '活动已开启' : '活动已关闭', 'success');
|
||||
await gsLoadActivities();
|
||||
} else {
|
||||
gsToast('操作失败:' + (r.error || '未知错误'), 'error');
|
||||
sw.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
gsToast('操作失败:' + (e && e.message ? e.message : e), 'error');
|
||||
sw.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// 顶栏拖动窗口(JS move 兜底,兼容 pywebview EdgeChromium)
|
||||
// 同时支持第一行标题栏(.shell-topnav)与第二行面包屑栏(.shell-topbar)
|
||||
['shell-topnav', 'shell-topbar'].forEach((sel) => {
|
||||
|
||||
Reference in new issue
Block a user