启动器:游戏更新拆分为当前/历史双子页,新增关于我们页
- 游戏更新页改为「当前更新 / 历史更新」两个标签页,历史页显示条数 - 更新日志合并仓库 Release 与本地 更新日志.txt,按日期去重、仓库优先;断网时仅用本地记录 - 主导航新增「关于我们」,内容与游戏内控制台关于页一致(四行文案 + 赞助/公众号二维码) - 新增 /qr/<name> 白名单路由读取启动器目录 qr\ 下图片,防止目录穿越 - 二维码支持点击放大(灯箱),图片缺失时显示占位而不是破图 - 打包 spec 补入 qr 三张图,静态响应统一 Cache-Control: no-store
This commit is contained in:
1 parent
1dd3775510
commit
6a4b7e58d3
8 files changed
+311
-36
No files matched your search
@@ -1,7 +1,8 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from PyInstaller.utils.hooks import collect_all
|
||||
|
||||
datas = [('index.html', '.'), ('style.css', '.'), ('shell.css', '.'), ('app.js', '.'), ('shell.js', '.'), ('sponsor.png', '.'), ('db.py', '.'), ('native.py', '.'), ('launcher.ico', '.')]
|
||||
datas = [('index.html', '.'), ('style.css', '.'), ('shell.css', '.'), ('app.js', '.'), ('shell.js', '.'), ('sponsor.png', '.'), ('db.py', '.'), ('native.py', '.'), ('launcher.ico', '.'),
|
||||
('qr/alipay.png', 'qr'), ('qr/wechat.png', 'qr'), ('qr/gzh.jpg', 'qr')]
|
||||
binaries = []
|
||||
hiddenimports = ['clr_loader', 'pythonnet']
|
||||
tmp_ret = collect_all('webview')
|
||||
|
||||
+52
-1
@@ -30,6 +30,7 @@
|
||||
<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>
|
||||
<button class="shell-nav-item" data-shell-page="about"><span class="nav-ico">💝</span>关于我们</button>
|
||||
<div class="shell-nav-gap"></div>
|
||||
</nav>
|
||||
<div class="shell-win-controls">
|
||||
@@ -127,7 +128,55 @@
|
||||
</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>
|
||||
<div class="page-pad">
|
||||
<div class="page-heading"><div><h2>游戏更新</h2><p class="muted">优先读取仓库(Lingbao/90CNServer)的发布记录,并用本地「更新日志.txt」补齐仓库还没发布的版本;断网时只显示本地记录。</p></div><button class="shell-btn" id="updRefresh">刷新</button></div>
|
||||
<div class="st-tabs" id="updTabs">
|
||||
<button class="st-tab active" data-upd="latest">当前更新</button>
|
||||
<button class="st-tab" data-upd="history">历史更新 <span class="upd-count" id="updHistoryCount"></span></button>
|
||||
</div>
|
||||
<div id="updLatestPane"><div class="upd-body" id="updLatest"><div class="empty-box">正在获取更新日志…</div></div></div>
|
||||
<div id="updHistoryPane" style="display:none"><div class="upd-body" id="updHistory"></div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell-page" id="page-about">
|
||||
<div class="page-pad about-pad">
|
||||
<div class="about-hero">
|
||||
<h2>地下城与勇士 · 九零轮回单机</h2>
|
||||
<p class="about-line">汉服电脑宅 · 灵宝小竹模拟器</p>
|
||||
<p class="about-line">由灵宝仙尊联合淡竹茹开发,由汉服电脑宅团队开发维护</p>
|
||||
<p class="about-line">本模拟器资源禁止倒卖,请到官方微信公众号或社群下载</p>
|
||||
</div>
|
||||
|
||||
<div class="about-panel">
|
||||
<div class="about-col about-col-l">
|
||||
<div class="about-col-hd">赞助我们</div>
|
||||
<div class="about-col-bd">
|
||||
<figure class="about-qr" data-qr="支付宝">
|
||||
<img src="/qr/alipay.png" alt="支付宝收款码">
|
||||
<figcaption>支付宝</figcaption>
|
||||
</figure>
|
||||
<figure class="about-qr" data-qr="微信">
|
||||
<img src="/qr/wechat.png" alt="微信收款码">
|
||||
<figcaption>微信</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="about-col-note">金额随意,感谢支持;扫码后备注游戏名,方便记录。</div>
|
||||
</div>
|
||||
<div class="about-col about-col-r">
|
||||
<div class="about-col-hd">团队公众号</div>
|
||||
<div class="about-col-bd">
|
||||
<figure class="about-qr" data-qr="团队公众号">
|
||||
<img src="/qr/gzh.jpg" alt="团队公众号二维码">
|
||||
<figcaption>扫码关注</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="about-col-note">更新公告 · 开服时间 · 玩法说明</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="about-tip">点击任意二维码可放大查看;把同名图片放进启动器目录下的 qr 文件夹即可替换。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell-page" id="page-gm">
|
||||
@@ -165,6 +214,8 @@
|
||||
<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="qr-lightbox hidden" id="qrLightbox"><img id="qrLightboxImg" src="" alt=""><div class="qr-lightbox-tip" id="qrLightboxTip"></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>
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
+124
-4
@@ -16,6 +16,7 @@ import ctypes
|
||||
import json
|
||||
import os
|
||||
import queue
|
||||
import re
|
||||
import shutil
|
||||
import socket
|
||||
import subprocess
|
||||
@@ -145,6 +146,16 @@ SPONSOR_IMG = os.path.join(BASE_DIR, "sponsor.png")
|
||||
if not os.path.exists(SPONSOR_IMG):
|
||||
SPONSOR_IMG = os.path.join(RES_DIR, "sponsor.png")
|
||||
|
||||
# 关于我们页的三张二维码:优先 EXE 同目录 qr\(可自行替换),其次打包内资源
|
||||
QR_FILES = {
|
||||
"alipay.png": "image/png",
|
||||
"wechat.png": "image/png",
|
||||
"gzh.jpg": "image/jpeg",
|
||||
}
|
||||
|
||||
# 断网时更新日志的兜底来源(EXE 同目录 / 服务端根目录的 更新日志.txt)
|
||||
LOCAL_CHANGELOG_NAME = "更新日志.txt"
|
||||
|
||||
# 数据库与实例配置候选路径(基于运行目录解析出的 SERVER_ROOT)
|
||||
def _instance_candidates():
|
||||
out = []
|
||||
@@ -373,6 +384,93 @@ def _release_summary(rel):
|
||||
}
|
||||
|
||||
|
||||
def _local_changelog():
|
||||
"""断网时的兜底:解析本地 更新日志.txt,按日期切成若干条(最新在前)。
|
||||
|
||||
文件长这样(日期行之间是正文):
|
||||
2026.11.05 更新:
|
||||
1.xxx
|
||||
2.xxx
|
||||
"""
|
||||
path = ""
|
||||
for d in (BASE_DIR, SERVER_ROOT):
|
||||
if not d:
|
||||
continue
|
||||
p = os.path.join(d, LOCAL_CHANGELOG_NAME)
|
||||
if os.path.exists(p):
|
||||
path = p
|
||||
break
|
||||
if not path:
|
||||
return []
|
||||
try:
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
lines = f.read().splitlines()
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
items = []
|
||||
cur = None
|
||||
for ln in lines:
|
||||
s = ln.strip()
|
||||
m = re.match(r"^(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})", s)
|
||||
if m:
|
||||
if cur:
|
||||
items.append(cur)
|
||||
date = "%s.%s.%s" % (m.group(1), m.group(2).zfill(2), m.group(3).zfill(2))
|
||||
cur = {
|
||||
"tag": date,
|
||||
"name": date + " 更新",
|
||||
"body": "",
|
||||
"published_at": "",
|
||||
"prerelease": False,
|
||||
"html_url": UPDATE_REPO_WEB,
|
||||
"assets": [],
|
||||
}
|
||||
continue
|
||||
if cur is not None and s:
|
||||
cur["body"] = (cur["body"] + "\n" + s).strip()
|
||||
if cur:
|
||||
items.append(cur)
|
||||
items.reverse() # 文件里是旧→新,接口要新→旧
|
||||
return items
|
||||
|
||||
|
||||
def _changelog_date_key(rel):
|
||||
"""从 tag / name / 发布时间里抽 20261105 这样的排序键;取不到返回空串。"""
|
||||
s = " ".join([rel.get("tag") or "", rel.get("name") or "", rel.get("published_at") or ""])
|
||||
m = re.search(r"(\d{4})[.\-/](\d{1,2})[.\-/](\d{1,2})", s)
|
||||
if not m:
|
||||
return ""
|
||||
return "%s%02d%02d" % (m.group(1), int(m.group(2)), int(m.group(3)))
|
||||
|
||||
|
||||
def _merge_changelog(remote, local):
|
||||
"""仓库发布记录 + 本地更新日志合并:按日期去重(仓库优先,内容更全),新的在前。
|
||||
|
||||
仓库没跟上发版时,本地 更新日志.txt 里的新条目会自动出现在「当前更新」。
|
||||
"""
|
||||
out, seen = [], set()
|
||||
for rel in remote or []:
|
||||
k = _changelog_date_key(rel)
|
||||
if k:
|
||||
if k in seen:
|
||||
continue
|
||||
seen.add(k)
|
||||
out.append(rel)
|
||||
for rel in local or []:
|
||||
k = _changelog_date_key(rel)
|
||||
if k:
|
||||
if k in seen:
|
||||
continue
|
||||
seen.add(k)
|
||||
out.append(rel)
|
||||
|
||||
dated = [r for r in out if _changelog_date_key(r)]
|
||||
undated = [r for r in out if not _changelog_date_key(r)]
|
||||
dated.sort(key=_changelog_date_key, reverse=True)
|
||||
return dated + undated
|
||||
|
||||
|
||||
def flow_start_game(account_username=None):
|
||||
"""一键开始游戏;account_username 为空时使用默认账号。"""
|
||||
log("== 开始游戏 ==")
|
||||
@@ -590,6 +688,8 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self._send_file(SPONSOR_IMG, "image/png")
|
||||
else:
|
||||
self._json({"error": "赞助二维码尚未提供"}, 404)
|
||||
elif path.startswith("/qr/"):
|
||||
self._send_qr(path[len("/qr/"):])
|
||||
# 启动器接口
|
||||
elif path == "/api/status":
|
||||
self._api_status()
|
||||
@@ -792,19 +892,39 @@ class Handler(BaseHTTPRequestHandler):
|
||||
})
|
||||
|
||||
def _api_update_changelog(self, qs):
|
||||
"""获取所有发布的更新日志。"""
|
||||
"""获取所有发布的更新日志;连不上仓库时退回本地 更新日志.txt。"""
|
||||
force = qs.get("force", ["0"])[0] == "1"
|
||||
releases, err = _fetch_releases(force=force)
|
||||
if err:
|
||||
self._json({"ok": False, "error": err, "releases": []})
|
||||
remote = [] if err else [_release_summary(r) for r in (releases or [])]
|
||||
local = _local_changelog()
|
||||
merged = _merge_changelog(remote, local)
|
||||
if not merged:
|
||||
self._json({"ok": False, "error": err or "没有任何更新记录", "releases": []})
|
||||
return
|
||||
self._json({
|
||||
"ok": True,
|
||||
"current": CURRENT_VERSION,
|
||||
"releases": [_release_summary(r) for r in (releases or [])],
|
||||
"releases": merged,
|
||||
# local = 完全没连上仓库;merged = 仓库记录 + 本地补齐
|
||||
"source": "remote" if remote else "local",
|
||||
"local_count": len(local),
|
||||
"warning": err,
|
||||
"web_url": UPDATE_REPO_WEB,
|
||||
})
|
||||
|
||||
def _send_qr(self, name):
|
||||
"""关于我们页的二维码图片:EXE 同目录 qr\\ 优先,其次打包内资源。"""
|
||||
ctype = QR_FILES.get(name)
|
||||
if not ctype:
|
||||
self._json({"error": "not found"}, 404)
|
||||
return
|
||||
for d in (os.path.join(BASE_DIR, "qr"), os.path.join(RES_DIR, "qr")):
|
||||
p = os.path.join(d, name)
|
||||
if os.path.exists(p):
|
||||
self._send_file(p, ctype)
|
||||
return
|
||||
self._json({"error": "二维码未配置"}, 404)
|
||||
|
||||
def _api_logs(self):
|
||||
logs = []
|
||||
while True:
|
||||
|
||||
@@ -393,3 +393,54 @@ body.gm-fullscreen .win-resize{display:none}
|
||||
/* 简易 GM 控制台运行时,完整 GM 工具入口置灰不可点 */
|
||||
.shell-nav-item.nav-disabled{opacity:.38;cursor:not-allowed;filter:grayscale(1)}
|
||||
.shell-nav-item.nav-disabled:hover{background:transparent;color:inherit}
|
||||
|
||||
/* ================= 游戏更新:当前 / 历史 两个子页 ================= */
|
||||
.upd-body{display:flex;flex-direction:column;gap:12px;margin-top:14px}
|
||||
.upd-count{color:#8d91a8;font-size:12px;margin-left:2px}
|
||||
.upd-source{margin-bottom:10px;padding:8px 12px;font-size:12px;color:#ffc96b;
|
||||
background:rgba(255,201,77,.08);border:1px solid rgba(255,201,77,.28);border-radius:8px}
|
||||
.changelog-item{border:1px solid rgba(255,255,255,.09);background:rgba(20,20,36,.62);
|
||||
border-radius:10px;padding:16px 18px}
|
||||
.changelog-item.featured{border-color:rgba(124,92,255,.5);
|
||||
background:linear-gradient(160deg,rgba(124,92,255,.16),rgba(20,20,36,.7));
|
||||
box-shadow:0 6px 22px rgba(124,92,255,.15)}
|
||||
.changelog-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:10px}
|
||||
.changelog-tag{font-family:Consolas,monospace;font-size:12px;color:#c9beff;background:rgba(124,92,255,.16);
|
||||
padding:2px 8px;border-radius:5px;border:1px solid rgba(124,92,255,.32)}
|
||||
.changelog-name{font-size:14px;font-weight:600;color:#e2e4f2}
|
||||
.changelog-time{font-size:11px;color:#6d7188;margin-left:auto}
|
||||
.changelog-body{font-size:13px;color:#b8bcd0;line-height:1.9;word-break:break-word}
|
||||
.changelog-assets{display:flex;flex-wrap:wrap;gap:8px;margin-top:10px}
|
||||
.changelog-asset{font-size:11px;color:#9fe6c4;text-decoration:none;border:1px solid rgba(255,255,255,.12);
|
||||
padding:3px 10px;border-radius:6px;background:rgba(255,255,255,.04)}
|
||||
.changelog-asset:hover{background:rgba(255,255,255,.1)}
|
||||
.changelog-link{display:inline-block;margin-top:12px;font-size:11px;color:#8d91a8;text-decoration:none}
|
||||
.changelog-link:hover{color:#c9beff}
|
||||
|
||||
/* ================= 关于我们 ================= */
|
||||
.about-pad{max-width:880px}
|
||||
.about-hero{padding:18px 22px;border:1px solid rgba(255,255,255,.09);border-radius:12px;
|
||||
background:linear-gradient(160deg,rgba(124,92,255,.18),rgba(20,20,36,.6))}
|
||||
.about-hero h2{margin:0 0 10px;font-size:21px;color:#f2f3f8;letter-spacing:.5px}
|
||||
.about-line{margin:3px 0;color:#b8bcd0;font-size:13px;line-height:1.9}
|
||||
.about-panel{display:flex;margin-top:14px;border:1px solid rgba(255,255,255,.09);border-radius:12px;
|
||||
background:rgba(20,20,36,.6);overflow:hidden}
|
||||
.about-col{padding:16px 20px}
|
||||
.about-col-l{flex:1 1 auto}
|
||||
.about-col-r{width:224px;flex:none;border-left:1px solid rgba(255,255,255,.09);background:rgba(0,0,0,.2)}
|
||||
.about-col-hd{color:var(--gold);font-size:14px;letter-spacing:1px;margin-bottom:12px}
|
||||
.about-col-bd{display:flex;gap:16px}
|
||||
.about-qr{margin:0;cursor:zoom-in}
|
||||
.about-qr img{display:block;width:136px;height:136px;background:#fff;border-radius:8px;padding:4px;
|
||||
transition:transform .15s,box-shadow .15s}
|
||||
.about-qr:hover img{transform:translateY(-2px);box-shadow:0 6px 18px rgba(124,92,255,.35)}
|
||||
.about-qr figcaption{margin-top:7px;text-align:center;font-size:12px;color:#9a9eb5}
|
||||
.about-col-note{margin-top:12px;font-size:11px;color:#6d7188;line-height:1.8}
|
||||
.about-tip{margin-top:12px;font-size:11px;color:#5d6178}
|
||||
|
||||
/* 二维码放大 */
|
||||
.qr-lightbox{position:fixed;inset:0;z-index:300;background:rgba(5,5,12,.85);
|
||||
display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;cursor:zoom-out}
|
||||
.qr-lightbox img{width:min(340px,56vh);height:auto;background:#fff;padding:10px;border-radius:12px;
|
||||
box-shadow:0 12px 40px rgba(0,0,0,.55)}
|
||||
.qr-lightbox-tip{color:#b8bcd0;font-size:12px}
|
||||
+82
-30
@@ -1,13 +1,13 @@
|
||||
/* 90轮回启动器 · 单窗口页面与工具整合 */
|
||||
(function () {
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const pages = ['start','changelog','gm','loadout','gamesettings'];
|
||||
const pages = ['start','changelog','gm','loadout','gamesettings','about'];
|
||||
let shellGmInit = false;
|
||||
let loConfig = null, loPage = 0, loScheme = -1;
|
||||
let loCooldownUntil = 0, loCooldownCharacter = '', loCooldownTimer = null;
|
||||
let hotkeyCapture = false;
|
||||
|
||||
const crumb = {start:'开始 / 本地服务端',changelog:'游戏更新',gm:'GM工具',loadout:'工具 / 一键换装',gamesettings:'工具 / 游戏设置'};
|
||||
const crumb = {start:'开始 / 本地服务端',changelog:'游戏更新',gm:'GM工具',loadout:'工具 / 一键换装',gamesettings:'工具 / 游戏设置',about:'关于我们'};
|
||||
|
||||
function shellGo(page) {
|
||||
if (!pages.includes(page)) page = 'start';
|
||||
@@ -258,43 +258,60 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 更新日志页面:拉取所有 Release 的 changelog
|
||||
async function loadChangelog() {
|
||||
const box = $('changelogBody');
|
||||
if (!box) return;
|
||||
box.innerHTML = '<div class="empty-box">正在获取更新日志…</div>';
|
||||
// 更新日志:当前更新 = 最新一条;历史更新 = 其余。优先 Gitea,断网退回本地 更新日志.txt
|
||||
function updDate(rel) {
|
||||
return rel.published_at ? rel.published_at.replace('T', ' ').slice(0, 16) : '';
|
||||
}
|
||||
|
||||
function updCard(rel, featured) {
|
||||
const badge = rel.prerelease ? '<span class="gm-tag gm-tag-gray">预发布</span>' : '';
|
||||
const body = (rel.body || '').replace(/\r?\n/g, '<br>');
|
||||
const assets = (rel.assets || []).map(a =>
|
||||
`<a class="changelog-asset" href="${escapeHtml(a.download_url)}" target="_blank">⬇ ${escapeHtml(a.name)} (${fmt(a.size)} B)</a>`
|
||||
).join('');
|
||||
const when = updDate(rel);
|
||||
return `<div class="changelog-item${featured ? ' featured' : ''}">
|
||||
<div class="changelog-head">
|
||||
<span class="changelog-tag">${escapeHtml(rel.tag || rel.name || '')}</span>
|
||||
<span class="changelog-name">${escapeHtml(rel.name || '')}</span>
|
||||
${badge}
|
||||
<span class="changelog-time">${escapeHtml(when)}</span>
|
||||
</div>
|
||||
<div class="changelog-body">${body || '<span class="muted">(这次没有填写更新说明)</span>'}</div>
|
||||
${assets ? '<div class="changelog-assets">' + assets + '</div>' : ''}
|
||||
${featured ? `<a class="changelog-link" href="${escapeHtml(rel.html_url)}" target="_blank">在浏览器中查看 ↗</a>` : ''}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function loadChangelog(force) {
|
||||
const latestBox = $('updLatest'), historyBox = $('updHistory');
|
||||
if (!latestBox || !historyBox) return;
|
||||
latestBox.innerHTML = '<div class="empty-box">正在获取更新日志…</div>';
|
||||
historyBox.innerHTML = '';
|
||||
try {
|
||||
const r = await api('/api/update/changelog');
|
||||
const r = await api('/api/update/changelog' + (force ? '?force=1' : ''));
|
||||
if (!r.ok) {
|
||||
box.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(r.error || '未知错误') + '</div>';
|
||||
latestBox.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(r.error || '未知错误') + '</div>';
|
||||
if ($('updHistoryCount')) $('updHistoryCount').textContent = '';
|
||||
return;
|
||||
}
|
||||
const rels = r.releases || [];
|
||||
if (!rels.length) {
|
||||
box.innerHTML = '<div class="empty-box">暂无发布记录。<br>在 Gitea 仓库「发布 Release」并填写更新说明后,这里会自动显示。</div>';
|
||||
latestBox.innerHTML = '<div class="empty-box">暂无更新记录。<br>在仓库发布 Release,或把「更新日志.txt」放在启动器目录,这里就会显示。</div>';
|
||||
if ($('updHistoryCount')) $('updHistoryCount').textContent = '';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = rels.map(rel => {
|
||||
const badge = rel.prerelease ? '<span class="gm-tag gm-tag-gray">预发布</span>' : '';
|
||||
const body = (rel.body || '').replace(/\r?\n/g, '<br>');
|
||||
const assets = (rel.assets || []).map(a =>
|
||||
`<a class="changelog-asset" href="${escapeHtml(a.download_url)}" target="_blank">⬇ ${escapeHtml(a.name)} (${fmt(a.size)} B)</a>`
|
||||
).join('');
|
||||
const when = rel.published_at ? rel.published_at.replace('T', ' ').slice(0, 16) : '';
|
||||
return `<div class="changelog-item">
|
||||
<div class="changelog-head">
|
||||
<span class="changelog-tag">${escapeHtml(rel.tag || rel.name || '')}</span>
|
||||
<span class="changelog-name">${escapeHtml(rel.name || '')}</span>
|
||||
${badge}
|
||||
<span class="changelog-time">${escapeHtml(when)}</span>
|
||||
</div>
|
||||
<div class="changelog-body">${body}</div>
|
||||
${assets ? '<div class="changelog-assets">' + assets + '</div>' : ''}
|
||||
<a class="changelog-link" href="${escapeHtml(rel.html_url)}" target="_blank">在浏览器中查看 ↗</a>
|
||||
</div>`;
|
||||
}).join('');
|
||||
const source = r.source === 'local'
|
||||
? '<div class="upd-source">未连接更新仓库,以下内容读取自本地「更新日志.txt」。</div>'
|
||||
: '';
|
||||
latestBox.innerHTML = source + updCard(rels[0], true);
|
||||
const rest = rels.slice(1);
|
||||
historyBox.innerHTML = rest.length
|
||||
? rest.map(rel => updCard(rel, false)).join('')
|
||||
: '<div class="empty-box">还没有更早的更新记录。</div>';
|
||||
if ($('updHistoryCount')) $('updHistoryCount').textContent = rest.length ? '(' + rest.length + ')' : '';
|
||||
} catch (e) {
|
||||
box.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(e && e.message ? e.message : e) + '</div>';
|
||||
latestBox.innerHTML = '<div class="empty-box">获取失败:' + escapeHtml(e && e.message ? e.message : e) + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -745,6 +762,41 @@
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------- 游戏更新:当前 / 历史 两个子页 ----------------
|
||||
(function bindUpdTabs() {
|
||||
const tabs = document.querySelectorAll('#updTabs .st-tab');
|
||||
tabs.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const which = btn.dataset.upd;
|
||||
tabs.forEach(b => b.classList.toggle('active', b === btn));
|
||||
$('updLatestPane').style.display = which === 'latest' ? '' : 'none';
|
||||
$('updHistoryPane').style.display = which === 'history' ? '' : 'none';
|
||||
});
|
||||
});
|
||||
const refresh = $('updRefresh');
|
||||
if (refresh) refresh.addEventListener('click', () => loadChangelog(true));
|
||||
})();
|
||||
|
||||
// ---------------- 关于我们:二维码放大 ----------------
|
||||
(function bindQrLightbox() {
|
||||
const box = $('qrLightbox'), img = $('qrLightboxImg'), tip = $('qrLightboxTip');
|
||||
if (!box || !img) return;
|
||||
document.querySelectorAll('#page-about .about-qr').forEach(fig => {
|
||||
fig.addEventListener('click', () => {
|
||||
const src = fig.querySelector('img');
|
||||
if (!src) return;
|
||||
img.src = src.getAttribute('src');
|
||||
img.alt = src.getAttribute('alt') || '';
|
||||
tip.textContent = (fig.dataset.qr || '') + ' · 点击任意处关闭';
|
||||
box.classList.remove('hidden');
|
||||
});
|
||||
});
|
||||
box.addEventListener('click', () => box.classList.add('hidden'));
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') box.classList.add('hidden');
|
||||
});
|
||||
})();
|
||||
|
||||
// 兼容旧 GM 页面里的返回
|
||||
window.shellGo = shellGo;
|
||||
loadStart();
|
||||
|
||||
Reference in new issue
Block a user