201 lines
9.0 KiB
HTML
201 lines
9.0 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||
<title>dsh 同步 · 安卓 App</title>
|
||
<style>
|
||
:root { --bg:#0f1115; --card:#171a21; --card2:#1e222b; --line:#262b36;
|
||
--text:#e6e8ee; --muted:#8b93a7; --acc:#4c8dff; --warn:#ffb020; --ok:#31c48d; }
|
||
* { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
|
||
body { margin:0; background:var(--bg); color:var(--text); font:15px/1.55 -apple-system,
|
||
BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC",
|
||
"Hiragino Sans GB","Microsoft YaHei",sans-serif; padding-bottom:32px; }
|
||
header { padding:20px 18px 12px; }
|
||
h1 { margin:0 0 4px; font-size:21px; letter-spacing:.3px; }
|
||
header p { margin:0; color:var(--muted); font-size:13px; }
|
||
.wrap { padding:0 14px; max-width:640px; margin:0 auto; }
|
||
.card { background:var(--card); border:1px solid var(--line); border-radius:14px;
|
||
padding:14px; margin:12px 0; }
|
||
.card h2 { margin:0 0 10px; font-size:15px; font-weight:600; }
|
||
.muted { color:var(--muted); font-size:13px; }
|
||
.row { display:flex; gap:10px; align-items:center; }
|
||
.grow { flex:1; min-width:0; }
|
||
a.btn, button.btn { display:block; width:100%; text-align:center; padding:14px;
|
||
border:0; border-radius:12px; background:var(--acc); color:#fff;
|
||
font-size:16px; font-weight:600; text-decoration:none; cursor:pointer; }
|
||
a.btn.ghost, button.btn.ghost { background:var(--card2); color:var(--text);
|
||
border:1px solid var(--line); font-weight:500; font-size:14px; padding:11px; }
|
||
a.btn[aria-disabled="true"] { background:var(--card2); color:var(--muted);
|
||
pointer-events:none; border:1px solid var(--line); }
|
||
.meta { display:grid; grid-template-columns:auto 1fr; gap:5px 12px;
|
||
font-size:12.5px; margin-top:12px; }
|
||
.meta dt { color:var(--muted); }
|
||
.meta dd { margin:0; word-break:break-all; font-family:ui-monospace,Menlo,Consolas,monospace; }
|
||
.qrbx { background:#fff; border-radius:14px; padding:10px; display:flex;
|
||
justify-content:center; margin:4px 0 10px; }
|
||
.qrbx img { width:min(210px,66vw); height:auto; }
|
||
ol { margin:8px 0 0; padding-left:20px; font-size:13.5px; }
|
||
ol li { margin-bottom:7px; }
|
||
code { background:var(--card2); padding:1.5px 6px; border-radius:5px;
|
||
font-size:12.5px; font-family:ui-monospace,Menlo,Consolas,monospace; }
|
||
.tag { display:inline-block; padding:1px 7px; border-radius:6px; font-size:11px;
|
||
background:var(--card2); color:var(--muted); margin-left:6px; }
|
||
.tag.ok { color:var(--ok); }
|
||
.tag.warn { color:var(--warn); }
|
||
.steps { counter-reset:s; list-style:none; padding:0; margin:0; }
|
||
.steps li { counter-increment:s; position:relative; padding-left:30px;
|
||
margin-bottom:12px; font-size:13.5px; }
|
||
.steps li::before { content:counter(s); position:absolute; left:0; top:0;
|
||
width:20px; height:20px; border-radius:50%; background:var(--card2);
|
||
color:var(--acc); font-size:11.5px; font-weight:600; display:flex;
|
||
align-items:center; justify-content:center; }
|
||
footer { text-align:center; color:var(--muted); font-size:12px; padding:18px 14px 0; }
|
||
.hidden { display:none; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header>
|
||
<h1>dsh 同步 · 安卓 App</h1>
|
||
<p>团队设置与插件同步的移动客户端。手机浏览器版仍在 <a href="/app" style="color:var(--acc)">/app</a>。</p>
|
||
</header>
|
||
|
||
<div class="wrap">
|
||
|
||
<!-- ============ download ============ -->
|
||
<div class="card" id="dl-card">
|
||
<h2>下载安装包 <span class="tag" id="ver-tag">…</span></h2>
|
||
<a class="btn" id="dl" aria-disabled="true" href="/apk/dsh-sync.apk" download>准备中…</a>
|
||
<div class="meta" id="meta"></div>
|
||
<div class="muted" id="hint" style="margin-top:10px"></div>
|
||
</div>
|
||
|
||
<!-- ============ qr ============ -->
|
||
<div class="card" id="qr-card">
|
||
<h2>用手机扫码直接在手机上装</h2>
|
||
<div class="qrbx"><img id="qr" alt="下载页二维码"></div>
|
||
<div class="muted">手机相机对准二维码 → 打开本页 → 点上面的下载按钮。
|
||
手机上传安装包比电脑再传过去省事。</div>
|
||
<div class="row" style="margin-top:10px">
|
||
<button class="btn ghost grow" id="copy">复制下载地址</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ install steps ============ -->
|
||
<div class="card">
|
||
<h2>安装步骤</h2>
|
||
<ol class="steps">
|
||
<li>点上面的下载按钮,浏览器会下载 <code>dsh-sync.apk</code>。
|
||
若提示"可能有害",选<strong>仍要下载</strong>——这是自签名包,不是商店应用。</li>
|
||
<li>打开下载好的文件。系统会拦截一次,提示"禁止安装未知应用"。</li>
|
||
<li>点<strong>设置</strong> → 允许当前浏览器<strong>安装未知应用</strong> → 返回 → 继续安装。
|
||
(不同品牌路径略有差异,通常在"设置 → 应用 → 特殊权限"里。)</li>
|
||
<li>首次打开会显示地址栏。填服务端地址,例如 <code id="host-eg">192.168.5.2:8020</code>,
|
||
点"前往"。地址会被记住,以后直接进页面。</li>
|
||
<li>若要从二维码一步到位:直接用 App 打开 <code>/app#invite=…</code> 深链,
|
||
邀请码会自动填进登录页。</li>
|
||
</ol>
|
||
</div>
|
||
|
||
<!-- ============ what it does ============ -->
|
||
<div class="card">
|
||
<h2>这个 App 是什么</h2>
|
||
<div class="muted">
|
||
一个原生 WebView 外壳,包住服务端自带的同一个 PWA 页面。它比手机浏览器多三件事:
|
||
</div>
|
||
<ol class="steps" style="margin-top:10px">
|
||
<li>记住服务端地址,省得每次输 IP。</li>
|
||
<li>支持原生文件选择器,手机上也能发布插件 tar.gz。</li>
|
||
<li>认领 <code>/app#invite=…</code> 深链——扫服务端二维码可直接跳进 App 并预填邀请码。</li>
|
||
</ol>
|
||
<div class="muted" style="margin-top:10px">
|
||
页面逻辑与服务端同源,服务端更新后 App 打开即是新版,不需要重新装。
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ============ build ============ -->
|
||
<div class="card">
|
||
<h2>服务端还没构建过?</h2>
|
||
<div class="muted">APK 由仓库内 <code>android/</code> 工程构建,产物落在 <code>data/dist/</code>。
|
||
在服务器上跑:</div>
|
||
<div style="margin-top:10px"><code>bash android/build.sh</code></div>
|
||
<div class="muted" style="margin-top:10px">
|
||
需要 JDK 17 + Android SDK(build-tools 34、platform 34)+ Gradle 8.7。
|
||
首次构建联网拉 AGP 依赖,之后可离线。
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<footer>dsh-sync · 自签名包,仅团队内网使用</footer>
|
||
|
||
<script>
|
||
"use strict";
|
||
const $ = id => document.getElementById(id);
|
||
|
||
function fmtSize(n) {
|
||
if (!n) return "-";
|
||
return n > 1048576 ? (n / 1048576).toFixed(2) + " MB" : (n / 1024).toFixed(1) + " KB";
|
||
}
|
||
|
||
(async function load() {
|
||
let info;
|
||
try {
|
||
info = await (await fetch("/apk/info")).json();
|
||
} catch (e) {
|
||
$("hint").textContent = "读取构建信息失败:" + e.message;
|
||
return;
|
||
}
|
||
|
||
if (!info.available) {
|
||
$("dl").textContent = "服务端尚未构建 APK";
|
||
$("ver-tag").textContent = "未构建";
|
||
$("ver-tag").className = "tag warn";
|
||
$("hint").textContent = "在服务器上执行 " + (info.build_hint || "bash android/build.sh") + " 后刷新本页。";
|
||
$("qr-card").classList.add("hidden");
|
||
return;
|
||
}
|
||
|
||
// Inside the app itself the install pitch is noise: say what this page is for
|
||
// and point back at the real UI instead.
|
||
if (/\bDshSyncApp\//.test(navigator.userAgent)) {
|
||
const banner = document.createElement("div");
|
||
banner.className = "card";
|
||
banner.innerHTML = '<h2>你正在用 dsh App</h2><div class="muted">本页用于把 App 装到'
|
||
+ '别的手机上。在 App 里请点下面的按钮回到登录/主界面。</div>'
|
||
+ '<div style="margin-top:10px"><a class="btn" href="/app">打开 dsh 同步</a></div>';
|
||
document.querySelector(".wrap").prepend(banner);
|
||
}
|
||
|
||
$("dl").textContent = "下载 dsh-sync.apk(" + fmtSize(info.size_bytes) + ")";
|
||
$("dl").removeAttribute("aria-disabled");
|
||
$("ver-tag").textContent = "v" + (info.version || "?");
|
||
$("ver-tag").className = "tag ok";
|
||
|
||
const rows = [
|
||
["版本", "v" + (info.version || "?") + " (" + (info.version_code ?? "?") + ")"],
|
||
["大小", fmtSize(info.size_bytes)],
|
||
["包名", info.application_id || "-"],
|
||
["系统", "Android " + (info.min_sdk ?? "?") + "+"],
|
||
["构建于", info.built_at || "-"],
|
||
["sha256", info.sha256 || "-"]
|
||
];
|
||
$("meta").innerHTML = rows.map(([k, v]) => `<dt>${k}</dt><dd>${v}</dd>`).join("");
|
||
|
||
try {
|
||
const r = await fetch("/apk/qr");
|
||
$("qr").src = URL.createObjectURL(await r.blob());
|
||
} catch { $("qr-card").classList.add("hidden"); }
|
||
|
||
$("copy").onclick = async () => {
|
||
const url = location.origin + "/apk/dsh-sync.apk";
|
||
try { await navigator.clipboard.writeText(url); $("copy").textContent = "已复制"; }
|
||
catch { $("copy").textContent = url; }
|
||
setTimeout(() => $("copy").textContent = "复制下载地址", 2000);
|
||
};
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|