跳转到内容
天空之翼
选择 · Enter跳转 · Esc关闭 由 Pagefind 提供索引
EN

前端设计指南

语义 token、配色系统、暗黑模式策略、统一组件(InfoBox、TabBar、PickerPopover、CodeEditor)、两种 tab 规范、组件全景清单与踩坑记录。

SDNUChronoSync 前端设计系统(Design System / DESIGN.md)

Section titled “SDNUChronoSync 前端设计系统(Design System / DESIGN.md)”

本文档是前端视觉、交互与主题设计的唯一事实来源(source of truth)。 它记录:语义 token 体系、暗黑模式策略、配色、字体、圆角/阴影、共享组件类、统一组件 (InfoBox/TabBar/PickerPopover/CodeEditor)、两种 tab 的规范与平滑切换、全部组件 全景清单、以及踩过的每一个坑。

规则:新增/修改任何颜色、字号、圆角、阴影、暗色行为、交互组件前,先读本文档,保持与既有体系一致。

配套文档:更新日志 docs/CHANGELOG.md;架构 docs/architecture.md;部署 docs/DEPLOYMENT.md; 开发流程 docs/DEVELOPMENT.md

  1. 文档目的与约定
  2. 主题策略总览
  3. 语义 Token 体系(CSS 变量)
  4. 配色系统(Tailwind 色板)
  5. 字体排版 Typography
  6. 圆角 / 阴影 / 间距
  7. 共享组件类(addComponents)
  8. 统一组件一:InfoBox(提示框)
  9. 统一组件二:TabBar(页签)
  10. 统一组件三:PickerPopover(日期/时间选择)
  11. 统一组件四:CodeEditor(代码编辑器)
  12. Tab 系统规范(两种 tab + 平滑切换)
  13. 头像与团队封面色板
  14. 后端联动(availability / 头像上传)
  15. 组件全景清单(按页/功能检索)
  16. 暗黑模式踩坑记录(Lession Learned)
  17. 验证方法论(浏览器审计 / 构建)
  18. 提交规范与工作流

本项目的深色模式、token 体系和统一组件是多次迭代、踩了大量坑之后收敛出来的。曾经出现过:

  • 每个组件各自硬编码颜色 → 深浅色不统一、反复遗漏;
  • :global(.dark) 在 Vue scoped 里不编译 → 深色永远不生效却查不到原因;
  • 激活 tab 与容器同色 → 深色下「看不出激活」;
  • 主按钮 hover 成近白 → 白字看不见;
  • 嵌套弹窗一点就关;
  • 全库修复脚本误伤 → 把正确的深色变体删掉。

这份文档把这些经验固化下来,让后续任何修改都能「一次做对」,并且让组件可检索、可复用。

  1. 能用 token 用 token(CSS 变量);只能局部的补 dark: 变体;禁止逐组件硬编码十六进制颜色
  2. 提示框一律用 InfoBox;页签/分段一律用 TabBar 或遵守其激活态基准。
  3. 深色激活态基准:容器 dark:bg-neutral-800,激活 dark:bg-neutral-700,文字 dark:text-neutral-50
  4. 主按钮 hover 用 hover:bg-primary-500,绝不用 hover:bg-primary-50,绝不用非法透明度 /300
  5. scoped 样式里的深色覆写必须用非 scoped <style><style scoped>:global(html.dark) 不编译。
  6. z-index 用 token--layer-*),不用 z-[9999] 魔数。
  7. 改完必须:grep -rnE "\]\s+dark:" 为空 → vue-tsceslintastro build → 无头浏览器逐页审计。

  • tailwind.config.mjsdarkMode: 'class'<html> 上挂 dark 类即进入深色。
  • storefrontend/src/stores/theme.ts 统一读写。localStorage key:app-theme,值 dark / light
  • 主题切换按钮Navigation.vue):为避免 SSR/客户端 hydration 不一致(sun/moon 图标 d 路径不同导致的 Vue warn),同时渲染太阳+月亮两个图标,用 dark:block / dark:hidden 的 CSS 类切换显示,而不是 v-if="isDark"。这样 SSR 与客户端 DOM 完全一致。
<SunIcon class="h-5 w-5 hidden dark:block" aria-hidden="true" />
<MoonIcon class="h-5 w-5 dark:hidden" aria-hidden="true" />

历史上有过一个 filter: invert() hack,已删除——它会连带反掉图片、品牌色、Logo,且无法精确控制。

机制适用场景说明
① 语义 token(CSS 变量)表面/边框/文字/强调色的页面级大面积BaseLayout.astro 定义,:root 浅色、html.dark 深色,组件消费 var(--...) 自动切换
② Tailwind dark: 变体单个组件内的小色块类名补 dark:bg-…dark:text-…

六个 showcase mock 组件(*Showcase.vuePerspectiveSchedule.vue)已全部改为消费 token(机制 ①)。


定义位置:frontend/src/layouts/BaseLayout.astro 的全局 <style is:global>

变量用途
--layer-dropdown1200下拉菜单
--layer-popover1250弹出层/选择器
--layer-modal1400弹窗
--layer-toast1500Toast 通知(最顶层)

使用:style="z-index: var(--layer-modal)"禁止写死 z-[9999] 等魔数(会跟 toast/modal 打架)。

变量浅色深色 (html.dark)用途
--bg-page#f8fafc#0f172a页面底
--bg-card#ffffff#1e293b卡片/面板表面
--bg-muted#f1f5f9#334155次级表面(输入底)
--bg-subtle#e2e8f0#475569更弱表面(分隔/悬停底)
--border#e2e8f0#334155常规边框
--border-strong#cbd5e1#475569强调边框
--text#0f172a#f8fafc主文字
--text-2#475569#cbd5e1次级文字
--text-3#64748b#94a3b8弱文字/占位
--text-4#94a3b8#64748b最弱文字
--accent#0ea5e9#38bdf8强调色(主色)
--accent-strong#0284c7#0ea5e9强调色(按下/实底)
--accent-soft#e0f2fe#0c4a6e强调色浅底

消费示例:

.card {
background: var(--bg-card);
color: var(--text);
border-color: var(--border);
}

frontend/tailwind.config.mjstheme.extend.colors

4.1 primary(强调主色 = 天蓝 sky)

Section titled “4.1 primary(强调主色 = 天蓝 sky)”

全站唯一强调色。历史曾有 secondary(fuchsia 紫)色板,已删除;sky 也已并入 primary

50#f0f9ff
100#e0f2fe
200#bae6fd
300#7dd3fc
400#38bdf8
500#0ea5e9
600#0284c7
700#0369a1
800#075985
900#0c4a6e

⚠️ primary 只有 50–900,没有 950。 任何 primary-950 类都是无效的、会被 Tailwind 静默丢弃(深色不生效)。 写深色浅底时用 primary-900,如 dark:bg-primary-900/30

全站统一用 neutral 替代散落的 gray/slate不要再引入 gray-*/slate-*

50#f8fafc
100#f1f5f9
200#e2e8f0
300#cbd5e1
400#94a3b8
500#64748b
600#475569
700#334155
800#1e293b
900#0f172a
950#020617

neutral-800(#1e293b)是卡片/面板标准深色;neutral-700(#334155)是激活态标准;neutral-900(#0f172a)是页面/凹陷轨道标准。

沿用 Tailwind 默认 blue/emerald/amber/red(均含 950)。深色统一约定:

语义浅色深色
信息bg-blue-50 text-blue-800 border-blue-200dark:bg-blue-900/30 dark:text-blue-200 dark:border-blue-800
成功bg-green-50 text-green-800dark:bg-green-900/25 dark:text-green-200
警告bg-amber-50 text-amber-800dark:bg-amber-900/25 dark:text-amber-200
危险bg-red-50 text-red-800dark:bg-red-900/25 dark:text-red-200
强调(紫)bg-purple-50 text-purple-800dark:bg-purple-900/30 dark:text-purple-200
强调(橙)bg-orange-50 text-orange-800dark:bg-orange-900/25 dark:text-orange-200

tailwind.config.mjstheme.extend.fontFamily

字体栈用途
sans系统无衬线栈(-apple-systemPingFang SCMicrosoft YaHei 等)正文/UI
displaySTSongSongti SCNoto Serif SCSimSun 等衬线展示/标题

BaseLayout.astrohtml 全局设了 font-family: -apple-system, ...,保证中文环境一致。


用途
小按钮 / 徽章rounded-md / rounded-lg
卡片 / 面板rounded-xl / rounded-2xl
大面板 / 弹窗rounded-[28px](如 ShareScheduleView)
胶囊(多选/分段)rounded-full
  • 卡片阴影统一 shadow-sm / shadow-xl(弹窗)。
  • 深色下大多数卡片阴影要关掉 dark:shadow-none(深色底上阴影不明显,且会显得脏)。
  • 主按钮光晕 shadow-[0_16px_36px_-18px_rgba(2,132,199,0.95)] 只在浅色用,深色 dark:shadow-none
  • 页面边距 p-4 md:p-6;弹窗内边距 px-4 pb-4 pt-5 sm:p-6
  • tab/分段容器内 p-1

定义位置:tailwind.config.mjspluginsaddComponents。全部已内置 dark: 变体(除注明外)。

类名用途暗色
.input-base标准输入框
.select-base标准下拉框
.input-search带左图标搜索框
.dropdown-button / .dropdown-menu / .dropdown-item / .dropdown-search / .dropdown-search-input / .dropdown-checkHeadless 下拉
.picker-input-base / .picker-trigger-buttonPicker 输入/触发钮
.btn-secondary / .btn-primary次级/主按钮
.warning-surface / .warning-surface-soft琥珀提示框(旧类,深色已补 .dark .warning-surface 覆写)
.btn-clear-selection取消选择小按钮⚠️ 仍用 slate-*,尚未迁移 neutral
.scrollbar-custom自定义滚动条

新代码优先复用这些类,而不是手写一整套 @apply。 ⚠️ addComponents 里的自定义类没有 dark: 自动变体,需要手动补 .dark .xxx { … } 覆写。


8. 统一组件一:InfoBox(提示框)

Section titled “8. 统一组件一:InfoBox(提示框)”

文件:frontend/src/components/InfoBox.vue

曾散落大量语义提示框(「重要提示」「注意事项」「危险操作」「临时约课」「已登录教务系统」「代码注入」等), 每个组件各自硬编码配色 → 深浅色不统一、反复遗漏。现收敛为单一组件,三种变体:

variant语义浅色深色
info说明/提示bg-primary-50 text-primary-900 border-primary-200dark:bg-primary-900/25 dark:text-primary-100 dark:border-primary-800/70
warning注意事项bg-amber-50 text-amber-900 border-amber-200dark:bg-amber-900/25 dark:text-amber-100 dark:border-amber-800/70
danger危险操作bg-red-50 text-red-900 border-red-200dark:bg-red-900/25 dark:text-red-100 dark:border-red-800/70
<InfoBox variant="warning" title="注意事项">
<ul class="list-disc list-inside">
<li>转让后您将不再是团队创建者</li>
<li>此操作不可撤销</li>
</ul>
</InfoBox>

TransferTeamModal(注意事项)、DissolveTeamModal(危险操作)、TemporaryTeamDrawer(临时约课说明)、 ScheduleImporter(重要提示)。新增同类提示框一律用 InfoBox不要再写硬编码色块


文件:frontend/src/components/TabBar.vue

系统设置、团队管理等处的 tab 栏各写一份,激活态深浅色不一致(曾出现激活 tab 与容器同为 dark:bg-neutral-800 导致深色下「看不出激活」)。现收敛为单一组件。

  • tabs: { id, name|label }[]modelValue(激活 id)、stretch?: boolean(按钮平分占满宽度)。
  • v-model 双向绑定。
<TabBar :tabs="tabs" v-model="activeTab" />
<TabBar stretch :tabs="[{id:'swap',name:'对调工作日'},{id:'holiday',name:'设置假期'}]" v-model="mode" />

参照 ~/AinOfficialWikiTabs.vue,实现滑动指示胶囊

  • 容器 relative,一个绝对定位的胶囊 <span>bg-white shadow dark:bg-neutral-700)铺在按钮下方。
  • getBoundingClientRect 测量激活按钮的 left/width,写入 transform: translate3d(...)width
  • CSS transition-[width,transform,opacity] duration-200 ease-out 让它平滑滑到激活 tab。
  • onMounted/resize/modelValue/tabs 变化时重新同步。
  • 按钮只保留文字色(激活 dark:text-neutral-50),背景由胶囊负责。

admin/SystemSettings.vue(站点/存储/邮箱/代码注入)、TeamEditorModal.vue(团队信息/成员管理/可见范围/操作)、 ScheduleAdjuster.vue(对调工作日/设置假期,stretch)。新增页签一律用 TabBar


10. 统一组件三:PickerPopover(日期/时间选择)

Section titled “10. 统一组件三:PickerPopover(日期/时间选择)”

文件:frontend/src/components/PickerPopover.vue

  • 触发按钮用共享类 .picker-input-base / .picker-trigger-buttonaddComponents 内置 dark)。
  • 面板 panelClass计算属性里的类字符串,曾是唯一浅色点(bg-white 无 dark)→ 已补 dark:border-neutral-700 dark:bg-neutral-800 dark:shadow-none dark:ring-neutral-700
  • 日历格子/时间/分/清空/今天/确定按钮全部有 dark 变体。
  • ⚠️ 在 JS 字符串里拼 class 时,别忘了 dark 变体.vue 模块检查看不到样式,用构建产物核对)。

11. 统一组件四:CodeEditor(代码编辑器)

Section titled “11. 统一组件四:CodeEditor(代码编辑器)”

文件:frontend/src/components/CodeEditor.vue(系统设置「代码注入」用)。

  • scoped 类(.code-editor-container/.line-numbers/.code-textarea)写死浅色背景,无 dark 自动变体
  • 修复:在 <style scoped> 后另起非 scoped <style>html.dark .code-editor-container { … } 等覆写, 覆盖背景、行号、文字、错误态、滚动条。
  • ⚠️ 这就是「scoped 里 :global(html.dark) 不编译」坑的正面解法,见 §16 第 9 条。

12. Tab 系统规范(两种 tab + 平滑切换)

Section titled “12. Tab 系统规范(两种 tab + 平滑切换)”

本项目有两种 tab/切换器,规范不同,务必区分:

12.1 场景一:页面选项切换(TabBar / ProfilePage)

Section titled “12.1 场景一:页面选项切换(TabBar / ProfilePage)”

用于「同一页面不同选项切换」:个人中心、系统设置、管理团队。

  • 组件:TabBar.vue(含滑动指示器)。
  • 容器 bg-neutral-100 dark:bg-neutral-800;激活由滑动胶囊负责背景,文字 dark:text-neutral-50
  • 不允许有 focus 光晕边框focus:ring-2 focus:ring-primary-500 会造成「点击后一直发光,点别处才消失」)—— 只留 focus:outline-none。基准参照 ProfilePage.vue(无光晕)。
  • 激活态深色基准:容器 dark:bg-neutral-800,激活胶囊 dark:bg-neutral-700,文字 dark:text-neutral-50

12.2 场景二:选择器切换(全选/清空、多选、智能排班切换器)

Section titled “12.2 场景二:选择器切换(全选/清空、多选、智能排班切换器)”

用于「全选/多选/清空」或模式切换(周/日、week/date、权限选择等),散落在各表单里(EmptyClassroomQueryBatchTeamEventModalTeamScheduleTaskModalAllTeamsViewPageSystemSettings 存储切换)。

  • 容器 bg-neutral-100 dark:bg-neutral-900(比卡片 neutral-800 深一档,形成凹陷轨道,否则与卡片同色看不清)。
  • 激活:bg-white dark:bg-neutral-700 + dark:text-neutral-50(文字必须浅色,否则黑字压深底不可读)。
  • 未激活:text-neutral-600 dark:text-neutral-300,hover dark:hover:bg-neutral-800
  • 平滑:已有 transition-all / transition-colors,色变平滑。
  1. 激活态文字必须有 dark:text-neutral-50——曾因脚本误删导致黑字压深底不可读。
  2. 激活背景不能等于容器色(都 dark:bg-neutral-800 就看不出激活)。
  3. 容器背景要区别于所在卡片(同 dark:bg-neutral-800 则轨道隐形)→ 选择器轨道用 dark:bg-neutral-900
  4. 页面 tab 不要 focus 光晕

  • avatarPresets 17 组 DiceBear 风格统一收敛为 primary/neutral 单锚调性(不再五颜六色)。
  • 预设头像上传:avatarPresetSvgToFile 会把生成的 SVG 栅格化为 PNG 再上传。 原因:后端 ALLOWED_IMAGE_EXTENSIONS 只收 jpg/jpeg/png/gif/webp,不收 SVG;且 SVG 静态服务是 XSS 向量。
  • 远程头像(gastigado.cnies.org 等)无 CORS 头时,浏览器 fetch 会被拦;<img> 展示不受影响。

14. 后端联动(availability / 头像上传)

Section titled “14. 后端联动(availability / 头像上传)”
  • 前端 getWeekNumber 返回的是日历周(一年最多 53);后端 availability 路由原来限制 week ≤ 30 → 返回 422。
  • 修复:team 与 temporary 两处 availability 路由 week 上限 le=30 → le=53reference_date 才是真实周解析依据, week 只是兜底)。
  • 后端 ALLOWED_IMAGE_EXTENSIONS = {jpg,jpeg,png,gif,webp}不含 SVG
  • 前端预设头像由 SVG 栅格化为 PNG 后上传,绕过后端校验且规避 SVG XSS。

15. 组件全景清单(按页/功能检索)

Section titled “15. 组件全景清单(按页/功能检索)”

完整清单,方便检索「某个界面用哪个组件、深色是否覆盖」。除注明外均已深色适配。

组件作用备注
BaseLayout.astro全局 token 变量、字体、body 底深色基础
Navigation.vue桌面侧栏主题切换按钮(图标 dark:block/hidden 双渲染)
MobileDrawer.vue移动抽屉
MobileBottomTabBar.vue移动底栏
Footer.vue页脚
组件作用
MySchedulePage.vue我的课表主页面(周/日/日历/列表视图、课表选择、分享管理、统计卡)
ScheduleGanttWeekView.vue / ScheduleDayListView.vue / ScheduleCalendar.vue周/日/日历视图(ScheduleCalendar 的 .week-view/.time-column非 scoped <style> 深色覆写)
ScheduleEditor.vue / EventModal.vue / EventDetailModal.vue / StackedEventsModal.vue / TeamEventDetailModal.vue编辑/事件弹窗
ScheduleAdjuster.vue调休(对调工作日/设置假期,TabBar stretch)
ScheduleImporter.vue / ImportScheduleModal.vue / JwxtConnectionModeSelector.vue / ImportOptionsModal.vue导入课表(WebVPN/教务选择、步骤提示)
ScheduleList.vue课表列表
ExportOptionsModal.vue / ShareOptionsModal.vue / ShareScheduleView.vue / TeamAvailabilityShareModal.vue导出/分享
PickerPopover.vue日期/时间选择器
EmptyClassroomQuery.vue / CsvImportModal.vue / PersonalizationModal.vue找空教室/CSV 导入/个性化
组件作用
MyTeamsPage.vue / TeamList.vue / AllTeamsViewPage.vue我的团队/团队列表/团队视图
TeamViewPage.vue团队视图(周/月/热力图、筛选栏)
TeamEditorModal.vue管理团队(团队信息/成员/可见范围/团队操作;TabBar;批量添加日程/智能排班/转让/解散 按钮)
DissolveTeamModal.vue / TransferTeamModal.vue / LeaveTeamModal.vue解散/转让/退出确认弹窗
TeamHeatmapDrawer.vue / TeamAvailabilityGrid.vue / TeamSlotDetailDrawer.vue热力图/共同空闲
TeamScheduleTaskModal.vue / BatchTeamEventModal.vue / BatchOperationsLog.vue / TemporaryTeamDrawer.vue智能排班/批量添加/批量操作日志/临时约课
CreateTeam.vue / JoinTeam.vue / AvatarPresetDrawer.vue / TutorialEntry.vue创建/加入团队、头像预设
UserScheduleViewer.vue / TeamMemberSchedulePanel.vue / TeamMemberStrip.vue / ActionConfirmModal.vue / FilterSidebar.vue成员课表/成员条/确认/筛选
组件作用
admin/UserManagementPage.vue用户管理(表格/下拉/封禁)
admin/AdminTeamManagement.vue团队管理(编辑/删除/成员)
admin/SystemSettings.vue系统设置(站点/存储/邮箱/代码注入;TabBar + CodeEditor)
admin/UserEditModal.vue / admin/BanUserModal.vue / admin/UserScheduleModal.vue / admin/ConfirmDeleteModal.vue / admin/BatchRestoreConfirmToast.vue子弹窗/确认/批量恢复
组件作用
LoginForm.vue / RegisterForm.vue / ForgetPasswordForm.vue / ForceBindEmailModal.vue / FirstStartAdminModal.vue登录/注册/找回/邮箱绑定/初始管理员
LandingNavbar.vue / CTASection.vue / FeatureSection.vue / 6 个 *Showcase.vue / PerspectiveSchedule.vue着陆页
ChangelogModal.vue更新日志(dark:prose-invert
InAppBrowserPrompt.vue应用内浏览器提示
  1. 提示框InfoBox.vue
  2. 页签/分段TabBar.vue;选择器切换器遵守「容器 dark:bg-neutral-900 + 激活 dark:bg-neutral-700」基准
  3. 输入框/下拉/选择器.input-base/.select-base/.dropdown-*/.picker-*addComponents,内置 dark)
  4. 语义色块 → 统一 bg-*-50 dark:bg-*-900/N + text-*-800 dark:text-*-200 约定
  5. 主按钮 hoverbg-primary-600 hover:bg-primary-500(不要 hover:bg-primary-50,不要非法透明度 /300

16. 暗黑模式踩坑记录(Lession Learned)

Section titled “16. 暗黑模式踩坑记录(Lession Learned)”

每条都曾造成「深色不生效」「构建失败」或「交互 bug」,务必牢记。

  1. primary-950 不存在dark:bg-primary-950/30 静默无效 → 深色下表面保持浅色。一律改用 primary-900
  2. :global(.dark) 不会编译进 Vue scoped style。正确做法:Tailwind dark: 变体或 CSS 变量。
  3. dark: 追加到 :class 绑定外 = 构建报错。检查:grep -rnE "\]\s+dark:" components/ 必须为空 (transition-[...] 任意值里的 ] 是误报)。
  4. addComponents 自定义类没有 dark 自动变体,需手动 .dark .xxx { … } 覆写(如 .warning-surface)。
  5. hover:bg-white/hover:bg-neutral-50 深色下会变亮,补 dark:hover:bg-neutral-800
  6. WSL2 文件监听不可靠,dev server 会 stale。用 curl 核对模块、删缓存干净重启、以 astro build 为准。
  7. z-index 用 token 不用魔数
  8. 无头浏览器审计:亮度 0.299r+0.587g+0.114b > 215 且面积 >40×40 记为漏网浅色。
  9. scoped 里 :global(html.dark) 不被编译 → 用非 scoped <style>html.dark .xxx(ScheduleCalendar/CodeEditor/PersonalizationModal)。
  10. 激活 tab = 容器色 = 看不见 → 激活 dark:bg-neutral-700
  11. 主按钮 hover:bg-primary-50 白字消失非法透明度 /300 让深色 hover 失效 → hover:bg-primary-500
  12. 嵌套弹窗一点就关:子弹窗 Teleport to="body" 在父面板 DOM 之外,点击子弹窗触发父 outside-click。 修复:子弹窗开关加进父 Dialog:static 加进 handleClose 的早返回守卫。
  13. 多个 dev server 抢端口:旧进程占 4322 → 浏览器拿旧模块。ss -tlnp | grep :4322 确认单实例。
  14. 语义色块统一约定bg-red-50 dark:bg-red-900/25bg-blue-50 dark:bg-blue-900/30 等 + text-*-800 dark:text-*-200
  15. hover:dark: 非规范写法会被误判:功能等价 dark:hover:,但正则按 dark:bg-* 会误匹配。一律写 dark:hover:
  16. 全库脚本「按行补 dark」会误伤:某行已有别的 dark: 变体时,别再补默认色;且勿把 hover:dark:bg-* 当冲突背景。
  17. JS 字符串拼 class 也要带 dark(如 PickerPopover panelClass);.vue 模块检查看不到样式,用构建产物核对。

17. 验证方法论(浏览器审计 / 构建)

Section titled “17. 验证方法论(浏览器审计 / 构建)”

改完任何深色/交互后,按序执行:

cd frontend
grep -rnE "\]\s+dark:" src/components/ # 必须为空
bun run type-check # vue-tsc 0 错误
bun run lint # 0 错误(存量 warning 可忽略)
bun run build # astro build 14 页成功

用 Playwright/puppeteer 打开每个页面,注入 access_token + app-theme=dark,遍历所有元素:

const lum = (c) => { const m = c.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/); return m ? 0.299*m[1]+0.587*m[2]+0.114*m[3] : -1; };
// 对每个元素:opacity>0.05 && width>40 && height>40 && lum(bgColor)>215 → 漏网浅色

按面积排序逐处修。

  • 后端用 PostgreSQLDATABASE_URL=postgresql+psycopg://chronosync:...@localhost:5432/chronosync), 不是 SQLite。SQLite 里插的用户对运行中的后端无效
  • admin 账号:admin / 975280hc(测试用)。
  • JWT 有效期 15 分钟,测试间隔久需重新登录刷新 token。
  • 确认只有一个 astro dev 进程占 4322:ss -tlnp | grep :4322
  • 模块是否最新:curl http://localhost:4322/src/components/<File>.vue
  • 样式是否进产物:astro build 后 grep dist/ 里的 CSS/JS(<style> 单独注入,不在 .vue 模块里)。

18.1 约定式提交(Conventional Commits)

Section titled “18.1 约定式提交(Conventional Commits)”

按功能/模块分组提交,格式 type(scope): subject

type用途
feat新功能/深色适配
fixbug 修复
docs文档
refactor重构(不改行为)
chore杂项

本项目按模块提交的示例:

feat(dark-mode): establish design token system and unified UI components
feat(dark-mode): adapt schedule and classroom views
feat(dark-mode): adapt team, heatmap and temporary-scheduling views
feat(dark-mode): adapt admin user, team and system pages
feat(dark-mode): adapt auth, landing, navigation and showcase views
feat(avatar): converge preset accents to primary palette and rasterize to PNG
fix(api): accept calendar-week in availability queries
  • 只提交自己的改动。见到 git 里不是你的(如 .gitignore 里的 _apkwork/*server-sync/,或别人的 未提交文件)不要动,用 git add <明确文件> 精确暂存,别用 git add .
  • 一次提交一个模块,body 写清「为什么改、约束、取舍」。
  • 提交前确保 type-check + build 通过。
  1. 先读本 DESIGN.md + tailwind.config.mjs + BaseLayout.astro
  2. 能用 token 用 token;局部用 dark:;提示框用 InfoBox;页签用 TabBar
  3. 改完跑 §17.1 必跑清单 + 无头浏览器逐页审计。
  4. 按模块约定式提交,只 git add 自己的文件。

19. 深色实现细则(正确 vs 错误对照)

Section titled “19. 深色实现细则(正确 vs 错误对照)”
<!-- ✅ 正确:卡片在深色用 neutral-800,阴影关掉 -->
<div class="rounded-xl bg-white shadow-sm dark:bg-neutral-800 dark:shadow-none">
<!-- ❌ 错误:深色没补背景 → 白卡片 -->
<div class="rounded-xl bg-white shadow-sm">
<!-- ✅ 正确:hover 到 primary-500(仍够深,白字可见) -->
<button class="bg-primary-600 text-white hover:bg-primary-500">
<!-- ❌ 错误:hover 到 primary-50(近白)→ 白字看不见 -->
<button class="bg-primary-600 text-white hover:bg-primary-50">
<!-- ❌ 错误:非法透明度 /300(仅支持 /0–/100)→ 深色 hover 失效 -->
<button class="bg-primary-600 text-white dark:hover:bg-primary-900/300">
<!-- ✅ TabBar:激活由滑动胶囊负责,文字浅色,无 focus 光晕 -->
<TabBar :tabs="tabs" v-model="activeTab" />

19.4 选择器切换器(全选/清空等)

Section titled “19.4 选择器切换器(全选/清空等)”
<!-- ✅ 容器凹陷轨道 neutral-900(区别于卡片 800),激活 neutral-700 + 浅文字 -->
<div class="rounded-2xl bg-neutral-100 p-1 dark:bg-neutral-900">
<button :class="selected
? 'bg-white text-neutral-900 shadow dark:bg-neutral-700 dark:text-neutral-50'
: 'text-neutral-600 dark:text-neutral-300 hover:bg-neutral-50 dark:hover:bg-neutral-800'">全选</button>
<!-- ❌ 错误:激活=容器同色(都 800)→ 看不出激活;激活文字没 dark:text → 黑字不可读 -->
<!-- ✅ 信息:bg-blue-50 dark:bg-blue-900/30 + text-blue-800 dark:text-blue-200 -->
<div class="rounded-md bg-blue-50 p-4 text-blue-800 dark:bg-blue-900/30 dark:text-blue-200">
<!-- ❌ 错误:只写浅色 -->
<div class="rounded-md bg-blue-50 p-4 text-blue-800">
<!-- ✅ 用共享类 .input-base(addComponents 内置 dark) -->
<input class="input-base" />
<!-- ❌ 错误:手写 bg-white 忘 dark -->
<input class="bg-white border-neutral-200" />

给「把某个页面/组件做深色」的完整步骤:

  1. 读规范:先看 DESIGN.md §4 色板、§7 共享类、§12 tab 规范。
  2. 找浅色grep -nE "bg-white|bg-neutral-50|text-neutral-900|border-neutral-200" <File>.vue
  3. 分两类
    • 页面大面积/卡片 → 用 token(var(--bg-card))或 dark:bg-neutral-800/900
    • 语义色块 → 按 §4.3 约定补 dark:bg-*-900/N + dark:text-*-200
    • 按钮/交互 → 按 §19 正确写法。
  4. scoped 样式里写死的颜色 → 另起非 scoped <style>html.dark .xxx
  5. 校验
    • grep -rnE "\]\s+dark:" 为空;
    • vue-tscastro build 通过;
    • 无头浏览器逐页审计(§17.2),确认 0 残留浅色、激活态可见、hover 正常。
  6. 提交:按模块 feat(dark-mode): ... 约定式提交,只 git add 本模块文件。

Q:深色下某块还是白的,代码里明明有 dark? A:大概率是 dev server stale(§16 第 6、13 条)。curl 核对模块、确认单实例、硬刷新;以 astro build 产物为准。

Q:激活 tab 看不见? A:激活背景与容器同色(都 dark:bg-neutral-800)。改激活为 dark:bg-neutral-700(§16 第 10 条)。

Q:点击 tab 后一直有个发光边框? A:是 focus:ring-2 focus:ring-primary-500。去掉,只留 focus:outline-none(§12.1)。

Q:选中的文字黑乎乎看不清? A:激活态忘了 dark:text-neutral-50(§16 第 10/15 条)。

Q:scoped 里写 :global(html.dark) 没用? A:Vue scoped 不编译它。用非 scoped <style> 块(§16 第 9 条)。

Q:嵌套弹窗(解散)一点就关? A:子弹窗 teleport 到 body,触发父弹窗 outside-click。把子开关加进父 :static 加进 handleClose 守卫(§16 第 12 条)。

Q:hover:dark:bg-* 对吗? A:功能等价 dark:hover:bg-*,但非规范且会被工具误判,一律写 dark:hover:(§16 第 15 条)。

Q:后端 availability 报 422? A:前端发日历周(最大 53),后端原来限 30。已放宽到 53(§14.1)。

Q:预设头像上传 400? A:SVG 不被后端接受。前端栅格化为 PNG 再传(§13)。


22. 各模块深色实现要点(深度复盘)

Section titled “22. 各模块深色实现要点(深度复盘)”
  • ScheduleCalendar.vue.week-view/.time-column/.time-column-headerscoped 类写死白底, 已用非 scoped <style>html.dark 覆写(§16 第 9 条)。滚动条 track/thumb 也深色化。
  • MySchedulePage.vue 的打印/导出视图(内联 background:#ffffff)是故意白(打印用),不要动
  • ScheduleAdjuster.vue 的「对调工作日/设置假期」已迁移到 TabBar stretch(平分两个标签)。
  • EmptyClassroomQuery.vue 的周/日/节次选择器:容器 dark:bg-neutral-900 凹陷轨道 + 激活 dark:bg-neutral-700
  • TeamEditorModal.vue 的批量添加日程/智能排班/转让/解散按钮:bg-white dark:bg-neutral-800 + border-*-300 dark:border-*-800 + text-*-700 dark:text-*-300 + hover:bg-*-50 dark:hover:bg-*-900/30
  • DissolveTeamModal 一点就关的根因是父 TeamEditorModalhandleClose 守卫漏了 showDissolveModal已修复:static 列表 + handleClose 早返回都含 showDissolveModal(§16 第 12 条)。
  • 团队成员徽章:创建者 bg-blue-100 dark:bg-blue-900/40、管理员 bg-purple-100 dark:bg-purple-900/40
  • 表格行操作下拉 z-[260],行 focus-within:z-[220] 让打开菜单的行高于相邻行(z-index 约定)。
  • 用户列表操作格 sticky right-0 z-[90],下拉菜单在 z-[260] 容器内。
  • SystemSettings.vue 的代码注入用 CodeEditor.vue(非 scoped 深色覆写)。
  • 管理端三页已全量深色:user-management(142 处 dark)、team-management(139)、system-settings(102)。
  • 主题切换图标用双渲染 + dark:block/hidden,消除 hydration mismatch(§2.1)。
  • 更新日志 ChangelogModaldark:prose-invert 让 markdown 正文在深色下变浅。
  • 六个 showcase mock 消费 token(var(--bg-card) 等),深色自动切换。
  • 17 组预设 accent 收敛到 primary/neutral。
  • 上传路径 SVG→PNG 栅格化(§13)。