dsh-remote-web 0.7.11-beta.3 → 0.7.12-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -30,7 +30,7 @@
30
30
  // 排在「连接微信机器人」按钮之前 —— 它是绑定的理由,又不把按钮挤出首屏。
31
31
  // ✨ 动效引导:未绑定时「连接」按钮做很慢的呼吸(.dru-wx-attn),
32
32
  // prefers-reduced-motion:reduce 下关闭;状态从不靠动效单独表达。
33
- // · bridge 状态与启停开关 + 关于 dsh-remote 说明卡片
33
+ // · bridge 状态与启停开关 + 关于 dsh-remote-web 说明卡片
34
34
  // · 🧰 账号卡「更多」:只收纳(用户关掉邀请引导之后的)「升级 / 带新用户」两个入口 ——
35
35
  // 主界面只剩一个入口,减少营销露出;**档位规格数字(Mbps / GB / 价格)一律不展示**
36
36
  // (0.6.11 用户约定:「插件面板里面不要展示『更多』里的 PRO 版本流量带宽」)
@@ -667,6 +667,26 @@ window.__ModuleLoader__.load({
667
667
  ".dru-ia-iconbtn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;flex:none;border-radius:8px;border:1px solid var(--dru-border-ctl);background:var(--dru-surface-2);color:var(--dsw-alias-label-secondary,var(--dru-fg-muted));cursor:pointer}",
668
668
  ".dru-ia-iconbtn:hover{color:var(--dru-accent);border-color:var(--dru-accent)}",
669
669
  ".dru-ia-iconbtn:focus-visible{outline:2px solid var(--dru-focus);outline-offset:2px}",
670
+ // 会员档位条 + 「会员功能」徽章(2026-11-07 桥侧会员判定;只做展示,不做门禁)
671
+ ".dru-ia-tier{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-radius:8px;font-size:12px;line-height:1.5;background:var(--dru-surface-3);color:var(--dsw-alias-label-secondary,var(--dru-fg-muted));border:1px solid var(--dru-border-soft)}",
672
+ ".dru-ia-tier.is-member{color:var(--dru-accent-fg);background:var(--dru-accent-tint);border-color:var(--dru-accent-tint-border)}",
673
+ ".dru-ia-tier-name{font-weight:600;min-width:0}",
674
+ ".dru-ia-tier-link{flex:none;font-size:12px;color:var(--dru-accent);text-decoration:none}",
675
+ ".dru-ia-tier-link:hover{text-decoration:underline}",
676
+ ".dru-ia-badge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:999px;font-size:11px;font-weight:600;line-height:1.4;color:var(--dru-accent-fg);background:var(--dru-accent-tint);border:1px solid var(--dru-accent-tint-border);cursor:help}",
677
+ "select.dru-ia-input[disabled]{opacity:.6;cursor:not-allowed;background:var(--dru-surface-3)}",
678
+ // A3/A6(2026-11-07 业主逐条 UI 修正):状态头右侧按钮组 / 后台服务横向紧凑布局 / 卸载反馈单选行
679
+ ".dru-ia-head-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end}",
680
+ ".dru-bridge-keep{display:flex;align-items:center;gap:6px;margin:8px 0 10px;padding:7px 10px;border-radius:8px;font-size:12px;line-height:1.5;color:var(--dru-warn-fg);background:var(--dru-warn-soft);border:1px solid var(--dru-warn-border)}",
681
+ ".dru-bridge-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 16px;align-items:center}",
682
+ "@media (max-width:520px){.dru-bridge-grid{grid-template-columns:minmax(0,1fr)}}",
683
+ ".dru-bridge-cell{min-width:0;font-size:12px;line-height:1.5;color:var(--dsw-alias-label-tertiary,var(--dru-fg-muted))}",
684
+ ".dru-bridge-cell .dru-status-line{margin:0}",
685
+ ".dru-bridge-cell-action{display:flex;justify-content:flex-end}",
686
+ "@media (max-width:520px){.dru-bridge-cell-action{justify-content:flex-start}}",
687
+ ".dru-actions-wrap{flex-wrap:wrap}",
688
+ ".dru-ia-row-compact{min-height:40px;padding:6px 10px}",
689
+ ".dru-ia-row-compact input[type=radio]{flex:none;margin:0}",
670
690
  // 配置页的「为什么要填这些」折叠按钮 + 原生 select 统一外观(业主:别文字堆砌,多余信息收起来)
671
691
  ".dru-ia-disclose{display:flex;align-items:center;justify-content:space-between;gap:8px;min-height:32px;padding:0;border:0;background:none;color:var(--dsw-alias-label-tertiary,var(--dru-fg-muted));font:inherit;font-size:12px;cursor:pointer;text-align:left}",
672
692
  ".dru-ia-disclose:hover{color:var(--dru-accent)}",
@@ -1956,7 +1976,7 @@ window.__ModuleLoader__.load({
1956
1976
  return {
1957
1977
  bucket: "permission",
1958
1978
  title: "没有写入权限(装不进运行目录)",
1959
- hint: "点「一键修复」重装到可写目录;若装了安全软件(如杀毒/EDR),请允许 dsh-remote 写入用户目录后重试。",
1979
+ hint: "点「一键修复」重装到可写目录;若装了安全软件(如杀毒/EDR),请允许 dsh-remote-web 写入用户目录后重试。",
1960
1980
  repair: true, retry: true
1961
1981
  };
1962
1982
  }
@@ -1990,7 +2010,7 @@ window.__ModuleLoader__.load({
1990
2010
  return {
1991
2011
  bucket: "permission",
1992
2012
  title: "没有写入权限(装不进运行目录)",
1993
- hint: "点「一键修复」重装到可写目录;若装了安全软件(如杀毒/EDR),请允许 dsh-remote 写入用户目录后重试。",
2013
+ hint: "点「一键修复」重装到可写目录;若装了安全软件(如杀毒/EDR),请允许 dsh-remote-web 写入用户目录后重试。",
1994
2014
  repair: true, retry: true
1995
2015
  };
1996
2016
  }
@@ -2804,6 +2824,16 @@ window.__ModuleLoader__.load({
2804
2824
  // 最近一次拉到的日志尾巴(**与 log 分开存**的原因见 applyUpdLog 收尾分支:
2805
2825
  // log 只服务于"正在更新"的黑框,收尾即清空;lastLog 留给用户主动回看)。
2806
2826
  var lastLogArr = useState(""); var lastLog = lastLogArr[0]; var setLastLog = lastLogArr[1];
2827
+ // A7(2026-11-07 业主):彻底卸载前先做一次**挽留反馈**("为什么要卸载?" + 建议),
2828
+ // 提交走现有反馈通道(POST /dsh-remote/feedback,kind=uninstall),然后再继续卸载。
2829
+ var unrOpenArr = useState(false); var unrOpen = unrOpenArr[0]; var setUnrOpen = unrOpenArr[1];
2830
+ var unrReasonArr = useState(""); var unrReason = unrReasonArr[0]; var setUnrReason = unrReasonArr[1];
2831
+ var unrNoteArr = useState(""); var unrNote = unrNoteArr[0]; var setUnrNote = unrNoteArr[1];
2832
+ var unrBusyArr = useState(false); var unrBusy = unrBusyArr[0]; var setUnrBusy = unrBusyArr[1];
2833
+ var unrErrArr = useState(null); var unrErr = unrErrArr[0]; var setUnrErr = unrErrArr[1];
2834
+ // 2026-11-07 业主:版本卡里给出可复制的安装/更新命令(npm 包名保持不变:@mrrisega/dsh-remote)。
2835
+ var INSTALL_CMD = "npx @mrrisega/dsh-remote@latest";
2836
+ var instCopyArr = useState(false); var instCopied = instCopyArr[0]; var setInstCopied = instCopyArr[1];
2807
2837
 
2808
2838
  var loadVer = useCallback(function () {
2809
2839
  api("/dsh-remote/self").then(function (b) {
@@ -2971,8 +3001,94 @@ window.__ModuleLoader__.load({
2971
3001
  }).finally(function () { setCxBusy(false); });
2972
3002
  };
2973
3003
 
3004
+ /** A7 弹层:为什么要卸载?+ 原因单选 + 建议 + 「仍要卸载 / 我再想想」。 */
3005
+ function renderUninstallFeedback() {
3006
+ var close = function () { if (!unrBusy) setUnrOpen(false); };
3007
+ return iaDialog(Object.assign({
3008
+ title: "卸载前,想听听你的想法",
3009
+ sub: "提交后会继续卸载;也可以点「我再想想」退出",
3010
+ onClose: close,
3011
+ foot: [
3012
+ h("button", { type: "button", className: "dru-ia-btn", disabled: unrBusy, onClick: close }, "我再想想"),
3013
+ h("button", {
3014
+ type: "button", className: "dru-ia-btn is-danger", disabled: unrBusy,
3015
+ onClick: function () { submitUninstallFeedback(true); }
3016
+ }, unrBusy ? "提交中…" : "仍要卸载")
3017
+ ],
3018
+ children: [
3019
+ h("div", { className: "dru-ia-field dru-field" },
3020
+ h("div", { className: "dru-ia-label" }, "为什么要卸载?"),
3021
+ h("div", { className: "dru-ia-list" }, UNINSTALL_REASONS.map(function (r) {
3022
+ return h("label", { key: r.id, className: "dru-ia-row dru-ia-row-compact", style: { cursor: "pointer" } },
3023
+ h("input", {
3024
+ type: "radio", name: "dru-uninstall-reason", value: r.id,
3025
+ checked: unrReason === r.id, disabled: unrBusy,
3026
+ onChange: function () { setUnrReason(r.id); setUnrErr(null); }
3027
+ }),
3028
+ h("span", { className: "dru-ia-row-main" }, h("span", { className: "dru-ia-row-title" }, r.label)));
3029
+ })),
3030
+ unrErr ? h("div", { className: "dru-ia-err", role: "alert" }, unrErr) : null),
3031
+ h("div", { className: "dru-ia-field dru-field" },
3032
+ h("label", { className: "dru-ia-label", htmlFor: "dru-uninstall-note" }, "还有什么想告诉我们的?(可选)"),
3033
+ h("textarea", {
3034
+ id: "dru-uninstall-note", className: "dru-ia-input", rows: 3, value: unrNote,
3035
+ placeholder: "比如卡在哪一步、希望我们怎么改", disabled: unrBusy,
3036
+ onChange: function (e) { setUnrNote(e.target.value); }
3037
+ })),
3038
+ // 后果说明(业主 A7:卸载前讲清楚会发生什么;也让"不再保留 bridge/配置"的文案有落点)
3039
+ h("div", { className: "dru-ia-hint" },
3040
+ "仍要卸载会:① 停止并移除 bridge 自启动服务;② 清空本地配置目录(~/.dsh-remote:账号、设备密钥、固化运行时等)。此操作不可撤销。")
3041
+ ] }));
3042
+ }
3043
+ // Esc 关闭(弹层键盘契约;与面板里 iaDialog 的交互保持一致)
3044
+ useEffect(function () {
3045
+ if (!unrOpen) return undefined;
3046
+ var onKey = function (ev) {
3047
+ try {
3048
+ if (!ev) return;
3049
+ if (ev.key === "Escape" || ev.keyCode === 27) { if (!unrBusy) setUnrOpen(false); }
3050
+ } catch (e) { /* 忽略 */ }
3051
+ };
3052
+ try { document.addEventListener("keydown", onKey, true); } catch (e) { /* 忽略 */ }
3053
+ return function () { try { document.removeEventListener("keydown", onKey, true); } catch (e) { /* 忽略 */ } };
3054
+ }, [unrOpen, unrBusy]);
3055
+
3056
+ /** 挽留反馈的选项(业主:几个原因单选 + 可填建议)。 */
3057
+ var UNINSTALL_REASONS = [
3058
+ { id: "unstable", label: "功能不好用 / 不稳定" },
3059
+ { id: "complex", label: "太复杂,看不懂怎么用" },
3060
+ { id: "alternative", label: "已经有别的替代工具" },
3061
+ { id: "resource", label: "占用资源 / 影响电脑运行" },
3062
+ { id: "temporary", label: "只是临时不用了,以后可能再装" },
3063
+ { id: "other", label: "其它原因" }
3064
+ ];
3065
+ /** A7:点「彻底卸载」→ 先弹挽留反馈(不直接卸载;确认语义由这个弹层承担)。 */
2974
3066
  var doUninstall = function () {
2975
- if (!armed) { setArmed(true); return; }
3067
+ setUnrReason("");
3068
+ setUnrNote("");
3069
+ setUnrErr(null);
3070
+ setUnrOpen(true);
3071
+ };
3072
+ /** 提交挽留反馈(失败也继续卸载:反馈是"尽量收集",不能反过来卡住用户)。 */
3073
+ var submitUninstallFeedback = function (thenUninstall) {
3074
+ if (unrBusy) return;
3075
+ var reason = String(unrReason || "");
3076
+ if (!reason) { setUnrErr("请选择一个卸载原因(必选)"); return; }
3077
+ var hit = null;
3078
+ for (var i = 0; i < UNINSTALL_REASONS.length; i++) { if (UNINSTALL_REASONS[i].id === reason) { hit = UNINSTALL_REASONS[i]; break; } }
3079
+ var content = "[卸载反馈] 原因:" + ((hit && hit.label) || reason)
3080
+ + (unrNote.trim() ? ("\n建议:" + unrNote.trim()) : "");
3081
+ setUnrBusy(true);
3082
+ setUnrErr(null);
3083
+ post("/dsh-remote/feedback", { kind: "uninstall", category: "uninstall", content: content })
3084
+ .catch(function () { /* 反馈失败静默:不能因此拦住用户卸载 */ })
3085
+ .finally(function () {
3086
+ setUnrBusy(false);
3087
+ setUnrOpen(false);
3088
+ if (thenUninstall) runUninstall();
3089
+ });
3090
+ };
3091
+ var runUninstall = function () {
2976
3092
  setUnBusy(true);
2977
3093
  setSelfMsg(null);
2978
3094
  // 卸载:服务端现在"先卸插件、先回响应、慢活交子进程",正常应在 1 秒内返回;
@@ -3036,6 +3152,26 @@ window.__ModuleLoader__.load({
3036
3152
  ? h("span", { className: "dru-ver-badge dru-ver-badge-new" }, "发现新版本 v" + chk.latest)
3037
3153
  : chk && !outdated ? h("span", { className: "dru-ver-badge dru-ver-badge-ok" }, "已是最新版本") : null
3038
3154
  ),
3155
+ // 安装 / 更新命令(可复制;复用账号卡那套 .dru-copy-* 样式与反馈)
3156
+ h("div", { className: "dru-copy-row", style: { marginTop: 8 } },
3157
+ h("div", { className: "dru-copy-label" }, "安装 / 更新命令"),
3158
+ h("div", { className: "dru-copy-box" },
3159
+ h("span", { className: "dru-code-val" }, INSTALL_CMD),
3160
+ h("button", {
3161
+ type: "button",
3162
+ className: "dru-copy-btn" + (instCopied ? " copied" : ""),
3163
+ "aria-label": "复制安装命令",
3164
+ onClick: function () {
3165
+ copyText(INSTALL_CMD).then(function (done) {
3166
+ if (!done) { setSelfMsg({ kind: "err", text: "复制失败:请手动选中上面的命令复制(浏览器可能限制了剪贴板权限)" }); return; }
3167
+ setInstCopied(true);
3168
+ try { setTimeout(function () { setInstCopied(false); }, 1600); } catch (e) { /* 忽略 */ }
3169
+ });
3170
+ }
3171
+ }, instCopied ? "✅ 已复制" : "复制命令")
3172
+ ),
3173
+ h("div", { className: "dru-hint", style: { marginTop: 6 } }, "已安装的更新:重跑这条命令即可。")
3174
+ ),
3039
3175
  h("div", { className: "dru-meta" },
3040
3176
  runtimeReady === false
3041
3177
  ? "⚠ 后台服务运行环境缺失(点下方「一键更新」会自动补齐并启动,不用手动装)"
@@ -3103,7 +3239,7 @@ window.__ModuleLoader__.load({
3103
3239
  style: { marginLeft: "auto" },
3104
3240
  disabled: unBusy || updating || upBusy,
3105
3241
  onClick: doUninstall
3106
- }, unBusy ? "卸载中…" : armed ? "⚠ 再点一次确认彻底卸载" : "彻底卸载")
3242
+ }, unBusy ? "卸载中…" : "彻底卸载")
3107
3243
  ),
3108
3244
  cxMsg ? h("div", { className: "dru-msg dru-msg-" + cxMsg.kind }, cxMsg.text) : null,
3109
3245
  updated
@@ -3137,8 +3273,10 @@ window.__ModuleLoader__.load({
3137
3273
  : null,
3138
3274
  selfMsg ? h("div", { className: "dru-msg dru-msg-" + selfMsg.kind }, selfMsg.text) : null,
3139
3275
  h("div", { className: "dru-hint", style: { marginTop: 8 } },
3140
- armed ? "⚠ 再次点击后即开始彻底卸载:① 移除 dsh web 配置中的插件引用与本地文件;② 停止并移除 bridge 自启动服务(即让「后台服务」不再开机自启:macOS com.dshremote.bridge / Linux dsh-bridge / Windows 任务计划程序 dsh-remote-bridge)并结束残留进程;③ 清空本地配置目录(~/.dsh-remote:账号、设备密钥、固化运行时等)。此操作不可撤销,如需再次使用:在插件市场搜索「dsh-remote-web」重新安装,或执行 npx @mrrisega/dsh-remote。" :
3141
- "检测新版、一键在线更新、彻底卸载都在本卡片完成;更新卡住时会出现「取消并重试」。")
3276
+ "", // A7:二次确认已改成"挽留反馈弹层"(见 renderUninstallFeedback),这里不再有 armed 文案
3277
+ "检测新版、一键在线更新、彻底卸载都在本卡片完成;更新卡住时会出现「取消并重试」。"
3278
+ + "彻底卸载前会先询问一句卸载原因(可跳过反馈直接卸载)。"),
3279
+ unrOpen ? renderUninstallFeedback() : null
3142
3280
  );
3143
3281
  }
3144
3282
 
@@ -3443,6 +3581,9 @@ window.__ModuleLoader__.load({
3443
3581
  // 从「飞书/钉钉即将支持 → 告诉我,优先支持」跳到反馈视图时带过去的一句引导(独立状态:
3444
3582
  // message 会被面板里各种动作清掉,跨视图的上下文不能押在它身上)。
3445
3583
  var iaFbHintArr = useState(""); var iaFbHint = iaFbHintArr[0]; var setIaFbHint = iaFbHintArr[1];
3584
+ // 未登录守卫:为什么弹登录(A4/A5)+ 登录后自动回到哪个流程("手机访问"登录完继续生成访问码)。
3585
+ var iaLoginWhyArr = useState(""); var iaLoginWhy = iaLoginWhyArr[0]; var setIaLoginWhy = iaLoginWhyArr[1];
3586
+ var iaAfterLoginArr = useState(""); var iaAfterLogin = iaAfterLoginArr[0]; var setIaAfterLogin = iaAfterLoginArr[1];
3446
3587
 
3447
3588
  /** 读通道配置(GET 只回脱敏视图:botId/hasSecret/url/targetChatId + 桥写的状态)。 */
3448
3589
  function loadImBots() {
@@ -3624,8 +3765,12 @@ window.__ModuleLoader__.load({
3624
3765
  .finally(function () { setImBotsBusy(""); });
3625
3766
  }
3626
3767
 
3627
- var refresh = useCallback(function () {
3628
- setBusy("status");
3768
+ /**
3769
+ * 拉全量状态。quiet=true:**不占用忙碌态**(用于"登录后异步补状态"——
3770
+ * 业主实测登录 7~8 秒的体验里,按钮一直转圈是主因之一;这里让按钮只反映登录请求本身)。
3771
+ */
3772
+ var refresh = useCallback(function (quiet) {
3773
+ if (!quiet) setBusy("status");
3629
3774
  var t0 = netNow(); // 📶 延迟采样点①:打开面板时这一次 /status(既有请求,不新增)
3630
3775
  api("/dsh-remote/status").then(function (body) {
3631
3776
  noteRtt(netNow() - t0); // 先记样本再 setSt —— 这一次的延迟当轮就能显示出来
@@ -3633,9 +3778,14 @@ window.__ModuleLoader__.load({
3633
3778
  setMode(body && body.config && body.config.mode === "local" ? "local" : "saas");
3634
3779
  setMessage(null);
3635
3780
  }).catch(function (e) { setMessage({ kind: "err", text: "读取状态失败: " + e.message }); })
3636
- .finally(function () { setBusy(""); });
3781
+ .finally(function () { if (!quiet) setBusy(""); });
3637
3782
  }, []);
3638
3783
 
3784
+ /** 后台静默补状态(不置忙碌、失败静默)——登录成功后用它把衍生信息补上。 */
3785
+ var refreshQuiet = function () {
3786
+ try { refresh(true); } catch (e) { /* 忽略 */ }
3787
+ };
3788
+
3639
3789
  function loadCaptcha(kind) {
3640
3790
  var setCap = kind === "login" ? setLcap : setRcap;
3641
3791
  setCap({ id: null, svg: '<span class="dru-hint">加载中</span>' });
@@ -4260,8 +4410,25 @@ window.__ModuleLoader__.load({
4260
4410
  * 于是"服务没重启成功"和"一切正常"在界面上长得一模一样。
4261
4411
  */
4262
4412
  function applyAccountSaved(cfg, okText) {
4263
- setSt(cfg);
4264
4413
  var br = cfg && cfg.bridgeRestart;
4414
+ // B2/B3:登录响应已改成"立即返回"(statusPending=true,只回最小 config 回显)——
4415
+ // 合并进现有 st(不覆盖 service/connect 等已渲染数据),其余信息后台静默补。
4416
+ if (cfg && cfg.statusPending) {
4417
+ setSt(function (prev) {
4418
+ var base = prev && typeof prev === "object" ? prev : {};
4419
+ return Object.assign({}, base, { ok: true, config: Object.assign({}, (base && base.config) || {}, cfg.config || {}) });
4420
+ });
4421
+ if (br && br.ok === false && br.status !== "running" && br.status !== "provisioning" && br.status !== "skipped") {
4422
+ setMsg("warn", okText + ";但后台服务未能自动重启:"
4423
+ + ((br.detail || br.status) || "未知原因")
4424
+ + " —— 新账号可能收不到这台设备,请点下方「重启服务」或重启一次 dsh web。");
4425
+ } else {
4426
+ setMsg("ok", okText + "(正在准备远程服务…)");
4427
+ }
4428
+ refreshQuiet();
4429
+ return;
4430
+ }
4431
+ setSt(cfg);
4265
4432
  if (br && br.ok === false && br.status !== "running" && br.status !== "provisioning" && br.status !== "skipped") {
4266
4433
  setMsg("warn", okText + ";但后台服务未能自动重启:"
4267
4434
  + ((br.detail || br.status) || "未知原因")
@@ -4277,12 +4444,28 @@ window.__ModuleLoader__.load({
4277
4444
  post("/dsh-remote/login", { phone: phone.trim(), password: pass, captcha_id: lcap.id, captcha_answer: lcapTxt.trim() })
4278
4445
  .then(function (body) {
4279
4446
  if (body.ok || body.status === 200) {
4447
+ // B3(2026-11-07):**登录请求成功即切已登录 UI**,不再等 /config(它要保存配对、
4448
+ // 重启 bridge、拉衍生信息)。按钮忙碌态只覆盖真正的登录请求。
4449
+ setBusy("");
4450
+ setSt(function (prev) {
4451
+ var base = prev && typeof prev === "object" ? prev : {};
4452
+ return Object.assign({}, base, {
4453
+ ok: true,
4454
+ config: Object.assign({}, (base && base.config) || {}, {
4455
+ phone: phone.trim(), hasPhone: true, hasPassword: true
4456
+ })
4457
+ });
4458
+ });
4459
+ setMsg("ok", "登录成功,正在准备远程服务…");
4280
4460
  return post("/dsh-remote/config", { phone: phone.trim(), password: pass }).then(function (cfg) {
4281
4461
  // 切换连接账号(手机号与之前不同):清除旧账号留下的反馈线程凭据
4282
4462
  if (prevPhone !== phone.trim()) fbClearThreads();
4283
4463
  setPwdOpen(false); // 登录成功(可能从「忘记密码」返回)→ 收起重置表单
4284
4464
  setPass(""); setLcapTxt(""); setLcap(null);
4285
4465
  applyAccountSaved(cfg, "✅ 登录成功,账号已保存");
4466
+ }).catch(function (e) {
4467
+ // 账号已登录成功,只是本机配对保存失败:如实提示,不影响已登录 UI
4468
+ setMsg("warn", "登录成功,但本机配对信息保存失败:" + (e && e.message ? e.message : e) + "(可重试或重启面板)");
4286
4469
  });
4287
4470
  }
4288
4471
  var relayBody = body.body || {};
@@ -4333,10 +4516,24 @@ window.__ModuleLoader__.load({
4333
4516
  post("/dsh-remote/register", regPayload)
4334
4517
  .then(function (body) {
4335
4518
  if (body.ok || body.status === 201 || (body.body && body.body.token)) {
4519
+ // B3:注册成功即切已登录 UI(与登录同一策略),/config 异步完成配对保存。
4520
+ setBusy("");
4521
+ setSt(function (prev) {
4522
+ var base = prev && typeof prev === "object" ? prev : {};
4523
+ return Object.assign({}, base, {
4524
+ ok: true,
4525
+ config: Object.assign({}, (base && base.config) || {}, {
4526
+ phone: rphone.trim(), hasPhone: true, hasPassword: true
4527
+ })
4528
+ });
4529
+ });
4530
+ setMsg("ok", "注册成功,正在准备远程服务…");
4336
4531
  return post("/dsh-remote/config", { phone: rphone.trim(), password: rpass }).then(function (cfg) {
4337
4532
  setPwdOpen(false);
4338
4533
  setRphone(""); setRpass(""); setRpass2(""); setRsms(""); setRcap(null); setRcapTxt(""); setRInvite("");
4339
4534
  applyAccountSaved(cfg, "✅ 注册成功,已自动登录");
4535
+ }).catch(function (e) {
4536
+ setMsg("warn", "注册成功,但本机配对信息保存失败:" + (e && e.message ? e.message : e));
4340
4537
  });
4341
4538
  }
4342
4539
  var b = body.body || {};
@@ -4543,8 +4740,18 @@ window.__ModuleLoader__.load({
4543
4740
  // 切换到自建服务(连接账号上下文变为本地无账号):清除 SaaS 账号的反馈线程凭据
4544
4741
  fbClearThreads();
4545
4742
  setPwdOpen(false);
4546
- setSt(body); setSelfHost(""); setLocalKey("");
4547
- setMsg(body.ok ? "ok" : "err", body.ok ? "✅ 已切换到自建服务,后台服务已重启" : (body.error || (body.body && body.body.error) || "保存失败"));
4743
+ // B2:登录/配对响应现在是精简的(statusPending)→ 合并最小 config 回显,其余静默补
4744
+ if (body && body.ok && body.statusPending) {
4745
+ setSt(function (prev) {
4746
+ var base = prev && typeof prev === "object" ? prev : {};
4747
+ return Object.assign({}, base, { ok: true, config: Object.assign({}, (base && base.config) || {}, body.config || {}) });
4748
+ });
4749
+ refreshQuiet();
4750
+ } else {
4751
+ setSt(body);
4752
+ }
4753
+ setSelfHost(""); setLocalKey("");
4754
+ setMsg(body && body.ok ? "ok" : "err", body && body.ok ? "✅ 已切换到自建服务,后台服务已重启" : ((body && (body.error || (body.body && body.body.error))) || "保存失败"));
4548
4755
  })
4549
4756
  .catch(function (e) { setMsg("err", "保存失败: " + e.message); })
4550
4757
  .finally(function () { setBusy(""); });
@@ -4918,15 +5125,8 @@ window.__ModuleLoader__.load({
4918
5125
  // 免费用户这里什么都不显示 —— 把「免费档」当标签挂在脸上,正是要减少的营销露出。
4919
5126
  isMember ? h("div", { className: "plan" }, plan === "pro_max" ? "Pro Max" : "PRO") : null
4920
5127
  ),
4921
- // 2026-10-17(业主):退出登录从按钮行挪到**账号行右侧** —— 它属于"账号维护",
4922
- // 与账号信息同一行动线;主按钮行也因此少一个按钮。
4923
- h("button", {
4924
- type: "button",
4925
- className: "dru-user-logout",
4926
- disabled: busy !== "",
4927
- title: "退出登录(电脑端后台服务与手机端连接不受影响)",
4928
- onClick: function () { doLogout(false); }
4929
- }, "退出登录")
5128
+ // A3(2026-11-07):退出登录统一放到**状态头右侧**(与「登录 / 注册」同一位置),
5129
+ // 账号弹层里不再重复一个同义按钮。
4930
5130
  ),
4931
5131
  // 额度状态:不限量说明 / 剩余额度百分比 / 会员到期日。
4932
5132
  // 首屏只说「还剩多少」,不提档位名、不写带宽数值(数值在下面「更多」里)。
@@ -4934,58 +5134,33 @@ window.__ModuleLoader__.load({
4934
5134
  h("span", { className: "dru-dot " + (isMember ? "dru-dot-on" : "dru-dot-off") }),
4935
5135
  h("span", null, planText)
4936
5136
  ),
4937
- h("div", { className: "dru-actions", style: { marginTop: 10 } },
4938
- // 主行动:免费=升级入口,会员=续费/转正(账号维护,永远在正面第一位)
5137
+ // A2(2026-11-07 业主:账号弹层不要折叠):弹层空间足够 → 所有功能**直接平铺**,
5138
+ // 不再有「更多」折叠体;一屏一个主操作(升级/续费仍是唯一 primary)。
5139
+ h("div", { className: "dru-actions dru-actions-wrap", style: { marginTop: 10 } },
4939
5140
  h("button", {
4940
5141
  type: "button", className: "dru-btn dru-btn-primary",
4941
5142
  style: { display: "inline-flex", alignItems: "center" }, disabled: busy !== "",
4942
5143
  onClick: openUpgradeAuth
4943
5144
  },
4944
- busy === "upgrade" ? "生成链接中…" : (!isMember ? "🚀 升级 PRO" : source === "trial" ? "🚀 转正式 PRO" : "🔄 续费会员")),
4945
- // 次要:用户反馈("联系我们"的高频入口,留在正面)
4946
- h("button", { type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "", onClick: function () { setView("feedback"); } }, "💬 用户反馈"),
4947
- // 「更多」变成同一行里的普通按钮(原来是一条整宽披露条,视觉重量和按钮完全不匹配)。
4948
- // 无障碍不变:id / aria-expanded / aria-controls 保持原样(用例按它们定位)。
4949
- h("button", {
4950
- type: "button",
4951
- id: MKT_MORE_TOGGLE_ID,
4952
- className: "dru-btn dru-btn-ghost dru-disclose-inline",
4953
- "aria-expanded": moreOpen ? "true" : "false",
4954
- "aria-controls": MKT_MORE_BODY_ID,
4955
- onClick: function () { setMoreOpen(!moreOpen); }
4956
- },
4957
- h("span", null, "更多"),
4958
- h("span", { className: "dru-disclose-caret", "aria-hidden": "true" }, moreOpen ? "▲" : "▼")
4959
- )
5145
+ busy === "upgrade" ? "生成链接中…" : (!isMember ? "升级 PRO" : source === "trial" ? "转正式 PRO" : "续费会员")),
5146
+ h("button", { type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "", onClick: function () { setView("feedback"); } }, "用户反馈"),
5147
+ invOff ? null : h("button", {
5148
+ type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "",
5149
+ title: invTipRule ? "带 " + invTipRule.n + " 位新用户可得 " + invTipRule.days + " 天 PRO 会员" : "带新用户换会员",
5150
+ onClick: function () { setView("invite"); loadInvite(); }
5151
+ }, "带新用户换会员"),
5152
+ h("button", { type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "", onClick: togglePwdForm }, pwdOpen ? "收起修改密码" : "修改密码"),
5153
+ community && community.qrcode
5154
+ ? h("button", {
5155
+ type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "",
5156
+ onClick: function () { setCommOpen(true); }
5157
+ }, "加入交流群")
5158
+ : null
4960
5159
  ),
4961
5160
  // 🎁 邀请引导气泡:紧跟按钮行(不遮挡按钮)。点 ✕ 只做"不再提示",不再改变入口位置。
4962
5161
  renderInviteTip(),
4963
- // 🧰 「更多」折叠体:带新用户 / 修改密码 / 加入交流群 + 修改密码表单
4964
- h("div", {
4965
- id: MKT_MORE_BODY_ID,
4966
- className: "dru-more-body",
4967
- hidden: !moreOpen
4968
- },
4969
- h("div", { className: "dru-more-actions" },
4970
- invOff ? null : h("button", {
4971
- type: "button",
4972
- className: "dru-btn dru-btn-ghost",
4973
- disabled: busy !== "",
4974
- title: invTipRule ? "带 " + invTipRule.n + " 位新用户可得 " + invTipRule.days + " 天 PRO 会员" : "带新用户换会员",
4975
- onClick: function () { setView("invite"); loadInvite(); }
4976
- }, "🎁 带新用户换会员"),
4977
- h("button", { type: "button", className: "dru-btn dru-btn-ghost", disabled: busy !== "", onClick: togglePwdForm }, pwdOpen ? "收起修改密码" : "🔒 修改密码"),
4978
- community && community.qrcode
4979
- ? h("button", {
4980
- type: "button",
4981
- className: "dru-btn dru-btn-ghost",
4982
- disabled: busy !== "",
4983
- onClick: function () { setCommOpen(true); }
4984
- }, "💬 加入交流群")
4985
- : null
4986
- ),
4987
- pwdOpen ? renderResetPwdForm(false) : null
4988
- ),
5162
+ // 修改密码表单直接展开在平铺按钮下方(不再藏在折叠体里)
5163
+ pwdOpen ? renderResetPwdForm(false) : null,
4989
5164
  );
4990
5165
  }
4991
5166
 
@@ -5599,7 +5774,9 @@ window.__ModuleLoader__.load({
5599
5774
  h("span", { className: "dru-dot " + (e.kind === "ok" ? "dru-dot-on" : "dru-dot-off") }),
5600
5775
  h("span", null, e.text));
5601
5776
  }
5602
- return card("📱 远程访问", [
5777
+ // A1(2026-11-07 业主截图指出):正文里别再重复「远程访问」标题 —— 设置页顶部已有标题,
5778
+ // 弹层标题也已经写了「手机访问」;这里只保留内容卡片。
5779
+ return h("div", { className: "dru-card" }, [
5603
5780
  // 【0.7.10】激活引导置顶:二维码链路的**收尾**(见 renderActivationGuide 的长注释)。
5604
5781
  // 放在状态 chip 之前 = 「设备连上后第一屏看到的就是拿手机做什么」,而不是先读一堆技术状态。
5605
5782
  renderActivationGuide(),
@@ -5795,8 +5972,9 @@ window.__ModuleLoader__.load({
5795
5972
  // ═════════════════════════════════════════════════════════════════════
5796
5973
 
5797
5974
  /** 弹层开关:同一时刻只开一个(真实产品里多弹层叠加只会让用户更迷路)。 */
5798
- function openIAModal(name) {
5975
+ function openIAModal(name, why) {
5799
5976
  setMessage(null);
5977
+ setIaLoginWhy(why ? String(why) : "");
5800
5978
  setIaModal(name);
5801
5979
  if (name === "channel") {
5802
5980
  setIaChStep("list");
@@ -5819,16 +5997,29 @@ window.__ModuleLoader__.load({
5819
5997
  }
5820
5998
 
5821
5999
  // 打开弹层时按需拉数据(幂等;失败由既有重试/红字逻辑兜底)。
5822
- useEffect(function () {
5823
- if (iaModal !== "mobile" || !loggedIn || akey) return;
5824
- try { loadAccessKey(); } catch (e1) { /* 忽略 */ }
5825
- }, [iaModal, loggedIn, akey]);
5826
- useEffect(function () {
5827
- if (iaModal !== "devices" || !loggedIn || devSessions !== null) return;
5828
- try { loadDevices(); } catch (e2) { /* 忽略 */ }
5829
- }, [iaModal, loggedIn, devSessions]);
6000
+ // 注:这里**不再**额外拉 access-key / 设备列表 —— 面板已有「已登录 home 停留即取码」的
6001
+ // 既有 effect(含 25s 轮换),弹层打开时那份数据本就已经在页面上。重复拉会让"一次登录"
6002
+ // 打两次 /access-key(生产里浪费签发额度,也和既有回归用例的期望冲突)。
5830
6003
  function closeIAModal() { setIaModal(null); }
5831
6004
 
6005
+ /** A4/A5:未登录时不要静默失败 —— 直接弹登录弹层,并说明"登录后才能用手机访问";
6006
+ * 登录成功后自动回到「手机访问」流程继续生成访问码(iaAfterLogin)。 */
6007
+ function openMobileAccess() {
6008
+ if (!loggedIn) {
6009
+ setIaAfterLogin("mobile");
6010
+ openIAModal("account", "登录后才能用手机访问");
6011
+ return;
6012
+ }
6013
+ openIAModal("mobile");
6014
+ }
6015
+
6016
+ // 登录成功回跳:把"刚才想做的事"接上(目前只有手机访问这一个意图)。
6017
+ useEffect(function () {
6018
+ if (!loggedIn || iaAfterLogin !== "mobile") return;
6019
+ setIaAfterLogin("");
6020
+ try { openIAModal("mobile"); } catch (e) { /* 忽略 */ }
6021
+ }, [loggedIn, iaAfterLogin]);
6022
+
5832
6023
  /** 连接状态 → pill 语义(圆点 + 文案;不只靠颜色)。 */
5833
6024
  function iaConnState() {
5834
6025
  if (st === null) return { cls: "", text: "连接中…" };
@@ -5857,8 +6048,22 @@ window.__ModuleLoader__.load({
5857
6048
  h("div", { className: "dru-ia-pill " + cs.cls, role: "status", "aria-live": "polite" },
5858
6049
  h("span", { className: "dru-ia-pill-dot", "aria-hidden": "true" }),
5859
6050
  cs.text),
5860
- h("button", { type: "button", className: "dru-ia-btn is-primary", onClick: function () { openIAModal("mobile"); } },
5861
- druIcon("phone"), "打开手机端")
6051
+ h("div", { className: "dru-ia-head-actions" },
6052
+ // A3:登录/退出登录**同一位置**(状态头右侧;账号弹层里不再放退出按钮)。
6053
+ // 未登录显示「登录 / 注册」入口,点击弹账号弹层。
6054
+ loggedIn
6055
+ ? h("button", {
6056
+ type: "button", className: "dru-ia-btn", disabled: busy !== "",
6057
+ title: "退出登录(电脑端后台服务与手机端连接不受影响)",
6058
+ onClick: function () { doLogout(false); }
6059
+ }, "退出登录")
6060
+ : h("button", {
6061
+ type: "button", className: "dru-ia-btn",
6062
+ onClick: function () { openIAModal("account"); }
6063
+ }, "登录 / 注册"),
6064
+ h("button", { type: "button", className: "dru-ia-btn is-primary", onClick: openMobileAccess },
6065
+ druIcon("phone"), "打开手机端")
6066
+ )
5862
6067
  );
5863
6068
  }
5864
6069
 
@@ -5882,13 +6087,13 @@ window.__ModuleLoader__.load({
5882
6087
  var remain = 0;
5883
6088
  if (akey) { var expMs = toMs(akey.expires_at); if (expMs) remain = expMs - nowTick; }
5884
6089
  var mobileMeta = !loggedIn
5885
- ? "登录后生成一次性访问码"
6090
+ ? "登录后即可生成一次性访问码"
5886
6091
  : (akey && akey.url && remain > 0 ? ("访问码有效 · " + fmtRemain(remain) + "后过期") : "尚未生成访问码");
5887
6092
 
5888
6093
  return h("div", { className: "dru-ia-grid" },
5889
6094
  iaCard("phone", "手机访问", mobileMeta,
5890
- h("button", { type: "button", className: "dru-ia-btn is-primary", onClick: function () { openIAModal("mobile"); } },
5891
- loggedIn ? (akey && akey.url ? "查看访问码" : "生成访问码") : "登录并生成")),
6095
+ h("button", { type: "button", className: "dru-ia-btn is-primary", onClick: openMobileAccess },
6096
+ loggedIn ? (akey && akey.url ? "查看访问码" : "生成访问码") : "登录并生成访问码")),
5892
6097
  iaCard("bell", "通知与消息通道", chMeta,
5893
6098
  h("button", { type: "button", className: "dru-ia-btn", onClick: function () { openIAModal("channel"); } }, "设置")),
5894
6099
  iaCard("devices", "已授权设备", devMeta,
@@ -5918,32 +6123,38 @@ window.__ModuleLoader__.load({
5918
6123
 
5919
6124
  /** 后台服务卡(从旧 renderHome 搬出来;文案压缩,不再解释内部实现细节)。 */
5920
6125
  function renderBridgeCard() {
6126
+ var autostartText = st ? (st.service && (st.service.plistExists || st.service.serviceManager === "detached")
6127
+ ? (st.service.serviceManager === "detached"
6128
+ ? (st.service.autostartTask
6129
+ ? "自启动:已注册登录任务(Windows 任务计划程序 dsh-remote-bridge)"
6130
+ : "自启动:未注册(由面板在 dsh web 启动时自动拉起)")
6131
+ : "自启动服务已安装")
6132
+ : "自启动服务未安装(启动时自动创建)") : "";
6133
+ var deviceText = st && st.config && st.config.deviceId ? ("设备 ID:" + st.config.deviceId) : "设备 ID:生成中";
5921
6134
  return h("div", { className: "dru-card" },
5922
6135
  h("h3", null, "后台服务"),
5923
- h("div", { className: "dru-status-line" },
5924
- h("span", { className: "dru-dot " + (serviceRunning ? "dru-dot-on" : "dru-dot-off") }),
5925
- h("span", null, st ? serviceStateText : "查询中…"),
5926
- launchdPid ? h("span", { className: "dru-meta", style: { marginTop: 0 } }, "(pid=" + launchdPid + ")") : null
6136
+ // A6:业主点名要这句提示 —— 显眼但不刺眼(浅色提示条 + 图标,不占满屏、不闪)
6137
+ h("div", { className: "dru-bridge-keep", role: "note" },
6138
+ druIcon("alert", 14),
6139
+ h("span", null, "保持在线,才能用手机端远程控制这台电脑,请不要关闭。")),
6140
+ // A6:横向紧凑布局(2 列网格):状态 / 启停按钮 / 自启动 / 设备 ID 不再一列排队
6141
+ h("div", { className: "dru-bridge-grid" },
6142
+ h("div", { className: "dru-bridge-cell" },
6143
+ h("div", { className: "dru-status-line" },
6144
+ h("span", { className: "dru-dot " + (serviceRunning ? "dru-dot-on" : "dru-dot-off") }),
6145
+ h("span", null, st ? serviceStateText : "查询中…"),
6146
+ launchdPid ? h("span", { className: "dru-meta", style: { marginTop: 0 } }, "(pid=" + launchdPid + ")") : null)),
6147
+ h("div", { className: "dru-bridge-cell dru-bridge-cell-action" },
6148
+ !serviceRunning
6149
+ ? h("button", { type: "button", className: "dru-btn dru-btn-primary", disabled: busy !== "", onClick: function () { toggleBridge(true); } }, busy === "start" ? "启动中…" : "启动后台服务")
6150
+ : h("button", { type: "button", className: "dru-btn dru-btn-danger", disabled: busy !== "", onClick: function () { toggleBridge(false); } }, busy === "stop" ? "停止中…" : "停止后台服务")),
6151
+ h("div", { className: "dru-bridge-cell dru-meta" }, autostartText),
6152
+ h("div", { className: "dru-bridge-cell dru-meta" }, deviceText)
5927
6153
  ),
5928
6154
  connInfo && connPhase && connPhase !== "no_account"
5929
- ? h("div", { className: "dru-hint", style: { marginTop: 6 } },
6155
+ ? h("div", { className: "dru-hint", style: { marginTop: 8 } },
5930
6156
  "连接进度:" + (connInfo.locked ? "本机已被锁定(额度已满,升级或解绑后自动恢复)" : connText))
5931
6157
  : null,
5932
- h("div", { className: "dru-actions", style: { marginTop: 10 } },
5933
- !serviceRunning
5934
- ? h("button", { type: "button", className: "dru-btn dru-btn-primary", disabled: busy !== "", onClick: function () { toggleBridge(true); } }, busy === "start" ? "启动中…" : "启动后台服务")
5935
- : h("button", { type: "button", className: "dru-btn dru-btn-danger", disabled: busy !== "", onClick: function () { toggleBridge(false); } }, busy === "stop" ? "停止中…" : "停止后台服务")
5936
- ),
5937
- // 自启动形态(平台差异要如实说:Windows 用任务计划程序、其它平台用 launchd/systemd 类服务)。
5938
- h("div", { className: "dru-meta" }, st ? (st.service && (st.service.plistExists || st.service.serviceManager === "detached")
5939
- ? (st.service.serviceManager === "detached"
5940
- ? (st.service.autostartTask
5941
- ? "自启动:已注册登录任务(Windows 任务计划程序 dsh-remote-bridge)"
5942
- : "自启动:未注册登录任务(由面板在 dsh web 启动时自动拉起后台服务)")
5943
- : "自启动服务已安装")
5944
- : "自启动服务未安装(启动时自动创建)") : ""),
5945
- // 设备 ID 属于"客服排查时要报的一行",留在高级设置里(主界面不露)。
5946
- h("div", { className: "dru-meta" }, st && st.config && st.config.deviceId ? "设备 ID:" + st.config.deviceId : "设备 ID:生成中"),
5947
6158
  st && !serviceRuntimeReady
5948
6159
  ? h("div", { className: "dru-hint", style: { marginTop: 8 } }, "运行环境正在自动补装,装完会自动启动,不需要你操作。")
5949
6160
  : serviceCrashing
@@ -5953,7 +6164,7 @@ window.__ModuleLoader__.load({
5953
6164
  ? (function (be) {
5954
6165
  var isLocked = be.code === "device_locked" || be.code === "device_limit_exceeded";
5955
6166
  var beLimit = (be.limit === 0 || be.limit) ? be.limit : null;
5956
- const isBindCap = be.code === "device_bind_cap";
6167
+ var isBindCap = be.code === "device_bind_cap";
5957
6168
  return h("div", { className: "dru-msg dru-msg-err", style: { marginTop: 8 } },
5958
6169
  (isLocked ? "本机已被锁定:" : "设备注册失败:") + (be.message || "未说明原因"),
5959
6170
  h("div", { className: "dru-hint", style: { marginTop: 4 } },
@@ -5973,7 +6184,7 @@ window.__ModuleLoader__.load({
5973
6184
  renderBridgeCard(),
5974
6185
  h(SelfManageCard, null),
5975
6186
  h("div", { className: "dru-card" },
5976
- h("h3", null, "关于 dsh-remote"),
6187
+ h("h3", null, "关于 dsh-remote-web"),
5977
6188
  h("div", { className: "dru-hint" }, "用手机或另一台电脑随时接管这台电脑上的 DeepSeek Harness:装上即得专属加密地址,免公网 IP、免内网穿透,全程加密。"),
5978
6189
  h("div", { className: "dru-hint", style: { marginTop: 6 } },
5979
6190
  "匿名装机统计:只上报「装机 / 连接是否成功」这类事件(安装开始与失败原因、后台服务是否注册成功、是否首次远程打通)——"
@@ -6099,6 +6310,32 @@ window.__ModuleLoader__.load({
6099
6310
  );
6100
6311
  }
6101
6312
 
6313
+ /** 企微通道档位条(2026-11-07 桥侧会员判定):取不到 tier 就**不渲染**,绝不猜。 */
6314
+ function iaTierBar(ws) {
6315
+ try {
6316
+ var tier = String((ws && ws.tier) || "");
6317
+ if (tier !== "free" && tier !== "pro" && tier !== "pro_max") return null;
6318
+ if (tier === "free") {
6319
+ var remaining = (typeof ws.remainingMessages === "number" && isFinite(ws.remainingMessages))
6320
+ ? ws.remainingMessages : null;
6321
+ var perMonth = Number(ws.messagesPerMonth) > 0 ? Number(ws.messagesPerMonth) : 0;
6322
+ var used = ws.usage && typeof ws.usage.messages === "number" ? ws.usage.messages : null;
6323
+ // 只有「桥明确给了余量」或「每月上限+本月已用」才显示数字;否则不瞎猜。
6324
+ if (remaining === null && perMonth && used !== null) remaining = Math.max(0, perMonth - used);
6325
+ var txt = "免费档";
6326
+ if (remaining !== null) txt += " · 本月剩余 " + remaining + " 条指令";
6327
+ if (perMonth) txt += "(每月 " + perMonth + " 条)";
6328
+ return h("div", { className: "dru-ia-tier", role: "status" },
6329
+ h("span", { className: "dru-ia-tier-name" }, txt),
6330
+ h("a", {
6331
+ className: "dru-ia-tier-link", href: "/app/promo", target: "_blank", rel: "noopener"
6332
+ }, "升级 Pro 不限量"));
6333
+ }
6334
+ return h("div", { className: "dru-ia-tier is-member", role: "status" },
6335
+ h("span", { className: "dru-ia-tier-name" }, "会员档 · 指令不限"));
6336
+ } catch (e) { return null; }
6337
+ }
6338
+
6102
6339
  /** 统一的表单字段结构(label + 控件 + 错误位;错误就近显示)。 */
6103
6340
  function iaField(label, htmlFor, control, errText, hintText, extraHintId) {
6104
6341
  // 同时带 dru-field 语义类名(任务里约定的字段结构名),样式仍由 .dru-ia-field 承担。
@@ -6172,8 +6409,14 @@ window.__ModuleLoader__.load({
6172
6409
  sessionOptions.push(h("option", { value: String(it.id) },
6173
6410
  iaTruncTitle(it.title) + " · " + iaShortId(it.id)));
6174
6411
  }
6412
+ // 免费档:桥会忽略 sessionId(bindingGated=true)→ 下拉置灰 + 「会员功能」徽章 + 悬停说明。
6413
+ var sessionGated = !!ws.bindingGated;
6414
+ var sessionIgnored = sessionGated && !!String(imBotsSessionId || "").trim();
6175
6415
  var sessionSelect = h("select", {
6176
- id: "dru-wecom-session", className: "dru-ia-input", value: String(imBotsSessionId || ""),
6416
+ id: "dru-wecom-session", className: "dru-ia-input", value: sessionGated ? "" : String(imBotsSessionId || ""),
6417
+ disabled: sessionGated ? true : undefined,
6418
+ "aria-disabled": sessionGated ? "true" : undefined,
6419
+ title: sessionGated ? "升级后可固定转接会话" : undefined,
6177
6420
  "aria-invalid": fieldErr.session ? "true" : "false",
6178
6421
  "aria-describedby": fieldErr.session ? "dru-wecom-session-err" : undefined,
6179
6422
  onChange: function (e) {
@@ -6206,6 +6449,8 @@ window.__ModuleLoader__.load({
6206
6449
  }, imBotsBusy === "preview" ? "发送中…" : "发送预览卡片")
6207
6450
  ]
6208
6451
  }, { children: [
6452
+ // 2026-11-07:档位条(免费档显示本月剩余指令 + 升级入口;会员档显示不限量;取不到不渲染)
6453
+ iaTierBar(ws),
6209
6454
  // 当前实际转发目标:一行说清楚(状态不只靠颜色/图标)
6210
6455
  h("div", { className: "dru-ia-state" }, druIcon("chat"), "绑定会话:" + iaBoundSessionText()),
6211
6456
  // 没有接收方时如实提示(不拦着点:桥会记日志跳过,用户自己能看到结果)
@@ -6215,11 +6460,21 @@ window.__ModuleLoader__.load({
6215
6460
  iaHHMM(ws.lastPreviewCardAt)
6216
6461
  ? h("div", { className: "dru-ia-hint" }, "上次预览:" + iaHHMM(ws.lastPreviewCardAt))
6217
6462
  : null,
6218
- iaField("转接到哪个会话", "dru-wecom-session", sessionSelect,
6463
+ iaField(
6464
+ sessionGated
6465
+ ? h("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 } },
6466
+ "转接到哪个会话",
6467
+ h("span", { className: "dru-ia-badge", title: "升级后可固定转接会话" }, "会员功能"))
6468
+ : "转接到哪个会话",
6469
+ "dru-wecom-session", sessionSelect,
6219
6470
  fieldErr.session || "",
6220
- sessions.length
6221
- ? "给机器人发消息时,转发到这个会话;默认跟随当前活跃会话。"
6222
- : "还没有会话,去创建一个任务后这里会出现。",
6471
+ sessionGated
6472
+ ? (sessionIgnored
6473
+ ? "当前跟随活跃会话(免费档不能固定转接:升级后可固定转接会话)。"
6474
+ : "免费档不能固定转接会话:当前跟随活跃会话,升级后可固定。")
6475
+ : (sessions.length
6476
+ ? "给机器人发消息时,转发到这个会话;默认跟随当前活跃会话。"
6477
+ : "还没有会话,去创建一个任务后这里会出现。"),
6223
6478
  "dru-wecom-session-err"),
6224
6479
  iaField("Bot ID", "dru-wecom-bot",
6225
6480
  h("input", {
@@ -6403,7 +6658,11 @@ window.__ModuleLoader__.load({
6403
6658
  sub: "登录后可同步设备、生成访问码;账号只用于你自己的设备之间同步",
6404
6659
  onClose: closeIAModal,
6405
6660
  foot: h("button", { type: "button", className: "dru-ia-btn", onClick: closeIAModal }, "完成")
6406
- }, { children: [renderAccountBody()] }));
6661
+ }, { children: [
6662
+ // A4:从「打开手机端 / 生成访问码」被守卫弹过来时,说明一句"为什么"(不留静默跳转)
6663
+ iaLoginWhy ? h("div", { className: "dru-ia-state is-warn", role: "status" }, iaLoginWhy) : null,
6664
+ renderAccountBody()
6665
+ ] }));
6407
6666
  }
6408
6667
 
6409
6668
  function renderIAModals() {
@@ -6694,8 +6953,9 @@ window.__ModuleLoader__.load({
6694
6953
  h("div", { className: "dru-settings-head" },
6695
6954
  h("span", { className: "dru-settings-icon" }, druIcon("phone", 20)),
6696
6955
  h("div", null,
6697
- h("h2", { className: "dru-settings-title" }, "远程访问"),
6698
- h("div", { className: "dru-settings-sub" }, "通过手机或另一台电脑远程使用同一份 dsh web,人在哪都能用(免公网 IP)"),
6956
+ // 2026-11-07 业主:「我的插件名字叫 dsh-remote-web,这个是全名」→ 面板标题统一成品牌全名。
6957
+ h("h2", { className: "dru-settings-title" }, "dsh-remote-web"),
6958
+ h("div", { className: "dru-settings-sub" }, "远程访问:通过手机或另一台电脑远程使用同一份 dsh web,人在哪都能用(免公网 IP)"),
6699
6959
  h("div", { className: "dru-settings-sub" }, "装上即得专属加密地址:人在外面也能用手机全功能接管这台电脑上的 DeepSeek Harness(免内网穿透、免公网 IP、全程加密)。")
6700
6960
  )
6701
6961
  ),