| 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)。
第 0 条(元规则)
- 本 skill 列出的规则是所有前端代码的基线。项目自身的
.eslintrc/.prettierrc 更严则从严,不得低于本基线。
- 遇到本 skill 未覆盖的新维度,优先参考同栈项目的现有代码:
- React -> assist-web / resonance / freedom / om-react
- Vue 3 + Nuxt -> cherry / cherry-pc
- Vue 3 + Vite -> chat-assist-mobile / dangoui
- uni-app 小程序 -> cactus
- Skill 中列出的项目名是证据来源,不是业务范例,不照搬项目内部业务代码。
1. 命名规范
1.1 文件命名
组件文件统一 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 个+)
1.2 组件在 template 中的引用
<!-- ✅ 必须 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 个+)
1.3 变量与函数命名
export function useConsignmentList() { ... }
export function useTaskPolling() { ... }
export const useConversationStore = createStore<ConversationStore>(...)
export const useMessageStore = defineStore('message', () => { ... })
function setMessageList(list: MessageType[]) { ... }
function setCurrentConversation(conv: ConversationData) { ... }
function handleCreateConversationSuccess(isSuccess: boolean) { ... }
async function handleOpenConversation(toUserId: string) { ... }
export const CLIENT_PACKAGE_ID = 1070
export const CONSIGNMENT_STATUS_MAP = { SENDING: '在途中' }
export const conversationStore = createStore(...)
export const useConversation = defineStore(...)
function onSuccess() { ... }
验证项目:assist-web、chat-assist-mobile、cherry、resonance(4 个+)
1.4 TypeScript 类型/接口命名
interface ConversationStore { ... }
interface PublishFormData { ... }
type ErosConfig = { ... }
enum WareHouse { UNKOWN = 'UNKOWN', COMPANY = 'COMPANY' }
enum TradeOrderType { C2C = 'C2C', BLIND_BOX_MACHINE = 'BLIND_BOX_MACHINE' }
验证项目:assist-web、cherry-pc、freedom、eros(4 个+)
1.5 CSS class 命名
- 组件库(dangoui):BEM 格式,
du- 前缀,du-{block}__{element}--{modifier}
- 业务项目:优先 UnoCSS 原子类;自定义语义类用 kebab-case(
left-menu、home-page)
- 禁止驼峰 class 名:
duButton、orderList
2. 目录结构
2.1 通用分层约定
所有项目都遵循以下分层(命名允许细微差异):
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 个+)
2.2 私有组件就近放置
pages/
chat/
chat.vue # 页面入口
components/ # ✅ 私有组件就近放,不提升到 src/components
Actions.vue
Sticker.vue
import Actions from './components/Actions.vue'
验证项目:chat-assist-mobile、cherry、freedom、om-react(4 个+)
2.3 自动生成代码隔离
import { kyanWeb } from "@/apis/x/kyan"
验证项目:assist-web、cherry、chat-assist-mobile、resonance、freedom、om-react(6 个+)
3. TypeScript
3.2 禁止 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 警告。
3.4 Props 定义方式
const props = withDefaults(defineProps<{
type: 'primary' | 'secondary'
disabled: boolean
}>(), {
type: 'primary',
disabled: false,
})
interface ItemTextProps {
message: Record<string, any>
isMine: boolean
}
export default function ItemText({ message, isMine }: ItemTextProps) { ... }
defineProps({ type: { type: String, default: 'primary' } })
3.7 非 .js 文件的 import 必须带文件后缀名
import './HelloWorld'
import './HelloWorld.vue'
飞书文档原文规则(uni-app 开发规范)。验证项目:cactus、飞书知识库(2 个+)
4. 组件规约
4.1 Vue - 使用 script setup(仅 Vue 项目)
<!-- ✅ 必须:新组件统一 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 为主,历史遗留)
4.2 Vue - style scoped
<!-- ✅ 必须加 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 个+)
4.4 React - 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>
}
)
const UBT = forwardRef<HTMLElement, UBTProps>((props, ref) => { ... })
UBT.displayName = "UBT"
const MyComp = forwardRef((props, ref) => {
return <div ref={ref}>...</div>
})
验证项目:om-react、resonance(2 个+)
4.5 通用组件必须是受控组件
<!-- ✅ 必须:同时提供 value prop 和 input/change 事件 -->
<SomeInput :value="someValue" @input="handleInput" @change="handleChange" />
<!-- ❌ 禁止:没有 value prop,或 value 不受外部控制(非受控组件) -->
<SomeInput @change="handleChange" />
通用组件内部状态不能脱离父组件控制;外部修改 value 必须同步反映到组件内部。
飞书文档原文规则(原文为建议性表述,按公司基线视为硬性要求)。验证项目:飞书知识库、dangoui(2 个+)
4.6 异步操作必须管理加载态
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 必须配对)、飞书文档。
4.7 乐观更新必须带竞态 ID 处理
涉及用户 toggle 操作(点赞/收藏等)且使用乐观更新时,必须用竞态 ID 防止多次操作互相覆盖:
const like = ref(false)
let toggleLikeId: number | null = null
const toggleLike = async () => {
const prevVal = like.value
const currId = genId()
toggleLikeId = currId
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 个+)
4.8 模板中禁止生成新对象
<!-- ❌ 禁止:模板里调用 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 个+)
4.9 具名 slot / Props 使用规范
<!-- ❌ 禁止:具名 slot 名称带 - -->
<template #my-slot></template>
<!-- ✅ 必须:camelCase 或无连字符 -->
<template #mySlot></template>
<!-- ❌ 禁止:在子组件中修改 props -->
<!-- ❌ 禁止:computed 中修改外部状态(computed 必须是纯函数) -->
飞书文档原文规则(uni-app 规范)。验证项目:cactus、飞书知识库(2 个+)
4.10 登录态处理
页面需要区分登录/未登录时:
watch(
() => me.id,
(val) => {
if (val) {
fetchDataNeedAuth()
} else {
fetchData()
}
},
{ immediate: true }
)
import { gotoLoginIfNot } from '@/modules/hooks/use-auth-page'
onLoad(async () => { await gotoLoginIfNot() })
飞书文档原文规则。验证项目:cactus、飞书知识库(2 个+)
5. 状态管理
详细规范和代码模板见 references/state-management.md
5.1 Pinia(仅 Vue 项目)
export const useMessageStore = defineStore('message', () => {
const messageList = ref<MessageType[]>([])
function setMessageList(list: MessageType[]) {
messageList.value = list
}
return { messageList, setMessageList }
})
export const useMessageStore = defineStore('message', {
state: () => ({ messageList: [] }),
actions: { ... }
})
验证项目:cherry、cherry-pc、chat-assist-mobile(3 个+)
5.2 Zustand(仅 React 项目)
export const useConversationStore = createStore(immer(conversationStore))
import { create } from 'zustand'
export const useConversationStore = create<ConversationStore>()((set) => ({ ... }))
const currentConversation = useConversationStore((state) => state.currentConversation)
const store = useConversationStore()
验证项目:assist-web、resonance(2 个+)
5.3 Vuex(仅 cactus / 遗留)
- mutation 必须 SCREAMING_SNAKE_CASE:
SET_VIP_INFO
- action 用 camelCase:
fetchVipInfo
- 新项目不使用 Vuex
6. 网络请求
6.1 API 代码必须自动生成
export const kyanWeb = new Api(getCommonParams({ prefix: '' }))
export const useDaolinkUrlDetail = (id?: string) => {
const swr = useSWR(key, async () => {
const res = await getAdminUrlGetDetail({ query: { id: id! } })
return res.data?.data
})
return { swr }
}
验证项目:assist-web、cherry、chat-assist-mobile、resonance、freedom、om-react、cactus(7 个+)
6.2 统一请求实例,禁止裸调用
import { apiFetch } from '@/apis'
import { kyanWeb } from '@/apis/x/kyan'
const res = await fetch('/api/xxx')
import axios from 'axios'
验证项目:assist-web、chat-assist-mobile、resonance(3 个+)
6.3 认证头统一在 interceptor 注入
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 ID
x-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 个+)
6.4 401 统一在 interceptor 跳转登录
if (statusCode === 401) {
window.location.href = `/login?redirect_uri=` + encodeURIComponent(window.location.href)
}
try {
await api.xxx()
} catch (err) {
if (err.status === 401) router.push('/login')
}
验证项目:assist-web、resonance、freedom(3 个+)
6.5 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)
})
飞书文档原文规则。验证项目:飞书知识库(如何创建新项目)(1 个+)
6.6 后端 code !== 0 必须在请求层 throw,业务层不判断 code
instance.interceptors.response.use((response) => {
if (response.data?.code !== 0) {
throw new Error(response.data?.msg || '请求失败')
}
return response
})
const data = await someApi()
const res = await someApi()
if (res.code !== 0) { ... }
飞书文档原文规则(swagger-typescript-api 自定义模板约定)。验证项目:cherry、cherry-pc、飞书知识库(3 个+)
6.7 资源上传规范
await uploadFile(file, { scene: 'product-image' })
await uploadFile(sensitiveFile, { scene: 'encryted-images' })
飞书文档原文规则。验证项目:飞书知识库(NULlwfCCziGtApkgWhDcGp0Inbb、UyISwA16QinJp4kLaUAcfOtynpc)(2 个+)
7. 样式方案
7.1 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 个+)
7.2 排版必须使用预定义 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 个+)
7.3 颜色禁止硬编码
.button--primary {
color: var(--du-bt-solid-color);
background: var(--du-bt-solid-bg);
}
const { token } = useToken()
<div style={{ borderColor: token.colorBorder }}>
.button--primary { color: #ffffff; background: #1677ff; }
<div style={{ borderColor: '#e8e8e8' }}>
验证项目:dangoui、resonance(2 个+)
7.6 小程序样式禁用标签选择器和通配符
img { width: 100%; }
p { margin: 0; }
.product-image { width: 100%; }
.paragraph { margin: 0; }
* { box-sizing: border-box; }
飞书文档原文规则(uni-app 开发规范)。验证项目:cactus、飞书知识库(2 个+)
7.7 移动端禁止混用 rpx 和 px
.container {
margin: 16rpx;
padding: 10px;
}
.container {
margin: 16rpx;
padding: 20rpx;
}
飞书文档原文规则(cherry-pc 目录规范)。验证项目:cactus、cherry-pc(2 个+)
7.4 移动端单位
- 小程序(cactus、chat-assist-mobile):
rpx 是唯一长度单位,stylelint 强制
- Web 端:UnoCSS rem(自动转 vw,基准 375px),数值填像素值
margin-left: 16rpx;
line-height: 64rpx;
margin-left: 16px;
验证项目:cactus、chat-assist-mobile(2 个+)
7.5 CSS 属性书写顺序(仅 stylelint-config-rational-order 项目)
顺序:Positioning -> Display -> Box Model -> Typography -> Visual -> Animation(cactus stylelint 强制)。
8. 国际化 (i18n)
详细规范见 references/i18n.md
8.1 硬编码中文禁令(核心规则)
message.error('加载失败,请稍后重试')
showToast({ title: '发生错误' })
button.text = '提交'
message.error(t('Toast.LoadFailed'))
showToast({ title: t('Toast.Error') })
适用于:cherry-pc(多语言项目),以及任何将来需要多语言的项目从一开始就避免技术债。
8.2 template 中用 $t,script 中用 useI18n
<!-- template:直接用 $t -->
<p>{{ $t('09_Product.CurrencyPageSubtitle') }}</p>
<script setup>
// script:必须通过 useI18n() 解构
const { t } = useI18n()
const text = computed(() => t('10_Order.ContactBuyer'))
</script>
8.3 动态渲染(JSX/h 函数)必须包裹响应式
label: () => h('span', {}, t('06_Me.MyListing'))
label: t('06_Me.MyListing')
9. 路由
9.1 路由配置集中管理
9.2 非首屏页面必须懒加载
const ConversationRecord = lazy(() => import("@/pages/ConversationRecord"))
<Suspense fallback={<Loading />}>
<ConversationRecord />
</Suspense>
import ConversationRecord from "@/pages/ConversationRecord"
验证项目:assist-web、resonance、freedom(3 个+)
9.3 鉴权页面必须声明
{ path: "home", element: <Chat />, meta: { protected: true } }
definePageMeta({ needAuth: false })
验证项目:assist-web、cherry-pc(2 个+)
10. 错误处理
10.1 HTTP 错误由 interceptor 统一处理
try {
await someApi()
} catch (err) {
setLoading(false)
}
try {
await someApi()
} catch (err) {
message.error(err.message)
Sentry.captureException(err)
}
验证项目:assist-web、resonance、freedom(3 个+)
10.2 错误必须暴露给用户,不能静默吞掉
async function handleBuy() {
try {
await doSomething()
} catch (err) {
showToast({ title: err.message })
}
}
async function handleBuy() {
await doSomething()
}
catch (err) {
showToast({ title: '发生错误' })
}
飞书文档原文规则。验证项目:飞书知识库、chat-assist-mobile、eros(3 个+)
10.3 Loading 状态必须配对关闭
try {
showLoadingToast({ message: '处理中...' })
await doWork()
closeToast()
} catch (err) {
closeToast()
showToast('处理失败')
}
try {
showLoadingToast({ message: '处理中...' })
await doWork()
closeToast()
} catch (e) {
}
验证项目:chat-assist-mobile(2 个+)
10.5 catch 块禁止解构(uni-app 专属 BUG)
try { ... } catch ({ errMsg }) { ... }
try { ... } catch (err) {
showToast(err.message)
}
try { ... } catch { }
飞书文档原文规则(uni-app BUGS 章节)。验证项目:cactus、飞书知识库(2 个+)
10.4 轮询/后台任务错误静默处理
try {
const res = await pollingApi()
} catch (error) {
}
验证项目:assist-web(1 个,通用原则)
11. 代码格式
11.1 Prettier 通用配置
以下配置被 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";
11.2 禁止提交 console.log
console.log('debug info')
console.warn('something wrong')
console.error('[Config] Failed:', e)
console.info('info message')
验证项目:cherry(ESLint error)、chat-assist-mobile、assist-web(3 个+)
12. 注释规约
12.1 JSDoc
export function compareVersion(a: string, b: string): 1 | -1 | 0 { ... }
const props = withDefaults(defineProps<{
type: 'primary' | 'secondary'
disabled: boolean
}>(), { ... })
验证项目:effuse(所有 export function)、dangoui(所有 prop)(2 个+)
13. Git Commit 规约
Conventional Commits 格式(<type>(<scope>): <description>),经 cherry、cherry-pc、chat-assist-mobile、freedom、eros(5 个+)验证:
- type 取值:
feat、fix、refactor、chore、docs、style、test
- 中文描述可接受
- 禁止跳过 pre-commit hook(
--no-verify)
13.2 分支命名与保护分支规范
git checkout -b feat-user-profile master
git checkout -b fix-login-redirect master
git push origin --delete feat-user-profile
git push --force origin master
飞书文档原文规则(Git 工作流)。验证项目:飞书知识库(前端开发 Git 开发规范和工作流)(1 个+)
14. uni-app 条件编译规范
14.1 JS 代码中优先用 process.env.UNI_PLATFORM
if (process.env.UNI_PLATFORM === 'mp-weixin') {
wx.share()
} else if (process.env.UNI_PLATFORM === 'app') {
bridge.share()
}
const platform = 'WEIXIN'
<!-- ✅ 允许:模板和 CSS 里可以用 #ifdef -->
<!-- #ifdef mp-weixin -->
<view class="weixin-only">...</view>
<!-- #endif -->
飞书文档原文规则(原文为建议性表述,按公司基线视为硬性要求)。验证项目:cactus、飞书知识库(2 个+)
14.2 禁止在模块顶级作用域声明名为 location 的变量
const location = getCurrentLocation()
const currentLocation = getCurrentLocation()
飞书文档原文规则(uni-app BUGS 章节)。验证项目:cactus(1 个+)
附录 A:项目专属规则
A.1 assist-web(React + Zustand + UnoCSS)
- Bearer Token 从 Zustand
baseStore 读取,不存放在模块作用域变量
- 双 token 机制:
ldapToken(内部系统)和 userToken(业务 API),根据请求域分别注入
- 所有路由在非 qiankun 环境下统一加
/assist-web/ 前缀
A.2 cactus(uni-app 小程序)
- rpx 是唯一长度单位(stylelint 强制,px/rem 报错)
- 大列表赋值必须
Object.freeze(),防止 Vue 深度响应式影响性能
- 图片走 CDN URL,modules 目录禁止提交静态资源
- 组件主体用 Options API,
setup() 只注入 page-store 事件总线
A.3 cherry-pc(Nuxt 4 + i18n + TanStack Query)
- i18n key 格式:
模块编号_模块名.功能.描述(详见附录 B)
- 数据请求必须通过
@tanstack/vue-query 的 useQuery/useMutation 管理,不手写 loading/error ref
- 语言切换优先级:URL query > 浏览器语言 > 默认语言
detectBrowserLanguage: false,完全由插件手动控制语言
A.4 dangoui(Vue 3 组件库)
- 组件目录全小写(
button/、icon-button/),组件文件 PascalCase(Button.vue)
- 所有组件必须同时导出原始名和
Du 前缀名:export { Button, DuButton }
- 所有组件必须支持
extClass 和 extStyle prop,使用 normalizeClass/normalizeStyle 处理
- 颜色只用 CSS 变量
var(--du-*), 禁止硬编码
- 支持
color prop 的组件必须提供 platte.ts 导出 fromPlatte 函数
- InjectionKey 必须类型化,统一放
helpers.ts
A.5 effuse(JS Bridge SDK)
- 参数对象统一命名为
p,回调分别命名为 success 和 fail
- 接口用
I + PascalCase 前缀:IVoidParams、ISuccessReturn
- 所有 Bridge 调用通过
callBridge 封装,禁止直接调用 window.dsBridge
- 调用前必须
omitSuccessFail 净化参数,pickSuccessFail 分离回调
- V2 迁移函数在原名后加
V2 后缀,V1/V2 同时导出
A.6 eros(OSS URL SDK)
- 所有对外暴露的函数必须 try/catch,失败时返回原始值(不抛出),
console.error 记录
- 禁止 any 作为参数/返回类型,改用
unknown + 类型收窄
- 平台特定代码必须按平台拆分子目录(
lib/web/、lib/wx/),不混入通用 lib/utils.ts
- 初始化必须防并发:Promise lock 模式(
let lock: Promise<void> | null = null)
A.7 freedom(React + easy-peasy + Emotion)
- 三层状态分离:easy-peasy(全局跨域)/ Zustand(模块内复杂)/ React Query(服务端)
- Emotion CSS 变量加 CSS 后缀:
const ContainerCSS = css\...`,不允许 containerStyle`
- 页面组件 PascalCase 目录(
views/Orders/),非页面文件 kebab-case
A.8 om-react(React + UmiJS + SWR)
- SWR 读数据 hook 必须返回
{ swr } 包裹,不直接展开 { data, isLoading }
- 写操作必须用
swr/mutation,不手写 loading state
- Context 消费方只能通过自定义 hook 访问,context 为 null 时必须 throw
A.9 resonance(React + Zustand)
- 事件处理函数统一
handle 前缀,禁止 on 前缀
useEffect 内的 async 操作提取为具名函数(async function init() {...}),不用 IIFE
- React 19:Ant Design
message 必须用 hook 方式(message.useMessage()),禁止静态方法
- 所有路径统一
/im/ 前缀(项目专属路由约定)
附录 B:飞书知识库参考
已吸收 3 篇飞书文档(2 篇 wiki + 1 篇旧版 Doc 手工粘贴合集)。
B.1 已吸收文档及规则映射
| 文件 | 内容摘要 | 已合并到正文的章节 |
|---|
开发规范-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 |
B.2 基础设施参考(流程类,不进主体规则)
创建新项目关键步骤(千岛体系):
- 在 GitLab 创建仓库
- 在
prod_island.packages 表创建 package 记录,获得 package_id(找 DBA 发起数据库变更工单)
- 接入登录:后端返回
refreshToken/accessToken/expiresIn,请求层封装自动刷新(见第 6.5 条)
- 接入 OSS:使用
@frontend/eros 库,存储 echotechoss:// 格式 URL(见第 6.7 条)
- 接入 API 签名:使用
@frontend/pigeon 注入签名头(见第 6.3 条)
- 配置多环境:HTTP 头
x-echoing-env: test-z(默认),可切换 test-a/test-b 等
- 接入 CI/CD:编写
.gitlab-ci.yml,Web 项目参考 freedom,小程序参考 crane 平台
- 配置合法域名(小程序必须);配置域名白名单(crane 部署前必须关闭)
远程配置中心:https://admin.echo.tech/config,支持 JSON/YAML/Markdown/HTML。
统一链接(DaoLink):多端资源位配置使用 @frontend/daolink,弥合不同端路径差异。
B.3 埋点规范(流程类)
B.4 错误监控
- 小程序:使用
@frontend/butterfly(自研),自动上报所有可捕获错误,也支持手动上报
- Web 端:暂无统一方案
B.5 Thumbnail 组件使用规约(cherry-pc)
- 非静态资源图片必须使用
Thumbnail 组件(静态资源即你明确知道图片尺寸且想渲染原图的情况除外)
- 必须指定
width 或 height 参数(控制加载质量,与 CSS 渲染尺寸无关)
B.6 待补充文档
| 文档 | 链接 | 状态 |
|---|
| EchoOSSUrl 详细规范 | 飞书 wiki wikcnBqr409jQwWriQdCkkCKExf | 待补 |
| 接口设计规范 | 飞书 wiki wikcnsi4Ez49wByPFq29UWpN5Ph | 待补 |
| package_id 说明 | 飞书 wiki wikcnh6oLH2iymhwhbLorRN9gfg | 待补 |
| 前端多环境 Q&A | 飞书 wiki wikcnN4DoktDkO78ja26VDhTd9b | 待补 |
附录 C:证据来源清单
本 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 时请配合批判性判断。