@wenbin_wb/dsh-bridge 2.8.7 → 2.9.0

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/client/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { MOBILE_STYLES_CSS } from './mobile-styles.js'
1
2
  // dsh-bridge 客户端插件:设置页「远程访问」面板
2
3
 
3
4
  // 兼容非 HTTPS 环境(如手机局域网 HTTP 访问):为非安全上下文补齐 crypto.randomUUID
@@ -111,6 +112,7 @@ const s = {
111
112
  tag: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: '3px 10px', borderRadius: 999, fontSize: 12, fontWeight: 500, whiteSpace: 'nowrap', flexShrink: 0, minWidth: 'max-content', lineHeight: 1.4 },
112
113
  input: { width: '100%', font: 'inherit', fontSize: 13, padding: '7px 10px', borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2,#d1d5db)', background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', color: 'var(--dsw-alias-label-primary,currentColor)', outline: 'none', boxSizing: 'border-box' },
113
114
  warn: { background: 'var(--dsw-alias-state-warn-bg,#fffbeb)', border: '1px solid var(--dsw-alias-state-warn-border,#fde68a)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-state-warn-primary,#92400e)', lineHeight: 1.6 },
115
+ err: { background: 'var(--dsw-alias-state-error-bg,#fef2f2)', border: '1px solid var(--dsw-alias-state-error-border,#fecaca)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-state-error-primary,#991b1b)', lineHeight: 1.6 },
114
116
  tip: { background: 'var(--dsw-alias-bg-layer-2,#f9fafb)', borderRadius: 8, padding: '10px 14px', fontSize: 12, color: 'var(--dsw-alias-label-secondary,#6b7280)', lineHeight: 1.6 },
115
117
  };
116
118
 
@@ -557,7 +559,11 @@ const CloudflareConfigForm = React.memo(function CloudflareConfigForm({ token, h
557
559
  setSaving(true);
558
560
  setMsg(null);
559
561
  try {
560
- await onSave({ token: tokenVal, hostname: hostnameVal });
562
+ // 只回传被修改的字段:掩码 '******' 与未变更的 token 都不上传,服务端保留现值
563
+ const patch = {};
564
+ if (hostnameVal !== (hostname || '')) patch.hostname = hostnameVal;
565
+ if (tokenVal !== (token || '')) patch.token = tokenVal;
566
+ if (Object.keys(patch).length > 0) await onSave(patch);
561
567
  setMsg({ ok: true, text: '✓ 固定域名配置已保存' });
562
568
  } catch (err) {
563
569
  setMsg({ ok: false, text: err.message || '保存失败' });
@@ -1059,7 +1065,7 @@ const AccessAuthCard = React.memo(function AccessAuthCard({ auth, rpcCall, onUpd
1059
1065
 
1060
1066
  // ---- 通用 IM 平台卡片 ----
1061
1067
 
1062
- function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatusChange }) {
1068
+ function PlatformCard({ platformId, platformName, platformDesc, rpcCall }) {
1063
1069
  const [platform, setPlatform] = React.useState(null);
1064
1070
  const [err, setErr] = React.useState(null);
1065
1071
  const [busy, setBusy] = React.useState(false);
@@ -1075,6 +1081,7 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1075
1081
  approvalTimeoutSec: String(platform.config.approvalTimeoutSec ?? 600),
1076
1082
  maxMessageChars: String((platform.config.maxMessageChars >= 500 ? platform.config.maxMessageChars : null) ?? (platformId === 'telegram' ? 4096 : 2000)),
1077
1083
  sendChunkDelayMs: String(platform.config.sendChunkDelayMs ?? 1500),
1084
+ groupAutoApprove: Boolean(platform.config.groupAutoApprove),
1078
1085
  appId: platform.config.appId ?? '',
1079
1086
  // Secret 不由后端回传;空值表示沿用已保存密钥
1080
1087
  clientSecret: '',
@@ -1086,12 +1093,6 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1086
1093
  }
1087
1094
  }, [platform?.config, platformId]);
1088
1095
 
1089
- // 向上传递连接状态(供平台列表卡片绿点使用)
1090
- React.useEffect(() => {
1091
- const connected = platform?.status === 'connected' || platform?.status === 'starting' || platform?.status === 'reconnecting';
1092
- onStatusChange?.(connected);
1093
- }, [platform?.status, onStatusChange]);
1094
-
1095
1096
  const loadInFlightRef = React.useRef(false);
1096
1097
  const seqRef = React.useRef(0);
1097
1098
  const load = React.useCallback(async (quiet = false) => {
@@ -1139,6 +1140,12 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1139
1140
 
1140
1141
  const onLogin = React.useCallback(() => act(BRIDGE_ENDPOINTS.platformLogin, {}), [act]);
1141
1142
  const onStop = React.useCallback(() => act(BRIDGE_ENDPOINTS.platformStop, {}), [act]);
1143
+ // 断开后重连:凭证仍在(configured)时直接用已存凭证重启网关,无需重新扫码;
1144
+ // 只有解绑后(无凭证)才走扫码/绑定流程
1145
+ const onReconnect = React.useCallback(() => {
1146
+ if (platform?.configured) return act(BRIDGE_ENDPOINTS.platformStart, {});
1147
+ return act(BRIDGE_ENDPOINTS.platformLogin, {});
1148
+ }, [act, platform?.configured]);
1142
1149
 
1143
1150
  // 白名单管理
1144
1151
  const [newId, setNewId] = React.useState('');
@@ -1183,11 +1190,16 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1183
1190
  // 高级设置保存
1184
1191
  const saveConfig = React.useCallback(async () => {
1185
1192
  if (!cfgDraft) return;
1193
+ // 非法数字输入回落到当前配置值/默认值,避免 NaN 发到服务端且使 cfgDirty 永真
1194
+ const num = (v, fallback) => {
1195
+ const n = Number(v);
1196
+ return Number.isFinite(n) ? n : fallback;
1197
+ };
1186
1198
  const payload = {
1187
- digestIntervalSec: Number(cfgDraft.digestIntervalSec),
1188
- approvalTimeoutSec: Number(cfgDraft.approvalTimeoutSec),
1189
- maxMessageChars: Number(cfgDraft.maxMessageChars),
1190
- sendChunkDelayMs: Number(cfgDraft.sendChunkDelayMs),
1199
+ digestIntervalSec: num(cfgDraft.digestIntervalSec, platform?.config?.digestIntervalSec ?? 300),
1200
+ approvalTimeoutSec: num(cfgDraft.approvalTimeoutSec, platform?.config?.approvalTimeoutSec ?? 600),
1201
+ maxMessageChars: num(cfgDraft.maxMessageChars, platform?.config?.maxMessageChars ?? 2000),
1202
+ sendChunkDelayMs: num(cfgDraft.sendChunkDelayMs, platform?.config?.sendChunkDelayMs ?? 1500),
1191
1203
  };
1192
1204
  // QQ / 飞书 / Telegram 平台额外携带凭证
1193
1205
  if (platformId === 'qq') {
@@ -1401,9 +1413,9 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1401
1413
  ),
1402
1414
  React.createElement('div', { style: { display: 'flex', gap: 8, marginTop: 10, flexWrap: 'wrap', alignItems: 'center' } },
1403
1415
  platform.status !== 'connected' && platform.status !== 'starting' &&
1404
- React.createElement('button', { style: s.btnPri, onClick: onLogin, disabled: busy }, '重新连接'),
1416
+ React.createElement('button', { style: s.btnPri, onClick: onReconnect, disabled: busy, title: platform?.configured ? '使用已保存的凭证直接恢复连接,无需重新扫码' : '首次使用需扫码绑定' }, platform?.configured ? '重新连接' : '连接'),
1405
1417
  (platform.status === 'connected' || platform.status === 'starting') &&
1406
- React.createElement('button', { style: s.btnGhost, onClick: onStop, disabled: busy }, '断开'),
1418
+ React.createElement('button', { style: s.btnGhost, onClick: onStop, disabled: busy, title: '停止接收消息;凭证保留,可随时"重新连接"恢复' }, '断开'),
1407
1419
  React.createElement('button', {
1408
1420
  style: { ...s.btnGhost, color: 'var(--dsw-alias-state-error-primary,#dc2626)', borderColor: 'var(--dsw-alias-state-error-primary,#dc2626)', opacity: busy ? 0.5 : 1 },
1409
1421
  disabled: busy,
@@ -1411,6 +1423,23 @@ function PlatformCard({ platformId, platformName, platformDesc, rpcCall, onStatu
1411
1423
  title: '清除登录凭证,下次需重新配置',
1412
1424
  }, '解绑账号'),
1413
1425
  ),
1426
+ // 群聊自动授权开关(仅支持群聊的平台):关闭时新群首次 @机器人 不会自动加白
1427
+ platformId !== 'wechat' && React.createElement('label', {
1428
+ style: { display: 'flex', alignItems: 'center', gap: 6, marginTop: 10, fontSize: 12, cursor: 'pointer', width: 'fit-content', color: 'var(--dsw-alias-label-secondary,#6b7280)' },
1429
+ title: '开启后,白名单之外的新群首次 @机器人 即自动授权入白名单;关闭(默认)时新群必须先在此手动添加',
1430
+ },
1431
+ React.createElement('input', {
1432
+ type: 'checkbox',
1433
+ checked: Boolean(cfgDraft?.groupAutoApprove),
1434
+ disabled: busy,
1435
+ onChange: (e) => {
1436
+ const checked = e.target.checked;
1437
+ setCfgDraft(d => ({ ...(d ?? {}), groupAutoApprove: checked }));
1438
+ act(BRIDGE_ENDPOINTS.platformSetConfig, { groupAutoApprove: checked });
1439
+ },
1440
+ }),
1441
+ React.createElement('span', null, '群聊自动授权(新群首次 @机器人 自动加入白名单,默认关闭)'),
1442
+ ),
1414
1443
  // 飞书 / Telegram 扫码直达对话引导卡片
1415
1444
  (platformId === 'feishu' || platformId === 'telegram') && platform.botQr && React.createElement('div', {
1416
1445
  style: {
@@ -1846,41 +1875,71 @@ function BackupRestoreWidget({ rpcCall, onUpdate }) {
1846
1875
  }
1847
1876
 
1848
1877
  // 运维 Tab 内的手动重启 DSH 服务小卡片
1849
- function RestartDshCard({ rpcCall }) {
1878
+ // 重启 + 健康轮询公共逻辑:定时器保存在 ref 中,组件卸载即清理。
1879
+ // 修复点:原先两处复制粘贴的 setInterval 不随组件卸载清理,用户切走 Tab 后
1880
+ // 轮询仍会继续运行,并在服务恢复时无条件执行 window.location.reload()。
1881
+ function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
1882
+ const T = {
1883
+ restarting: '正在向 DSH 服务发送重启指令…',
1884
+ reconnecting: 'DSH 服务正在重启中,正在自动重新连接…',
1885
+ success: '🎉 重启成功!已重新建立连接,正在刷新页面…',
1886
+ timeout: '重连等待超时,请手动刷新页面。',
1887
+ ...texts,
1888
+ };
1850
1889
  const [restarting, setRestarting] = React.useState(false);
1851
1890
  const [status, setStatus] = React.useState(null);
1891
+ const pollRef = React.useRef(null);
1892
+ const reloadRef = React.useRef(null);
1852
1893
 
1853
- const handleRestart = async () => {
1894
+ React.useEffect(() => () => {
1895
+ if (pollRef.current) clearInterval(pollRef.current);
1896
+ if (reloadRef.current) clearTimeout(reloadRef.current);
1897
+ }, []);
1898
+
1899
+ const startRestart = React.useCallback(async () => {
1854
1900
  setRestarting(true);
1855
- setStatus({ phase: 'restarting', text: '正在向 DSH 服务发送重启指令…' });
1901
+ setStatus({ phase: 'restarting', text: T.restarting });
1856
1902
  try {
1857
1903
  await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
1858
- } catch {}
1904
+ } catch {
1905
+ // 服务可能瞬间关闭导致连接断开,忽略
1906
+ }
1859
1907
 
1860
- setStatus({ phase: 'reconnecting', text: 'DSH 服务正在重启中,正在自动重新连接…' });
1908
+ setStatus({ phase: 'reconnecting', text: T.reconnecting });
1861
1909
  await new Promise(r => setTimeout(r, 2000));
1862
1910
 
1863
1911
  let attempts = 0;
1864
- const maxAttempts = 30;
1865
- const pollHealth = setInterval(async () => {
1912
+ pollRef.current = setInterval(async () => {
1866
1913
  attempts++;
1867
1914
  try {
1868
1915
  const r = await rpcCall(BRIDGE_ENDPOINTS.checkVersion, {});
1869
1916
  if (r?.ok) {
1870
- clearInterval(pollHealth);
1871
- setStatus({ phase: 'success', text: '🎉 重启成功!已重新建立连接,正在刷新页面…' });
1872
- setTimeout(() => { window.location.reload(); }, 1000);
1917
+ clearInterval(pollRef.current);
1918
+ pollRef.current = null;
1919
+ setStatus({ phase: 'success', text: T.success });
1920
+ reloadRef.current = setTimeout(() => { window.location.reload(); }, 1000);
1873
1921
  return;
1874
1922
  }
1875
- } catch {}
1923
+ } catch {
1924
+ // 仍在启动中,继续等待
1925
+ }
1876
1926
 
1877
1927
  if (attempts >= maxAttempts) {
1878
- clearInterval(pollHealth);
1879
- setStatus({ phase: 'timeout', text: '重连等待超时,请手动刷新页面。' });
1928
+ clearInterval(pollRef.current);
1929
+ pollRef.current = null;
1930
+ setStatus({ phase: 'timeout', text: T.timeout });
1880
1931
  setRestarting(false);
1881
1932
  }
1882
1933
  }, 1000);
1883
- };
1934
+ }, [rpcCall, maxAttempts]);
1935
+
1936
+ const resetStatus = React.useCallback(() => setStatus(null), []);
1937
+
1938
+ return { restarting, status, startRestart, resetStatus };
1939
+ }
1940
+
1941
+ function RestartDshCard({ rpcCall }) {
1942
+ const { restarting, status, startRestart: handleRestart } = useDshRestart({ rpcCall });
1884
1943
 
1885
1944
  return React.createElement('div', { style: { ...s.card, marginBottom: 16 } },
1886
1945
  React.createElement('div', { style: { marginBottom: 10 } },
@@ -1954,8 +2013,6 @@ function RemoteWorkspaceCard({ rpcCall }) {
1954
2013
  onClick: () => {
1955
2014
  if (typeof window.__dshOpenRemoteWorkspaceModal === 'function') {
1956
2015
  window.__dshOpenRemoteWorkspaceModal();
1957
- } else if (typeof showRemoteWorkspaceDialog === 'function') {
1958
- showRemoteWorkspaceDialog(rpcCall, () => load());
1959
2016
  }
1960
2017
  },
1961
2018
  }, '+ 远程添加工作区'),
@@ -1987,6 +2044,20 @@ function RemoteWorkspaceCard({ rpcCall }) {
1987
2044
  );
1988
2045
  }
1989
2046
 
2047
+ // 手动升级命令行:展示命令 + 一键复制
2048
+ function UpgradeCommandRow({ cmd }) {
2049
+ const [copied, copy] = useCopy();
2050
+ return React.createElement('div', { style: { display: 'flex', gap: 6, alignItems: 'center' } },
2051
+ React.createElement('code', {
2052
+ style: { ...s.code, flex: '1 1 auto', fontSize: 12, lineHeight: 1.5, wordBreak: 'break-all' },
2053
+ }, cmd),
2054
+ React.createElement('button', {
2055
+ style: { ...s.btnGhost, height: 26, padding: '0 10px', fontSize: 11, flex: '0 0 auto' },
2056
+ onClick: () => copy(cmd),
2057
+ }, copied ? '✓ 已复制' : '复制'),
2058
+ );
2059
+ }
2060
+
1990
2061
  // 版本检查 + 一键升级 + GitHub/反馈入口
1991
2062
  function VersionBanner({ rpcCall }) {
1992
2063
  const [info, setInfo] = React.useState(null);
@@ -1994,15 +2065,22 @@ function VersionBanner({ rpcCall }) {
1994
2065
  const [upgrading, setUpgrading] = React.useState(false);
1995
2066
  const [upgradeResult, setUpgradeResult] = React.useState(null);
1996
2067
  const [showManual, setShowManual] = React.useState(false);
1997
- const [restarting, setRestarting] = React.useState(false);
1998
- const [restartStatus, setRestartStatus] = React.useState(null);
1999
2068
  const [dismissRestart, setDismissRestart] = React.useState(false);
2069
+ const { restarting, status: restartStatus, startRestart: handleRestart, resetStatus: resetRestartStatus } = useDshRestart({
2070
+ rpcCall,
2071
+ texts: {
2072
+ restarting: '正在调度 DSH 服务重启…',
2073
+ success: '🎉 重启成功!已自动加载最新版本。正在刷新页面…',
2074
+ },
2075
+ });
2000
2076
 
2001
2077
  const check = React.useCallback(async () => {
2002
2078
  setLoading(true);
2003
2079
  try {
2004
2080
  const r = await rpcCall(BRIDGE_ENDPOINTS.checkVersion, {});
2005
2081
  if (r?.ok) setInfo(r.value);
2082
+ } catch {
2083
+ // 版本检查失败:保留现有 info,不打断面板
2006
2084
  } finally {
2007
2085
  setLoading(false);
2008
2086
  }
@@ -2018,7 +2096,7 @@ function VersionBanner({ rpcCall }) {
2018
2096
  setUpgrading(true);
2019
2097
  setUpgradeResult(null);
2020
2098
  setDismissRestart(false);
2021
- setRestartStatus(null);
2099
+ resetRestartStatus();
2022
2100
  try {
2023
2101
  const r = await rpcCall(BRIDGE_ENDPOINTS.upgradePlugin, { version: info.latest });
2024
2102
  if (r?.ok && r.value?.ok) {
@@ -2035,46 +2113,6 @@ function VersionBanner({ rpcCall }) {
2035
2113
  }
2036
2114
  }, [info?.latest, upgrading, rpcCall]);
2037
2115
 
2038
- const handleRestart = React.useCallback(async () => {
2039
- setRestarting(true);
2040
- setRestartStatus({ phase: 'restarting', text: '正在调度 DSH 服务重启…' });
2041
- try {
2042
- await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
2043
- } catch {
2044
- // 忽略 RPC 错误(因为服务可能瞬间关闭导致网络连接断开)
2045
- }
2046
-
2047
- setRestartStatus({ phase: 'reconnecting', text: 'DSH 服务正在重启中,正在自动重新连接…' });
2048
-
2049
- // 等待 2 秒后开始健康检查轮询
2050
- await new Promise(r => setTimeout(r, 2000));
2051
-
2052
- let attempts = 0;
2053
- const maxAttempts = 30; // 最多探测 30 次(约 30 秒)
2054
- const pollHealth = setInterval(async () => {
2055
- attempts++;
2056
- try {
2057
- const r = await rpcCall(BRIDGE_ENDPOINTS.checkVersion, {});
2058
- if (r?.ok) {
2059
- clearInterval(pollHealth);
2060
- setRestartStatus({ phase: 'success', text: '🎉 重启成功!已自动加载最新版本。正在刷新页面…' });
2061
- setTimeout(() => {
2062
- window.location.reload();
2063
- }, 1000);
2064
- return;
2065
- }
2066
- } catch {
2067
- // 仍在启动中,继续等待
2068
- }
2069
-
2070
- if (attempts >= maxAttempts) {
2071
- clearInterval(pollHealth);
2072
- setRestartStatus({ phase: 'timeout', text: '重连等待超时,请手动刷新页面。' });
2073
- setRestarting(false);
2074
- }
2075
- }, 1000);
2076
- }, [rpcCall]);
2077
-
2078
2116
  const links = React.createElement('div', { style: { display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' } },
2079
2117
  React.createElement('a', {
2080
2118
  href: GITHUB_URL, target: '_blank', rel: 'noreferrer', style: s.btnLink,
@@ -2408,7 +2446,6 @@ function BridgePanel({ rpcCall }) {
2408
2446
  // 本机物理访问自动静默获取 adminToken,免输密码直通管理(支持 3080 原生端口与 3082 代理端口)
2409
2447
  const fetchLoopbackToken = React.useCallback(async () => {
2410
2448
  if (!isLocalhost) return null;
2411
- const currentPort = typeof window !== 'undefined' ? (window.location.port || (window.location.protocol === 'https:' ? '443' : '80')) : '3082';
2412
2449
  const proxyPort = status?.proxy?.port || 3082;
2413
2450
  const candidateUrls = [
2414
2451
  '/__dsh_bridge__/loopback-token',
@@ -2441,6 +2478,9 @@ function BridgePanel({ rpcCall }) {
2441
2478
  }
2442
2479
  }, [isLocalhost, adminUnlocked, fetchLoopbackToken]);
2443
2480
 
2481
+ // 被管理员鉴权拦截的操作在此暂存:解锁成功后自动续办,用户无需再点一次
2482
+ const pendingRetryRef = React.useRef(null);
2483
+
2444
2484
  const authRpcCall = React.useCallback(async (endpoint, payload = {}, signal) => {
2445
2485
  let token = adminToken || getGlobalAdminToken();
2446
2486
  if (isLocalhost && !token) {
@@ -2455,6 +2495,7 @@ function BridgePanel({ rpcCall }) {
2455
2495
  if (res?.ok === false) {
2456
2496
  const msg = res?.error?.message || '';
2457
2497
  if (msg.includes('管理员权限') || msg.includes('管理密码解锁')) {
2498
+ pendingRetryRef.current = { endpoint, payload: enriched };
2458
2499
  setUnlockErr(msg);
2459
2500
  setShowUnlockModal(true);
2460
2501
  }
@@ -2546,6 +2587,28 @@ function BridgePanel({ rpcCall }) {
2546
2587
  return () => clearInterval(t);
2547
2588
  }, [load]);
2548
2589
 
2590
+ // 解锁成功后自动续办被拦截的操作:用户不再需要重新点击一次
2591
+ React.useEffect(() => {
2592
+ if (!adminUnlocked) return;
2593
+ const pending = pendingRetryRef.current;
2594
+ if (!pending) return;
2595
+ pendingRetryRef.current = null;
2596
+ const token = adminToken || getGlobalAdminToken();
2597
+ (async () => {
2598
+ try {
2599
+ const r = await rpcCall(pending.endpoint, { ...pending.payload, adminToken: token });
2600
+ if (r?.ok) {
2601
+ if (r.value) setStatus(r.value);
2602
+ setErr(null);
2603
+ } else {
2604
+ setErr(r?.error?.message || '刚才的操作重试失败,请手动重试');
2605
+ }
2606
+ } catch (e) {
2607
+ setErr(e.message || '刚才的操作重试失败,请手动重试');
2608
+ }
2609
+ })();
2610
+ }, [adminUnlocked, adminToken, rpcCall]);
2611
+
2549
2612
  const act = React.useCallback(async (endpoint, payload) => {
2550
2613
  try {
2551
2614
  const r = await authRpcCall(endpoint, payload ?? {});
@@ -2745,7 +2808,6 @@ function BridgePanel({ rpcCall }) {
2745
2808
  platformName: IM_PLATFORMS.find(p => p.id === selectedPlatform)?.label ?? selectedPlatform,
2746
2809
  platformDesc: IM_PLATFORMS.find(p => p.id === selectedPlatform)?.desc ?? '',
2747
2810
  rpcCall: authRpcCall,
2748
- onStatusChange: () => {}, // 状态变化已由 listPlatforms 轮询处理,不需要回调
2749
2811
  }),
2750
2812
  );
2751
2813
  }
@@ -2782,7 +2844,7 @@ function BridgePanel({ rpcCall }) {
2782
2844
  },
2783
2845
  React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 救急解除锁定指引:'),
2784
2846
  React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改策略或清除密码。'),
2785
- React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器救急指令'), ':在宿主电脑/服务器终端执行 ', React.createElement('code', { style: s.code }, 'touch ~/.dsh/dsh-bridge/reset-auth'), ' 即可瞬间清空密码恢复初始状态。'),
2847
+ React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器 / 无头环境'), ':救急重置步骤参见 GitHub README 的「三重容灾保命体系」章节。'),
2786
2848
  ),
2787
2849
  )
2788
2850
  ) : (
@@ -2833,7 +2895,7 @@ function BridgePanel({ rpcCall }) {
2833
2895
  },
2834
2896
  React.createElement('div', { style: { fontWeight: 600, color: 'var(--dsw-alias-label-primary,currentColor)', marginBottom: 4 } }, '🛟 找回与重置密码指引:'),
2835
2897
  React.createElement('div', null, '1. ', React.createElement('strong', null, '电脑本机直连修改'), ':直接在运行本程序的电脑本机打开本控制台(127.0.0.1 享有物理免锁特权),可随时修改管理密码。'),
2836
- React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器救急指令'), ':在宿主电脑终端执行 ', React.createElement('code', { style: s.code }, 'touch ~/.dsh/dsh-bridge/reset-auth'), ' 即可瞬间清空密码恢复初始状态。'),
2898
+ React.createElement('div', { style: { marginTop: 4 } }, '2. ', React.createElement('strong', null, '服务器 / 无头环境'), ':救急重置步骤参见 GitHub README 的「三重容灾保命体系」章节。'),
2837
2899
  ),
2838
2900
  )
2839
2901
  )
@@ -2980,805 +3042,7 @@ function injectMobileStyles() {
2980
3042
 
2981
3043
  const style = document.createElement('style');
2982
3044
  style.id = 'dsh-bridge-mobile-styles';
2983
- style.textContent = `
2984
- /* DSH Bridge 隐藏 Tab 栏原生滚动条并保持平滑滑动 */
2985
- .dsh-tabbar-container {
2986
- scrollbar-width: none !important;
2987
- -ms-overflow-style: none !important;
2988
- }
2989
- .dsh-tabbar-container::-webkit-scrollbar {
2990
- display: none !important;
2991
- width: 0 !important;
2992
- height: 0 !important;
2993
- }
2994
-
2995
- /* DSH Bridge 移动端自适应与触控交互增强样式 */
2996
- :root {
2997
- --dsh-mobile-header-h: 52px;
2998
- --dsh-mobile-safe-top: env(safe-area-inset-top, 0px);
2999
- --dsh-mobile-safe-bottom: env(safe-area-inset-bottom, 0px);
3000
- }
3001
-
3002
- @media (max-width: 768px) {
3003
- /* 1. 主框架为 Header 腾出顶部空间 */
3004
- div[class*="_frame"] {
3005
- display: flex !important;
3006
- flex-direction: column !important;
3007
- width: 100vw !important;
3008
- height: 100dvh !important;
3009
- margin: 0 !important;
3010
- padding-top: var(--dsh-mobile-header-h) !important;
3011
- position: relative !important;
3012
- grid-template-columns: 1fr !important;
3013
- overflow: hidden !important;
3014
- box-sizing: border-box !important;
3015
- }
3016
-
3017
- /* 2. 顶部原生导航条:100% 还原 DeepSeek App (左侧双横线,右侧(+),中间留白,无多余设置按钮) */
3018
- .dsh-mobile-app-header {
3019
- position: fixed !important;
3020
- top: 0 !important;
3021
- left: 0 !important;
3022
- right: 0 !important;
3023
- height: var(--dsh-mobile-header-h) !important;
3024
- padding-top: var(--dsh-mobile-safe-top) !important;
3025
- background: transparent !important;
3026
- display: flex !important;
3027
- align-items: center !important;
3028
- justify-content: space-between !important;
3029
- padding-left: 16px !important;
3030
- padding-right: 16px !important;
3031
- z-index: 9998 !important;
3032
- box-sizing: border-box !important;
3033
- user-select: none !important;
3034
- pointer-events: none !important;
3035
- }
3036
-
3037
- /* 左侧双横线按钮 (DeepSeek App 原生图标) */
3038
- .dsh-header-menu-btn {
3039
- width: 40px;
3040
- height: 40px;
3041
- border-radius: 50%;
3042
- border: none;
3043
- background: transparent;
3044
- color: var(--dsw-alias-label-primary, #111827);
3045
- display: inline-flex;
3046
- align-items: center;
3047
- justify-content: flex-start;
3048
- cursor: pointer;
3049
- padding: 0;
3050
- transition: opacity 0.15s;
3051
- pointer-events: auto !important;
3052
- }
3053
- .dsh-header-menu-btn:active {
3054
- opacity: 0.6;
3055
- }
3056
-
3057
- /* 右侧 (+) 新建会话按钮 (DeepSeek App 原生图标) */
3058
- .dsh-header-new-btn {
3059
- width: 40px;
3060
- height: 40px;
3061
- border-radius: 50%;
3062
- border: none;
3063
- background: transparent;
3064
- color: var(--dsw-alias-label-primary, #111827);
3065
- display: inline-flex;
3066
- align-items: center;
3067
- justify-content: flex-end;
3068
- cursor: pointer;
3069
- padding: 0;
3070
- transition: opacity 0.15s;
3071
- pointer-events: auto !important;
3072
- }
3073
- .dsh-header-new-btn:active {
3074
- opacity: 0.6;
3075
- }
3076
-
3077
- /* 中间动态会话标题 (单行居中打点截断,100% 还原原生 App 导航体验) */
3078
- .dsh-mobile-header-title {
3079
- flex: 1 1 auto !important;
3080
- min-width: 0 !important;
3081
- text-align: center !important;
3082
- font-size: 15px !important;
3083
- font-weight: 600 !important;
3084
- color: var(--dsw-alias-label-primary, #111827) !important;
3085
- overflow: hidden !important;
3086
- text-overflow: ellipsis !important;
3087
- white-space: nowrap !important;
3088
- padding: 0 10px !important;
3089
- user-select: none !important;
3090
- pointer-events: none !important;
3091
- letter-spacing: -0.2px !important;
3092
- }
3093
-
3094
- /* 3. 中间主内容区与输入框 */
3095
- div[class*="_centerCol"] {
3096
- flex: 1 1 100% !important;
3097
- width: 100% !important;
3098
- min-width: 0 !important;
3099
- max-width: 100% !important;
3100
- display: flex !important;
3101
- height: 100% !important;
3102
- }
3103
-
3104
- div[class*="_detailsCol"],
3105
- div[class*="toggleCluster"],
3106
- div[class*="W-zNGW_toggleCluster"] {
3107
- display: none !important;
3108
- }
3109
-
3110
- /* 3.0 工作区 Workbench / 任务管理 / 多 Tab 栏移动端自适应适配 */
3111
- body:not(.dsh-workbench-open) div[class*="nArs4W_panel"],
3112
- body:not(.dsh-workbench-open) div[class*="workbench_panel"],
3113
- body:not(.dsh-workbench-open) div[class*="workbenchPanel"],
3114
- div[class*="nArs4W_panel"][class*="panelHidden"],
3115
- div[class*="workbench_panel"][class*="panelHidden"],
3116
- div[class*="workbenchPanel"][class*="panelHidden"],
3117
- div[class*="panelHidden"] {
3118
- display: none !important;
3119
- visibility: hidden !important;
3120
- pointer-events: none !important;
3121
- width: 0 !important;
3122
- height: 0 !important;
3123
- max-height: 0 !important;
3124
- z-index: -1 !important;
3125
- opacity: 0 !important;
3126
- transform: translateX(105%) !important;
3127
- }
3128
-
3129
- body.dsh-workbench-open div[class*="nArs4W_panel"]:not([class*="panelHidden"]),
3130
- body.dsh-workbench-open div[class*="workbench_panel"]:not([class*="panelHidden"]),
3131
- body.dsh-workbench-open div[class*="workbenchPanel"]:not([class*="panelHidden"]) {
3132
- display: flex !important;
3133
- visibility: visible !important;
3134
- pointer-events: auto !important;
3135
- top: var(--dsh-mobile-header-h, 52px) !important;
3136
- height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
3137
- max-height: calc(100dvh - var(--dsh-mobile-header-h, 52px)) !important;
3138
- z-index: 50 !important;
3139
- box-sizing: border-box !important;
3140
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3141
- transform: none !important;
3142
- opacity: 1 !important;
3143
- }
3144
-
3145
- /* Tab 栏:横向滑动手势 + 干净的底部边框,杜绝与顶部移动端 Header 重叠 */
3146
- div[class*="nArs4W_tabBar"],
3147
- div[class*="workbench_tabBar"],
3148
- div[class*="tabBar"] {
3149
- min-height: 40px !important;
3150
- height: 40px !important;
3151
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3152
- border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
3153
- display: flex !important;
3154
- align-items: center !important;
3155
- justify-content: space-between !important;
3156
- padding: 0 8px !important;
3157
- gap: 6px !important;
3158
- overflow: visible !important;
3159
- box-sizing: border-box !important;
3160
- }
3161
-
3162
- div[class*="nArs4W_tabList"],
3163
- div[class*="tabList"] {
3164
- display: flex !important;
3165
- align-items: center !important;
3166
- gap: 6px !important;
3167
- flex: 1 1 auto !important;
3168
- min-width: 0 !important;
3169
- overflow-x: auto !important;
3170
- scrollbar-width: none !important;
3171
- -webkit-overflow-scrolling: touch !important;
3172
- }
3173
- div[class*="nArs4W_tabList"]::-webkit-scrollbar,
3174
- div[class*="tabList"]::-webkit-scrollbar {
3175
- display: none !important;
3176
- }
3177
-
3178
- /* 单个 Tab 胶囊化,文字超长自动打点,防止 Tab 互相挤压 */
3179
- div[class*="nArs4W_tab"],
3180
- div[class*="workbench_tab"] {
3181
- flex: 0 0 auto !important;
3182
- max-width: 170px !important;
3183
- min-width: 70px !important;
3184
- height: 30px !important;
3185
- padding: 0 8px 0 10px !important;
3186
- border-radius: 6px !important;
3187
- font-size: 12.5px !important;
3188
- display: inline-flex !important;
3189
- align-items: center !important;
3190
- justify-content: space-between !important;
3191
- gap: 6px !important;
3192
- background: var(--dsw-alias-bg-layer-2, #f3f4f6) !important;
3193
- color: var(--dsw-alias-label-secondary, #6b7280) !important;
3194
- cursor: pointer !important;
3195
- user-select: none !important;
3196
- box-sizing: border-box !important;
3197
- }
3198
-
3199
- div[class*="nArs4W_tabActive"],
3200
- div[class*="workbench_tabActive"] {
3201
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3202
- color: var(--dsw-alias-label-primary, #111827) !important;
3203
- font-weight: 600 !important;
3204
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important;
3205
- }
3206
-
3207
- span[class*="nArs4W_tabTitle"],
3208
- span[class*="tabTitle"] {
3209
- overflow: hidden !important;
3210
- text-overflow: ellipsis !important;
3211
- white-space: nowrap !important;
3212
- flex: 1 1 auto !important;
3213
- }
3214
-
3215
- button[class*="nArs4W_tabClose"],
3216
- button[class*="tabClose"] {
3217
- width: 18px !important;
3218
- height: 18px !important;
3219
- border-radius: 50% !important;
3220
- display: inline-flex !important;
3221
- align-items: center !important;
3222
- justify-content: center !important;
3223
- flex-shrink: 0 !important;
3224
- opacity: 0.6 !important;
3225
- padding: 0 !important;
3226
- }
3227
-
3228
- button[class*="nArs4W_tabBarPlus"],
3229
- button[class*="tabBarPlus"] {
3230
- width: 28px !important;
3231
- height: 28px !important;
3232
- border-radius: 50% !important;
3233
- display: inline-flex !important;
3234
- align-items: center !important;
3235
- justify-content: center !important;
3236
- flex-shrink: 0 !important;
3237
- }
3238
-
3239
- /* 移动端面板右上角“返回对话 / ✕ 收起”按钮:常驻右侧,醒目且易触达 */
3240
- .dsh-mobile-panel-close-btn {
3241
- margin-left: 8px !important;
3242
- flex: 0 0 auto !important;
3243
- height: 28px !important;
3244
- padding: 0 10px !important;
3245
- border-radius: 14px !important;
3246
- font-size: 12px !important;
3247
- font-weight: 600 !important;
3248
- background: #2563eb !important;
3249
- color: #ffffff !important;
3250
- border: none !important;
3251
- display: inline-flex !important;
3252
- align-items: center !important;
3253
- justify-content: center !important;
3254
- gap: 4px !important;
3255
- cursor: pointer !important;
3256
- user-select: none !important;
3257
- box-shadow: 0 2px 6px rgba(37, 99, 235, 0.28) !important;
3258
- white-space: nowrap !important;
3259
- transition: transform 0.1s, opacity 0.15s !important;
3260
- z-index: 10 !important;
3261
- }
3262
- .dsh-mobile-panel-close-btn:active {
3263
- transform: scale(0.95) !important;
3264
- opacity: 0.85 !important;
3265
- }
3266
-
3267
- /* 3.1 会话对话头部顶栏:移动端防挤压与空间释放优化(严格排除 .dsh-mobile-app-header) */
3268
- div[class*="_centerCol"] header,
3269
- header[class*="wSkVaW_header"] {
3270
- padding: 4px 16px 2px 16px !important;
3271
- position: relative !important;
3272
- overflow: visible !important;
3273
- }
3274
-
3275
- div[class*="wSkVaW_titleRow"],
3276
- div[class*="titleRow"] {
3277
- display: flex !important;
3278
- flex-direction: row !important;
3279
- align-items: center !important;
3280
- justify-content: space-between !important;
3281
- gap: 8px !important;
3282
- min-height: 32px !important;
3283
- width: 100% !important;
3284
- box-sizing: border-box !important;
3285
- }
3286
-
3287
- /* 移动端将原有嵌入在内容区的长面包屑标题隐藏(已统一提升至顶部导航栏正中),彻底释放第二行空间 */
3288
- nav[class*="wSkVaW_crumbs"],
3289
- nav[class*="crumbs"],
3290
- div[class*="wSkVaW_crumbs"],
3291
- div[class*="crumbs"],
3292
- [class*="wSkVaW_crumbs"] {
3293
- display: none !important;
3294
- }
3295
-
3296
- /* 子代理/智能体模式胶囊 (Actions):紧凑圆角胶囊 */
3297
- div[class*="wSkVaW_headerActions"],
3298
- div[class*="headerActions"] {
3299
- flex: 0 0 auto !important;
3300
- display: inline-flex !important;
3301
- align-items: center !important;
3302
- gap: 4px !important;
3303
- margin-left: 0 !important;
3304
- }
3305
-
3306
- button[class*="h8S2Va_trigger"],
3307
- button[class*="subagent"] {
3308
- min-height: 26px !important;
3309
- height: 26px !important;
3310
- padding: 2px 8px !important;
3311
- font-size: 11.5px !important;
3312
- line-height: 16px !important;
3313
- border-radius: 13px !important;
3314
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.04)) !important;
3315
- white-space: nowrap !important;
3316
- flex-shrink: 0 !important;
3317
- }
3318
-
3319
- /* Session Log 导出下载按钮 (Utilities):在移动端极简为 28px 圆形纯图标按钮,隐藏长文本,极大释放顶部空间 */
3320
- div[class*="wSkVaW_headerUtilities"],
3321
- div[class*="headerUtilities"] {
3322
- flex: 0 0 auto !important;
3323
- margin-left: 4px !important;
3324
- display: inline-flex !important;
3325
- align-items: center !important;
3326
- }
3327
-
3328
- button[class*="nL4_yW_sessionLogButton"],
3329
- button[class*="sessionLogButton"] {
3330
- min-width: 28px !important;
3331
- width: 28px !important;
3332
- height: 28px !important;
3333
- padding: 0 !important;
3334
- border-radius: 50% !important;
3335
- display: inline-flex !important;
3336
- align-items: center !important;
3337
- justify-content: center !important;
3338
- flex-shrink: 0 !important;
3339
- border: 1px solid var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1)) !important;
3340
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.03)) !important;
3341
- color: var(--dsw-alias-label-secondary, #6b7280) !important;
3342
- margin-left: 0 !important;
3343
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03) !important;
3344
- }
3345
-
3346
- button[class*="nL4_yW_sessionLogButton"]:hover:not(:disabled),
3347
- button[class*="sessionLogButton"]:hover:not(:disabled) {
3348
- background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06)) !important;
3349
- color: var(--dsw-alias-label-primary, #111827) !important;
3350
- }
3351
-
3352
- button[class*="nL4_yW_sessionLogButton"] span,
3353
- button[class*="sessionLogButton"] span {
3354
- display: none !important;
3355
- }
3356
-
3357
- button[class*="nL4_yW_sessionLogButton"] svg,
3358
- button[class*="sessionLogButton"] svg {
3359
- width: 13px !important;
3360
- height: 13px !important;
3361
- margin: 0 !important;
3362
- }
3363
-
3364
- /* 子代理展开菜单在移动端右对齐与宽度自适应 */
3365
- div[class*="h8S2Va_menu"] {
3366
- max-width: calc(100vw - 32px) !important;
3367
- left: auto !important;
3368
- right: 0 !important;
3369
- }
3370
-
3371
- /* 输入框底座:DeepSeek App 居中及底部固定 */
3372
- div[class*="wSkVaW_scrollBody"] {
3373
- padding-bottom: max(16px, env(safe-area-inset-bottom)) !important;
3374
- }
3375
-
3376
- /* 输入卡片:DeepSeek App 圆角大胶囊造型 */
3377
- div[class*="uV2eYG_card"] {
3378
- border-radius: 26px !important;
3379
- padding: 14px 16px 12px !important;
3380
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.05) !important;
3381
- border: 1px solid rgba(0, 0, 0, 0.07) !important;
3382
- background: var(--dsw-alias-bg-layer-2, #f4f4f7) !important;
3383
- }
3384
-
3385
- /* 输入框底部工具栏:弹性自适应,彻底杜绝权限选择器(Full access)与模型选择器重叠碰撞 */
3386
- div[class*="uV2eYG_row"] {
3387
- display: flex !important;
3388
- align-items: center !important;
3389
- justify-content: space-between !important;
3390
- gap: 6px !important;
3391
- width: 100% !important;
3392
- padding: 2px 2px 4px !important;
3393
- box-sizing: border-box !important;
3394
- }
3395
-
3396
- div[class*="uV2eYG_tools"] {
3397
- display: flex !important;
3398
- align-items: center !important;
3399
- gap: 6px !important;
3400
- flex: 0 0 auto !important;
3401
- min-width: 0 !important;
3402
- }
3403
-
3404
- div[class*="uV2eYG_modes"] {
3405
- display: flex !important;
3406
- align-items: center !important;
3407
- gap: 4px !important;
3408
- flex: 0 0 auto !important;
3409
- min-width: 0 !important;
3410
- }
3411
-
3412
- button[class*="Sh0Q9G_trigger"] {
3413
- flex: 0 0 auto !important;
3414
- min-width: 0 !important;
3415
- }
3416
-
3417
- div[class*="uV2eYG_trailing"] {
3418
- display: flex !important;
3419
- align-items: center !important;
3420
- justify-content: flex-end !important;
3421
- gap: 6px !important;
3422
- flex: 1 1 auto !important;
3423
- min-width: 0 !important;
3424
- }
3425
-
3426
- div[class*="_7KE1Ra_root"] {
3427
- flex: 0 1 auto !important;
3428
- min-width: 0 !important;
3429
- max-width: 180px !important;
3430
- }
3431
-
3432
- button[class*="_7KE1Ra_trigger"] {
3433
- max-width: 100% !important;
3434
- min-width: 0 !important;
3435
- flex: 1 1 auto !important;
3436
- padding: 0 4px 0 6px !important;
3437
- }
3438
-
3439
- span[class*="_7KE1Ra_triggerLabel"] {
3440
- overflow: hidden !important;
3441
- text-overflow: ellipsis !important;
3442
- white-space: nowrap !important;
3443
- min-width: 0 !important;
3444
- }
3445
-
3446
- /* 4. 原生侧边栏抽屉化 (Drawer) */
3447
- div[class*="_sidebarCol"] {
3448
- position: fixed !important;
3449
- left: 0 !important;
3450
- top: 0 !important;
3451
- bottom: 0 !important;
3452
- height: 100dvh !important;
3453
- width: 290px !important;
3454
- max-width: 82vw !important;
3455
- z-index: 10000 !important;
3456
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3457
- transform: translateX(-105%);
3458
- transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1);
3459
- overflow-y: auto !important;
3460
- border-right: 1px solid rgba(0, 0, 0, 0.06) !important;
3461
- pointer-events: auto !important;
3462
- }
3463
- body.dsh-drawer-open div[class*="_sidebarCol"] {
3464
- transform: translateX(0) !important;
3465
- box-shadow: 4px 0 28px rgba(0, 0, 0, 0.25) !important;
3466
- pointer-events: auto !important;
3467
- }
3468
-
3469
- /* 抽屉内部:强制 100% 宽度,无论内部状态如何均正常展开并展示 DSH 自带的顶部收起侧边栏图标 */
3470
- body.dsh-drawer-open div[class*="hHd-Xa_root"] {
3471
- width: 100% !important;
3472
- max-width: 100% !important;
3473
- min-width: 100% !important;
3474
- display: flex !important;
3475
- flex-direction: column !important;
3476
- }
3477
- body.dsh-drawer-open div[class*="hHd-Xa_collapsed"] div[class*="hHd-Xa_regionArea"],
3478
- body.dsh-drawer-open div[class*="hHd-Xa_collapsed"] button[class*="hHd-Xa_newSession"],
3479
- body.dsh-drawer-open div[class*="hHd-Xa_collapsed"] div[class*="qDHVXG_root"] {
3480
- display: flex !important;
3481
- visibility: visible !important;
3482
- }
3483
- div[class*="hHd-Xa_logoRow"] {
3484
- display: flex !important;
3485
- align-items: center !important;
3486
- justify-content: space-between !important;
3487
- width: 100% !important;
3488
- padding: 10px 14px 6px 14px !important;
3489
- box-sizing: border-box !important;
3490
- }
3491
- div[class*="hHd-Xa_logoRow"] button[class*="hHd-Xa_toggle"] {
3492
- display: inline-flex !important;
3493
- align-items: center !important;
3494
- justify-content: center !important;
3495
- width: 32px !important;
3496
- height: 32px !important;
3497
- border-radius: 8px !important;
3498
- color: var(--dsw-alias-label-secondary, #6b7280) !important;
3499
- background: transparent !important;
3500
- border: none !important;
3501
- cursor: pointer !important;
3502
- margin-left: auto !important;
3503
- transition: background 0.15s, color 0.15s !important;
3504
- }
3505
- div[class*="hHd-Xa_logoRow"] button[class*="hHd-Xa_toggle"]:active {
3506
- background: var(--dsw-alias-bg-layer-2, rgba(0, 0, 0, 0.06)) !important;
3507
- color: var(--dsw-alias-label-primary, #111827) !important;
3508
- }
3509
-
3510
- /* 设置弹窗打开时解除抽屉隐藏限制 */
3511
- div[class*="_sidebarCol"]:has(div[class*="VOzbGW_overlay"]) {
3512
- transform: none !important;
3513
- width: 100vw !important;
3514
- max-width: 100vw !important;
3515
- background: transparent !important;
3516
- box-shadow: none !important;
3517
- pointer-events: none !important;
3518
- }
3519
-
3520
- /* 5. 半透明背景遮罩 */
3521
- .dsh-mobile-backdrop {
3522
- position: fixed;
3523
- inset: 0;
3524
- background: rgba(0, 0, 0, 0.4);
3525
- z-index: 9999;
3526
- display: none !important;
3527
- }
3528
- body.dsh-drawer-open .dsh-mobile-backdrop {
3529
- display: block !important;
3530
- pointer-events: auto !important;
3531
- }
3532
-
3533
- /* 6. 设置中心全自适应适配 */
3534
- div[class*="VOzbGW_overlay"] {
3535
- position: fixed !important;
3536
- inset: 0 !important;
3537
- width: 100vw !important;
3538
- height: 100dvh !important;
3539
- display: flex !important;
3540
- align-items: center !important;
3541
- justify-content: center !important;
3542
- background: rgba(0, 0, 0, 0.45) !important;
3543
- backdrop-filter: blur(4px) !important;
3544
- -webkit-backdrop-filter: blur(4px) !important;
3545
- z-index: 10002 !important;
3546
- padding: 10px !important;
3547
- box-sizing: border-box !important;
3548
- pointer-events: auto !important;
3549
- }
3550
- div[class*="VOzbGW_panel"] {
3551
- width: 100% !important;
3552
- max-width: 100% !important;
3553
- height: 92dvh !important;
3554
- max-height: 92dvh !important;
3555
- display: flex !important;
3556
- flex-direction: row !important;
3557
- border-radius: 18px !important;
3558
- overflow: hidden !important;
3559
- box-shadow: 0 20px 40px rgba(0, 0, 0, 0.25) !important;
3560
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3561
- }
3562
- nav[class*="VOzbGW_nav"] {
3563
- width: 78px !important;
3564
- min-width: 78px !important;
3565
- max-width: 78px !important;
3566
- padding: 10px 4px !important;
3567
- box-sizing: border-box !important;
3568
- border-right: 1px solid var(--dsw-alias-border-l2, #e5e7eb) !important;
3569
- display: flex !important;
3570
- flex-direction: column !important;
3571
- gap: 6px !important;
3572
- overflow-y: auto !important;
3573
- }
3574
- nav[class*="VOzbGW_nav"] button[class*="VOzbGW_navCell"],
3575
- button[class*="VOzbGW_navCell"] {
3576
- padding: 8px 2px !important;
3577
- display: flex !important;
3578
- flex-direction: column !important;
3579
- align-items: center !important;
3580
- justify-content: center !important;
3581
- text-align: center !important;
3582
- height: auto !important;
3583
- min-height: 48px !important;
3584
- gap: 4px !important;
3585
- border-radius: 10px !important;
3586
- }
3587
- span[class*="VOzbGW_navLabel"] {
3588
- font-size: 10.5px !important;
3589
- line-height: 1.2 !important;
3590
- white-space: normal !important;
3591
- word-break: break-all !important;
3592
- text-align: center !important;
3593
- }
3594
- div[class*="VOzbGW_content"] {
3595
- flex: 1 1 auto !important;
3596
- min-width: 0 !important;
3597
- width: calc(100% - 78px) !important;
3598
- max-width: calc(100% - 78px) !important;
3599
- display: flex !important;
3600
- flex-direction: column !important;
3601
- overflow: hidden !important;
3602
- }
3603
- div[class*="VOzbGW_options"] {
3604
- flex: 1 1 auto !important;
3605
- width: 100% !important;
3606
- max-width: 100% !important;
3607
- box-sizing: border-box !important;
3608
- padding: 0 14px 20px !important;
3609
- overflow-x: hidden !important;
3610
- overflow-y: auto !important;
3611
- -webkit-overflow-scrolling: touch !important;
3612
- }
3613
-
3614
- /* 设置中心选项行手机自适应(垂直流式,防文字单字折行) */
3615
- div[class*="VOzbGW_options"] div[class*="_row"] {
3616
- display: flex !important;
3617
- flex-direction: column !important;
3618
- align-items: stretch !important;
3619
- gap: 8px !important;
3620
- width: 100% !important;
3621
- padding: 12px 0 !important;
3622
- box-sizing: border-box !important;
3623
- }
3624
- div[class*="VOzbGW_options"] div[class*="_rowText"] {
3625
- width: 100% !important;
3626
- max-width: 100% !important;
3627
- }
3628
- div[class*="VOzbGW_options"] button[class*="_selector"],
3629
- div[class*="VOzbGW_options"] select,
3630
- div[class*="VOzbGW_options"] input {
3631
- width: 100% !important;
3632
- max-width: 100% !important;
3633
- box-sizing: border-box !important;
3634
- }
3635
-
3636
- /* 7. 代码块、表格与徽标自适应 */
3637
- pre, code, pre > code, table {
3638
- max-width: 100% !important;
3639
- overflow-x: auto !important;
3640
- -webkit-overflow-scrolling: touch !important;
3641
- font-size: 12.5px !important;
3642
- }
3643
-
3644
- /* 状态徽标与药丸按钮永不折字 */
3645
- span[style*="border-radius: 999"],
3646
- span[style*="border-radius:999"] {
3647
- white-space: nowrap !important;
3648
- flex-shrink: 0 !important;
3649
- min-width: max-content !important;
3650
- }
3651
-
3652
- /* 二维码与图片移动端弹性缩放 */
3653
- img[alt="QR"], img[src^="data:image"] {
3654
- max-width: 100% !important;
3655
- box-sizing: border-box !important;
3656
- }
3657
-
3658
- /* 8. 确保所有 Popover 弹出菜单、操作气泡、下拉框位于抽屉之上且支持触控交互 */
3659
- div[class*="_portal"],
3660
- div[class*="portal"],
3661
- div[class*="popup"],
3662
- div[class*="dropdown"],
3663
- div[class*="menu"],
3664
- div[role="menu"],
3665
- div[role="dialog"] {
3666
- z-index: 10005 !important;
3667
- pointer-events: auto !important;
3668
- }
3669
-
3670
- /* 9. 移动端侧边栏:会话与工作区三点操作按钮始终清晰可见且易于点击 */
3671
- div[class*="sessionRow"] span[class*="rowActions"],
3672
- div[class*="sessionRow"] button[class*="iconButton"],
3673
- div[class*="treeBody"] button[class*="iconButton"] {
3674
- opacity: 0.8 !important;
3675
- display: inline-flex !important;
3676
- visibility: visible !important;
3677
- pointer-events: auto !important;
3678
- }
3679
- div[class*="sessionRow"]:active {
3680
- background: var(--dsw-alias-bg-layer-2, #f3f4f6) !important;
3681
- }
3682
-
3683
- /* 全局 overlayLayer 绝不被染黑 */
3684
- div[class*="overlayLayer"],
3685
- div[class*="uV2eYG_overlayAnchor"] {
3686
- background: transparent !important;
3687
- pointer-events: none !important;
3688
- }
3689
- div[class*="overlayLayer"] > * {
3690
- pointer-events: auto !important;
3691
- }
3692
- }
3693
-
3694
- /* 远程工作区选择弹窗移动端/桌面端自适应样式 */
3695
- #dsh-remote-workspace-modal {
3696
- position: fixed !important;
3697
- inset: 0 !important;
3698
- z-index: 100000 !important;
3699
- background: rgba(0, 0, 0, 0.65) !important;
3700
- backdrop-filter: blur(5px) !important;
3701
- display: flex !important;
3702
- align-items: center !important;
3703
- justify-content: center !important;
3704
- padding: 16px !important;
3705
- box-sizing: border-box !important;
3706
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
3707
- color: var(--dsw-alias-label-primary, #111827) !important;
3708
- }
3709
-
3710
- .dsh-ws-dialog-card {
3711
- background: var(--dsw-alias-bg-layer-1, #ffffff) !important;
3712
- border: 1px solid var(--dsw-alias-border-l1, #e5e7eb) !important;
3713
- border-radius: 16px !important;
3714
- width: 100% !important;
3715
- max-width: 620px !important;
3716
- max-height: 88vh !important;
3717
- display: flex !important;
3718
- flex-direction: column !important;
3719
- box-shadow: 0 25px 35px -5px rgba(0,0,0,0.3), 0 12px 16px -5px rgba(0,0,0,0.2) !important;
3720
- overflow: hidden !important;
3721
- animation: dshModalFadeIn 0.2s ease-out !important;
3722
- }
3723
-
3724
- .dsh-ws-chips-scroll {
3725
- display: flex !important;
3726
- align-items: center !important;
3727
- gap: 6px !important;
3728
- overflow-x: auto !important;
3729
- white-space: nowrap !important;
3730
- scrollbar-width: none !important;
3731
- -ms-overflow-style: none !important;
3732
- -webkit-overflow-scrolling: touch !important;
3733
- padding: 2px 0 !important;
3734
- }
3735
- .dsh-ws-chips-scroll::-webkit-scrollbar {
3736
- display: none !important;
3737
- }
3738
-
3739
- @media (max-width: 640px) {
3740
- #dsh-remote-workspace-modal {
3741
- align-items: flex-end !important;
3742
- padding: 0 !important;
3743
- }
3744
-
3745
- .dsh-ws-dialog-card {
3746
- max-height: 92dvh !important;
3747
- height: 92dvh !important;
3748
- border-bottom-left-radius: 0 !important;
3749
- border-bottom-right-radius: 0 !important;
3750
- border-left: none !important;
3751
- border-right: none !important;
3752
- border-bottom: none !important;
3753
- max-width: 100vw !important;
3754
- width: 100vw !important;
3755
- margin: 0 !important;
3756
- animation: dshBottomSheetUp 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
3757
- }
3758
-
3759
- .dsh-ws-drag-handle {
3760
- display: block !important;
3761
- }
3762
- }
3763
-
3764
- @keyframes dshModalFadeIn {
3765
- from { opacity: 0; transform: scale(0.96); }
3766
- to { opacity: 1; transform: scale(1); }
3767
- }
3768
-
3769
- @keyframes dshBottomSheetUp {
3770
- from { transform: translateY(100%); }
3771
- to { transform: translateY(0); }
3772
- }
3773
-
3774
- @media (min-width: 769px) {
3775
- .dsh-mobile-app-header,
3776
- .dsh-mobile-backdrop,
3777
- .dsh-mobile-panel-close-btn {
3778
- display: none !important;
3779
- }
3780
- }
3781
- `;
3045
+ style.textContent = MOBILE_STYLES_CSS;
3782
3046
  document.head.appendChild(style);
3783
3047
  }
3784
3048