@wendongfly/myhi 1.3.182 → 1.3.185

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/dist/index.html CHANGED
@@ -99,33 +99,50 @@
99
99
  }
100
100
  .hd-btn:hover { color: var(--text); border-color: var(--border2); }
101
101
  #user-name { max-width: 7rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
102
+ /* 顶栏放不下一行时逐级收紧(fitHeader 按实际宽度判断,大字号/窄窗口都适用):先藏用户名,再只留图标 */
103
+ #header.hd-fit1 #user-name { display: none; }
104
+ #header.hd-fit2 .hd-btn .hd-txt { display: none; }
105
+ #header.hd-fit2 .hd-actions { gap: 0.4rem; }
106
+ #header.hd-fit2 .hd-btn { padding: 0.32rem 0.48rem; }
102
107
 
103
- /* ── 设置 sheet(全局设置收口:外观 / 本机名称 / @ 引用范围 / 语音 / 账户 / 服务)── */
108
+ /* ── 设置 sheet(全局设置收口:外观 / 本机名称 / 语音 / 账户 / 服务)── */
104
109
  #set-backdrop { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.55); z-index: 30; }
105
110
  #set-sheet { display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 31; max-width: 640px; margin: 0 auto;
106
111
  background: var(--surface); border-radius: 16px 16px 0 0; padding: 0 1rem calc(1rem + env(safe-area-inset-bottom)); max-height: 88vh; overflow-y: auto; }
107
112
  #set-sheet.open, #set-backdrop.open { display: block; }
108
- .set-sec { padding: 0.75rem 0; border-top: 1px solid var(--border); }
109
- .sheet-title + .set-sec { border-top: none; padding-top: 0; }
110
- .set-sec h4 { font-size: 0.85rem; font-weight: 600; margin-bottom: 0.2rem; display: flex; align-items: center; gap: 0.4rem; }
111
- .set-scope { font-size: 0.66rem; font-weight: 400; color: var(--muted); border: 1px solid var(--border2); border-radius: 4px; padding: 0 0.3rem; }
112
- .set-hint { font-size: 0.72rem; color: var(--muted); line-height: 1.5; margin-bottom: 0.4rem; }
113
- .set-status { font-size: 0.72rem; min-height: 1.1em; margin-top: 0.25rem; color: var(--muted); }
113
+ /* 分组卡片(仿系统设置):组标题说明作用范围,卡片内一行一项,说明文字收成一行小字 */
114
+ .set-top { position: sticky; top: 0; z-index: 1; background: var(--surface); padding-bottom: 0.35rem; }
115
+ .set-top .sheet-handle { margin: 0.6rem auto 0.5rem; }
116
+ .set-head { display: flex; align-items: center; justify-content: space-between; }
117
+ .set-head b { font-size: 1.05rem; }
118
+ .set-x { background: none; border: none; color: var(--muted); font-size: 1.1rem; cursor: pointer; padding: 0.2rem 0.4rem; border-radius: 6px; }
119
+ .set-x:hover { color: var(--text); background: var(--bg); }
120
+ .set-group-t { font-size: 0.72rem; color: var(--muted); margin: 1rem 0.2rem 0.35rem; }
121
+ .set-group-t small { font-size: 0.68rem; opacity: 0.8; margin-left: 0.3rem; }
122
+ .set-card { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; overflow: hidden; }
123
+ .set-item { padding: 0.7rem 0.8rem; display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
124
+ .set-item + .set-item, .set-card > * + .set-link, .set-item + .set-link { border-top: 1px solid var(--border); }
125
+ .set-lbl { font-size: 0.84rem; color: var(--text); min-width: 4.5rem; }
126
+ .set-lbl small { display: block; font-size: 0.68rem; color: var(--muted); margin-top: 0.1rem; }
127
+ .set-item.stack { flex-direction: column; align-items: stretch; gap: 0.45rem; }
128
+ .set-val { margin-left: auto; font-size: 0.8rem; color: var(--muted); }
129
+ .set-status { font-size: 0.72rem; color: var(--muted); }
130
+ .set-status:empty { display: none; }
114
131
  .set-row { display: flex; gap: 0.4rem; }
115
- .set-inp { flex: 1; min-width: 0; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.82rem; padding: 0.45rem 0.6rem; outline: none; }
132
+ .set-inp { flex: 1; min-width: 0; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; color: var(--text); font-size: 0.82rem; padding: 0.45rem 0.6rem; outline: none; }
116
133
  .set-inp:focus { border-color: var(--accent); }
117
- .set-btn { flex-shrink: 0; white-space: nowrap; background: var(--accent); color: #fff; border: none; border-radius: 8px; padding: 0.45rem 0.85rem; font-size: 0.78rem; cursor: pointer; }
118
- .set-btn.ghost { background: var(--bg); color: var(--text2); border: 1px solid var(--border); }
119
- .set-btn.danger { background: transparent; color: var(--red); border: 1px solid var(--red); }
120
- .set-seg { display: flex; gap: 0.35rem; flex-wrap: wrap; }
121
- .set-seg button { flex: 1; min-width: 3rem; background: var(--bg); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.3rem; font-size: 0.8rem; cursor: pointer; white-space: nowrap; }
134
+ .set-inp:disabled { opacity: 0.6; }
135
+ /* 保存按钮平时是淡的,改了内容才亮起来(回车也能保存) */
136
+ .set-btn { flex-shrink: 0; white-space: nowrap; background: var(--surface); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.45rem 0.85rem; font-size: 0.78rem; cursor: pointer; }
137
+ .set-btn.dirty { background: var(--accent); border-color: var(--accent); color: #fff; }
138
+ .set-seg { flex: 1; display: flex; gap: 0.3rem; min-width: 12rem; }
139
+ .set-seg button { flex: 1; background: var(--surface); color: var(--muted); border: 1px solid var(--border); border-radius: 8px; padding: 0.4rem 0.3rem; font-size: 0.78rem; cursor: pointer; white-space: nowrap; }
122
140
  .set-seg button.on { background: var(--accent); border-color: var(--accent); color: #fff; }
123
- .set-sub { font-size: 0.75rem; color: var(--text2); margin: 0.5rem 0 0.3rem; }
124
- .atr-row { display: flex; align-items: center; gap: 0.4rem; padding: 0.2rem 0; font-size: 0.78rem; }
125
- .atr-row span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
126
- .atpick-row { padding: 0.42rem 0.7rem; cursor: pointer; color: var(--text2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 0.8rem; }
127
- .atpick-row:hover { background: var(--bg); color: var(--text); }
128
- .dr-btn { flex-shrink: 0; white-space: nowrap; padding: 0.28rem 0.7rem; font-size: 0.72rem; border: 1px solid var(--border); border-radius: 6px; background: var(--bg); color: var(--text2); cursor: pointer; }
141
+ .set-link { display: flex; align-items: center; width: 100%; background: none; border: none; color: var(--text); font-size: 0.84rem; padding: 0.75rem 0.8rem; cursor: pointer; text-align: left; text-decoration: none; }
142
+ .set-link:hover { background: var(--surface); }
143
+ .set-link::after { content: '›'; margin-left: auto; color: var(--muted); font-size: 1rem; }
144
+ .set-link.danger { color: var(--red); }
145
+ .set-foot { font-size: 0.7rem; color: var(--muted); line-height: 1.5; margin: 0.9rem 0.2rem 0.2rem; }
129
146
  /* 手机(≤540px):藏用户名、文字标签只留图标,一行放下且不憋屈 */
130
147
  @media (max-width: 540px) {
131
148
  #user-name { display: none; }
@@ -556,7 +573,7 @@
556
573
  <button id="doctor-btn" class="hd-btn" onclick="openDoctor()" title="会话体检">🩺<span class="hd-txt"> 体检</span></button>
557
574
  <button id="usage-btn" class="hd-btn" onclick="showUsage()" title="用量">📊<span class="hd-txt"> 用量</span></button>
558
575
  <button id="help-btn" class="hd-btn" onclick="openHelpSheet()" title="使用说明 / 快速上手">❓<span class="hd-txt"> 帮助</span></button>
559
- <button id="settings-btn" class="hd-btn" onclick="openSettings()" title="设置(外观 / 本机名称 / @ 引用范围 / 语音 / 账户)">⚙<span class="hd-txt"> 设置</span></button>
576
+ <button id="settings-btn" class="hd-btn" onclick="openSettings()" title="设置(外观 / 本机名称 / 语音 / 账户)">⚙<span class="hd-txt"> 设置</span></button>
560
577
  <div id="conn-dot"></div>
561
578
  </div>
562
579
  </div>
@@ -656,72 +673,53 @@
656
673
  <!-- 设置 sheet:首页是全局设置的唯一入口(会话页 ⚙ 只管本设备的聊天显示/快捷栏) -->
657
674
  <div id="set-backdrop" onclick="closeSettings()"></div>
658
675
  <div id="set-sheet">
659
- <div class="sheet-handle"></div>
660
- <div class="sheet-title">⚙ 设置</div>
661
- <div class="set-sec">
662
- <h4>外观 <span class="set-scope">本设备</span></h4>
663
- <div class="set-sub">主题</div>
664
- <div class="set-seg" id="set-theme"></div>
665
- <div class="set-sub">字号</div>
666
- <div class="set-seg" id="set-font"></div>
676
+ <div class="set-top">
677
+ <div class="sheet-handle"></div>
678
+ <div class="set-head"><b>⚙ 设置</b><button class="set-x" onclick="closeSettings()" title="关闭 (Esc)">✕</button></div>
667
679
  </div>
668
- <div class="set-sec">
669
- <h4>本机名称 <span class="set-scope">全局</span></h4>
670
- <div class="set-hint">多台电脑都跑 myhi 时用来区分:浏览器标题以它开头,标签图标按名字自动配色。留空 = 用电脑主机名。</div>
671
- <div class="set-row">
672
- <input id="set-machine-name" class="set-inp" maxlength="32" autocomplete="off">
673
- <button class="set-btn" onclick="saveMachineName()">保存</button>
674
- </div>
675
- <div id="set-machine-status" class="set-status"></div>
680
+
681
+ <div class="set-group-t">本设备<small>只影响这台手机 / 电脑的浏览器</small></div>
682
+ <div class="set-card">
683
+ <div class="set-item"><div class="set-lbl">主题</div><div class="set-seg" id="set-theme"></div></div>
684
+ <div class="set-item"><div class="set-lbl">字号</div><div class="set-seg" id="set-font"></div></div>
676
685
  </div>
677
- <div class="set-sec">
678
- <h4>@ 引用范围 <span class="set-scope">全局</span></h4>
679
- <div class="set-hint">除了任务目录,再把常用的资料文件夹(如公共资料库)加进来,打 @ 时也能搜到里面的文件。</div>
680
- <div id="atroots-list"></div>
681
- <button id="atpick-open" class="set-btn" onclick="openAtPicker()" style="width:100%;margin-top:0.4rem">📁 选择文件夹添加…</button>
682
- <div id="atpick" style="display:none;margin-top:0.4rem;border:1px solid var(--border);border-radius:10px;overflow:hidden">
683
- <div id="atpick-drives" style="display:none;gap:0.3rem;padding:0.4rem 0.5rem 0;flex-wrap:wrap"></div>
684
- <div style="display:flex;align-items:center;gap:0.35rem;padding:0.4rem 0.5rem;border-bottom:1px solid var(--border)">
685
- <button id="atpick-up" class="dr-btn" onclick="atPickUp()" title="上一级">⬆ 上一级</button>
686
- <span id="atpick-cur" style="flex:1;min-width:0;font-size:0.72rem;color:var(--text2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;direction:rtl;text-align:left"></span>
687
- </div>
688
- <div id="atpick-list" style="max-height:200px;overflow-y:auto;scrollbar-width:thin"></div>
689
- <div style="display:flex;gap:0.4rem;padding:0.45rem 0.5rem;border-top:1px solid var(--border)">
690
- <button class="set-btn ghost" onclick="closeAtPicker()" style="flex:1">取消</button>
691
- <button class="set-btn" onclick="addAtPicked()" style="flex:2">✓ 添加当前文件夹</button>
686
+
687
+ <div class="set-group-t">这台电脑<small>运行 myhi 的电脑,所有访问设备都生效</small></div>
688
+ <div class="set-card">
689
+ <div class="set-item stack">
690
+ <div class="set-lbl">本机名称<small>显示在浏览器标签上,好区分多台电脑;留空用主机名</small></div>
691
+ <div class="set-row">
692
+ <input id="set-machine-name" class="set-inp" maxlength="32" autocomplete="off" data-save="saveMachineName">
693
+ <button class="set-btn" onclick="saveMachineName()">保存</button>
692
694
  </div>
695
+ <div id="set-machine-status" class="set-status"></div>
693
696
  </div>
694
- <details style="margin-top:0.35rem;font-size:0.72rem;color:var(--muted)">
695
- <summary style="cursor:pointer">手动输入路径</summary>
696
- <div class="set-row" style="margin-top:0.35rem">
697
- <input id="atroot-new" class="set-inp" placeholder="例如 D:\资料库" autocomplete="off">
698
- <button class="set-btn" onclick="addAtRoot()">添加</button>
697
+ <div class="set-item stack">
698
+ <div class="set-lbl">语音识别服务<small>SenseVoice 等服务地址;留空 = 不用语音输入</small></div>
699
+ <div class="set-row">
700
+ <input id="set-asr-url" class="set-inp" placeholder="http://192.168.x.x:8765" autocomplete="off" data-save="saveAsrUrl">
701
+ <button class="set-btn" onclick="saveAsrUrl()">保存</button>
699
702
  </div>
700
- </details>
701
- <div id="atroot-status" class="set-status"></div>
702
- </div>
703
- <div class="set-sec">
704
- <h4>语音识别 (ASR) <span class="set-scope">全局</span></h4>
705
- <div class="set-hint">SenseVoice 或兼容服务的地址,留空表示禁用语音。</div>
706
- <div class="set-row">
707
- <input id="set-asr-url" class="set-inp" placeholder="http://192.168.x.x:8765" autocomplete="off">
708
- <button class="set-btn" onclick="saveAsrUrl()">保存</button>
703
+ <div id="set-asr-status" class="set-status"></div>
709
704
  </div>
710
- <div id="set-asr-status" class="set-status"></div>
711
705
  </div>
712
- <div class="set-sec" id="set-account" style="display:none">
713
- <h4>账户 <span id="set-account-name" class="set-scope"></span></h4>
714
- <div class="set-row" style="margin-top:0.4rem">
715
- <button id="passwd-btn" class="set-btn ghost" onclick="closeSettings();openPasswdSheet()" style="flex:1;display:none">🔑 修改密码</button>
716
- <button id="logout-btn" class="set-btn danger" onclick="doLogout()" style="flex:1;display:none">退出登录</button>
706
+
707
+ <div id="set-account" style="display:none">
708
+ <div class="set-group-t">账户</div>
709
+ <div class="set-card">
710
+ <div class="set-item"><div class="set-lbl">当前用户</div><span id="set-account-name" class="set-val"></span></div>
711
+ <button id="passwd-btn" class="set-link" onclick="closeSettings();openPasswdSheet()" style="display:none">🔑 修改密码</button>
712
+ <button id="logout-btn" class="set-link danger" onclick="doLogout()" style="display:none">退出登录</button>
717
713
  </div>
718
714
  </div>
719
- <div class="set-sec">
720
- <h4>服务</h4>
721
- <div class="set-hint">聊天显示、回车键、快捷栏按钮在会话页的 ⚙ 设置里调(只影响本设备)。</div>
722
- <button class="set-btn danger" onclick="restartMyhi()" style="width:100%">重启 myhi</button>
723
- <div id="set-restart-status" class="set-status"></div>
715
+
716
+ <div class="set-group-t">服务</div>
717
+ <div class="set-card">
718
+ <a id="set-admin-link" class="set-link" href="/admin" style="display:none">🛠 管理后台(用户 / 大模型网关 / 密钥库 / 升级)</a>
719
+ <button class="set-link danger" onclick="restartMyhi()">🔄 重启 myhi</button>
724
720
  </div>
721
+ <div id="set-restart-status" class="set-status" style="margin:0.4rem 0.2rem 0"></div>
722
+ <div class="set-foot">聊天显示、回车键、快捷栏按钮在会话页右上角 ⚙ 里设置。</div>
725
723
  </div>
726
724
 
727
725
  <!-- 修改密码 sheet(成员用户自助改密;管理员改密码在 /admin) -->
@@ -851,10 +849,12 @@
851
849
  if ((data.exclusive || data.hasUsers) && data.name) {
852
850
  document.getElementById('user-name').textContent = data.name;
853
851
  document.getElementById('set-account-name').textContent = data.name;
852
+ fitHeader();
854
853
  document.getElementById('set-account').style.display = '';
855
854
  document.getElementById('logout-btn').style.display = '';
856
855
  }
857
856
  // 成员用户(users.json 登录)才显示自助改密入口;管理员改密码在 /admin
857
+ if (data.role === 'admin') document.getElementById('set-admin-link').style.display = '';
858
858
  if (data.hasUsers && data.role === 'operator') {
859
859
  document.getElementById('set-account').style.display = '';
860
860
  document.getElementById('passwd-btn').style.display = '';
@@ -980,7 +980,20 @@
980
980
  r.setAttribute('data-font', f);
981
981
  const meta = document.querySelector('meta[name="theme-color"]');
982
982
  if (meta) meta.setAttribute('content', THEME_META[t] || '#0d1117');
983
+ fitHeader();
983
984
  }
985
+ // 顶栏按钮组折行了就逐级收紧:字号「大」时按钮跟着 rem 变宽,固定断点兜不住
986
+ function fitHeader() {
987
+ const h = document.getElementById('header');
988
+ const acts = h.querySelector('.hd-actions');
989
+ const wraps = () => acts.offsetHeight > document.getElementById('settings-btn').offsetHeight * 1.5;
990
+ h.classList.remove('hd-fit1', 'hd-fit2');
991
+ if (wraps()) h.classList.add('hd-fit1');
992
+ if (wraps()) h.classList.add('hd-fit2');
993
+ }
994
+ window.addEventListener('resize', fitHeader);
995
+ document.fonts?.ready.then(fitHeader);
996
+ fitHeader();
984
997
  // ── 设置 sheet ─────────────────────────────────────────
985
998
  const THEMES = [['system','跟随系统'],['dark','深色'],['light','浅色'],['black','纯黑']];
986
999
  const FONTS = [['small','小'],['medium','中'],['large','大']];
@@ -1010,8 +1023,8 @@
1010
1023
 
1011
1024
  window.openSettings = function() {
1012
1025
  renderAppearance();
1013
- ['set-machine-status', 'atroot-status', 'set-asr-status', 'set-restart-status'].forEach(id => setStatus(id, ''));
1014
- closeAtPicker();
1026
+ ['set-machine-status', 'set-asr-status', 'set-restart-status'].forEach(id => setStatus(id, ''));
1027
+ document.querySelectorAll('#set-sheet .set-btn').forEach(b => b.classList.remove('dirty'));
1015
1028
  document.getElementById('set-backdrop').classList.add('open');
1016
1029
  document.getElementById('set-sheet').classList.add('open');
1017
1030
  fetch('/api/machine-name').then(readJson).then(d => {
@@ -1024,8 +1037,14 @@
1024
1037
  fetch('/api/server-config').then(readJson).then(d => {
1025
1038
  document.getElementById('set-asr-url').value = d.asrUrl || '';
1026
1039
  }).catch(() => {});
1027
- loadAtRoots();
1028
1040
  };
1041
+ // 输入框改了内容 → 保存按钮亮起;回车 = 保存
1042
+ document.querySelectorAll('#set-sheet input[data-save]').forEach(inp => {
1043
+ const btn = inp.nextElementSibling;
1044
+ inp.addEventListener('input', () => btn.classList.add('dirty'));
1045
+ inp.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.isComposing) { e.preventDefault(); window[inp.dataset.save](); } });
1046
+ });
1047
+ const _savedOk = (inp) => document.getElementById(inp).nextElementSibling.classList.remove('dirty');
1029
1048
  window.closeSettings = function() {
1030
1049
  document.getElementById('set-backdrop').classList.remove('open');
1031
1050
  document.getElementById('set-sheet').classList.remove('open');
@@ -1043,7 +1062,8 @@
1043
1062
  const d = await readJson(r);
1044
1063
  if (!d.ok) throw new Error(d.error || '未知错误');
1045
1064
  window.myhiSetMachineName?.(d.name);
1046
- setStatus('set-machine-status', `已保存:${d.name}(其他已打开的页面刷新后生效)`, 'green');
1065
+ _savedOk('set-machine-name');
1066
+ setStatus('set-machine-status', `✓ 已保存为「${d.name}」,其他已打开的页面刷新后生效`, 'green');
1047
1067
  } catch (e) { setStatus('set-machine-status', '保存失败:' + e.message, 'red'); }
1048
1068
  };
1049
1069
 
@@ -1056,7 +1076,8 @@
1056
1076
  });
1057
1077
  const d = await readJson(r);
1058
1078
  if (!d.ok) throw new Error(d.error || '未知错误');
1059
- setStatus('set-asr-status', url ? '已保存,重启后生效(如已设置 ASR_URL 环境变量则以环境变量为准)' : '已清空', 'green');
1079
+ _savedOk('set-asr-url');
1080
+ setStatus('set-asr-status', url ? '✓ 已保存,重启 myhi 后生效(设了 ASR_URL 环境变量时以环境变量为准)' : '✓ 已清空,语音输入关闭', 'green');
1060
1081
  } catch (e) { setStatus('set-asr-status', '保存失败:' + e.message, 'red'); }
1061
1082
  };
1062
1083
 
@@ -1066,87 +1087,6 @@
1066
1087
  setStatus('set-restart-status', '服务重启中,请稍候…');
1067
1088
  };
1068
1089
 
1069
- // @ 引用范围(项目外目录):@ 补全可引用这些目录
1070
- let _atRoots = [];
1071
- function renderAtRoots() {
1072
- document.getElementById('atroots-list').innerHTML = _atRoots.length
1073
- ? _atRoots.map((r, i) => `<div class="atr-row"><span title="${esc(r)}">📁 ${esc(r)}</span><button class="dr-btn" onclick="delAtRoot(${i})">删</button></div>`).join('')
1074
- : '<div class="set-hint" style="margin:0">(未配置——@ 只搜任务目录)</div>';
1075
- }
1076
- function loadAtRoots() {
1077
- fetch('/api/atroots').then(readJson).then(d => { _atRoots = d.roots || []; renderAtRoots(); }).catch(() => {});
1078
- }
1079
- async function saveAtRoots(roots) {
1080
- try {
1081
- const r = await fetch('/api/atroots', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ roots }) });
1082
- const d = await readJson(r);
1083
- if (!d.ok) throw new Error(d.error || '未知错误');
1084
- _atRoots = d.roots; renderAtRoots(); setStatus('atroot-status', '已保存', 'green');
1085
- return true;
1086
- } catch (e) { setStatus('atroot-status', '保存失败:' + e.message, 'red'); return false; }
1087
- }
1088
- window.addAtRoot = function() {
1089
- const inp = document.getElementById('atroot-new');
1090
- const v = inp.value.trim();
1091
- if (v) saveAtRoots([..._atRoots, v]).then(ok => { if (ok) inp.value = ''; });
1092
- };
1093
- window.delAtRoot = function(i) { saveAtRoots(_atRoots.filter((_, j) => j !== i)); };
1094
-
1095
- // 文件夹点选(复用 /api/dirs 目录浏览):普通用户不会敲绝对路径,点进去选中即可
1096
- let _atPick = { current: '', parent: null };
1097
- window.openAtPicker = function() {
1098
- document.getElementById('atpick').style.display = '';
1099
- document.getElementById('atpick-open').style.display = 'none';
1100
- atPickGo('');
1101
- };
1102
- window.closeAtPicker = function() {
1103
- document.getElementById('atpick').style.display = 'none';
1104
- document.getElementById('atpick-open').style.display = '';
1105
- };
1106
- window.atPickGo = async function(path) {
1107
- const list = document.getElementById('atpick-list');
1108
- list.innerHTML = '<div class="set-hint" style="padding:0.6rem;margin:0">读取中…</div>';
1109
- try {
1110
- const r = await fetch('/api/dirs' + (path ? '?path=' + encodeURIComponent(path) : ''));
1111
- const d = await readJson(r);
1112
- if (!r.ok) throw new Error(d.error || r.status);
1113
- _atPick = d;
1114
- const cur = document.getElementById('atpick-cur');
1115
- cur.textContent = '\u200e' + d.current; // rtl 截断时保留路径尾部;LRM 防标点跑位
1116
- cur.title = d.current;
1117
- const up = document.getElementById('atpick-up');
1118
- up.disabled = !d.parent; up.style.opacity = d.parent ? '' : '0.4';
1119
- const dv = document.getElementById('atpick-drives');
1120
- dv.style.display = (d.drives || []).length ? 'flex' : 'none';
1121
- dv.innerHTML = '';
1122
- (d.drives || []).forEach(dr => {
1123
- const b = document.createElement('button');
1124
- b.className = 'dr-btn';
1125
- b.textContent = '💽 ' + dr.replace(/\\$/, '');
1126
- if (d.current.toUpperCase().startsWith(dr.toUpperCase())) b.style.cssText = 'background:var(--accent);color:#fff;border-color:var(--accent)';
1127
- b.onclick = () => atPickGo(dr);
1128
- dv.appendChild(b);
1129
- });
1130
- list.innerHTML = d.dirs.length ? '' : '<div class="set-hint" style="padding:0.6rem;margin:0">(没有子文件夹)</div>';
1131
- d.dirs.forEach(x => {
1132
- const row = document.createElement('div');
1133
- row.className = 'atpick-row';
1134
- row.textContent = '📁 ' + x.name;
1135
- row.onclick = () => atPickGo(x.path);
1136
- list.appendChild(row);
1137
- });
1138
- } catch (e) {
1139
- list.innerHTML = '<div style="padding:0.6rem;color:var(--red);font-size:0.75rem">读取失败:' + esc(String(e.message || e)) +
1140
- (String(e.message).includes('权限') ? '(仅管理员可浏览目录,可展开下方「手动输入路径」)' : '') + '</div>';
1141
- }
1142
- };
1143
- window.atPickUp = function() { if (_atPick.parent) atPickGo(_atPick.parent); };
1144
- window.addAtPicked = async function() {
1145
- const p = _atPick.current;
1146
- if (!p) return;
1147
- if (_atRoots.includes(p)) { setStatus('atroot-status', '这个文件夹已经在列表里了'); return; }
1148
- if (await saveAtRoots([..._atRoots, p])) closeAtPicker();
1149
- };
1150
1090
  // 会话页「更多 → 全局设置」跳过来:/?settings=1 直接打开
1151
1091
  if (new URLSearchParams(location.search).get('settings')) {
1152
1092
  history.replaceState(null, '', location.pathname);