Code Review 检查清单
来源: 从 15 位团队成员在 804 条 MR 讨论中的评审意见中提炼而来。 用途: 每次提交代码前,AI 必须基于本文档对代码进行审查,确保已出现过的问题不再重犯。 适用范围: 所有代码修改,包括 AI 辅助生成的代码。
一、代码清洁与提交规范
1.1 禁止提交调试代码
规则: 提交前必须删除所有调试语句。
必须删除的内容:
console.log/console.warn/console.error(除非是有意的错误日志)debugger语句alert()调用- 注释掉的代码块(如
// const oldLogic = ...) - 空的
.codex等无用文件
检查方法: 提交前全局搜索 console.log、debugger、alert(,确认零匹配。
为什么重要: 调试代码会污染生产日志,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.$message 或 useMessage(),禁止使用浏览器原生 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()
为什么:
useI18n()是 Vue 3 + vue-i18n 的标准用法,方便后续升级- 只有
useI18n()才能访问组件局部的<i18n>翻译文件 - 模板中使用
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.instacart和Instacart.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 而非 ref 或 reactive。
为什么重要: 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
规则: 如果模板中的 :disabled、v-if、v-show 表达式包含多个条件组合,必须提取为 computed 属性。
为什么重要:
- 提高模板可读性
- 便于调试(可以在 devtools 中查看 computed 的值)
- 避免模板中出现难以理解的长表达式
真实案例 (MR !10956): disabled 的逻辑过于复杂,直接写在模板中难以理解,应改为 computed。 真实案例 (MR !12008): 多处模板中写了过于复杂的表达式,被要求全部改为 computed。
3.5 Options API 中不要引入 useI18n
规则: Options API 写法的组件中,直接使用 this.$t(),不需要引入 useI18n。useI18n 是给 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-case 比 if-else 更简洁清晰。
真实案例 (MR !12008): 长 if-else 链被建议改为 switch-case 写法。
4.6 使用 lodash 的 isEqual 进行深度比较
规则: 对象/数组的深度比较使用 _.isEqual(),不要自己手写比较逻辑。
真实案例 (MR !12008): 自己写了对象比较逻辑,应使用
_.isEqual()。
4.7 数字格式化统一使用 formatNumber
规则: 所有数字格式化统一使用 @xmars/ui 的 formatNumber 方法,不要自己写格式化逻辑,也不要使用 Vue filters。
关于空值处理:
0就是0,不要格式化为'--'。0 和 null 的含义不同:0 表示"值为零",null 表示"没有数据"- 空值(null/undefined)格式化为
'--'的逻辑统一交给formatNumber处理 - 数据的初始值不要用
'--'这种字符串,应该用null或0
真实案例 (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 中约定为框架内部属性) - 不要用
data、item、index2等语义不清的变量名 - 不同平台的 ID 字段要区分命名(如
walmartTenantIdvstenantId)
真实案例 (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-select、xp-dialog),有则直接使用。
真实案例 (MR !16455): 为 el-select 写了自定义样式,但项目已有
xp-multiple-selectclass。 真实案例 (MR !4622): el-dialog 应加上xp-dialogclass 以应用统一样式。
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-rightslot。
6.5 组件暴露方法而非链式 $refs
规则: 组件内部数据的获取,应在组件内部封装方法暴露出来,外部直接调用。不要通过链式 $refs 调用内部实现。
- ❌
this.$refs.table?.getTableData()?.tableData || [] - ✅ 在 XpGrid 内部封装
getTableData()方法暴露
为什么重要: 链式 $refs 调用非常脆弱,如果组件内部改动,外部很难发现受影响的地方。
真实案例 (MR !15493): 通过连续的 $refs 调用获取表格数据,被建议封装为组件方法。
6.6 新增全局组件前先确认是否已有类似组件
规则: 新增全局组件前,先确认是否已有功能相似的组件。区分"需要完整功能"和"只需要样式"的场景。
真实案例 (MR !16179): 新增了全局的
XpLabelSelect组件,但项目中已有类似的SimpleFilterItem组件可以满足需求。
6.7 大量常量/配置放到单独文件
规则: 如果组件中有大量的常量定义、配置对象、工具方法,应该拆分到单独的文件中(如 utils.js、config.js、constants.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_DOWmutation 中判断是否是 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 带上
platformquery 参数 - 跨平台页面不要依赖特定平台的 URL 参数
真实案例 (MR !16781): 从 Instacart 跳转到任务中心时,走了 Amazon 逻辑导致 tenantId 为 -2。
7.10 判断条件要统一提取
规则: 如果多处需要同一个判断条件(如"是否是 SQP 页面"),必须提取为公共方法,禁止在不同文件中各自硬编码。
真实案例 (MR !16079): 判断 SQP 页面的条件在多个文件中不一致,应提取公共方法。 真实案例 (MR !16073): 判断 isSqpRoute 的条件和其他地方不一致。
八、时区与日期处理
8.1 不要直接操作时区 offset
规则: 业务代码不要直接操作时区 offset(如 +8、-5),因为同一个时区名在冬夏令时会对应不同的 offset。
正确做法:
- 先确定时区名:
const tzName = 'America/Los_Angeles' - 全程使用 moment/dayjs 的时区 API 操作
- 各平台的时区信息从各自的配置或店铺数据中获取
真实案例 (MR !16965): 根据时区 offset 反推时区名,然后手动操作时间字符串。应先确定时区名,再全程使用 moment API。
8.2 不要读取其他平台的全局变量
规则: 各平台的全局信息从各自的配置中获取。Instacart 不要读 store.getters.globalCurStore/timeZoneUTC(那是 Amazon 的)。
真实案例 (MR !16965): Instacart 代码读取了 Amazon 平台的全局时区变量。
九、性能与数据处理
9.1 表格增量操作
规则: 表格数据的增删操作,优先使用增量 API(table.insertAt、table.remove),避免每次全量 loadData。
真实案例 (MR !15845): 每次添加/删除关键词后都全量 loadData,应使用增量操作。
9.2 不要对同步操作使用 await
规则: 纯内存的同步操作不需要 await,它不会带来任何性能收益。
真实案例 (MR !15845): 对纯内存的同步操作使用了 await。
9.3 防御性编程要适度
规则: 不要对明显不可能为 null 的 API 做空值检查,过度保护会降低代码可读性。
真实案例 (MR !15845): 对
requestAnimationFrame、table.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 的分析说反了,
_tableName和currentTableName在触发 watch 时是同一个值,永远相等。
十二、UI 组件库(ui-core / ui-biz)专项规范
以下规范适用于
packages/ui-core和packages/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: 220px、max-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 teleportprop 统一使用XTeleportTo- 事件 payload 类型禁止使用
any
真实案例 (MR !16828):
XSwitchSize未复用全局XSize。 真实案例 (MR !16894):chartInit事件 payload 类型为any。
12.4 测试覆盖要求
不能遗漏的测试场景:
- 边界值:负数、0、NaN、极大数字、undefined、空字符串
- 键盘导航:Space、Enter、Home、End、Arrow 键
- 可访问性:
aria-expanded、aria-hidden、role属性 - 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_DOWmutation 中判断是否是 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.log、debugger、alert? - [ ] 是否删除了注释掉的代码和无用文件?
- [ ] 提交内容是否只包含当前需求相关的改动?
- [ ] 删除文件后是否清理了所有引用(包括动态引用)?
国际化
- [ ] 是否使用了
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 等敏感信息?