Files

201 lines
9.0 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>