Skip to content

Code Review 检查清单

来源: 从 15 位团队成员在 804 条 MR 讨论中的评审意见中提炼而来。 用途: 每次提交代码前,AI 必须基于本文档对代码进行审查,确保已出现过的问题不再重犯。 适用范围: 所有代码修改,包括 AI 辅助生成的代码。


一、代码清洁与提交规范

1.1 禁止提交调试代码

规则: 提交前必须删除所有调试语句。

必须删除的内容:

  • console.log / console.warn / console.error(除非是有意的错误日志)
  • debugger 语句
  • alert() 调用
  • 注释掉的代码块(如 // const oldLogic = ...
  • 空的 .codex 等无用文件

检查方法: 提交前全局搜索 console.logdebuggeralert(,确认零匹配。

为什么重要: 调试代码会污染生产日志,debugger 会在用户浏览器中暂停执行,alert 会阻塞线程。

真实案例 (MR !8485): 5 处 console.log 和调试代码未清理就提交了 MR,涉及 store、组件、视图多个文件。 真实案例 (MR !5467): 提交了 debugger 语句到 XpGrid 组件中。 真实案例 (MR !16925): 提交了一个空的 .codex 文件。

1.2 提交内容必须纯净

规则: 每次提交只包含与当前需求直接相关的改动。

禁止夹带的内容:

  • 无关文件的格式化(缩进、空行、分号等)
  • 无关变量的重命名
  • 无关注释的删除或修改
  • IDE 自动生成的配置变更

为什么重要: 无关改动会增加 review 难度,掩盖真正的逻辑变更,增加合并冲突的概率。

处理方法: 如果 IDE 或 lint 工具自动格式化了不相关的文件,提交前用 git diff 检查,回退不相关的改动。

真实案例 (MR !16079): xTableConfigDialog.vue 中存在大量与功能无关的格式化和变量改动(700 行),被要求全部回退,仅保留必要改动。

1.3 删除文件后必须清理所有引用

规则: 删除一个文件后,必须全局搜索该文件名,清理所有引用。

需要检查的引用类型:

  • 静态 import / require 语句
  • 动态拼接的引用:import(\@/store/${moduleName}`)require('@/store/' + name)`
  • Vuex store 的 modules 注册
  • Vue Router 的 routes 配置
  • main.js 中的全局注册
  • 其他文件中的路径字符串引用

检查方法: 用文件名(不含扩展名和路径前缀)在项目中做全局文本搜索,确认零匹配。

为什么重要: 遗留的引用会导致运行时报错(模块找不到),且动态拼接的引用无法通过 IDE 的"查找引用"功能发现。

真实案例 (MR !16998): 删除了 Instacart 相关的 store 文件,但 store/index.js 中仍然注册了该模块,需要一并清理。 真实案例 (MR !16925): 删除了 .env.development 文件,但需要确认没有其他地方引用。

1.4 用户提示使用 $message 而非 alert

规则: 所有用户可见的提示信息必须使用 Element UI 的 this.$messageuseMessage(),禁止使用浏览器原生 alert()

为什么重要:

  • alert() 会阻塞 JavaScript 线程
  • alert() 无法国际化
  • alert() 的样式无法自定义,与系统 UI 不协调

真实案例 (MR !13288): 使用了 alert 方法弹出错误提示,且文案是硬编码的中文。应改为 this.$message.error(this.$t('xxx'))


二、国际化(i18n)规范

2.1 组件内翻译方法的正确用法

规则:

  • Composition API 中使用 const { t } = useI18n()
  • Options API 中使用 this.$t()
  • <template> 中统一使用 $t()不要使用 t()
  • 禁止全局 import i18n 对象后调用 i18n.t()

为什么:

  1. useI18n() 是 Vue 3 + vue-i18n 的标准用法,方便后续升级
  2. 只有 useI18n() 才能访问组件局部的 <i18n> 翻译文件
  3. 模板中使用 t() 而非 $t() 曾导致页面崩溃(原因不明但已有先例)

真实案例 (MR !15389): 在组件中 import i18n from '@/i18n' 然后调用 i18n.t(),应改为 const { t } = useI18n()真实案例 (MR !16073): 在模板中使用了 t 而非 $t,被要求统一改为 $t真实案例 (MR !16079): 多处遗漏未替换的 $t,需要逐一检查。

2.2 禁止硬编码中文文案

规则: 所有用户可见的文案必须使用 $t() 翻译 key,禁止直接写中文字符串。

包括但不限于:

  • 按钮文字、标题、提示信息
  • 错误提示文案
  • 表单标签、占位符
  • 弹窗内容

真实案例 (MR !10178): 在 Vue 模板中直接写了中文文案,没有使用翻译 key。 真实案例 (MR !13288): 报错文案是硬编码的中文,应改为 $t 版本。 真实案例 (MR !11179): 代码中出现硬编码的中文"复制1",虽然是沿用旧代码,但应该翻译。

2.3 翻译 key 不要重复定义

规则: 新增翻译前,先搜索是否已有相同含义的 key,有则复用。

检查方法: 在翻译文件中搜索关键词(如 "this month"、"successful"),确认是否已有可复用的 key。

真实案例 (MR !6242): 重复定义了"本月"的翻译,但 common.thisMonth 已经存在。 真实案例 (MR !5467): 重复定义了"成功"的翻译,但 common.successful 已经存在。

2.4 不要修改现有翻译字段的含义

规则: 如果现有翻译 key 在其他模块中已有特定含义,不要为了新功能修改它的翻译文案。应新增一个翻译 key。

为什么重要: 修改现有 key 的文案会影响所有使用该 key 的地方,可能导致其他模块的文案变得不准确。

真实案例 (MR !6005): 修改了"广告活动状态"的翻译,但在亚马逊模块中"广告活动状态"和"广告活动投放状态"是两个不同字段,修改后影响了其他模块。应新增字段。

2.5 翻译文件结构要统一

规则:

  • 新平台的翻译 key 统一放在 {PlatformName}.xxx 命名空间下(如 Instacart.tableHead
  • 不要创建 tableHead.instacart 这种反向结构
  • 子 key 要放在正确的父级下面,注意层级关系

真实案例 (MR !16948): tableHead.instacartInstacart.tableHead 两种结构并存,需要统一。 真实案例 (MR !16179): 翻译 key 应该放在 auth 的下面,而不是和 auth 平级。

2.6 三语言翻译必须同步

规则: 新增翻译时,en.json、zh.json、ja.json 三个文件必须同步更新,不能遗漏。

真实案例 (MR !12008): 日语翻译文件少了字段。 真实案例 (MR !12657): 日语翻译和中英文意思对不上,需要确认。

2.7 翻译不要放在请求回调中

规则: 翻译逻辑应放在 computed 或模板中,不要在数据请求的回调中调用 t()

为什么重要: 切换语言时不会重新请求数据,放在请求回调中的翻译就不会更新。

真实案例 (MR !16079): 在请求数据的方法中调用 t() 做翻译,切换语言后翻译不会更新。

2.8 翻译要放在响应式容器中

规则: 包含翻译的数据结构必须使用 computed 而非 refreactive

为什么重要: ref/reactive 只在初始化时计算一次,切换语言后不会重新翻译。computed 会在依赖变化时自动重新计算。

真实案例 (MR !13223): 使用 ref 定义了包含 t('xx') 的数据,切换语言时翻译不会更新。应改为 computed真实案例 (MR !4622): 使用 reactive 定义了包含翻译的 label,建议改为 computed

2.9 不要为单个场景开放自定义翻译入口

规则: 如果产品提供了和公共翻译不同的文案,先确认是否是公共翻译需要修改,而不是为单个页面开放自定义翻译入口。

为什么重要: 自定义翻译入口表面上灵活,实际上会导致同一个术语在系统不同位置翻译不一致。

真实案例 (MR !16042): 为了改标签页面的日语翻译,在 SearchInput 组件中开放了自定义翻译入口。但传入的值和公共翻译含义相同,应直接修改公共翻译。


三、Vue 编码规范

3.1 Props 访问方式

规则: 访问 props 时直接使用属性名。

  • ✅ Options API: this.profiles
  • ✅ Composition API: props.profiles
  • this.$props.profiles
  • $props.profiles

真实案例 (MR !15845): 多处使用 this.$props.profiles,应直接写 this.profiles

3.2 模板属性绑定顺序

规则: 先数据绑定 :prop,再事件绑定 @event

  • <MyComp :data="list" @change="onChange" />
  • <MyComp @change="onChange" :data="list" />

真实案例 (MR !15845): :profiles="profiles" 放在了 @treeAddTarget 后面,应调换顺序。

3.3 从 vue 导入 Composition API

规则: 直接 import { ref, computed } from 'vue',不要使用已废弃的 @vue/composition-api 包。

为什么: 项目已升级到 Vue 2.7+,原生支持 Composition API,不再需要兼容层。

真实案例 (MR !15389, !11179): 从 @vue/composition-api 导入 ref/computed,应改为从 vue 导入。

3.4 复杂模板逻辑提取为 computed

规则: 如果模板中的 :disabledv-ifv-show 表达式包含多个条件组合,必须提取为 computed 属性。

为什么重要:

  • 提高模板可读性
  • 便于调试(可以在 devtools 中查看 computed 的值)
  • 避免模板中出现难以理解的长表达式

真实案例 (MR !10956): disabled 的逻辑过于复杂,直接写在模板中难以理解,应改为 computed。 真实案例 (MR !12008): 多处模板中写了过于复杂的表达式,被要求全部改为 computed。

3.5 Options API 中不要引入 useI18n

规则: Options API 写法的组件中,直接使用 this.$t(),不需要引入 useI18nuseI18n 是给 Composition API 使用的。

真实案例 (MR !12008): Options API 组件中引入了 useI18n,应直接用 this.$t

3.6 setup 中的 t 不需要 return

规则: 在 setup() 函数中使用 const { t } = useI18n() 后,t 不需要在 return 中暴露给模板(模板中用 $t)。

真实案例 (MR !11652): setup 中 return 了 t,这是不必要的。

3.7 布尔 prop 的简写

规则: 值为 true 的布尔 prop 可以简写。

  • <MyComp is-tab />
  • <MyComp :is-tab="true" />

真实案例 (MR !12008): :is-tab="true" 可缩写为 is-tab

3.8 默认插槽可省略 template

规则: 使用默认插槽时,可以省略 <template #default> 包裹。

真实案例 (MR !5467): 默认插槽使用了多余的 <template> 包裹。

3.9 v-if 放在组件上而非 template 上

规则: 如果 <template> 内只有一个子组件,且 v-if 是唯一的指令,可以直接把 v-if 放到子组件上,去掉多余的 <template>

真实案例 (MR !5467): 可以去掉 <template>,把 v-if 放到下面的组件上。


四、JavaScript 编码规范

4.1 优先使用 const

规则: 如果变量不会被重新赋值,必须使用 const 而非 let

真实案例 (MR !11179): 多处 let 可以改为 const

4.2 使用严格相等 ===

规则: 比较操作统一使用 ===!==,禁止使用 ==!=

真实案例 (MR !5467): 使用了 == 进行比较,应改为 ===

4.3 布尔值不需要判等

规则: 如果变量本身就是布尔值,直接使用,不需要 === true=== false

  • this.budgetRuleOpen ? 1 : 0
  • this.budgetRuleOpen === true ? 1 : 0

真实案例 (MR !12008): 布尔值还用了 === true 判等,多余。

4.4 switch-case 中 return 后不需要 break

规则: 如果 case 分支中已经 return 了,不需要再加 break

真实案例 (MR !16214): 每个分支都直接 return 了,不需要 break。 真实案例 (MR !15101): sku/asin/parentAsin 共享相同逻辑(fall-through),处理完后已经 return,不会 fall through 到 productLine。

4.5 使用 switch-case 替代长 if-else 链

规则: 当多个条件分支基于同一个变量的不同值时,使用 switch-caseif-else 更简洁清晰。

真实案例 (MR !12008): 长 if-else 链被建议改为 switch-case 写法。

4.6 使用 lodash 的 isEqual 进行深度比较

规则: 对象/数组的深度比较使用 _.isEqual(),不要自己手写比较逻辑。

真实案例 (MR !12008): 自己写了对象比较逻辑,应使用 _.isEqual()

4.7 数字格式化统一使用 formatNumber

规则: 所有数字格式化统一使用 @xmars/uiformatNumber 方法,不要自己写格式化逻辑,也不要使用 Vue filters。

关于空值处理:

  • 0 就是 0,不要格式化为 '--'。0 和 null 的含义不同:0 表示"值为零",null 表示"没有数据"
  • 空值(null/undefined)格式化为 '--' 的逻辑统一交给 formatNumber 处理
  • 数据的初始值不要用 '--' 这种字符串,应该用 null0

真实案例 (MR !13223): 自己写了格式化逻辑,且把 0 格式化为 '--'。应统一使用 formatNumber,且 0 就是 0。 真实案例 (MR !16492): 数字格式化没有使用统一的 formatNumber 方法。

4.8 数据查找优先使用 O(1) 的 Map/Object

规则: 如果有一个"key → value"的映射关系,用对象结构直接查找,不要用数组循环。

  • const map = { '/path1': 'msg1', '/path2': 'msg2' }; return map[path]
  • array.forEach(item => { if (item.paths.includes(path)) return item.msg })

真实案例 (MR !15452): 用数组循环查找路径对应的错误信息,应改为对象直接查找。

4.9 变量命名规范

规则:

  • JavaScript 变量统一使用小写驼峰(camelCase)
  • 不要用 $ 前缀命名普通变量($ 在 Vue 中约定为框架内部属性)
  • 不要用 dataitemindex2 等语义不清的变量名
  • 不同平台的 ID 字段要区分命名(如 walmartTenantId vs tenantId

真实案例 (MR !12008): 变量命名 index2 语义不清,应使用有意义的名称。 真实案例 (MR !13223): 变量名 data 太宽泛,应改为 aiActionData真实案例 (MR !16599): 用 $ 前缀命名普通变量,与 Vue 框架约定冲突。 真实案例 (MR !8522): URL 中直接用了 tenantId,Walmart 应使用 walmartTenantId

4.10 emit 的位置

规则: 在 <script setup> 或 Composition API 中,emit 的定义一般放在 script 部分的开头,在 props 后面。

真实案例 (MR !12008): emit 定义放在了文件中间,应移到开头。

4.11 不要对 ref 做额外装箱

规则: 外部使用 ref 值时,应使用 unref() 取值,不要对已经是 ref 的值再做一层 ref() 包装。

为什么重要: 额外的装箱会增加不必要的响应式开销。

真实案例 (MR !15153): 对已经是 ref 的值又做了一层包装,应使用 unref() 取值。


五、CSS 与样式规范

5.1 使用项目 CSS 变量

规则: 优先使用 src/styles/_variables_new.scss 中定义的 CSS 变量。

常见映射:

  • 文字颜色: var(--color-text), var(--color-title), var(--color-text-secondary)
  • 主色: var(--color-primary)
  • 白色: var(--color-white)
  • 边框: var(--border)
  • 字号: var(--font-12), var(--font-14)
  • 间距: var(--space-8), var(--space-16)

禁止:

  • 直接从 Figma 复制 CSS 变量名(Figma 变量名和项目变量名通常不一致)
  • 使用不存在的 CSS 变量(如果项目中没有,直接写色值)
  • 在 CSS var() 中填写不必要的默认值

真实案例 (MR !12144): 多处使用了硬编码色值如 #4e5969,应替换为 var(--color-text)真实案例 (MR !15452): 从 Figma 复制了项目中不存在的 CSS 变量。

5.2 间距必须是 4 的倍数

规则: 宽度、高度、边距等尺寸值尽量保证是 4 的倍数,其次是 2 的倍数。不需要完全按照 UI 图上的取值。

  • padding: 8px 16px
  • padding: 7px 15px

真实案例 (MR !12008): 使用了 7px 和 15px 的间距,应改为 8px 和 16px。

5.3 禁止使用 !important

规则: 尽量避免使用 !important,通过提高选择器优先级或调整 CSS 结构来解决样式覆盖问题。

真实案例 (MR !12008): 使用了 !important,被要求避免。

5.4 禁止行内样式

规则: 不要在模板中使用 style="..." 行内样式,应该写在 <style> 中。

真实案例 (MR !12008): 使用了行内样式 padding: 7px 15px,应移到 CSS 中。

5.5 禁止在单独元素上设置 font-family

规则: font-family 应该在全局样式中统一设置,不要在单独元素上设置。

真实案例 (MR !5467): 在单独元素上设置了 font-family

5.6 CSS class 命名必须有前缀

规则: CSS class 名必须有组件/模块前缀,禁止使用无前缀的通用单词。

  • ai-action-summary-header, x-select__dropdown
  • .header, .content, .data, .title

为什么重要: 无前缀的 class 名极易被外部全局 CSS 意外影响。

真实案例 (MR !13223): 使用了 .header, .title, .data, .num, .unit 等过于宽泛的 class 名。 真实案例 (MR !15436): 使用了没有前缀的常用单词作为 CSS class name。

5.7 禁止在模板中内联 SVG

规则: SVG 图标应该让 UI 写到字体文件中(icon 级别),或以 SVG 文件形式引入(大图片)。不要直接在 <template> 中写 SVG 代码。

真实案例 (MR !12008): 多处直接在 template 里面写了 SVG 代码。

5.8 样式修复优先使用公共 class

规则: 修复样式问题时,先检查项目中是否已有对应的公共 class(如 xp-multiple-selectxp-dialog),有则直接使用。

真实案例 (MR !16455): 为 el-select 写了自定义样式,但项目已有 xp-multiple-select class。 真实案例 (MR !4622): el-dialog 应加上 xp-dialog class 以应用统一样式。

5.9 Figma CSS 需要二次处理

规则: 从 Figma 复制出来的 CSS 代码通常有很多问题(变量名不对、代码冗余),必须手动处理后才能使用。

真实案例 (MR !12008): CSS 代码里面直接粘贴了 Figma 的 CSS,变量名不对且代码冗余。


六、组件设计规范

6.1 组件文件位置要和使用范围匹配

规则:

  • 只在一个页面使用的组件 → 放在该页面目录下
  • 多个页面共用的组件 → 放在公共组件目录
  • 不要把只有一个使用方的组件注册为全局组件

真实案例 (MR !16179): AuthCard.vue 只在 auth/index.vue 中使用,却放在了 home/ 目录下。应放到 auth/ 目录下。

6.2 确定的事件逻辑不要 emit

规则: 如果组件的事件处理逻辑是确定的(不需要外部自定义),直接在组件内部处理,不要 emit 出去再在父组件处理。

真实案例 (MR !16179): AuthCard.vue 的 auth 和 logout 点击事件逻辑是确定的,不需要 emit 出去。

6.3 公共组件修改要注意 Breaking Changes

规则: 修改公共组件的默认行为时,必须评估对所有使用方的影响。如果原来某个功能默认不展示,现在改为默认展示,应通过新增配置项控制,保持默认行为不变。

真实案例 (MR !16079): asinCardCell.js 原本不展示 AmountCell,改为默认展示后影响了所有使用方。应新增 showAmount 配置项,默认为 false。

6.4 优先使用 slot 而非 config 控制 UI

规则: 如果某个 UI 元素的显隐和内容由调用方决定,优先通过 slot 传入,而不是在组件 config 中加布尔开关。

真实案例 (MR !6711): 通过 config 配置项控制工具栏按钮的显隐,应直接使用 toolbar-right slot。

6.5 组件暴露方法而非链式 $refs

规则: 组件内部数据的获取,应在组件内部封装方法暴露出来,外部直接调用。不要通过链式 $refs 调用内部实现。

  • this.$refs.table?.getTableData()?.tableData || []
  • ✅ 在 XpGrid 内部封装 getTableData() 方法暴露

为什么重要: 链式 $refs 调用非常脆弱,如果组件内部改动,外部很难发现受影响的地方。

真实案例 (MR !15493): 通过连续的 $refs 调用获取表格数据,被建议封装为组件方法。

6.6 新增全局组件前先确认是否已有类似组件

规则: 新增全局组件前,先确认是否已有功能相似的组件。区分"需要完整功能"和"只需要样式"的场景。

真实案例 (MR !16179): 新增了全局的 XpLabelSelect 组件,但项目中已有类似的 SimpleFilterItem 组件可以满足需求。

6.7 大量常量/配置放到单独文件

规则: 如果组件中有大量的常量定义、配置对象、工具方法,应该拆分到单独的文件中(如 utils.jsconfig.jsconstants.js),不要全部堆在组件文件里。

真实案例 (MR !12008): 大量常量和配置直接写在组件文件中,被要求放到单独文件。 真实案例 (MR !12008): rule、ruleAction、ruleCondition 等组件中的大段配置被要求放到单独文件中。

6.8 单个文件改动不应超过 300-400 行

规则: 如果一个功能需要大量改动,说明需要拆分为多个子组件或独立模块。

真实案例 (MR !16079): xTableConfigDialog.vue 产生了 700 行改动,被要求拆分为独立组件。

6.9 重复逻辑必须封装

规则: 如果发现多个组件有相同的逻辑或样式,必须提取为共享的 composable、mixin 或基础组件。

真实案例 (MR !16608): AiActionSummary.vue 中大量重复逻辑,被要求封装。 真实案例 (MR !15893): 三个 Dialog 组件有相同的 .el-dialog 样式覆盖,应提取为共享 mixin。 真实案例 (MR !16039): AI 星星的判断条件在 4 个地方重复出现,应提取为公共工具方法。


七、架构与设计原则

7.1 消除硬编码,数据驱动

规则: 当系统中已存在注册表、配置列表、枚举等数据源时,必须从数据源读取,禁止硬编码具体值。

判断标准: 如果新增一个同类实体(如新平台)需要改动当前文件,说明存在硬编码。

真实案例 (MR !16892): 在 login.vue 中硬编码了权限 key 和路由路径,每新增一个平台都要手动加一段 if-else。应通过 OmniCenter.platformList 动态匹配。 真实案例 (MR !16079): 在两个不同文件中分别硬编码了日期 20250323,且值还不一致。

7.2 状态与配置分离

规则: 配置对象只放静态声明,不放运行时状态变量。运行时状态放在独立的 store 或 service 文件中。

真实案例 (MR !16892): 在 platformConfig.js 的 export default 对象上放了 _authChecked 状态变量。应抽离到独立的 service 文件中。

7.3 平台特有逻辑不要放在公共文件中

规则:

  • 平台特有的路由守卫 → 使用 Vue Router 的路由独享守卫 (beforeEnter)
  • 平台特有的登录后逻辑 → 通过 eventBus 监听 loginSuccess 事件
  • 平台特有的全局 store 判断 → 不要在全局 store 的 mutation 中做特定页面的判断

真实案例 (MR !16179): 在全局 permission.js 中通过路径字符串判断是否是 Instacart 页面。应在 Instacart 路由定义中使用 beforeEnter真实案例 (MR !16179): 在 login.vue 中直接调用 checkInstacartAuth()。应通过 eventBus 在平台内部处理。 真实案例 (MR !16073): 在全局 store 的 SET_DOW mutation 中判断是否是 SQP 页面。应在 SQP 业务层处理。

7.4 通用性设计:先克制,后扩展

规则:

  • 公共组件新增配置项时,要区分通用需求和特定类型需求
  • 如果一个配置项只对某一种类型有意义,不要放在全局 config 中
  • 不要为"未来可能需要"预留未实现的功能代码(YAGNI 原则)

真实案例 (MR !16276): 在全局 config 中加了 allowNegative,但这只对数值类型有意义。应通过 slot 自定义。 真实案例 (MR !16079): batchOperations 中定义了标签操作的 command,但实际功能未实现。

7.5 缓存 key 设计

规则:

  • 缓存 key 必须包含 userId,确保不同用户之间缓存隔离
  • 不要把业务特定的 ID(如 profileId)硬编码在通用的缓存逻辑中
  • 前端缓存要谨慎:如果后端接口已经能恢复状态,优先依赖后端

真实案例 (MR !16235): 通用的 cacheKey 逻辑中硬编码了 profileId,且没有拼 userId。 真实案例 (MR !16773): 前端自行缓存了 profile 选择状态,但后端接口本身就能恢复。

7.6 使用枚举值而非硬编码字符串

规则: 系统内有明确定义的类型(如 profileType、campaignType 等),必须使用枚举值,不要硬编码字符串。

真实案例 (MR !16334): 硬编码了 'SponsoredDSP' 字符串,但系统内其他地方不存在这个 profileType。应使用枚举值。

7.7 全局错误处理要谨慎

规则: 在全局 request.js 拦截器中处理错误码时:

  • 确认该错误码确实是全局性的(多个接口都会返回且含义一致)
  • 确认后端微服务之间不会复用同一个错误码表示不同含义
  • 如果只是个别接口的特定错误,应在业务层处理

真实案例 (MR !15281): 在全局拦截器中处理了特定错误码,但后端是微服务架构,需要确认错误码的唯一性。

7.8 环境判断不要用 hostname

规则: 不要通过 window.location.hostname 判断环境,使用项目的环境变量(.env.* 文件)。

真实案例 (MR !16055): 通过判断 hostname 区分测试和生产环境,localhost 或域名变更时会出问题。

7.9 跨平台页面处理

规则:

  • 全局性页面(任务中心、偏好设置等)需要注册到 crossPlatformPages
  • 跳转时 URL 带上 platform query 参数
  • 跨平台页面不要依赖特定平台的 URL 参数

真实案例 (MR !16781): 从 Instacart 跳转到任务中心时,走了 Amazon 逻辑导致 tenantId 为 -2。

7.10 判断条件要统一提取

规则: 如果多处需要同一个判断条件(如"是否是 SQP 页面"),必须提取为公共方法,禁止在不同文件中各自硬编码。

真实案例 (MR !16079): 判断 SQP 页面的条件在多个文件中不一致,应提取公共方法。 真实案例 (MR !16073): 判断 isSqpRoute 的条件和其他地方不一致。


八、时区与日期处理

8.1 不要直接操作时区 offset

规则: 业务代码不要直接操作时区 offset(如 +8-5),因为同一个时区名在冬夏令时会对应不同的 offset。

正确做法:

  1. 先确定时区名:const tzName = 'America/Los_Angeles'
  2. 全程使用 moment/dayjs 的时区 API 操作
  3. 各平台的时区信息从各自的配置或店铺数据中获取

真实案例 (MR !16965): 根据时区 offset 反推时区名,然后手动操作时间字符串。应先确定时区名,再全程使用 moment API。

8.2 不要读取其他平台的全局变量

规则: 各平台的全局信息从各自的配置中获取。Instacart 不要读 store.getters.globalCurStore/timeZoneUTC(那是 Amazon 的)。

真实案例 (MR !16965): Instacart 代码读取了 Amazon 平台的全局时区变量。


九、性能与数据处理

9.1 表格增量操作

规则: 表格数据的增删操作,优先使用增量 API(table.insertAttable.remove),避免每次全量 loadData

真实案例 (MR !15845): 每次添加/删除关键词后都全量 loadData,应使用增量操作。

9.2 不要对同步操作使用 await

规则: 纯内存的同步操作不需要 await,它不会带来任何性能收益。

真实案例 (MR !15845): 对纯内存的同步操作使用了 await。

9.3 防御性编程要适度

规则: 不要对明显不可能为 null 的 API 做空值检查,过度保护会降低代码可读性。

真实案例 (MR !15845): 对 requestAnimationFrametable.remove 等做了不必要的空值检查。

9.4 watch 的 immediate 要谨慎

规则: 将 watch 的 immediate 设为 true 可能影响较大,造成时序问题或死循环。修改前要评估影响。

真实案例 (MR !15104): 建议将 watch 改为 immediate: true,但评估后发现可能造成时序问题。

9.5 竞态条件处理

规则: 异步请求需要处理竞态条件,确保旧请求的结果不会覆盖新请求的结果。

常见方案:

  • 使用版本计数器(fetchVersion++),请求完成后检查版本是否匹配
  • 使用 AbortController 取消旧请求
  • 使用 debounce 的 cancel 方法

真实案例 (MR !16317): TopTable 的 syncSelection 需要处理竞态条件,用计数器替代布尔标志。 真实案例 (MR !16079): 滚动加载时需要 cancel 掉 pending 的 debounce 请求,避免竞态。


十、依赖与包管理

10.1 版本号使用固定版本

规则: package.json 中的依赖版本号移除 ^ 前缀,使用固定版本号。

真实案例 (MR !13223): 依赖版本号带了 ^,被要求移除。

10.2 开发依赖放到 devDependencies

规则: 只在开发/构建时使用的依赖(如测试框架、lint 工具),放到 devDependencies 而非 dependencies

真实案例 (MR !5467): 开发依赖放在了 dependencies 中。

10.3 第三方库声明为 peerDependencies

规则: 宿主应用也会使用的第三方库(如 echarts),应声明为 peerDependencies 而非 dependencies,避免打包出双实例。

真实案例 (MR !17005): echarts 声明为 dependencies,可能导致双实例。


十一、AI 生成代码的审查

11.1 必须逐行审查 AI 生成的代码

规则: AI 辅助生成的代码必须逐行审查,特别注意:

  • 变量名是否存在(AI 可能"幻觉"出不存在的变量)
  • API 调用是否正确(参数名、返回值结构)
  • 是否引入了不必要的依赖或过度设计
  • 翻译 key 是否真实存在

真实案例 (MR !16079): 代码中出现了 c_profile_ids 变量,系统中不存在,是 AI 幻觉。

11.2 不要盲目信任 AI 的 review 意见

规则: AI(如 GitLab Duo)的 review 意见也可能不准确,需要人工判断。

真实案例 (MR !16395): GitLab Duo 提出的多条 review 意见被逐一分析后,发现部分不合理(如建议在 group 内 emit 单个 checkbox 事件、建议添加运行时类型防御等)。 真实案例 (MR !16169): GitLab Duo 的分析说反了,_tableNamecurrentTableName 在触发 watch 时是同一个值,永远相等。


十二、UI 组件库(ui-core / ui-biz)专项规范

以下规范适用于 packages/ui-corepackages/ui-biz 下的组件开发。

12.1 CSS Token 使用

规则:

  • 必须使用语义化 CSS 变量(如 --x-space-inline-base),禁止使用数字索引变量(如 --x-space-1
  • 尺寸值禁止硬编码 px 值,必须引用 design token
  • 颜色值必须引用语义化 color token
  • 新增的私有 CSS 变量使用 --_x-{component}-{property} 命名

真实案例 (MR !16710, !16652): 多次使用 --x-space-1 等数字索引变量。 真实案例 (MR !16884): width: 220pxmax-height: 256px 硬编码。

12.2 需求文档 frontmatter 规范

Props 默认值格式:

  • 布尔值/数字/undefined → 裸值:false, 0, undefined
  • 字符串默认值 → 单引号:'md', 'body'
  • 内联字面量联合类型 → 双引号包裹单引号:"'button' | 'a'"
  • 类型别名 → 裸标识符:XPlacement, XSize

其他要求:

  • tokens.consumed 列表必须与实际消费的 CSS token 完全匹配
  • 行为描述必须与实现和测试保持一致

真实案例 (MR !16658): frontmatter 中默认值格式不统一。 真实案例 (MR !16715): frontmatter tokens.consumed 缺少实际使用的 token。

12.3 类型定义

规则:

  • 尺寸类型优先复用全局 XSize
  • teleport prop 统一使用 XTeleportTo
  • 事件 payload 类型禁止使用 any

真实案例 (MR !16828): XSwitchSize 未复用全局 XSize真实案例 (MR !16894): chartInit 事件 payload 类型为 any

12.4 测试覆盖要求

不能遗漏的测试场景:

  • 边界值:负数、0、NaN、极大数字、undefined、空字符串
  • 键盘导航:Space、Enter、Home、End、Arrow 键
  • 可访问性:aria-expandedaria-hiddenrole 属性
  • focus-visible 样式
  • disabled/readonly 状态
  • 默认 size(不传 size prop 时的行为)

真实案例 (MR !16652): 缺少边界值测试。 真实案例 (MR !16675): 缺少 focus-visible 样式测试。

12.5 事件触发要精确

规则: 事件只在语义正确的时机触发,不要过度触发。

真实案例 (MR !16884): expandChange 不应在 hover 叶子节点时触发。 真实案例 (MR !16332): change 事件不应在值未实际变化时触发。

12.6 修复要完整

规则: 修复一个访问点时,必须检查同一函数/文件中是否有其他相同的访问点也需要保护。

真实案例 (MR !16326): 修复了 strategyList.value[0].split() 的空值问题,但同函数内其他 strategyList.value[0] 访问未做保护。

12.7 英文文案质量

规则: 组件的英文 locale 文案必须语法正确,不能是中式英语直译。

  • 'Search no results'
  • 'No search results'

真实案例 (MR !16658): 英文 locale 语法错误。

12.8 英文文案大小写规范

规则: 默认英文规则是仅首单词首字母大写,其他单词首字母小写(sentence case),除非是专有名词。

  • AI managed campaigns
  • AI Managed Campaigns

真实案例 (MR !15277): "AI managed campaigns" 被写成了全部首字母大写。 真实案例 (MR !15241): 英文文案大小写不符合规范。


十三、Vuex Store 规范

13.1 不要在全局 store 中做页面级判断

规则: 全局 store 的 mutation/action 中不要判断当前是哪个页面。页面级的特殊逻辑应在页面组件中处理。

真实案例 (MR !16073): 在全局 store 的 SET_DOW mutation 中判断是否是 SQP 页面。

13.2 Instacart 不要使用全局 store

规则: Instacart 的全局信息应从 OmniCenter 获取,不要使用 Amazon 平台的全局 store(如 store.getters.globalCurStore)。

真实案例 (MR !16925): Instacart 的全局 store 应该都没用了,应从 OmniCenter 读取。

13.3 全局权限方法

规则: 权限判断使用全局方法 $hasPermission,不要自己从 store 中读取权限列表做判断。

真实案例 (MR !12008): 自己从 store 读取权限做判断,应使用 $hasPermission

13.4 watch 初始化逻辑要加守卫

规则: 如果 watch 只是为了初始化(如等待某个值从默认值变为有效值),应该加条件守卫(如 oldVal === -2),并且执行后不再 watch。

真实案例 (MR !7397): watch 中的初始化逻辑需要检测 oldVal === -2 才执行,并且执行后不再 watch。

13.5 登出时清理状态

规则: 新增用户维度的状态时,必须在登出逻辑中添加清理。

需要清理的位置:

  • store/modules/user.js 的 logout action
  • 状态所在模块的 reset 函数

真实案例 (MR !16892): _authChecked 是用户维度的状态,退出登录/切换用户时需要重置。


十四、API 与数据处理

14.1 不要在 API 层做 UI 相关的处理

规则: API 层只负责数据的请求和基本转换,不要在 API 层做翻译、格式化等 UI 相关的处理。

真实案例 (MR !16079): 在请求数据的方法中调用 t() 做翻译。

14.2 后端参数类型要匹配

规则: 传给后端的参数类型要和后端期望的一致。如果后端期望 Number,前端要确保传 Number 而非 String。

真实案例 (MR !16925): 后端不支持 string 格式的 id,需要转换为 Number。

14.3 环境变量要完整配置

规则: 所有环境(local、test、qa、pre、prd)的环境变量都要配置完整,不要遗漏某个环境。

为什么重要: 遗漏环境变量会导致该环境下功能异常,且可能在上线前才发现。

真实案例 (MR !16925): PRD 环境缺少 VUE_APP_INSTACART_DEVELOPER_URL 配置。应该把真实环境的 URL 也配置为环境变量,保持使用方式统一。

14.4 API 目录结构要和模块结构一致

规则: API 文件应该放在对应模块的目录下,不要散落在全局 API 目录中。

真实案例 (MR !16925): src/api/instacart/autoRule 目录应移动到 src/omni/instacart/api/autoRule

14.5 条件组的 cacheKey 设计

规则: 筛选器的 cacheKey 应该能唯一标识每个筛选器实例。如果后端接口按 tab 存储条件组,前端的 cacheKey 也应该按 tab 区分,保持粒度一致。

真实案例 (MR !16073): cacheKey 统一都是 sqp,但条件组接口每个 tab 存一份,导致本地缓存和后端接口存取数据的粒度不一致。


十五、安全相关

15.1 禁止提交敏感信息

规则: 不要在代码中硬编码或提交以下内容:

  • 用户密码、token
  • 测试用的登录凭证
  • 内部 API 密钥

真实案例 (MR !16925): 在 store/modules/user.js 中硬编码了测试用的登录信息,被要求立即删除。评论原文:"删掉,这么做太危险了"。


提交前检查清单

每次提交代码前,逐项自查:

代码清洁

  • [ ] 是否删除了所有 console.logdebuggeralert
  • [ ] 是否删除了注释掉的代码和无用文件?
  • [ ] 提交内容是否只包含当前需求相关的改动?
  • [ ] 删除文件后是否清理了所有引用(包括动态引用)?

国际化

  • [ ] 是否使用了 useI18n() / $t() 而非全局 import i18n?
  • [ ] 模板中是否统一使用 $t() 而非 t()
  • [ ] 是否有硬编码的中文文案?
  • [ ] 新增翻译前是否搜索了已有的 key?
  • [ ] en/zh/ja 三语言文件是否同步更新?
  • [ ] 翻译是否放在了 computed 或模板中(而非请求回调中)?

Vue 规范

  • [ ] Props 访问是否使用了正确的方式(非 $props.xxx)?
  • [ ] 是否从 vue 而非 @vue/composition-api 导入?
  • [ ] 复杂的模板逻辑是否提取为 computed?
  • [ ] 模板属性顺序是否正确(先 :prop@event)?

JavaScript

  • [ ] 是否优先使用 const
  • [ ] 是否使用 === 而非 ==
  • [ ] 变量命名是否符合小写驼峰规范?
  • [ ] 是否使用了项目统一的工具函数(formatNumber、waitUntil 等)?

CSS

  • [ ] 是否使用了项目 CSS 变量而非硬编码色值?
  • [ ] 间距是否是 4 的倍数?
  • [ ] CSS class 是否有组件前缀?
  • [ ] 是否避免了 !important

组件设计

  • [ ] 组件文件位置是否和使用范围匹配?
  • [ ] 是否有 Breaking Changes?默认行为是否被改变?
  • [ ] 是否有重复逻辑需要封装?
  • [ ] 单文件改动是否超过 300-400 行?

架构

  • [ ] 是否存在硬编码?新增同类实体时是否需要改这个文件?
  • [ ] 平台特有逻辑是否放在了平台目录内?
  • [ ] 是否使用了枚举值而非硬编码字符串?
  • [ ] 缓存 key 是否包含 userId?

AI 代码

  • [ ] AI 生成的代码是否逐行审查过?
  • [ ] 变量名是否真实存在?API 调用是否正确?

安全

  • [ ] 是否有硬编码的密码、token 等敏感信息?