@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 +6 -0
- package/dashboard/app/AuthenticatorSetup.tsx +21 -0
- package/dashboard/app/Dashboard.tsx +21 -7
- package/dashboard/app/LoginForm.tsx +3 -2
- package/dashboard/app/globals.css +28 -0
- package/dashboard/app/settings.css +9 -0
- package/docs/interface-design.md +2 -0
- package/package.json +1 -1
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
|
|
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"
|
|
1505
|
+
<h2 id="events-title">最近进展</h2>
|
|
1501
1506
|
</div>
|
|
1502
1507
|
<div className="event-panel-actions">
|
|
1503
|
-
<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
|
-
{
|
|
1517
|
-
|
|
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"
|
|
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 && <
|
|
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)}
|
|
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) {
|
package/docs/interface-design.md
CHANGED
|
@@ -21,3 +21,5 @@ Airalogy BD 使用系统无衬线字体:优先采用操作系统界面字体
|
|
|
21
21
|
公司标识位于应用最上方,工作区导航和账号操作在同一栏;「待确认」作为头像旁的操作入口,有草稿时显示数量,没有时弱化显示;页面标题、页面操作和局部导航位于下方。管理中心负责全局配置和成员权限;个人连接由成员在“我的账号”管理。
|
|
22
22
|
|
|
23
23
|
导出报告有独立的纸张与排版要求,使用报告组件的专用字号,不随界面字号批量调整。
|
|
24
|
+
|
|
25
|
+
业务总览的“最近进展”默认按事件发生日期倒序展示当前权限和筛选范围内的最近 6 条进展,包含普通和重要事件;重要事件显示“重要”标签。“仅看关键”仅筛选该预览列表,“查看全部”仍展示当前范围的完整时间线。
|