@airalogy/airalogy-bd 1.3.1 → 1.3.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.3.2] - 2026-10-27
8
+
9
+ - 业务总览默认展示按事件发生日期倒序排列的最近 6 条进展,包含普通和重要事件;重要事件显示标签,并提供“仅看关键”切换,保留事项权限、筛选范围及完整时间线。
10
+ - 修复 Markdown 无序列表圆点、有序列表编号和多级列表标记被基础样式清除的问题,保留任务清单复选框及折叠记录中的列表。
11
+ - 在身份验证器首次绑定步骤增加三步指南,提供 Microsoft Authenticator(微软)和 Google Authenticator(谷歌)的操作入口、官方说明和验证码排错提示;补充日常登录时获取动态验证码的说明。
12
+
7
13
  ## [1.3.1] - 2026-10-27
8
14
 
9
15
  - 将「待确认」移到右上角头像旁,有草稿时显示数量,没有草稿时使用弱化样式,保持主导航聚焦于工作台和管理中心。
@@ -0,0 +1,21 @@
1
+ export default function AuthenticatorSetup({ secret, otp_uri }: { secret: string; otp_uri: string }) {
2
+ return <section className="auth-setup" aria-labelledby="auth-setup-title">
3
+ <h2 id="auth-setup-title">绑定身份验证器</h2>
4
+ <p>身份验证器是生成六位动态验证码的手机 App,登录时用它确认身份。</p>
5
+ <ol className="auth-setup-steps">
6
+ <li>安装 <a href="https://support.microsoft.com/en-us/authenticator/how-to-add-your-accounts-to-microsoft-authenticator" target="_blank" rel="noopener noreferrer">Microsoft Authenticator(微软)</a>或 <a href="https://support.google.com/accounts/answer/1066447?hl=zh-Hans" target="_blank" rel="noopener noreferrer">Google Authenticator(谷歌)</a>,任选一个。</li>
7
+ <li>在 App 中手动添加账号。名称可填“Airalogy BD + 你的用户名”,密钥填写下方内容;类型选“基于时间”(如有)。</li>
8
+ <li>输入 App 当前显示的六位验证码,点击“完成绑定”。以后登录也从这个 App 获取验证码。</li>
9
+ </ol>
10
+ <details className="auth-setup-guide">
11
+ <summary>App 操作入口与常见问题</summary>
12
+ <p><strong>微软:</strong>点击“+”添加账号 → “其他账户” → “手动输入代码”。</p>
13
+ <p><strong>谷歌:</strong>点击“+”→“输入设置密钥”。不同版本的按钮名称可能略有差异。</p>
14
+ <p>验证码不可用时,等下一组数字刷新后再试,并检查手机已开启自动设置时间。</p>
15
+ </details>
16
+ <h3>设置密钥</h3>
17
+ <code aria-label="身份验证器设置密钥">{secret}</code>
18
+ <p className="admin-help">此密钥仅用于你自己的身份验证器,请勿分享。</p>
19
+ <p><a href={otp_uri}>在此设备的身份验证器中打开</a></p>
20
+ </section>;
21
+ }
@@ -519,6 +519,7 @@ function EventItem({
519
519
  <div className="event-meta">
520
520
  <time dateTime={event.date}>{formatDate(event.date, workspace)}</time>
521
521
  {sourceLabel && <span>{sourceLabel}</span>}
522
+ {event.importance === "key" && <span className="event-importance">重要</span>}
522
523
  <span className={`event-type event-${event.type}`}>
523
524
  {eventTypeLabels[event.type] ?? event.type}
524
525
  </span>
@@ -1027,6 +1028,7 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1027
1028
  const [selectedProjectValue, setSelectedProject] = useState<Project | null>(null);
1028
1029
  const selectedProject = data.projects.find(project => project.id === selectedProjectValue?.id) || null;
1029
1030
  const [selectedRecord, setSelectedRecord] = useState<RecordSelection | null>(null);
1031
+ const [keyEventsOnly, setKeyEventsOnly] = useState(false);
1030
1032
  const [keyPeopleOnly, setKeyPeopleOnly] = useState(true);
1031
1033
  const [peopleSort, setPeopleSort] = useState<PeopleSort>("recent");
1032
1034
 
@@ -1044,6 +1046,7 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1044
1046
  clearFacetFilters();
1045
1047
  setProjectSort("recent");
1046
1048
  setPeopleSort("recent");
1049
+ setKeyEventsOnly(false);
1047
1050
  setSelectedProject(null);
1048
1051
  };
1049
1052
 
@@ -1228,7 +1231,9 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1228
1231
  : [];
1229
1232
  const allEvents = [...projectEvents, ...companyEvents].sort(eventSort);
1230
1233
 
1231
- const keyEvents = allEvents.filter((event) => event.importance === "key").slice(0, 6);
1234
+ const recentEvents = (keyEventsOnly
1235
+ ? allEvents.filter((event) => event.importance === "key")
1236
+ : allEvents).slice(0, 6);
1232
1237
 
1233
1238
  const scopedProjectIds = new Set(filteredProjects.map((project) => project.id));
1234
1239
  const scopedPeople = data.people
@@ -1497,10 +1502,19 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1497
1502
  <div className="section-heading">
1498
1503
  <div>
1499
1504
  <div className="eyebrow">最近事件</div>
1500
- <h2 id="events-title">关键进展</h2>
1505
+ <h2 id="events-title">最近进展</h2>
1501
1506
  </div>
1502
1507
  <div className="event-panel-actions">
1503
- <span>最近 {keyEvents.length} 条</span>
1508
+ <span role="status">最近 {recentEvents.length} 条</span>
1509
+ <button
1510
+ type="button"
1511
+ className="history-trigger event-key-filter"
1512
+ aria-pressed={keyEventsOnly}
1513
+ aria-controls="recent-progress-list"
1514
+ onClick={() => setKeyEventsOnly((current) => !current)}
1515
+ >
1516
+ 仅看关键
1517
+ </button>
1504
1518
  <EventHistoryDialog
1505
1519
  events={allEvents}
1506
1520
  workspace={data.workspace}
@@ -1512,9 +1526,9 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1512
1526
  />
1513
1527
  </div>
1514
1528
  </div>
1515
- <div className="event-list">
1516
- {keyEvents.length > 0 ? (
1517
- keyEvents.map((event) => (
1529
+ <div className="event-list" id="recent-progress-list">
1530
+ {recentEvents.length > 0 ? (
1531
+ recentEvents.map((event) => (
1518
1532
  <EventItem
1519
1533
  key={`${event.scopeType}-${event.sourceLabel}-${event.id}`}
1520
1534
  event={event}
@@ -1526,7 +1540,7 @@ export default function Dashboard({ data, workspaceStatus, workspaceError, onWor
1526
1540
  />
1527
1541
  ))
1528
1542
  ) : (
1529
- <div className="panel-empty-state">当前筛选下暂无关键事件。</div>
1543
+ <div className="panel-empty-state">{keyEventsOnly ? "当前筛选下暂无关键进展,可关闭“仅看关键”查看普通进展。" : "当前筛选下暂无进展。"}</div>
1530
1544
  )}
1531
1545
  </div>
1532
1546
  </section>
@@ -3,6 +3,7 @@ import {useEffect,useRef,useState} from "react";
3
3
  import {teamRequest} from "./lib/team-client";
4
4
  import {useRouter} from "next/navigation";
5
5
  import {BrandIdentity} from "./Branding";
6
+ import AuthenticatorSetup from "./AuthenticatorSetup";
6
7
  import {usernamePattern,usernameHelp} from "./lib/username-policy";
7
8
 
8
9
  type Setup={secret:string;otp_uri:string};
@@ -70,8 +71,8 @@ export default function LoginForm({join=false}:{join?:boolean}) {
70
71
  {join&&<label>用户名<input required autoComplete="username" autoCapitalize="none" spellCheck={false} pattern={usernamePattern} minLength={3} maxLength={30} value={username} onChange={e=>setUsername(e.target.value)} readOnly={!!invitation?.username}/><span className="admin-help">{invitation?.username?"账号恢复保留原用户名、事项权限和历史记录。":usernameHelp}</span></label>}</>}
71
72
  {(step==="password"||step==="setup")&&<label>{join&&!accepted?"设置密码(至少 12 个字符)":"密码"}<input required type="password" minLength={12} maxLength={256} autoComplete={join&&!accepted?"new-password":"current-password"} value={password} onChange={e=>setPassword(e.target.value)}/></label>}
72
73
  {step==="email"&&<><h2>验证账号邮箱</h2><p role="status">{emailSent?"验证码已发送至账号邮箱,10 分钟内有效。这是邮箱收到的验证码,身份验证器的动态验证码将在下一步输入。":"账号已保留,但验证邮件暂未发送成功。请稍后重新发送,或联系管理员检查发信配置。"}</p><label>邮箱验证码<input required inputMode="numeric" pattern="[0-9]{6}" maxLength={6} autoComplete="one-time-code" value={emailCode} onChange={e=>setEmailCode(e.target.value)}/></label><button className="admin-button" type="button" onClick={resendEmail} disabled={busy}>重新发送验证码</button><p className="admin-help">每分钟最多发送一次;重新发送后请使用最新验证码。也可以返回登录页稍后继续。</p><a href="/login">返回登录</a></>}
73
- {setup && <div className="auth-setup"><h2>绑定身份验证器</h2><p>在你的身份验证器中添加账号,选择“输入设置密钥”,填入以下密钥。请勿分享此密钥。</p><code>{setup.secret}</code><p><a href={setup.otp_uri}>在支持的身份验证器中打开</a></p></div>}
74
- {step==="verify"&&<label>六位动态验证码<input required inputMode="numeric" pattern="[0-9]{6}" maxLength={6} autoComplete="one-time-code" value={code} onChange={e=>setCode(e.target.value)}/></label>}
74
+ {setup && <AuthenticatorSetup {...setup} />}
75
+ {step==="verify"&&<label>六位动态验证码<input required inputMode="numeric" pattern="[0-9]{6}" maxLength={6} autoComplete="one-time-code" value={code} onChange={e=>setCode(e.target.value)}/>{!setup&&<span className="admin-help">打开你绑定的身份验证器 App,输入本账号当前显示的验证码。</span>}</label>}
75
76
  <button className="admin-button admin-primary" type="submit" disabled={busy}>{busy?"正在验证…":step==="email"?"验证邮箱并继续":step==="verify"?(setup?"完成绑定":"登录"):step==="setup"?"继续绑定":join&&!accepted?"设置密码并继续":"下一步"}</button>
76
77
  </form>}
77
78
  <p className="admin-help">仅限受邀成员。忘记密码或无法取得验证码时,请联系管理员重新签发邀请。设置中断后,可从登录页面继续绑定。</p>
@@ -840,9 +840,15 @@ select:focus-visible {
840
840
  scroll-margin-top: 20px;
841
841
  }
842
842
 
843
+ .events-panel .section-heading {
844
+ flex-wrap: wrap;
845
+ gap: 12px;
846
+ }
847
+
843
848
  .event-panel-actions {
844
849
  align-items: center;
845
850
  display: flex;
851
+ flex-wrap: wrap;
846
852
  gap: 9px;
847
853
  }
848
854
 
@@ -869,6 +875,12 @@ select:focus-visible {
869
875
  color: var(--navy);
870
876
  }
871
877
 
878
+ .event-key-filter[aria-pressed="true"] {
879
+ background: var(--navy);
880
+ border-color: var(--navy);
881
+ color: #fff;
882
+ }
883
+
872
884
  .event-list {
873
885
  display: grid;
874
886
  margin-top: 14px;
@@ -939,6 +951,15 @@ select:focus-visible {
939
951
  padding: 2px 7px;
940
952
  }
941
953
 
954
+ .event-importance {
955
+ background: #fff3d6;
956
+ border: 1px solid #eed49b;
957
+ border-radius: 999px;
958
+ color: #82510d;
959
+ font-weight: 700;
960
+ padding: 1px 7px;
961
+ }
962
+
942
963
  .event-agreement {
943
964
  background: #f9efe1;
944
965
  color: #8f5b1b;
@@ -1299,9 +1320,16 @@ select:focus-visible {
1299
1320
 
1300
1321
  .markdown-record ul,
1301
1322
  .markdown-record ol {
1323
+ list-style-position: outside;
1302
1324
  padding-left: 1.7em;
1303
1325
  }
1304
1326
 
1327
+ .markdown-record ul { list-style-type: disc; }
1328
+ .markdown-record ol { list-style-type: decimal; }
1329
+ .markdown-record ul ul { list-style-type: circle; }
1330
+ .markdown-record ul ul ul { list-style-type: square; }
1331
+ .markdown-record li.task-list-item { list-style-type: none; }
1332
+
1305
1333
  .markdown-record li + li {
1306
1334
  margin-top: 5px;
1307
1335
  }
@@ -235,6 +235,15 @@
235
235
  .auth-setup { padding:16px; background:#f3f6fa; border:1px solid var(--line); border-radius:9px; }
236
236
  .auth-setup p { margin:10px 0; font-size:var(--text-small); line-height:var(--leading-reading); }
237
237
  .auth-setup code { display:block; overflow-wrap:anywhere; user-select:all; font-size:var(--text-body); padding:10px; background:white; border-radius:6px; }
238
+ .auth-setup-steps { list-style:decimal outside; padding-left:1.5em; margin:12px 0; font-size:var(--text-small); line-height:var(--leading-reading); }
239
+ .auth-setup-steps li + li { margin-top:8px; }
240
+ .auth-setup-guide { margin:14px 0; font-size:var(--text-small); }
241
+ .auth-setup-guide summary { color:var(--blue); cursor:pointer; }
242
+ .auth-setup-guide p { margin:8px 0; }
243
+ .auth-setup h3 { margin:16px 0 8px; font-size:var(--text-small); font-weight:600; }
244
+ .auth-setup > .admin-help { color:var(--muted); font-size:var(--text-caption); }
245
+ .auth-setup > :last-child { margin-bottom:0; }
246
+
238
247
  .auth-card a { color:var(--blue); font-size:var(--text-small); }
239
248
 
240
249
  @media(max-width:1050px) {
@@ -21,3 +21,5 @@ Airalogy BD 使用系统无衬线字体:优先采用操作系统界面字体
21
21
  公司标识位于应用最上方,工作区导航和账号操作在同一栏;「待确认」作为头像旁的操作入口,有草稿时显示数量,没有时弱化显示;页面标题、页面操作和局部导航位于下方。管理中心负责全局配置和成员权限;个人连接由成员在“我的账号”管理。
22
22
 
23
23
  导出报告有独立的纸张与排版要求,使用报告组件的专用字号,不随界面字号批量调整。
24
+
25
+ 业务总览的“最近进展”默认按事件发生日期倒序展示当前权限和筛选范围内的最近 6 条进展,包含普通和重要事件;重要事件显示“重要”标签。“仅看关键”仅筛选该预览列表,“查看全部”仍展示当前范围的完整时间线。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@airalogy/airalogy-bd",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Business development workspace for local use and team collaboration.",
5
5
  "publishConfig": {
6
6
  "access": "public",