coding-standards
公司前端通用编码规范(从 11 个项目 + 飞书知识库抽取)。当用户开始编写/修改前端 TS/TSX/Vue/SCSS 代码,或者需要 code review / 检查规范一致性时,**必须**加载本 skill 作为硬性规则基础。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
公司前端通用编码规范(从 11 个项目 + 飞书知识库抽取)。当用户开始编写/修改前端 TS/TSX/Vue/SCSS 代码,或者需要 code review / 检查规范一致性时,**必须**加载本 skill 作为硬性规则基础。
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
在任意空目录中从零创建数字人智能体应用。自动识别场景(业务流/纯对话/能力展示),从内置模板复制项目文件,引导用户改配置、本地调通。Use when 用户说"新建数字人应用""开发智能体""做 XX 场景的 Demo""搭一个数字人""创建一个数字人项目"。
接到"加 Biz 类型 / 加新消息类型 / 加新消息样式 / 发某某卡片 / 想发个表单让用户填 / 消息发出去对端没收到"等需求时触发。引导决策:基础类型 vs 已有 BIZ vs 新增 BIZ;新增 BIZ 必须走后端专属接口 + 前后端共同约定 4 步流程;前端职责仅"调发送接口 + 写展示卡片"。
合并 master 前检查 swagger-repo.json 是否残留 feat 分支,半自动逐条切回并重跑 gen:api。**触发场景**:准备合并到 master 之前(由 git-ship skill 自动调用)/ 修改过 swagger-repo.json 的 branch 字段 / 拉到陌生分支后想确认 API 类型来源 / 用户输入 /check-api-branch。
多模型并行 Code Review。**自动触发场景**:用户拍板 plan 后改完代码(中等 / 复杂任务),AI 主动询问「需要 code review 吗?」**手动触发**:用户输入 `/code-review` 或说「review 一下这次改动 / 帮我看看这次提交」。按改动复杂度分级派 SubAgent,**轻量改动只派 1 个 sonnet 兜底,复杂任务才派 3 个含 opus 架构师**——不浪费最强模型在简单任务上。
启动开发环境 + 调试。触发场景:"看一下效果 / 跑起来看看 / 启动一下 / 打开看看 / 启动 dev / 跑 dev"。本 skill 读 profile.devServer 配置 → 校验前置 → 后台 spawn dev server → 等 ready → 拼带 token 的真实域名 URL → open 浏览器。同时含项目特定的"关键调试场景"片段(按 profile 加载)。
检查本地开发环境(node / pnpm / lark-cli / figma-to-code / Figma MCP / npm registry / glab / 代理 等 15 项)是否就绪。新会话开始时由主 Agent 调用一次;只检测 + 给安装命令,不自动跑。如有 ERR 级缺失 -> 告知用户并停止后续动作;只有 WARN/INFO -> 简短提示后继续。glab 缺失 → 用 `glab-setup` skill 引导。
| name | coding-standards |
| description | 公司前端通用编码规范(从 11 个项目 + 飞书知识库抽取)。当用户开始编写/修改前端 TS/TSX/Vue/SCSS 代码,或者需要 code review / 检查规范一致性时,**必须**加载本 skill 作为硬性规则基础。 |
适用范围:所有前端 TS/TSX/Vue/SCSS 代码。技术栈覆盖 React(assist-web、om-react、resonance、freedom)、Vue 3(cherry、cherry-pc、chat-assist-mobile、dangoui、effuse)、uni-app 小程序(cactus)、纯 TS SDK(eros、effuse/api)。
.eslintrc/.prettierrc 更严则从严,不得低于本基线。组件文件统一 PascalCase。 工具/Hook/Store/Service 文件统一 camelCase。
MessageList.vue ✅ Vue 组件
Button.vue ✅ Vue 组件(dangoui)
MessageList/index.tsx ✅ React 组件
useConsignmentList.ts ✅ Hook
conversation.ts ✅ Store slice / 工具
request.ts ✅ Service
message-list.vue ❌ 禁止 kebab-case 组件文件(cherry-pc 的文件名 kebab-case 是 unicorn ESLint 规则的项目专属约定,不通用)
messageList.vue ❌ 禁止 camelCase 组件文件
自动生成文件标记为 *.gen.ts,禁止手改:sdk.gen.ts、types.gen.ts(om-react)。
SCSS Module 文件用 kebab-case:item-common.module.scss(resonance)。
验证项目:assist-web、chat-assist-mobile、cherry、resonance、dangoui(5 个+)
<!-- ✅ 必须 PascalCase -->
<MessageList />
<DuIcon name="message" />
<TopBar :is-fixed="true" />
<!-- ❌ 禁止 kebab-case(ESLint vue/component-name-in-template-casing: error) -->
<message-list />
<du-icon name="message" />
验证项目:cherry(ESLint error 级别)、chat-assist-mobile、dangoui(3 个+)
// ✅ Hook/Composable:use 前缀 + camelCase
export function useConsignmentList() { ... }
export function useTaskPolling() { ... }
// ✅ Store 导出 hook:use + PascalCase + Store
export const useConversationStore = createStore<ConversationStore>(...)
export const useMessageStore = defineStore('message', () => { ... })
// ✅ Store setter 方法:set 前缀
function setMessageList(list: MessageType[]) { ... }
function setCurrentConversation(conv: ConversationData) { ... }
// ✅ React 事件处理:handle 前缀(resonance 约定,通用)
function handleCreateConversationSuccess(isSuccess: boolean) { ... }
async function handleOpenConversation(toUserId: string) { ... }
// ✅ 常量:SCREAMING_SNAKE_CASE
export const CLIENT_PACKAGE_ID = 1070
export const CONSIGNMENT_STATUS_MAP = { SENDING: '在途中' }
// ❌ 禁止 Store 无 use 前缀
export const conversationStore = createStore(...)
// ❌ 禁止 Store 无 Store 后缀
export const useConversation = defineStore(...)
// ❌ 禁止事件处理函数用 on 前缀(resonance 明确禁止)
function onSuccess() { ... }
验证项目:assist-web、chat-assist-mobile、cherry、resonance(4 个+)
// ✅ interface 和 type:PascalCase,不加 I 前缀(通用约定)
interface ConversationStore { ... }
interface PublishFormData { ... }
type ErosConfig = { ... }
// ✅ 枚举:名 PascalCase,键 SCREAMING_SNAKE_CASE
enum WareHouse { UNKOWN = 'UNKOWN', COMPANY = 'COMPANY' }
enum TradeOrderType { C2C = 'C2C', BLIND_BOX_MACHINE = 'BLIND_BOX_MACHINE' }
// 例外:effuse SDK 使用 I 前缀接口(IVoidParams、ISuccessReturn),仅限该 SDK 内部
验证项目:assist-web、cherry-pc、freedom、eros(4 个+)
du- 前缀,du-{block}__{element}--{modifier}left-menu、home-page)duButton、orderList所有项目都遵循以下分层(命名允许细微差异):
src/
__generated__/ # swagger/openapi 自动生成,禁止手动修改
apis/ 或 api/ # 请求封装
components/ # 跨页面共享组件(PascalCase 子目录)
composables/ 或 hooks/ # 自定义 Hook(use 前缀)
constants/ # 全局常量(按业务域分文件)
pages/ 或 views/ # 页面
store/ # 状态管理
types/ # 全局 TypeScript 类型
utils/ # 纯函数工具
验证项目:assist-web、cherry、chat-assist-mobile、resonance、freedom、om-react(6 个+)
pages/
chat/
chat.vue # 页面入口
components/ # ✅ 私有组件就近放,不提升到 src/components
Actions.vue
Sticker.vue
// ✅ 页面内引用私有组件
import Actions from './components/Actions.vue'
// ❌ 禁止把只用于一个页面的组件放到全局 src/components/
验证项目:chat-assist-mobile、cherry、freedom、om-react(4 个+)
// ❌ 禁止手动修改 __generated__/ 或 *.gen.ts 下的任何文件
// 重新生成:pnpm gen:api(或 sapi gen)
// ✅ 在 apis/x/ 或 apis/hooks/ 中做二次封装后给业务调用
import { kyanWeb } from "@/apis/x/kyan"
验证项目:assist-web、cherry、chat-assist-mobile、resonance、freedom、om-react(6 个+)
// ❌ 禁止:新代码函数参数/返回值使用 any
function setCurrentConversation(conversation: any) { ... }
// ✅ 必须:使用生成的具体类型
import { KyanConversationData } from '@/__generated__/apis/kyan'
function setCurrentConversation(conversation: KyanConversationData) { ... }
// ✅ 允许:接口数据层真正不确定的数据结构
Record<string, any> // 仅用于无法类型化的外部数据
@typescript-eslint/no-explicit-any 在大多数项目为 warn(不是 error),但新代码必须消除 any 警告。
// ✅ Vue 项目:泛型 defineProps + withDefaults
const props = withDefaults(defineProps<{
type: 'primary' | 'secondary'
disabled: boolean
}>(), {
type: 'primary',
disabled: false,
})
// ✅ React 项目:interface 或 type 定义 Props
interface ItemTextProps {
message: Record<string, any>
isMine: boolean
}
export default function ItemText({ message, isMine }: ItemTextProps) { ... }
// ❌ 禁止:运行时对象声明 Vue props(dangoui 明确禁止)
defineProps({ type: { type: String, default: 'primary' } })
// ❌ 禁止:导入非 .js 文件时省略后缀名
import './HelloWorld'
// @import "./a"
// ✅ 必须:带完整后缀名
import './HelloWorld.vue'
// @import "./a.scss"
飞书文档原文规则(uni-app 开发规范)。验证项目:cactus、飞书知识库(2 个+)
<!-- ✅ 必须:新组件统一 script setup lang="ts" -->
<template>
<div>...</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const count = ref(0)
</script>
<style scoped>
/* 必须加 scoped */
</style>
<!-- ❌ 禁止:Options API(新代码) -->
<script>
export default {
data() { return { count: 0 } },
methods: { ... }
}
</script>
SFC 块顺序固定:template -> script -> style,块之间必须有空行(vue/padding-line-between-blocks: error)。
验证项目:cherry、chat-assist-mobile、cherry-pc(3 个+);cactus 例外(Options API 为主,历史遗留)
<!-- ✅ 必须加 scoped -->
<style scoped lang="scss">
.title { font-size: 16px; }
</style>
<!-- ✅ 覆盖第三方组件库时用 :deep() -->
<style lang="scss" scoped>
.my-wrapper :deep(.n-menu-item--selected) {
background-color: #e8e5f2 !important;
}
</style>
<!-- ❌ 禁止在组件内写非 scoped 的业务样式 -->
<style>
.title { font-size: 16px; } /* 全局污染 */
</style>
验证项目:cherry、chat-assist-mobile、cherry-pc(3 个+)
// ✅ 必须同时使用 forwardRef + useImperativeHandle
export type DaolinkEditFormRef = {
validateFields: () => Promise<any>
getFieldsValue: () => any
}
const DaolinkEditForm = forwardRef<DaolinkEditFormRef, DaolinkEditFormProps>(
({ data }, ref) => {
const [form] = Form.useForm()
useImperativeHandle(ref, () => ({
validateFields: () => form.validateFields(),
getFieldsValue: () => form.getFieldsValue(),
}), [form])
return <Form form={form}>...</Form>
}
)
// ✅ forwardRef 组件必须设置 displayName(resonance)
const UBT = forwardRef<HTMLElement, UBTProps>((props, ref) => { ... })
UBT.displayName = "UBT"
// ❌ 禁止 forwardRef 不配 useImperativeHandle(暴露业务方法时)
const MyComp = forwardRef((props, ref) => {
return <div ref={ref}>...</div> // 不允许:没有 useImperativeHandle
})
验证项目:om-react、resonance(2 个+)
<!-- ✅ 必须:同时提供 value prop 和 input/change 事件 -->
<SomeInput :value="someValue" @input="handleInput" @change="handleChange" />
<!-- ❌ 禁止:没有 value prop,或 value 不受外部控制(非受控组件) -->
<SomeInput @change="handleChange" />
通用组件内部状态不能脱离父组件控制;外部修改 value 必须同步反映到组件内部。 飞书文档原文规则(原文为建议性表述,按公司基线视为硬性要求)。验证项目:飞书知识库、dangoui(2 个+)
// ✅ 必须:异步操作期间禁用按钮/显示加载,结束后恢复
async function handleBuy() {
try {
isLoading.value = true
await doAsyncLogic()
} catch (err) {
showToast({ title: err.message })
} finally {
isLoading.value = false
}
}
// ❌ 禁止:异步操作无加载态,允许用户反复点击
async function handleBuy() {
await doAsyncLogic()
}
飞书知识库原文规则:对用户操作做防抖/限流/禁用。验证项目:cactus(showLoadingToast + closeToast)、chat-assist-mobile(showLoadingToast 必须配对)、飞书文档。
涉及用户 toggle 操作(点赞/收藏等)且使用乐观更新时,必须用竞态 ID 防止多次操作互相覆盖:
const like = ref(false)
let toggleLikeId: number | null = null
const toggleLike = async () => {
const prevVal = like.value
const currId = genId() // 应用周期内唯一 ID
toggleLikeId = currId
// 乐观更新:立即反映到 UI
like.value = !prevVal
try {
const likeState = await requestToggleLike()
if (toggleLikeId !== currId) return // 已被更新的操作覆盖,丢弃
like.value = likeState
} catch (err) {
if (toggleLikeId !== currId) return // 竞态:丢弃
showToast(err.message)
like.value = prevVal // 恢复原值
}
}
飞书文档原文规则。验证项目:飞书知识库、cactus(2 个+)
<!-- ❌ 禁止:模板里调用 filter/map 或内联对象字面量 -->
<Foo :infos="items.filter(item => item.valid)" />
<Foo :infos="{ a: item.a, b: item.b }" />
<!-- ✅ 必须:提取为 computed -->
<Foo :infos="infos" />
const infos = computed(() => items.value.filter(item => item.valid))
每次渲染生成新对象会破坏 Vue 的依赖追踪,并触发子组件不必要的重渲染。 飞书文档原文规则。验证项目:cactus、飞书知识库(2 个+)
<!-- ❌ 禁止:具名 slot 名称带 - -->
<template #my-slot></template>
<!-- ✅ 必须:camelCase 或无连字符 -->
<template #mySlot></template>
<!-- ❌ 禁止:在子组件中修改 props -->
<!-- ❌ 禁止:computed 中修改外部状态(computed 必须是纯函数) -->
飞书文档原文规则(uni-app 规范)。验证项目:cactus、飞书知识库(2 个+)
页面需要区分登录/未登录时:
// ✅ 必须:用 watch 响应登录状态变化,分别调用不同取数逻辑
watch(
() => me.id,
(val) => {
if (val) {
fetchDataNeedAuth()
} else {
fetchData()
}
},
{ immediate: true }
)
// ✅ 纯登录页面:用 gotoLoginIfNot 工具函数
import { gotoLoginIfNot } from '@/modules/hooks/use-auth-page'
onLoad(async () => { await gotoLoginIfNot() })
// ❌ 禁止:在需要登录的接口请求中直接中断整条取数流
// (导致未登录用户看到数据不完整/空白页面)
飞书文档原文规则。验证项目:cactus、飞书知识库(2 个+)
详细规范和代码模板见
references/state-management.md
// ✅ 必须:Setup Store 函数式写法
export const useMessageStore = defineStore('message', () => {
const messageList = ref<MessageType[]>([])
function setMessageList(list: MessageType[]) {
messageList.value = list
}
return { messageList, setMessageList }
})
// ❌ 禁止:Options Store
export const useMessageStore = defineStore('message', {
state: () => ({ messageList: [] }),
actions: { ... }
})
验证项目:cherry、cherry-pc、chat-assist-mobile(3 个+)
// ✅ 必须:通过统一 createStore 工厂创建,自动注入 devtools
export const useConversationStore = createStore(immer(conversationStore))
// ❌ 禁止:直接调用 create() 绕过工厂
import { create } from 'zustand'
export const useConversationStore = create<ConversationStore>()((set) => ({ ... }))
// ✅ 组件内按需 selector 订阅,不整个 store 订阅
const currentConversation = useConversationStore((state) => state.currentConversation)
// ❌ 禁止整个 store 订阅
const store = useConversationStore()
验证项目:assist-web、resonance(2 个+)
SET_VIP_INFOfetchVipInfo// ✅ 必须:新接口通过 pnpm gen:api(或 sapi gen)从 Swagger/OpenAPI 生成
// 生成产物放 __generated__/apis/ 或 apis/client/
// ✅ 在 apis/x/ 或 apis/hooks/ 中二次封装后供业务使用
// cherry/chat-assist-mobile 风格:
export const kyanWeb = new Api(getCommonParams({ prefix: '' }))
// om-react 风格(SWR):
export const useDaolinkUrlDetail = (id?: string) => {
const swr = useSWR(key, async () => {
const res = await getAdminUrlGetDetail({ query: { id: id! } })
return res.data?.data
})
return { swr }
}
// ❌ 禁止:手动在 __generated__/ 或 *.gen.ts 中编写代码
// ❌ 禁止:新功能手写接口调用而不走生成层
验证项目:assist-web、cherry、chat-assist-mobile、resonance、freedom、om-react、cactus(7 个+)
// ✅ 必须:通过封装的请求实例/函数
import { apiFetch } from '@/apis'
import { kyanWeb } from '@/apis/x/kyan'
// ❌ 禁止:裸 fetch 或直接 import axios
const res = await fetch('/api/xxx')
import axios from 'axios' // 应 import 封装好的实例
验证项目:assist-web、chat-assist-mobile、resonance(3 个+)
// ✅ interceptor 统一注入 Authorization、x-request-shop-id、x-echoing-env 等
instance.interceptors.request.use(async (config) => {
config.headers["Authorization"] = `Bearer ${token}`
config.headers["x-request-package-id"] = packageId
config.headers["x-echoing-env"] = envVersion
config.headers["x-request-shop-id"] = shopId
return config
})
// ❌ 禁止:业务层手动在每个请求中添加认证头
axios.get('/api/foo', { headers: { Authorization: `Bearer ${token}` } })
通用 HTTP 头部(来自飞书文档):
Authorization: Bearer {accessToken} - 鉴权x-request-package-id - package IDx-request-shop-id - 店铺 ID(按店铺操作时)x-echoing-env - 后端测试环境(默认 test-z)x-request-sign、x-request-sign-type、x-request-sign-version、x-request-timestamp(由 @frontend/pigeon 注入)验证项目:assist-web、resonance、freedom、cherry-pc + 飞书文档(5 个+)
// ✅ interceptor 层统一处理 401,清除 token 并跳转登录
if (statusCode === 401) {
window.location.href = `/login?redirect_uri=` + encodeURIComponent(window.location.href)
}
// ❌ 禁止:业务代码自己处理 401
try {
await api.xxx()
} catch (err) {
if (err.status === 401) router.push('/login') // 不允许
}
验证项目:assist-web、resonance、freedom(3 个+)
// ✅ 必须:interceptor 层捕获 401,自动用 refreshToken 换新 accessToken,
// 换取成功后自动重放原请求,上层业务不感知
instance.interceptors.response.use(null, async (error) => {
if (error.response?.status === 401 && !error.config._retried) {
error.config._retried = true
const newToken = await refreshAccessToken()
error.config.headers['Authorization'] = `Bearer ${newToken}`
return instance(error.config) // 重放
}
return Promise.reject(error)
})
// ❌ 禁止:业务层自己检测 token 过期并重新调用
飞书文档原文规则。验证项目:飞书知识库(如何创建新项目)(1 个+)
// ✅ swagger-typescript-api 自定义模板的约定:
// 后端返回的 code !== 0 视为错误,interceptor 层直接 throw
instance.interceptors.response.use((response) => {
if (response.data?.code !== 0) {
throw new Error(response.data?.msg || '请求失败')
}
return response
})
// ✅ 业务层只处理成功路径,catch 只做流程控制
const data = await someApi() // code !== 0 已 throw,业务层不用判断
// ❌ 禁止:业务层重复判断 code
const res = await someApi()
if (res.code !== 0) { ... } // 不允许
飞书文档原文规则(swagger-typescript-api 自定义模板约定)。验证项目:cherry、cherry-pc、飞书知识库(3 个+)
// ✅ 必须:上传时指定 scene 值(基于 bucket 的抽象)
await uploadFile(file, { scene: 'product-image' })
// ✅ 必须:OSS 资源 URL 在业务代码/DB 中以 EchoOSSUrl 形式存储,禁止存完整 HTTP URL
// echotechoss://{scene}/{filename}.{extName}
// 正例:'echotechoss://admin-common.dev/abc123.png'
// ❌ 禁止:存储完整 HTTP URL(换域名后需要刷库)
// 'https://cdn.qiandaoapp.com/abc123.png'
// ✅ 必须:上传敏感图片(身份证/营业执照等)必须指定专用 bucket
// 千岛体系使用 encryted-images bucket
await uploadFile(sensitiveFile, { scene: 'encryted-images' })
// ❌ 禁止:敏感图片与普通图片混用同一 bucket/scene
飞书文档原文规则。验证项目:飞书知识库(NULlwfCCziGtApkgWhDcGp0Inbb、UyISwA16QinJp4kLaUAcfOtynpc)(2 个+)
// ✅ 必须:优先 UnoCSS 原子类
<div className="h-full flex items-center text-b5 otext">
<div class="flex justify-between px-11 py-6 items-center">
// ❌ 禁止:内联 style 写布局/间距(动态值除外)
<div style={{ height: '100%', display: 'flex', alignItems: 'center' }}>
验证项目:assist-web、cherry、chat-assist-mobile、resonance、om-react(5 个+)
// ✅ 必须:用设计规范 shortcut
<div class="text-h5">标题</div> // 16px fw-500
<div class="text-b6 otext">说明</div> // 12px fw-400,单行溢出省略
<span class="text-n4">1234</span> // 数字体(Roboto)
// ❌ 禁止:手写字号+行高+字重组合
<div style="font-size: 16px; font-weight: 500; line-height: 24px;">标题</div>
<div class="text-14 fw-500 lh-22">标题</div>
shortcut 体系(across cherry / chat-assist-mobile / resonance / assist-web):
| 类名 | 含义 |
|---|---|
text-h1 ~ text-h8 | 标题,fw-500,字号 24px -> 10px |
text-b1 ~ text-b8 | 正文,fw-400,字号 24px -> 10px |
text-n1 ~ text-n8 | 数字体(Roboto),fw-500 |
otext | 单行溢出省略(text-ellipsis overflow-hidden whitespace-nowrap) |
数值单位:remBase: 1(大多数项目),即 text-14 = 14px,不是 rem 换算。
验证项目:assist-web、cherry、chat-assist-mobile、resonance(4 个+)
/* ✅ 必须:用 CSS 变量 */
.button--primary {
color: var(--du-bt-solid-color);
background: var(--du-bt-solid-bg);
}
/* ✅ React:用 Ant Design token */
const { token } = useToken()
<div style={{ borderColor: token.colorBorder }}>
/* ❌ 禁止:硬编码颜色值 */
.button--primary { color: #ffffff; background: #1677ff; }
<div style={{ borderColor: '#e8e8e8' }}>
验证项目:dangoui、resonance(2 个+)
/* ❌ 禁止:小程序项目使用 CSS 标签选择器(uni-app v3 不再自动转换) */
img { width: 100%; }
p { margin: 0; }
/* ✅ 必须:改用类选择器 */
.product-image { width: 100%; }
.paragraph { margin: 0; }
/* ❌ 禁止:小程序项目使用通配符选择器(在小程序中不会生效) */
* { box-sizing: border-box; }
飞书文档原文规则(uni-app 开发规范)。验证项目:cactus、飞书知识库(2 个+)
/* ❌ 禁止:在同一移动端项目中混用 rpx 和 px */
.container {
margin: 16rpx;
padding: 10px; /* 混用!*/
}
/* ✅ 必须:小程序项目统一用 rpx;cherry-pc 的 mobile-only/ 目录统一用 rpx */
.container {
margin: 16rpx;
padding: 20rpx;
}
飞书文档原文规则(cherry-pc 目录规范)。验证项目:cactus、cherry-pc(2 个+)
rpx 是唯一长度单位,stylelint 强制/* ✅ 小程序 rpx */
margin-left: 16rpx;
line-height: 64rpx;
/* ❌ 小程序禁止 px/rem(stylelint 报错) */
margin-left: 16px;
验证项目:cactus、chat-assist-mobile(2 个+)
顺序:Positioning -> Display -> Box Model -> Typography -> Visual -> Animation(cactus stylelint 强制)。
详细规范见
references/i18n.md
// ❌ 禁止:新代码硬编码面向用户的中文文案
message.error('加载失败,请稍后重试')
showToast({ title: '发生错误' })
button.text = '提交'
// ✅ 必须:所有面向用户的文案走 i18n key
message.error(t('Toast.LoadFailed'))
showToast({ title: t('Toast.Error') })
适用于:cherry-pc(多语言项目),以及任何将来需要多语言的项目从一开始就避免技术债。
<!-- template:直接用 $t -->
<p>{{ $t('09_Product.CurrencyPageSubtitle') }}</p>
<script setup>
// script:必须通过 useI18n() 解构
const { t } = useI18n()
const text = computed(() => t('10_Order.ContactBuyer'))
</script>
// ✅ 必须用 () => t(...) 保证切换语言后更新
label: () => h('span', {}, t('06_Me.MyListing'))
// ❌ 静态字符串,切换语言不更新
label: t('06_Me.MyListing')
// ✅ 路由配置统一在一个文件中(src/router/index.tsx 或文件系统路由)
// ❌ 禁止在页面组件中动态注册路由(assist-web 规则)
// ✅ React:lazy() + Suspense
const ConversationRecord = lazy(() => import("@/pages/ConversationRecord"))
<Suspense fallback={<Loading />}>
<ConversationRecord />
</Suspense>
// ✅ Vue Nuxt:文件系统路由自动懒加载,手动路由同样需要 defineAsyncComponent
// ❌ 禁止:非首屏页面直接 import
import ConversationRecord from "@/pages/ConversationRecord"
验证项目:assist-web、resonance、freedom(3 个+)
// ✅ 需要鉴权:显式声明 meta.protected(React)或 definePageMeta(Nuxt)
{ path: "home", element: <Chat />, meta: { protected: true } }
// ✅ Nuxt 页面:默认需鉴权,不需要时显式声明
definePageMeta({ needAuth: false })
验证项目:assist-web、cherry-pc(2 个+)
// ✅ interceptor 层:统一 toast + Sentry 上报
// 业务层 catch 只做流程控制,不重复 toast
try {
await someApi()
} catch (err) {
// interceptor 已经 toast,业务层只管状态
setLoading(false)
}
// ❌ 禁止:业务层重复处理已在 interceptor 处理过的错误
try {
await someApi()
} catch (err) {
message.error(err.message) // 重复 toast
Sentry.captureException(err) // 重复上报
}
验证项目:assist-web、resonance、freedom(3 个+)
// ✅ 必须:展示错误原始信息
async function handleBuy() {
try {
await doSomething()
} catch (err) {
showToast({ title: err.message }) // 暴露真实错误
}
}
// ❌ 禁止:静默失败(用户不知道发生了错误)
async function handleBuy() {
await doSomething() // 无 try/catch
}
// ❌ 禁止:展示固定文本掩盖真实错误
catch (err) {
showToast({ title: '发生错误' }) // 没有 err.message
}
飞书文档原文规则。验证项目:飞书知识库、chat-assist-mobile、eros(3 个+)
// ✅ 必须:在 finally 或两个分支都关闭 loading
try {
showLoadingToast({ message: '处理中...' })
await doWork()
closeToast()
} catch (err) {
closeToast() // ✅ catch 中也必须关闭
showToast('处理失败')
}
// ❌ 禁止:catch 中忘记关闭 loading(loading 永远不消失)
try {
showLoadingToast({ message: '处理中...' })
await doWork()
closeToast()
} catch (e) {
// 没有 closeToast!
}
验证项目:chat-assist-mobile(2 个+)
// ❌ 禁止:catch 中用解构(在 uni-app 某些环境会触发 BUG)
try { ... } catch ({ errMsg }) { ... }
// ✅ 必须:catch (err) 或 catch {}
try { ... } catch (err) {
showToast(err.message)
}
// ✅ 允许:不需要 error 对象时用 catch {}
try { ... } catch { }
飞书文档原文规则(uni-app BUGS 章节)。验证项目:cactus、飞书知识库(2 个+)
// ✅ 轮询中静默处理,不打扰用户
try {
const res = await pollingApi()
// process res
} catch (error) {
// 静默,下次轮询重试,不弹 toast
}
验证项目:assist-web(1 个,通用原则)
以下配置被 cherry、cherry-pc、chat-assist-mobile、freedom(4 个+)采用:
{
"singleQuote": true,
"semi": false,
"tabWidth": 2,
"htmlWhitespaceSensitivity": "ignore"
}
// ✅ 正例
import { gapiReq } from './request'
const foo = 'bar'
// ❌ 反例
import { gapiReq } from "./request";
const foo = "bar";
// ❌ 禁止提交 console.log(ESLint no-console: error 或 warn)
console.log('debug info')
// ✅ 允许(仅 cherry)
console.warn('something wrong')
console.error('[Config] Failed:', e)
console.info('info message')
验证项目:cherry(ESLint error)、chat-assist-mobile、assist-web(3 个+)
// ✅ 工具函数、库导出函数必须有 JSDoc
/**
* 比较两个版本号,支持 "1.2.3" 格式。
* @param a 版本号 A
* @param b 版本号 B
* @returns 1 表示 a > b;-1 表示 a < b;0 表示相等
*/
export function compareVersion(a: string, b: string): 1 | -1 | 0 { ... }
// ✅ dangoui:每个 prop 必须有 JSDoc 注释
const props = withDefaults(defineProps<{
/** 按钮类型 */
type: 'primary' | 'secondary'
/** 是否禁用 */
disabled: boolean
}>(), { ... })
验证项目:effuse(所有 export function)、dangoui(所有 prop)(2 个+)
Conventional Commits 格式(<type>(<scope>): <description>),经 cherry、cherry-pc、chat-assist-mobile、freedom、eros(5 个+)验证:
feat、fix、refactor、chore、docs、style、test--no-verify)# ✅ Feature 分支:feat-name(从最新 master 切出)
git checkout -b feat-user-profile master
# ✅ Bugfix 分支:fix-name(从最新 master 切出)
git checkout -b fix-login-redirect master
# ✅ 合并后必须删除远端分支
git push origin --delete feat-user-profile
# ❌ 禁止:force push 到 master 或 develop
git push --force origin master # 禁止
# ❌ 禁止:合并到保护分支的 PR 未通过 CI
# (master 和 develop 是保护分支,必须 CI 全绿才可合并)
飞书文档原文规则(Git 工作流)。验证项目:飞书知识库(前端开发 Git 开发规范和工作流)(1 个+)
// ✅ 必须:JS 中用 process.env.UNI_PLATFORM 做条件判断
if (process.env.UNI_PLATFORM === 'mp-weixin') {
wx.share()
} else if (process.env.UNI_PLATFORM === 'app') {
bridge.share()
}
// ❌ 禁止:JS 代码中用 #ifdef(无法被 ESLint/TS 等工具分析)
// #ifdef mp-weixin
const platform = 'WEIXIN'
// #endif
<!-- ✅ 允许:模板和 CSS 里可以用 #ifdef -->
<!-- #ifdef mp-weixin -->
<view class="weixin-only">...</view>
<!-- #endif -->
飞书文档原文规则(原文为建议性表述,按公司基线视为硬性要求)。验证项目:cactus、飞书知识库(2 个+)
// ❌ 禁止:uni-app 陷阱,会导致 dev 下开发者工具报错
const location = getCurrentLocation()
// ✅ 必须:换一个不冲突的名字
const currentLocation = getCurrentLocation()
飞书文档原文规则(uni-app BUGS 章节)。验证项目:cactus(1 个+)
baseStore 读取,不存放在模块作用域变量ldapToken(内部系统)和 userToken(业务 API),根据请求域分别注入/assist-web/ 前缀Object.freeze(),防止 Vue 深度响应式影响性能setup() 只注入 page-store 事件总线模块编号_模块名.功能.描述(详见附录 B)@tanstack/vue-query 的 useQuery/useMutation 管理,不手写 loading/error refdetectBrowserLanguage: false,完全由插件手动控制语言button/、icon-button/),组件文件 PascalCase(Button.vue)Du 前缀名:export { Button, DuButton }extClass 和 extStyle prop,使用 normalizeClass/normalizeStyle 处理var(--du-*), 禁止硬编码color prop 的组件必须提供 platte.ts 导出 fromPlatte 函数helpers.tsp,回调分别命名为 success 和 failI + PascalCase 前缀:IVoidParams、ISuccessReturncallBridge 封装,禁止直接调用 window.dsBridgeomitSuccessFail 净化参数,pickSuccessFail 分离回调V2 后缀,V1/V2 同时导出console.error 记录unknown + 类型收窄lib/web/、lib/wx/),不混入通用 lib/utils.tslet lock: Promise<void> | null = null)const ContainerCSS = css\...`,不允许 containerStyle`views/Orders/),非页面文件 kebab-case{ swr } 包裹,不直接展开 { data, isLoading }swr/mutation,不手写 loading statehandle 前缀,禁止 on 前缀useEffect 内的 async 操作提取为具名函数(async function init() {...}),不用 IIFEmessage 必须用 hook 方式(message.useMessage()),禁止静态方法/im/ 前缀(项目专属路由约定)已吸收 3 篇飞书文档(2 篇 wiki + 1 篇旧版 Doc 手工粘贴合集)。
| 文件 | 内容摘要 | 已合并到正文的章节 |
|---|---|---|
开发规范-test (NULlwfCCziGtApkgWhDcGp0Inbb.md) | 受控组件、乐观更新、错误暴露、登录态处理、图片规格、敏感图片 | 4.5/4.6/4.7/4.8/4.9/4.10/10.5 |
如何创建新项目 (UyISwA16QinJp4kLaUAcfOtynpc.md) | Token 刷新重放、OSS EchoOSSUrl、API 签名、多环境头部、CI/CD、埋点双报、butterfly 监控 | 6.5/6.6/6.7、附录 B.2/B.3/B.4 |
旧版 wiki 合集 (pasted-old-docs.md) | 前端新人指南、uni-app 规范、Git 工作流、cherry-pc 目录规范 | 3.7/7.6/7.7/10.5/13.2/14 |
创建新项目关键步骤(千岛体系):
prod_island.packages 表创建 package 记录,获得 package_id(找 DBA 发起数据库变更工单)refreshToken/accessToken/expiresIn,请求层封装自动刷新(见第 6.5 条)@frontend/eros 库,存储 echotechoss:// 格式 URL(见第 6.7 条)@frontend/pigeon 注入签名头(见第 6.3 条)x-echoing-env: test-z(默认),可切换 test-a/test-b 等.gitlab-ci.yml,Web 项目参考 freedom,小程序参考 crane 平台远程配置中心:https://admin.echo.tech/config,支持 JSON/YAML/Markdown/HTML。
统一链接(DaoLink):多端资源位配置使用 @frontend/daolink,弥合不同端路径差异。
@frontend/ubt,自研,供算法/内部看板)+ 神策(https://sensors.echo.tech,日常数据分析)@frontend/butterfly(自研),自动上报所有可捕获错误,也支持手动上报Thumbnail 组件(静态资源即你明确知道图片尺寸且想渲染原图的情况除外)width 或 height 参数(控制加载质量,与 CSS 渲染尺寸无关)| 文档 | 链接 | 状态 |
|---|---|---|
| EchoOSSUrl 详细规范 | 飞书 wiki wikcnBqr409jQwWriQdCkkCKExf | 待补 |
| 接口设计规范 | 飞书 wiki wikcnsi4Ez49wByPFq29UWpN5Ph | 待补 |
| package_id 说明 | 飞书 wiki wikcnh6oLH2iymhwhbLorRN9gfg | 待补 |
| 前端多环境 Q&A | 飞书 wiki wikcnN4DoktDkO78ja26VDhTd9b | 待补 |
本 Skill 的规则来自以下 11 个项目 + 3 篇飞书规范文档的横向对比。原始调研文档(raw/)已归档删除,这里仅保留来源声明,避免 AI 把"某项目这么写"误当成"行业标准"。
| 来源 | 技术栈 |
|---|---|
| assist-web | React 19 + Zustand + UnoCSS |
| cactus | uni-app + Vue 3 + Vuex |
| chat-assist-mobile | Vue 3 + Pinia + UnoCSS |
| cherry | Nuxt 3 + Vue 3 + Pinia |
| cherry-pc | Nuxt 4 + Vue 3 + i18n + TanStack Query |
| dangoui | Vue 3 组件库 + Vite lib |
| effuse | JS Bridge SDK + Nuxt 3 文档站 |
| eros | TypeScript OSS SDK |
| freedom | React 18 + easy-peasy + Emotion |
| om-react | React 18 + UmiJS + SWR |
| resonance | React 19 + Zustand + UnoCSS |
| 飞书:🚧 开发规范 / 开发规范-test | - |
| 飞书:如何创建新项目 | - |
| 飞书:前端开发 Git 工作流 / 排查案例 / cherry-pc 目录规范 | - |
注意:11 个项目来自同一团队,共享同一批人的技术偏好;"N 个项目都这么做" ≠ "行业标准"。使用 Skill 时请配合批判性判断。