本文记录 CSubtitleWorkstation 的项目开发惯例。新增功能、重构页面、抽取组件或提交代码前,优先按这里的规则判断。
- 先尊重现有产品边界,再新增入口。工具页、压制页、信息页、Aegisub 能力之间不要重复造入口。
- 功能扩展先写清楚范围,再实现。尤其是工具页、新流程、新分类,先列出放在哪里、解决什么问题、哪些能力暂缓。
- 保持实现靠近当前代码结构。前端页面逻辑放在对应
src/views,可复用 UI 放在src/components,可复用纯逻辑放在src/utils,Tauri 调用封装放在src/api。 - 默认值必须来自真实配置或代码,不从界面文案推断。涉及
AppConfig::default、serde(default)、本地持久化字段时,要先读代码确认。 - 不给用户隐藏的默认规则。词库、替换表、自动修正规则默认应为空,除非明确要求内置 starter rules。
满足以下任意情况,应优先抽成通用组件或复用已有通用组件:
- 两个及以上页面出现同一种交互结构,例如弹窗、表格编辑、原文/结果预览、文件选择、保存覆盖确认。
- UI 骨架一致,只是标题、说明、按钮文案、校验开关或提示文案不同。
- 多个页面共享同一种数据结构、解析规则、序列化规则或校验规则。
- 用户操作心智一致,分开实现会导致按钮位置、字段顺序、错误提示或可访问性行为不一致。
- 后续维护需要同步改动多个页面,例如词库编辑、规则列表、保存覆盖流程。
已有例子:
RuleDictionaryModal.vue是自定义词库的统一弹窗骨架。ruleDictionary.ts是词库解析、序列化、增删改、有效规则过滤的共享逻辑。- 字幕校对、繁简转换、CC 字幕整理应共享同一个词库弹窗结构,只调整各自的说明、校验能力和业务映射。
以下情况先保留在页面内,等第二个真实场景出现后再抽:
- 只有一个页面使用,抽取后只是把模板搬到另一个文件。
- 两个功能看起来相似,但输入输出关系、状态机、错误处理或用户目标不同。
- 组件需要暴露大量业务开关才能兼容多个页面,导致通用组件比页面本身更难读。
- 抽取会把页面核心流程拆散,例如压制任务编排、媒体处理命令预览、复杂保存流程。
- 复用的是视觉外观但不是交互模型。这类优先复用 CSS class、设计 token 或小控件,而不是抽大组件。
判断标准:通用组件应该消除真实重复,并让页面只表达业务差异;如果页面需要为通用组件做大量适配,说明抽象过早。
- 通用组件负责交互骨架和稳定行为,页面负责业务文案、业务开关和数据来源。
- props 应表达能力差异,例如是否校验正则、是否支持捕获组、弹窗标题、说明、ARIA 标签。
- 不要为了一个页面写死文案、默认词库、路径、业务状态或后端命令。
- 组件事件保持少而清晰,优先使用
v-model管理主要值,用明确事件处理确认、关闭、删除等动作。 - 同一个组件在不同页面使用时,结构应一致;差异放在说明文字、字段标签、校验策略和提交映射上。
- 可访问性属性要作为组件契约维护。Vue 组件 props 使用 camelCase 传入,例如
ariaLabel。
- 需要用户选择本地文件或目录的功能,必须同时支持拖拽和点击选择。
- 空状态优先使用压制页
VideoMetaCard.vue的 dropzone 模式:明确提示“拖入什么文件”、支持格式、以及一个选择文件按钮。 - 已选择文件后,优先使用
PathPickerField.vue展示当前路径和选择按钮;需要清除、编辑输出路径或命名模板时,再扩展为业务组件。 - 多输入场景要按文件类型自动分发,例如视频、字幕、音频、封面图、TS 分片目录,而不是要求用户按严格顺序操作。
- 拖拽进入时要有全局或页面级 overlay 提示,说明松开后会被当前页面如何处理。
- 文件选择对话框必须设置明确的标题和扩展名过滤;目录选择、保存路径和输入文件选择要分开处理。
- 输出路径应优先自动生成,但允许用户手动修改;用户手动修改后,不要因为输入文件变化随意覆盖用户选择。
涉及 ffmpeg、批处理、文件转换、压制、封装、合并、拆分等会执行外部命令或长任务的功能,交互应参考压制页:
- 必须有命令预览能力,优先复用
CommandPreviewCard.vue展示最终命令参数。 - 参数变化后自动刷新命令预览,使用 debounce,避免每个输入字符都触发后端构建。
- 命令预览默认可以折叠;主操作区优先复用
CommandTaskActions.vue提供显示命令预览 / 隐藏命令预览、开始和取消按钮。 - 主操作按钮放在操作区右侧或主要位置,文案使用具体动作,例如
开始压制、开始转换、开始合并。 - 运行中主按钮切换为取消动作,使用
danger样式,例如取消压制、取消转换。 - 主按钮必须绑定完整的可执行条件:依赖环境可用、必填输入存在、输出路径存在、当前没有运行、额外输入满足当前模式。
- 运行时锁定会改变命令的输入项、模式切换和文件选择;只允许查看日志、复制日志、取消任务等安全操作。
- 任务进度和日志优先复用
JobLogPanel.vue,保持空态、进度、耗时、ETA、速度、日志复制等表现一致。 - 失败信息要落到日志或 toast 中;如果失败原因属于不兼容容器、缺少 ffmpeg、缺少滤镜等,应给出下一步去哪里处理。
新增选项控件时,优先参考压制页已有控件,而不是临时写一套新样式:
- 下拉选择:使用
AppSelect.vue。适用于编码器、码率模式、样式选择、命名模板等有限枚举;需要描述时用 option description。 - 数字输入:参考
EncodeSettingsFields.vue的质量值、码率输入。必须限制min/max或在 setter 中 clamp;允许留空时用null或undefined表示“不生成参数”。 - 布尔开关:使用 checkbox,并让标签文字说明开启后的真实效果,例如是否启用 LOGO、反交错、AVS、自定义词库。
- 互斥模式:使用分段按钮或 tablist,例如工具页的模式切换、繁简转换方向;按钮要有 active 态,运行中需要禁用。
- 自定义文本参数:使用单行 input 或 textarea,并明确说明会拼到什么命令或规则中;危险参数不要悄悄生效。
- 文件路径:使用只读路径 input 或路径行加
选择按钮;输出路径需要支持保存对话框或可编辑路径。 - 参数说明:涉及 ffmpeg 参数、质量、码率、编码器差异时,使用
InfoHint.vue给出说明和对应命令片段。 - 图形化复杂配置:例如 LOGO 布局这类需要预览和拖拽的配置,应使用专门编辑器组件,而不是堆叠普通输入框。
- 操作按钮样式:普通次级操作用
secondary,命令预览切换可使用secondary command-toggle,危险取消用danger,主要执行动作使用默认主按钮样式。
- 工具页适合放短流程、明确输入输出、通常 0 到 10 秒内能完成的辅助能力。
- 压制参数、字幕压制、LOGO、水印、旋转、镜像、缩放、帧率、码率、反交错等优先留在压制页。
- 视频信息展示优先留在信息页,不在工具页重复做只读信息卡。
- 通用字幕编辑、时间轴、样式、格式转换、复杂清理优先交给 Aegisub。
- 工具页当前按
文字处理、格式转换和媒体处理组织;新增工具要先判断属于哪一类,还是应该留在现有页面。 格式转换放字幕格式转换、容器格式转换这类“同一内容换输出格式”的入口,例如字幕格式转换、视频转 MP4。
- 默认不内置词库或替换规则,避免用户不知情地改变文本。
- 同类规则编辑优先复用
RuleDictionaryModal.vue和ruleDictionary.ts。 - 共享规则结构保持
target和pattern语义一致;如果某页面后端契约不同,在页面层做映射。 - 繁简转换需要保持
from = pattern、to = target的业务映射,不改变共享弹窗的左右字段心智。 - 字幕校对和 CC 字幕整理启用正则校验及捕获组能力;繁简转换按普通词条替换处理。
- Vue 页面不直接散落
invoke调用,优先通过src/api/*封装 Tauri command。 - Rust command 负责本地文件、任务执行、命令拼装、错误返回;前端负责状态、交互、预览和结果展示。
- 共享纯逻辑优先放
src/utils,避免在多个页面复制解析和格式化代码。 - 后端新增 command 时同步检查注册位置、前端 API 封装和必要测试。
- 前端或共享组件变更后,至少运行
npm run build。 - Rust command、配置模型、后端服务或命令注册变更后,运行
cargo test。 - 只改文档时不强制跑构建,但要检查 Markdown 文件位置和命名是否符合现有
docs结构。
- 提交信息必须使用中文,并保留 Conventional Commits 前缀,例如
feat: 增加环境检测提示。 - 拉取代码必须使用 rebase,例如
git pull --rebase。 - 提交前检查
git status --short,不要把无关用户改动混入当前提交。 - 如果状态显示有文件变更但 diff 不明显,重新检查暂存区和工作区,不要直接回滚用户改动。