Use 比特骆驼 logo for PWA/brand marks, remove install button, docs Ubuntu 22.04 only.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-26 22:07:11 +08:00
parent 86d3c94f32
commit 8813fd3e37
15 changed files with 66 additions and 192 deletions
+10 -3
View File
@@ -1,7 +1,6 @@
import type { ReactNode } from "react";
import { NavLink, Navigate, Route, Routes } from "react-router-dom";
import { clearSession, getToken, getUsername } from "./api/client";
import InstallBadge from "./components/InstallBadge";
import LoginPage from "./pages/Login";
import PlanPage from "./pages/Plan";
import TradesPage from "./pages/Trades";
@@ -14,8 +13,16 @@ function Shell({ children }: { children: ReactNode }) {
<div className="app-shell">
<header className="topnav">
<div className="topnav-side left">
<div className="brand"></div>
<InstallBadge />
<div className="brand">
<img
className="brand-mark"
src="/icons/brand.png"
alt=""
width={28}
height={28}
/>
<span></span>
</div>
</div>
<nav className="topnav-center">
<NavLink to="/plan" className={({ isActive }) => (isActive ? "active" : "")}>
-118
View File
@@ -1,118 +0,0 @@
import { useEffect, useState } from "react";
import {
BeforeInstallPromptEvent,
isAppleTouchDevice,
isStandaloneDisplay,
} from "../pwa/install";
/**
* Desktop/tablet install affordance:
* - Chrome/Edge/Android: native install via beforeinstallprompt
* - iPad/iOS: tip to Add to Home Screen
* - Already installed: show 已安装标识
*/
export default function InstallBadge() {
const [standalone, setStandalone] = useState(isStandaloneDisplay);
const [deferred, setDeferred] = useState<BeforeInstallPromptEvent | null>(
null,
);
const [tipOpen, setTipOpen] = useState(false);
const [busy, setBusy] = useState(false);
const apple = isAppleTouchDevice();
useEffect(() => {
const mq = window.matchMedia("(display-mode: standalone)");
const sync = () => setStandalone(isStandaloneDisplay());
mq.addEventListener?.("change", sync);
sync();
const onBip = (e: Event) => {
e.preventDefault();
setDeferred(e as BeforeInstallPromptEvent);
};
const onInstalled = () => {
setDeferred(null);
setStandalone(true);
setTipOpen(false);
};
window.addEventListener("beforeinstallprompt", onBip);
window.addEventListener("appinstalled", onInstalled);
return () => {
mq.removeEventListener?.("change", sync);
window.removeEventListener("beforeinstallprompt", onBip);
window.removeEventListener("appinstalled", onInstalled);
};
}, []);
if (standalone) {
return (
<span className="install-badge installed" title="已安装为独立应用">
</span>
);
}
const canNativeInstall = Boolean(deferred);
async function onInstall() {
if (!deferred) {
setTipOpen((v) => !v);
return;
}
setBusy(true);
try {
await deferred.prompt();
await deferred.userChoice;
setDeferred(null);
} finally {
setBusy(false);
}
}
return (
<div className="install-wrap">
<button
type="button"
className="btn ghost install-btn"
disabled={busy}
onClick={() => void onInstall()}
title={
canNativeInstall
? "安装到电脑或平板"
: apple
? "添加到主屏幕(平板/手机)"
: "安装说明"
}
>
{canNativeInstall ? (busy ? "安装中…" : "安装应用") : "安装应用"}
</button>
{tipOpen && !canNativeInstall ? (
<div className="install-tip" role="dialog" aria-label="安装说明">
{apple ? (
<>
<p>iPad / iPhone Safari </p>
<p className="muted"></p>
</>
) : (
<>
<p>
Chrome / Edge
</p>
<p>
/
</p>
<p className="muted"> HTTPS localhost</p>
</>
)}
<button
type="button"
className="btn ghost"
onClick={() => setTipOpen(false)}
>
</button>
</div>
) : null}
</div>
);
}
+7 -2
View File
@@ -1,7 +1,6 @@
import { FormEvent, useState } from "react";
import { useNavigate } from "react-router-dom";
import { login, setSession } from "../api/client";
import InstallBadge from "../components/InstallBadge";
export default function LoginPage() {
const nav = useNavigate();
@@ -29,8 +28,14 @@ export default function LoginPage() {
<div className="login-wrap">
<form className="login-box" onSubmit={onSubmit}>
<div className="login-title-row">
<img
className="login-brand-mark"
src="/icons/brand.png"
alt="比特骆驼"
width={48}
height={48}
/>
<h1></h1>
<InstallBadge />
</div>
{err ? <div className="err">{err}</div> : null}
<div className="field">
+17 -53
View File
@@ -81,70 +81,35 @@ input {
}
.brand {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--accent);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 0;
}
.install-wrap {
position: relative;
.brand-mark,
.login-brand-mark {
flex-shrink: 0;
border-radius: 6px;
object-fit: cover;
background: #1a1a1a;
}
.install-btn {
font-size: 12px;
padding: 4px 10px;
border: 1px solid var(--line);
color: var(--accent);
}
.install-badge {
display: inline-flex;
align-items: center;
font-size: 11px;
font-weight: 600;
letter-spacing: 0.04em;
padding: 3px 8px;
border-radius: 4px;
white-space: nowrap;
}
.install-badge.installed {
color: #0b0e11;
background: var(--accent);
}
.install-tip {
position: absolute;
top: calc(100% + 8px);
left: 0;
z-index: 30;
width: min(320px, calc(100vw - 24px));
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--bg-elev);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45);
}
.install-tip p {
margin: 0 0 8px;
font-size: 12px;
line-height: 1.5;
color: var(--text);
}
.install-tip p.muted,
.install-tip .muted {
color: var(--muted);
.login-brand-mark {
border-radius: 10px;
}
@media (max-width: 900px) {
.topnav-side.left .brand {
max-width: 11em;
.topnav-side.left .brand span {
max-width: 9em;
overflow: hidden;
text-overflow: ellipsis;
}
}
@@ -406,9 +371,8 @@ input {
.login-title-row {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 10px;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}