前端设计指南
语义 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。
- 文档目的与约定
- 主题策略总览
- 语义 Token 体系(CSS 变量)
- 配色系统(Tailwind 色板)
- 字体排版 Typography
- 圆角 / 阴影 / 间距
- 共享组件类(addComponents)
- 统一组件一:InfoBox(提示框)
- 统一组件二:TabBar(页签)
- 统一组件三:PickerPopover(日期/时间选择)
- 统一组件四:CodeEditor(代码编辑器)
- Tab 系统规范(两种 tab + 平滑切换)
- 头像与团队封面色板
- 后端联动(availability / 头像上传)
- 组件全景清单(按页/功能检索)
- 暗黑模式踩坑记录(Lession Learned)
- 验证方法论(浏览器审计 / 构建)
- 提交规范与工作流
1. 文档目的与约定
Section titled “1. 文档目的与约定”1.1 为什么写这份文档
Section titled “1.1 为什么写这份文档”本项目的深色模式、token 体系和统一组件是多次迭代、踩了大量坑之后收敛出来的。曾经出现过:
- 每个组件各自硬编码颜色 → 深浅色不统一、反复遗漏;
:global(.dark)在 Vue scoped 里不编译 → 深色永远不生效却查不到原因;- 激活 tab 与容器同色 → 深色下「看不出激活」;
- 主按钮 hover 成近白 → 白字看不见;
- 嵌套弹窗一点就关;
- 全库修复脚本误伤 → 把正确的深色变体删掉。
这份文档把这些经验固化下来,让后续任何修改都能「一次做对」,并且让组件可检索、可复用。
1.2 核心约定(必须遵守)
Section titled “1.2 核心约定(必须遵守)”- 能用 token 用 token(CSS 变量);只能局部的补
dark:变体;禁止逐组件硬编码十六进制颜色。 - 提示框一律用
InfoBox;页签/分段一律用TabBar或遵守其激活态基准。 - 深色激活态基准:容器
dark:bg-neutral-800,激活dark:bg-neutral-700,文字dark:text-neutral-50。 - 主按钮 hover 用
hover:bg-primary-500,绝不用hover:bg-primary-50,绝不用非法透明度/300。 - scoped 样式里的深色覆写必须用非 scoped
<style>块,<style scoped>内:global(html.dark)不编译。 - z-index 用 token(
--layer-*),不用z-[9999]魔数。 - 改完必须:
grep -rnE "\]\s+dark:"为空 →vue-tsc→eslint→astro build→ 无头浏览器逐页审计。
2. 主题策略总览
Section titled “2. 主题策略总览”2.1 暗黑模式开关
Section titled “2.1 暗黑模式开关”tailwind.config.mjs:darkMode: 'class'。<html>上挂dark类即进入深色。- store:
frontend/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" />2.2 深色不靠「反色滤镜」
Section titled “2.2 深色不靠「反色滤镜」”历史上有过一个 filter: invert() hack,已删除——它会连带反掉图片、品牌色、Logo,且无法精确控制。
2.3 两条并行机制
Section titled “2.3 两条并行机制”| 机制 | 适用场景 | 说明 |
|---|---|---|
| ① 语义 token(CSS 变量) | 表面/边框/文字/强调色的页面级大面积 | BaseLayout.astro 定义,:root 浅色、html.dark 深色,组件消费 var(--...) 自动切换 |
② Tailwind dark: 变体 | 单个组件内的小色块 | 类名补 dark:bg-…、dark:text-… 等 |
六个 showcase mock 组件(
*Showcase.vue、PerspectiveSchedule.vue)已全部改为消费 token(机制 ①)。
3. 语义 Token 体系(CSS 变量)
Section titled “3. 语义 Token 体系(CSS 变量)”定义位置:frontend/src/layouts/BaseLayout.astro 的全局 <style is:global>。
3.1 层级(z-index)token
Section titled “3.1 层级(z-index)token”| 变量 | 值 | 用途 |
|---|---|---|
--layer-dropdown | 1200 | 下拉菜单 |
--layer-popover | 1250 | 弹出层/选择器 |
--layer-modal | 1400 | 弹窗 |
--layer-toast | 1500 | Toast 通知(最顶层) |
使用:style="z-index: var(--layer-modal)"。禁止写死 z-[9999] 等魔数(会跟 toast/modal 打架)。
3.2 语义色 token
Section titled “3.2 语义色 token”| 变量 | 浅色 | 深色 (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);}4. 配色系统(Tailwind 色板)
Section titled “4. 配色系统(Tailwind 色板)”frontend/tailwind.config.mjs → theme.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。
4.2 neutral(中性色 = slate)
Section titled “4.2 neutral(中性色 = slate)”全站统一用
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)是页面/凹陷轨道标准。
4.3 强调辅助色(语义色)
Section titled “4.3 强调辅助色(语义色)”沿用 Tailwind 默认 blue/emerald/amber/red(均含 950)。深色统一约定:
| 语义 | 浅色 | 深色 |
|---|---|---|
| 信息 | bg-blue-50 text-blue-800 border-blue-200 | dark:bg-blue-900/30 dark:text-blue-200 dark:border-blue-800 |
| 成功 | bg-green-50 text-green-800 | dark:bg-green-900/25 dark:text-green-200 |
| 警告 | bg-amber-50 text-amber-800 | dark:bg-amber-900/25 dark:text-amber-200 |
| 危险 | bg-red-50 text-red-800 | dark:bg-red-900/25 dark:text-red-200 |
| 强调(紫) | bg-purple-50 text-purple-800 | dark:bg-purple-900/30 dark:text-purple-200 |
| 强调(橙) | bg-orange-50 text-orange-800 | dark:bg-orange-900/25 dark:text-orange-200 |
5. 字体排版 Typography
Section titled “5. 字体排版 Typography”tailwind.config.mjs → theme.extend.fontFamily。
| 族 | 字体栈 | 用途 |
|---|---|---|
sans | 系统无衬线栈(-apple-system、PingFang SC、Microsoft YaHei 等) | 正文/UI |
display | STSong、Songti SC、Noto Serif SC、SimSun 等衬线 | 展示/标题 |
BaseLayout.astro 里 html 全局设了 font-family: -apple-system, ...,保证中文环境一致。
6. 圆角 / 阴影 / 间距
Section titled “6. 圆角 / 阴影 / 间距”6.1 圆角约定
Section titled “6.1 圆角约定”| 用途 | 值 |
|---|---|
| 小按钮 / 徽章 | rounded-md / rounded-lg |
| 卡片 / 面板 | rounded-xl / rounded-2xl |
| 大面板 / 弹窗 | rounded-[28px](如 ShareScheduleView) |
| 胶囊(多选/分段) | rounded-full |
6.2 阴影约定
Section titled “6.2 阴影约定”- 卡片阴影统一
shadow-sm/shadow-xl(弹窗)。 - 深色下大多数卡片阴影要关掉
dark:shadow-none(深色底上阴影不明显,且会显得脏)。 - 主按钮光晕
shadow-[0_16px_36px_-18px_rgba(2,132,199,0.95)]只在浅色用,深色dark:shadow-none。
6.3 间距
Section titled “6.3 间距”- 页面边距
p-4 md:p-6;弹窗内边距px-4 pb-4 pt-5 sm:p-6。 - tab/分段容器内
p-1。
7. 共享组件类(addComponents)
Section titled “7. 共享组件类(addComponents)”定义位置:tailwind.config.mjs → plugins → addComponents。全部已内置 dark: 变体(除注明外)。
| 类名 | 用途 | 暗色 |
|---|---|---|
.input-base | 标准输入框 | ✅ |
.select-base | 标准下拉框 | ✅ |
.input-search | 带左图标搜索框 | ✅ |
.dropdown-button / .dropdown-menu / .dropdown-item / .dropdown-search / .dropdown-search-input / .dropdown-check | Headless 下拉 | ✅ |
.picker-input-base / .picker-trigger-button | Picker 输入/触发钮 | ✅ |
.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。
8.1 背景
Section titled “8.1 背景”曾散落大量语义提示框(「重要提示」「注意事项」「危险操作」「临时约课」「已登录教务系统」「代码注入」等), 每个组件各自硬编码配色 → 深浅色不统一、反复遗漏。现收敛为单一组件,三种变体:
| variant | 语义 | 浅色 | 深色 |
|---|---|---|---|
info | 说明/提示 | bg-primary-50 text-primary-900 border-primary-200 | dark:bg-primary-900/25 dark:text-primary-100 dark:border-primary-800/70 |
warning | 注意事项 | bg-amber-50 text-amber-900 border-amber-200 | dark:bg-amber-900/25 dark:text-amber-100 dark:border-amber-800/70 |
danger | 危险操作 | bg-red-50 text-red-900 border-red-200 | dark:bg-red-900/25 dark:text-red-100 dark:border-red-800/70 |
8.2 用法
Section titled “8.2 用法”<InfoBox variant="warning" title="注意事项"> <ul class="list-disc list-inside"> <li>转让后您将不再是团队创建者</li> <li>此操作不可撤销</li> </ul></InfoBox>8.3 已迁移位置
Section titled “8.3 已迁移位置”TransferTeamModal(注意事项)、DissolveTeamModal(危险操作)、TemporaryTeamDrawer(临时约课说明)、
ScheduleImporter(重要提示)。新增同类提示框一律用 InfoBox,不要再写硬编码色块。
9. 统一组件二:TabBar(页签)
Section titled “9. 统一组件二:TabBar(页签)”文件:frontend/src/components/TabBar.vue。
9.1 背景
Section titled “9.1 背景”系统设置、团队管理等处的 tab 栏各写一份,激活态深浅色不一致(曾出现激活 tab 与容器同为
dark:bg-neutral-800 导致深色下「看不出激活」)。现收敛为单一组件。
9.2 Props 与用法
Section titled “9.2 Props 与用法”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" />9.3 滑动指示器(平滑切换)
Section titled “9.3 滑动指示器(平滑切换)”参照 ~/AinOfficialWiki 的 Tabs.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),背景由胶囊负责。
9.4 已迁移位置
Section titled “9.4 已迁移位置”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-button(addComponents内置 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、权限选择等),散落在各表单里(EmptyClassroomQuery、
BatchTeamEventModal、TeamScheduleTaskModal、AllTeamsViewPage、SystemSettings 存储切换)。
- 容器
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,hoverdark:hover:bg-neutral-800。 - 平滑:已有
transition-all/transition-colors,色变平滑。
12.3 关键教训(反复踩)
Section titled “12.3 关键教训(反复踩)”- 激活态文字必须有
dark:text-neutral-50——曾因脚本误删导致黑字压深底不可读。 - 激活背景不能等于容器色(都
dark:bg-neutral-800就看不出激活)。 - 容器背景要区别于所在卡片(同
dark:bg-neutral-800则轨道隐形)→ 选择器轨道用dark:bg-neutral-900。 - 页面 tab 不要 focus 光晕。
13. 头像与团队封面色板
Section titled “13. 头像与团队封面色板”avatarPresets17 组 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 / 头像上传)”14.1 availability 周参数
Section titled “14.1 availability 周参数”- 前端
getWeekNumber返回的是日历周(一年最多 53);后端 availability 路由原来限制week ≤ 30→ 返回 422。 - 修复:team 与 temporary 两处 availability 路由
week上限le=30 → le=53(reference_date才是真实周解析依据,week只是兜底)。
14.2 头像上传
Section titled “14.2 头像上传”- 后端
ALLOWED_IMAGE_EXTENSIONS = {jpg,jpeg,png,gif,webp},不含 SVG。 - 前端预设头像由 SVG 栅格化为 PNG 后上传,绕过后端校验且规避 SVG XSS。
15. 组件全景清单(按页/功能检索)
Section titled “15. 组件全景清单(按页/功能检索)”完整清单,方便检索「某个界面用哪个组件、深色是否覆盖」。除注明外均已深色适配。
15.1 顶层布局
Section titled “15.1 顶层布局”| 组件 | 作用 | 备注 |
|---|---|---|
BaseLayout.astro | 全局 token 变量、字体、body 底 | 深色基础 |
Navigation.vue | 桌面侧栏 | 主题切换按钮(图标 dark:block/hidden 双渲染) |
MobileDrawer.vue | 移动抽屉 | |
MobileBottomTabBar.vue | 移动底栏 | |
Footer.vue | 页脚 |
15.2 课表(Schedule)
Section titled “15.2 课表(Schedule)”| 组件 | 作用 |
|---|---|
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 导入/个性化 |
15.3 团队(Team)
Section titled “15.3 团队(Team)”| 组件 | 作用 |
|---|---|
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 | 成员课表/成员条/确认/筛选 |
15.4 管理后台(admin)
Section titled “15.4 管理后台(admin)”| 组件 | 作用 |
|---|---|
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 | 子弹窗/确认/批量恢复 |
15.5 认证 / 着陆 / 导航
Section titled “15.5 认证 / 着陆 / 导航”| 组件 | 作用 |
|---|---|
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 | 应用内浏览器提示 |
15.6 统一组件与复用规则
Section titled “15.6 统一组件与复用规则”- 提示框 →
InfoBox.vue - 页签/分段 →
TabBar.vue;选择器切换器遵守「容器dark:bg-neutral-900+ 激活dark:bg-neutral-700」基准 - 输入框/下拉/选择器 →
.input-base/.select-base/.dropdown-*/.picker-*(addComponents,内置 dark) - 语义色块 → 统一
bg-*-50 dark:bg-*-900/N + text-*-800 dark:text-*-200约定 - 主按钮 hover →
bg-primary-600 hover:bg-primary-500(不要hover:bg-primary-50,不要非法透明度/300)
16. 暗黑模式踩坑记录(Lession Learned)
Section titled “16. 暗黑模式踩坑记录(Lession Learned)”每条都曾造成「深色不生效」「构建失败」或「交互 bug」,务必牢记。
primary-950不存在。dark:bg-primary-950/30静默无效 → 深色下表面保持浅色。一律改用primary-900。:global(.dark)不会编译进 Vue scoped style。正确做法:Tailwinddark:变体或 CSS 变量。dark:追加到:class绑定外 = 构建报错。检查:grep -rnE "\]\s+dark:" components/必须为空 (transition-[...]任意值里的]是误报)。addComponents自定义类没有 dark 自动变体,需手动.dark .xxx { … }覆写(如.warning-surface)。hover:bg-white/hover:bg-neutral-50深色下会变亮,补dark:hover:bg-neutral-800。- WSL2 文件监听不可靠,dev server 会 stale。用
curl核对模块、删缓存干净重启、以astro build为准。 - z-index 用 token 不用魔数。
- 无头浏览器审计:亮度
0.299r+0.587g+0.114b > 215且面积>40×40记为漏网浅色。 - scoped 里
:global(html.dark)不被编译 → 用非 scoped<style>写html.dark .xxx(ScheduleCalendar/CodeEditor/PersonalizationModal)。 - 激活 tab = 容器色 = 看不见 → 激活
dark:bg-neutral-700。 - 主按钮
hover:bg-primary-50白字消失;非法透明度/300让深色 hover 失效 →hover:bg-primary-500。 - 嵌套弹窗一点就关:子弹窗
Teleport to="body"在父面板 DOM 之外,点击子弹窗触发父 outside-click。 修复:子弹窗开关加进父Dialog的:static且加进handleClose的早返回守卫。 - 多个 dev server 抢端口:旧进程占 4322 → 浏览器拿旧模块。
ss -tlnp | grep :4322确认单实例。 - 语义色块统一约定:
bg-red-50 dark:bg-red-900/25、bg-blue-50 dark:bg-blue-900/30等 +text-*-800 dark:text-*-200。 hover:dark:非规范写法会被误判:功能等价dark:hover:,但正则按dark:bg-*会误匹配。一律写dark:hover:。- 全库脚本「按行补 dark」会误伤:某行已有别的
dark:变体时,别再补默认色;且勿把hover:dark:bg-*当冲突背景。 - JS 字符串拼 class 也要带 dark(如 PickerPopover
panelClass);.vue模块检查看不到样式,用构建产物核对。
17. 验证方法论(浏览器审计 / 构建)
Section titled “17. 验证方法论(浏览器审计 / 构建)”17.1 必跑清单
Section titled “17.1 必跑清单”改完任何深色/交互后,按序执行:
cd frontendgrep -rnE "\]\s+dark:" src/components/ # 必须为空bun run type-check # vue-tsc 0 错误bun run lint # 0 错误(存量 warning 可忽略)bun run build # astro build 14 页成功17.2 无头浏览器逐页审计
Section titled “17.2 无头浏览器逐页审计”用 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 → 漏网浅色按面积排序逐处修。
17.3 测试账号
Section titled “17.3 测试账号”- 后端用 PostgreSQL(
DATABASE_URL=postgresql+psycopg://chronosync:...@localhost:5432/chronosync), 不是 SQLite。SQLite 里插的用户对运行中的后端无效。 - admin 账号:
admin/975280hc(测试用)。 - JWT 有效期 15 分钟,测试间隔久需重新登录刷新 token。
17.4 dev server 排障
Section titled “17.4 dev server 排障”- 确认只有一个 astro dev 进程占 4322:
ss -tlnp | grep :4322。 - 模块是否最新:
curl http://localhost:4322/src/components/<File>.vue。 - 样式是否进产物:
astro build后 grepdist/里的 CSS/JS(<style>单独注入,不在.vue模块里)。
18. 提交规范与工作流
Section titled “18. 提交规范与工作流”18.1 约定式提交(Conventional Commits)
Section titled “18.1 约定式提交(Conventional Commits)”按功能/模块分组提交,格式 type(scope): subject:
| type | 用途 |
|---|---|
feat | 新功能/深色适配 |
fix | bug 修复 |
docs | 文档 |
refactor | 重构(不改行为) |
chore | 杂项 |
本项目按模块提交的示例:
feat(dark-mode): establish design token system and unified UI componentsfeat(dark-mode): adapt schedule and classroom viewsfeat(dark-mode): adapt team, heatmap and temporary-scheduling viewsfeat(dark-mode): adapt admin user, team and system pagesfeat(dark-mode): adapt auth, landing, navigation and showcase viewsfeat(avatar): converge preset accents to primary palette and rasterize to PNGfix(api): accept calendar-week in availability queries18.2 提交纪律
Section titled “18.2 提交纪律”- 只提交自己的改动。见到 git 里不是你的(如
.gitignore里的_apkwork/*、server-sync/,或别人的 未提交文件)不要动,用git add <明确文件>精确暂存,别用git add .。 - 一次提交一个模块,body 写清「为什么改、约束、取舍」。
- 提交前确保
type-check+build通过。
18.3 修改流程
Section titled “18.3 修改流程”- 先读本
DESIGN.md+tailwind.config.mjs+BaseLayout.astro。 - 能用 token 用 token;局部用
dark:;提示框用InfoBox;页签用TabBar。 - 改完跑 §17.1 必跑清单 + 无头浏览器逐页审计。
- 按模块约定式提交,只
git add自己的文件。
19. 深色实现细则(正确 vs 错误对照)
Section titled “19. 深色实现细则(正确 vs 错误对照)”19.1 卡片/面板
Section titled “19.1 卡片/面板”<!-- ✅ 正确:卡片在深色用 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">19.2 主按钮(白字)
Section titled “19.2 主按钮(白字)”<!-- ✅ 正确: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">19.3 页面 tab(TabBar)
Section titled “19.3 页面 tab(TabBar)”<!-- ✅ 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 → 黑字不可读 -->19.5 语义色块
Section titled “19.5 语义色块”<!-- ✅ 信息: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">19.6 输入框
Section titled “19.6 输入框”<!-- ✅ 用共享类 .input-base(addComponents 内置 dark) --><input class="input-base" />
<!-- ❌ 错误:手写 bg-white 忘 dark --><input class="bg-white border-neutral-200" />20. 深色实现 Playbook(一步步)
Section titled “20. 深色实现 Playbook(一步步)”给「把某个页面/组件做深色」的完整步骤:
- 读规范:先看
DESIGN.md§4 色板、§7 共享类、§12 tab 规范。 - 找浅色:
grep -nE "bg-white|bg-neutral-50|text-neutral-900|border-neutral-200" <File>.vue。 - 分两类:
- 页面大面积/卡片 → 用 token(
var(--bg-card))或dark:bg-neutral-800/900; - 语义色块 → 按 §4.3 约定补
dark:bg-*-900/N + dark:text-*-200; - 按钮/交互 → 按 §19 正确写法。
- 页面大面积/卡片 → 用 token(
- scoped 样式里写死的颜色 → 另起非 scoped
<style>写html.dark .xxx。 - 校验:
grep -rnE "\]\s+dark:"为空;vue-tsc、astro build通过;- 无头浏览器逐页审计(§17.2),确认 0 残留浅色、激活态可见、hover 正常。
- 提交:按模块
feat(dark-mode): ...约定式提交,只git add本模块文件。
21. 常见问题 FAQ
Section titled “21. 常见问题 FAQ”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. 各模块深色实现要点(深度复盘)”22.1 课表模块
Section titled “22.1 课表模块”ScheduleCalendar.vue的.week-view/.time-column/.time-column-header是 scoped 类写死白底, 已用非 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。
22.2 团队模块
Section titled “22.2 团队模块”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一点就关的根因是父TeamEditorModal的handleClose守卫漏了showDissolveModal; 已修复::static列表 +handleClose早返回都含showDissolveModal(§16 第 12 条)。- 团队成员徽章:创建者
bg-blue-100 dark:bg-blue-900/40、管理员bg-purple-100 dark:bg-purple-900/40。
22.3 管理后台
Section titled “22.3 管理后台”- 表格行操作下拉
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)。
22.4 认证/着陆
Section titled “22.4 认证/着陆”- 主题切换图标用双渲染 +
dark:block/hidden,消除 hydration mismatch(§2.1)。 - 更新日志
ChangelogModal用dark:prose-invert让 markdown 正文在深色下变浅。 - 六个 showcase mock 消费 token(
var(--bg-card)等),深色自动切换。
22.5 头像
Section titled “22.5 头像”- 17 组预设 accent 收敛到 primary/neutral。
- 上传路径 SVG→PNG 栅格化(§13)。