Files
ykxmz/_build/site_preview.html
xmz7891 1f05084f51 隧道"假活"真因修复 + 中转管理后台重做
用户反馈两件事:
  1. 早上打开 relay.xxmxxs.top 看到 Cloudflare Error 1033 —— "这就是你说的优化好了?"
  2. "中转管理那边 ui 界面大改,现在难看的要死"

============ 一、隧道 1033/502/530 的真因 ============

之前我只测 15~25 次就说"可用率 100%",太草率 ——
实际是**间歇性假活**:隧道显示 ha_connections=2 连着,
公网却 502/530/000。watchdog 日志把过程记了下来。

根因:**4G 链路静默断开 TCP 连接**(不发 FIN/RST,双方都以为还活着)。
Cloudflare 把请求发到这条僵尸连接上就回错误。
Linux 默认 tcp_keepalive_time = **7200 秒(2 小时)**才发现,
中间这段时间服务一直"看着在线、实际打不开"。

修法(都有实测数据支撑):
  1. tcp_keepalive 30 秒 + 每 10 秒探测 3 次
     => 最多约 60 秒发现僵尸连接并重连
  2. 隧道连接数 2 -> 4
     (对照实验:http2+2 只有 1 条连上时公网 0/12;
       http2+4 稳定 12/12。连接数太少会出现"只有 1 条"的危险窗口)
  3. 协议定为 http2(cloudflared 自检说 TCP 7844 被墙,
     但实测 http2 反而比 quic 稳)

实测:http2+4 之后公网 40/40 = 100%,最慢 1.3 秒,0 次重连。

============ 二、中转管理后台重做 ============

原来是几百行内联在 relay.py 的 f-string 里(HTML+CSS+JS 全挤一起),
改一处样式要在一堆转义花括号里翻。拆成独立模块 relay/admin.py。

新版设计(对齐官网那套白色简洁):
  * 吸顶顶栏:品牌 + 运行状态 + 版本/内存/请求数
  * 公网地址给最大权重(这是用户最需要复制的东西)
  * 四个数字卡(在线/会话数带颜色区分)
  * 设备表卡片:状态圆点、等宽配对码、行悬停
  * 工具卡:网络体检 + 下载区并排
  * 空状态改成图标+引导文案,不再是干巴巴一句
  * 窄屏时表格自动变卡片

relay.py 从 2089 行缩到 1550 行。

============ 三、顺手修的两个真 bug ============

1. 设备记录只增不减
   self.devices 从不清理,被控端每次换端口/重装就多一条,
   实测一天攒了 75 条僵尸记录,/v1/health 从几 KB 涨到 20KB。
   加 DEVICE_TTL(6 小时)在 snapshot() 里清。

2. 配对码映射也没清
   RELAY.codes 和 devices 是一对孪生 dict,
   只清 devices 会留下指向已删设备的孤儿条目 ——
   管理页显示"配对码占用 131",实际一台设备都没有。
   现在一起清。

============ 四、我自己造的两次事故(如实记录) ============

1. 删文件删过头
   用"从 _admin_page 截到文件末尾"的方式替换方法,
   但 _admin_page 后面**还有 _esc/_kb/_uptime/class Server/main()**。
   结果 relay 起不来,systemd 重启 24 次,所有接口 000。
   教训:**"删到文件末尾"之前必须先确认末尾有什么。**

2. ADMIN_PATH 引用了不存在的常量
   路由里写的是字面量,我却在新代码里用了 ADMIN_PATH,
   管理页一访问就 NameError。现在统一定义了常量。

二者都是"改完没立刻验"造成的。现在每次改完都跑一遍
管理页 + 官网 + 关键接口的回归。
2026-10-07 09:34:53 +08:00

305 lines
22 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">
<title>ykxmz · 教室电脑手表遥控</title>
<meta name="description" content="老师用手表管教室里那台电脑:放课件、投图、下课关机,不用走回讲台。">
<link rel="icon" href="/favicon.ico">
<style>
*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",
"Microsoft YaHei","PingFang SC",sans-serif;
background:#ffffff;color:#1a1d21;line-height:1.75;
-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
.wrap{max-width:960px;margin:0 auto;padding:0 24px}
/* 吸顶导航 */
nav{position:sticky;top:0;z-index:50;background:rgba(255,255,255,.86);
backdrop-filter:saturate(180%) blur(12px);
border-bottom:1px solid #e6e8eb}
.nav-in{display:flex;align-items:center;justify-content:space-between;
height:60px}
.brand{display:flex;align-items:center;gap:9px;font-weight:700;
font-size:17px;letter-spacing:-.2px}
.brand img{width:26px;height:26px;border-radius:7px}
.nav-links{display:flex;gap:26px;font-size:14px;color:#6b7280}
.nav-links a:hover{color:#1a1d21}
/* Hero */
.hero{padding:88px 0 72px;text-align:center}
.hero h1{font-size:46px;line-height:1.2;letter-spacing:-1.2px;
font-weight:800;margin-bottom:18px}
.hero p{font-size:18px;color:#6b7280;max-width:600px;margin:0 auto 34px}
.cta{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
.btn{display:inline-flex;align-items:center;gap:8px;
padding:12px 26px;border-radius:10px;font-weight:600;font-size:15px;
transition:all .15s}
.btn-p{background:#2563eb;color:#fff}
.btn-p:hover{background:#1d4ed8;transform:translateY(-1px)}
.btn-s{background:#fafbfc;color:#1a1d21;
border:1px solid #e6e8eb}
.btn-s:hover{border-color:#d1d5db;background:#f3f4f6}
.badge{display:inline-block;margin-top:22px;padding:4px 14px;
border-radius:999px;background:#eff6ff;color:#2563eb;
font-size:13px;font-weight:600}
/* 通用区段 */
section{padding:72px 0;border-top:1px solid #e6e8eb}
.sec-head{text-align:center;margin-bottom:48px}
.sec-head h2{font-size:30px;letter-spacing:-.6px;font-weight:700;
margin-bottom:10px}
.sec-head p{color:#6b7280;font-size:16px}
/* 功能网格 */
.feats{display:grid;gap:1px;background:#e6e8eb;
border:1px solid #e6e8eb;border-radius:14px;overflow:hidden}
@media(min-width:640px){.feats{grid-template-columns:repeat(3,1fr)}}
.feat{background:#ffffff;padding:28px 24px}
.feat-icon{color:#2563eb;margin-bottom:14px}
.feat-title{font-weight:650;font-size:16px;margin-bottom:6px}
.feat-desc{color:#6b7280;font-size:14px;line-height:1.7}
/* 三步 */
.steps{display:grid;gap:20px}
@media(min-width:640px){.steps{grid-template-columns:repeat(3,1fr)}}
.step{padding:26px 22px;border:1px solid #e6e8eb;border-radius:14px;
background:#fafbfc}
.step-num{width:30px;height:30px;border-radius:50%;background:#2563eb;
color:#fff;display:flex;align-items:center;justify-content:center;
font-weight:700;font-size:14px;margin-bottom:14px}
.step-title{font-weight:650;font-size:16px;margin-bottom:6px}
.step-desc{color:#6b7280;font-size:14px;line-height:1.7}
/* 下载 */
.dls{display:grid;gap:16px}
@media(min-width:760px){.dls{grid-template-columns:repeat(3,1fr)}}
.dl-card{border:1px solid #e6e8eb;border-radius:14px;padding:24px 20px;
display:flex;flex-direction:column;background:#ffffff;
transition:border-color .15s}
.dl-card:hover{border-color:#d1d5db}
.dl-head{display:flex;align-items:baseline;gap:8px;margin-bottom:4px}
.dl-tag{font-weight:700;font-size:17px}
.dl-who{color:#9ca3af;font-size:13px}
.dl-note{color:#6b7280;font-size:13px;margin-bottom:18px;flex:1}
.dl-btn{display:block;text-align:center;background:#2563eb;color:#fff;
padding:11px 16px;border-radius:9px;font-weight:600;font-size:14px;
transition:background .15s}
.dl-btn:hover{background:#1d4ed8}
.dl-file{color:#9ca3af;font-size:11px;margin-top:9px;text-align:center;
word-break:break-all}
.dl-soon{text-align:center;color:#9ca3af;font-size:14px;padding:11px 0;
border:1px dashed #e6e8eb;border-radius:9px}
.dl-req{color:#9ca3af;font-size:12px;margin-top:12px;text-align:center}
/* FAQ */
.faqs{max-width:720px;margin:0 auto}
.faq{border-bottom:1px solid #e6e8eb}
.faq summary{padding:20px 0;font-weight:600;font-size:16px;cursor:pointer;
list-style:none;display:flex;justify-content:space-between;
align-items:center;gap:16px}
.faq summary::-webkit-details-marker{display:none}
.faq-mark{width:9px;height:9px;border-right:2px solid #9ca3af;
border-bottom:2px solid #9ca3af;transform:rotate(45deg);
transition:transform .2s;flex:none;margin-right:4px}
.faq[open] .faq-mark{transform:rotate(-135deg)}
.faq-body{color:#6b7280;font-size:15px;padding:0 0 22px;line-height:1.8}
/* 页脚 */
footer{padding:52px 0 64px;border-top:1px solid #e6e8eb;
color:#6b7280;font-size:14px}
.foot-in{display:flex;justify-content:space-between;gap:24px;
flex-wrap:wrap;align-items:center}
.foot-links{display:flex;gap:22px}
.foot-links a:hover{color:#1a1d21}
.copy{color:#9ca3af;font-size:13px;margin-top:26px;text-align:center}
/* ---------- 产品展示 ---------- */
.showcase{display:grid;gap:34px;align-items:start;
justify-items:center}
@media(min-width:820px){.showcase{grid-template-columns:1fr 1fr}}
/* 手表 mockup。尺寸按真实手表屏幕比例 */
.watch{width:300px;padding:10px;border-radius:34px;
background:linear-gradient(160deg,#2b3038,#14171c);
box-shadow:0 18px 40px rgba(16,24,40,.18);
position:relative}
.watch::after{content:'';position:absolute;right:-3px;top:110px;
width:5px;height:44px;border-radius:3px;background:#3a4048}
.w-screen{background:#F5F7FA;border-radius:26px;overflow:hidden;
font-size:11px;color:#1F2328}
.w-status{display:flex;justify-content:space-between;align-items:center;
padding:11px 13px 7px;font-size:10px;font-weight:600}
.w-conn{color:#16a34a;font-weight:600}
.w-tabs{display:flex;gap:4px;overflow:hidden;padding:0 11px 8px;
border-bottom:1px solid #E3E7ED}
.w-tab{flex:none;padding:3px 8px;border-radius:999px;color:#656D76;
font-size:10px;white-space:nowrap}
.w-tab.on{background:#185CE8;color:#fff;font-weight:600}
.w-body{padding:12px 13px 14px}
.w-head{font-size:12px;font-weight:700;margin-bottom:10px}
.w-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:7px}
.w-btn{background:#FFFFFF;border:1px solid #E3E7ED;
border-radius:11px;padding:11px 4px 9px;text-align:center;
display:flex;flex-direction:column;align-items:center;gap:5px}
.w-ic{font-size:15px;line-height:1}
.w-lb{font-size:9px;color:#1F2328;white-space:nowrap}
.w-foot{padding:9px 13px 12px;color:#656D76;font-size:9px;
border-top:1px solid #E3E7ED}
/* 电脑端截图 */
.pcshot{margin:0;text-align:center}
.pcshot img{width:100%;max-width:360px;height:auto;
border:1px solid #E3E7ED;border-radius:12px;
box-shadow:0 18px 40px rgba(16,24,40,.14)}
.pcshot figcaption{margin-top:14px;color:#656D76;font-size:13px}
/* 连接方式示意 */
.flow{display:grid;gap:18px}
@media(min-width:760px){.flow{grid-template-columns:1fr 1fr}}
.flow-col{border:1px solid #e6e8eb;border-radius:14px;padding:24px 20px;
background:#fafbfc}
.flow-tag{display:inline-block;padding:3px 11px;border-radius:999px;
font-size:12px;font-weight:600;margin-bottom:20px}
.flow-tag.ok{background:#dcfce7;color:#15803d}
.flow-tag.warn{background:#fef3c7;color:#a16207}
.flow-row{display:flex;align-items:center;justify-content:center;
gap:2px;margin-bottom:16px;flex-wrap:nowrap}
.node{display:flex;flex-direction:column;align-items:center;gap:4px;
font-size:22px;flex:none}
.node span{font-size:11px;color:#6b7280}
.node.relay{font-size:19px}
.link{flex:1;min-width:16px;position:relative;display:flex;
align-items:center;justify-content:center}
.link .line{width:100%;height:2px;background:#d1d5db;border-radius:2px}
.link .ms{position:absolute;top:-15px;font-size:10px;color:#15803d;
font-weight:600;white-space:nowrap}
.flow-col p{color:#6b7280;font-size:13px;line-height:1.7;margin:0}
</style>
</head>
<body>
<nav><div class="wrap nav-in">
<a class="brand" href="/">
<svg width="26" height="26" viewBox="0 0 32 32" fill="none" aria-hidden="true"><rect x="3" y="3" width="26" height="26" rx="8" fill="#2563eb"/><rect x="10" y="9" width="12" height="14" rx="4" stroke="#fff" stroke-width="2"/><path d="M13 9V6h6v3M13 23v3h6v-3" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
<span>ykxmz</span>
</a>
<div class="nav-links">
<a href="#features">功能</a>
<a href="#how">怎么用</a>
<a href="#download">下载</a>
<a href="#faq">常见问题</a>
</div>
</div></nav>
<div class="wrap hero">
<h1>老师抬一下手腕<br>就能管教室那台电脑</h1>
<p>放课件、放音频、投图到大屏、看看有没有人在跑无关程序、下课关机 ——
不用走回讲台去点鼠标。</p>
<div class="cta">
<a class="btn btn-p" href="#download">下载安装</a>
<a class="btn btn-s" href="#how">先看看怎么用</a>
</div>
<div class="badge">最新版 1.7.1</div>
</div>
<section id="features"><div class="wrap">
<div class="sec-head">
<h2>上课用得上的,基本都在这</h2>
<p>手表上十个分页,常用操作都在第一屏</p>
</div>
<div class="feats"><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M3 5h18v11H3z"/><path d="M1 20h22"/></svg></div><div class="feat-title">屏幕查看</div><div class="feat-desc">手表上看教室里那台电脑在显示什么,顺手一键截屏存到手表</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M6 4l14 8-14 8z"/></svg></div><div class="feat-title">课件播放</div><div class="feat-desc">放音频、放视频、调音量、静音 —— 不用走回讲台去点鼠标</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="8.5" cy="9.5" r="1.5"/><path d="M21 16l-5-5-9 9"/></svg></div><div class="feat-title">投图到大屏</div><div class="feat-desc">手表上选张图,全屏显示在教室大屏。考试时贴个提示特别方便</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/></svg></div><div class="feat-title">传文件</div><div class="feat-desc">课件在两台机器之间来回倒腾,不用插 U 盘</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="6" width="12" height="12" rx="3"/><path d="M9 6V3h6v3M9 18v3h6v-3"/></svg></div><div class="feat-title">课堂专注</div><div class="feat-desc">一键限制游戏相关站点、关闭游戏窗口,下课一键恢复</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v10"/><path d="M6 6a9 9 0 1 0 12 0"/></svg></div><div class="feat-title">电源控制</div><div class="feat-desc">锁屏、睡眠、关机、重启,还能设「30 分钟后自动关机」</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5a10 10 0 0 1 14 0"/><path d="M8.5 16a5.5 5.5 0 0 1 7 0"/><circle cx="12" cy="19.5" r="1"/></svg></div><div class="feat-title">局域网优先</div><div class="feat-desc">手表和电脑在同一个 WiFi 时直连,几十毫秒,不绕服务器</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2l8 4v6c0 5-3.5 8.5-8 10-4.5-1.5-8-5-8-10V6z"/></svg></div><div class="feat-title">跨网络</div><div class="feat-desc">不在同一个网络时自动走中转,办公室也能管教室</div></div><div class="feat"><div class="feat-icon"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="10" width="16" height="11" rx="2"/><path d="M8 10V7a4 4 0 0 1 8 0v3"/></svg></div><div class="feat-title">可见可控</div><div class="feat-desc">屏幕被看时电脑上一定有提示条,关不掉;本机随时能掐断</div></div></div>
</div></section>
<section id="how"><div class="wrap">
<div class="sec-head">
<h2>三步装好</h2>
<p>电脑装一个程序,手表装一个 App,一台电脑配一次就行</p>
</div>
<div class="steps"><div class="step"><div class="step-num">1</div><div class="step-title">电脑端</div><div class="step-desc">在教室那台 Windows 电脑上运行安装包,填一个 6 位配对码,装完自动后台运行</div></div><div class="step"><div class="step-num">2</div><div class="step-title">手表端</div><div class="step-desc">在安卓手表上装 APK,首页点「局域网连接」或「输入配对码连接」</div></div><div class="step"><div class="step-num">3</div><div class="step-title">连上就用</div><div class="step-desc">配对一次之后,以后打开就自动连,不用再输数字</div></div></div>
</div></section>
<section id="shots"><div class="wrap">
<div class="sec-head">
<h2>长什么样</h2>
<p>手表上十个分页,常用操作都在第一屏</p>
</div>
<div class="showcase">
<div class="watch"><div class="w-screen"><div class="w-status"><span>ykxmz</span><span class="w-conn">● 已连接 · 局域网 3 ms</span></div><div class="w-tabs"><span class="w-tab on">媒体</span><span class="w-tab">屏幕</span><span class="w-tab">文件</span><span class="w-tab">语音</span><span class="w-tab">电脑</span><span class="w-tab">网络</span><span class="w-tab">系统</span><span class="w-tab">教室</span><span class="w-tab">效率</span><span class="w-tab">娱乐</span></div><div class="w-body"><div class="w-head">媒体</div><div class="w-grid"><div class="w-btn"><span class="w-ic">🔊</span><span class="w-lb">放音频</span></div><div class="w-btn"><span class="w-ic">▶</span><span class="w-lb">放视频</span></div><div class="w-btn"><span class="w-ic">⏸</span><span class="w-lb">暂停</span></div><div class="w-btn"><span class="w-ic">⏵</span><span class="w-lb">继续</span></div><div class="w-btn"><span class="w-ic">+</span><span class="w-lb">音量 +</span></div><div class="w-btn"><span class="w-ic">−</span><span class="w-lb">音量 −</span></div><div class="w-btn"><span class="w-ic">🔇</span><span class="w-lb">静音</span></div><div class="w-btn"><span class="w-ic">⏭</span><span class="w-lb">下一首</span></div><div class="w-btn"><span class="w-ic">🖼</span><span class="w-lb">投图到屏幕</span></div></div></div><div class="w-foot">被控端:教室电脑 · 192.168.2.64</div></div></div>
<figure class="pcshot"><img src="/shot/pc-main.png" alt="ykxmz 电脑端首次设置界面" loading="lazy"><figcaption>电脑端:装完填个 6 位配对码就行</figcaption></figure>
</div>
</div></section>
<section id="how-connect"><div class="wrap">
<div class="sec-head">
<h2>怎么连上的</h2>
<p>同一个 WiFi 就走局域网直连,不同网络自动切中转</p>
</div>
<div class="flow">
<div class="flow-col">
<div class="flow-tag ok">同一个 WiFi</div>
<div class="flow-row">
<div class="node">⌚<span>手表</span></div>
<div class="link"><span class="ms">3 ms</span>
<div class="line"></div></div>
<div class="node">🖥<span>教室电脑</span></div>
</div>
<p>直连,不经过任何服务器。几十毫秒,画面基本跟手。</p>
</div>
<div class="flow-col">
<div class="flow-tag warn">不在同一个网络</div>
<div class="flow-row">
<div class="node">⌚<span>手表</span></div>
<div class="link"><div class="line"></div></div>
<div class="node relay">☁<span>中转</span></div>
<div class="link"><div class="line"></div></div>
<div class="node">🖥<span>电脑</span></div>
</div>
<p>两边都主动连到中转,绕过 NAT。办公室也能管教室。</p>
</div>
</div>
</div></section>
<section id="download"><div class="wrap">
<div class="sec-head">
<h2>下载</h2>
<p>当前版本 1.7.1</p>
</div>
<div class="dls"><div class="dl-card"><div class="dl-head"><span class="dl-tag">电脑端</span><span class="dl-who">教室那台 Windows 电脑</span></div><div class="dl-note">装完自动后台运行</div><a class="dl-btn" href="/download/ykxmz-setup-1.7.1.exe">下载 安装包</a><div class="dl-file">ykxmz-setup-1.7.1.exe · 14.4 MB</div><div class="dl-req">Windows 10 / 11</div></div><div class="dl-card"><div class="dl-head"><span class="dl-tag">手表端</span><span class="dl-who">安卓手表</span></div><div class="dl-note">直接安装,不用 root</div><a class="dl-btn" href="/download/ykxmz-watch-1.7.1.apk">下载 APK</a><div class="dl-file">ykxmz-watch-1.7.1.apk · 0.2 MB</div><div class="dl-req">Android 7.0+</div></div><div class="dl-card"><div class="dl-head"><span class="dl-tag">服务器端</span><span class="dl-who">一台能上外网的小机器</span></div><div class="dl-note">只在跨网络时才需要</div><a class="dl-btn" href="/download/ykxmz-server-1.7.1.zip">下载 压缩包</a><div class="dl-file">ykxmz-server-1.7.1.zip · 0.2 MB</div><div class="dl-req">Debian / Ubuntu</div></div></div>
</div></section>
<section id="faq"><div class="wrap">
<div class="sec-head"><h2>常见问题</h2></div>
<div class="faqs"><details class="faq"><summary>手表连不上电脑怎么办?<span class="faq-mark"></span></summary><div class="faq-body">先确认电脑上程序在运行(任务管理器里找 ykxmz.exe)。如果被控端窗口提示「防火墙未放行」,点旁边的一键修复。还是不行就跑一次网络自检 —— 电脑上打开工具箱选 3,然后手表上点「检查服务器」。</div></details><details class="faq"><summary>为什么显示「延迟 2000 毫秒」这么慢?<span class="faq-mark"></span></summary><div class="faq-body">跨网络时数据要绕到中转服务器再回来,延迟取决于你那台小机器的线路。如果手表和电脑在同一个 WiFi,会自动走局域网直连,延迟通常只有几十毫秒。</div></details><details class="faq"><summary>需要另外买服务器吗?<span class="faq-mark"></span></summary><div class="faq-body">只有一个教室用的话不需要 —— 手表和电脑连同一个 WiFi 就能直连。要在办公室管教室才需要一台能上外网的小机器当中转。</div></details><details class="faq"><summary>配对码忘了怎么办?<span class="faq-mark"></span></summary><div class="faq-body">电脑上双击程序图标打开窗口就能看到。也可以在窗口里改一个自己好记的。</div></details><details class="faq"><summary>手表重装了还能连上原来的电脑吗?<span class="faq-mark"></span></summary><div class="faq-body">能。重新输一次那个 6 位配对码就行 —— 配对码本身就是钥匙。</div></details><details class="faq"><summary>屏幕被别人看着,我能知道吗?<span class="faq-mark"></span></summary><div class="faq-body">能。只要有人在看你的屏幕,电脑右上角就会出现一条红色提示条,没有开关能关掉它。电脑前面的人随时可以点「停止共享」断开。</div></details></div>
</div></section>
<footer><div class="wrap">
<div class="foot-in">
<div class="brand"><svg width="26" height="26" viewBox="0 0 32 32" fill="none" aria-hidden="true"><rect x="3" y="3" width="26" height="26" rx="8" fill="#2563eb"/><rect x="10" y="9" width="12" height="14" rx="4" stroke="#fff" stroke-width="2"/><path d="M13 9V6h6v3M13 23v3h6v-3" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg><span>ykxmz</span></div>
<div class="foot-links">
<a href="https://gitee.com/xmz7891/ykxmz" target="_blank" rel="noopener">源码</a>
<a href="https://gitee.com/xmz7891/ykxmz/releases" target="_blank" rel="noopener">历史版本</a>
<a href="#faq">常见问题</a>
</div>
</div>
<div class="copy">ykxmz · 教室电脑手表遥控</div>
</div></footer>
<script>
// 点导航链接后收起移动端(这里没做汉堡菜单,留个锚点平滑滚动就行)
document.querySelectorAll('a[href^="#"]').forEach(function (a) {
a.addEventListener('click', function (e) {
var target = document.querySelector(a.getAttribute('href'));
if (target) { e.preventDefault(); target.scrollIntoView({behavior:'smooth'}); }
});
});
</script>
</body>
</html>