编码规范
本文档基于 AGENTS.md 中的规范,为贡献者提供编码指南。
TypeScript 规范
类型定义
优先使用 interface
typescript
// ✅ 推荐
interface DesignerConfig {
ueditorPath?: string
}
// ❌ 避免
type DesignerConfig = {
ueditorPath?: string
}类型导入
typescript
// ✅ 推荐
import type { DesignerConfig } from '../types'
// ❌ 避免
import { DesignerConfig } from '../types'类型注解
为公共 API 添加完整的类型注解:
typescript
// ✅ 推荐
export async function loadMaterials(
params?: MaterialQueryParams
): Promise<StyleListData> {
// Implementation
}
// ❌ 避免(缺少返回类型)
export async function loadMaterials(params?) {
// Implementation
}JSDoc 注释
所有导出的函数、接口和类型必须有 JSDoc 注释:
typescript
/**
* Load materials from API
*
* @param params - Query parameters
* @returns Promise resolving to material list data
*/
export async function loadMaterials(
params?: MaterialQueryParams
): Promise<StyleListData> {
// Implementation
}Vue 组件规范
文件结构
vue
<template>
<!-- Template content -->
</template>
<script setup lang="ts">
// 1. Vue core imports
import { ref, computed, onMounted } from 'vue'
// 2. Local composables
import { useMaterial } from '../composables/useMaterial'
// 3. Local components
import MaterialPanel from './MaterialPanel.vue'
// 4. Type imports
import type { DesignerConfig } from '../types'
// 5. Props definition
interface Props {
config?: DesignerConfig
}
const props = withDefaults(defineProps<Props>(), {
config: () => ({})
})
// 6. Emits definition
const emit = defineEmits<{
ready: []
change: [content: string]
}>()
// 7. Component logic
</script>
<style scoped>
/* Styles */
</style>命名规范
typescript
// ✅ 变量/函数: camelCase
const currentSection = ref(null)
const loadMaterials = async () => {}
// ✅ 组件: PascalCase
import MaterialPanel from './MaterialPanel.vue'
// ✅ 常量: PascalCase 或 UPPER_SNAKE_CASE
const Config = { /* ... */ }
const MAX_SIZE = 100
// ✅ CSS 类: kebab-case
<div class="material-panel"></div>Props 定义
使用 TypeScript interface + withDefaults:
vue
<script setup lang="ts">
interface Props {
config?: DesignerConfig
isEnabled?: boolean
}
const props = withDefaults(defineProps<Props>(), {
config: () => ({}),
isEnabled: false
})
</script>Emits 定义
使用 TypeScript 类型定义:
vue
<script setup lang="ts">
const emit = defineEmits<{
ready: [] // 无参数
change: [content: string] // 一个参数
update: [id: number, data: any] // 多个参数
}>()
</script>Composables 规范
命名和结构
typescript
// ✅ 文件名: useMaterial.ts
// ✅ 函数名: useMaterial
export function useMaterial(config: DesignerConfig) {
// Reactive state
const materials = ref<MaterialItem[]>([])
const loading = ref(false)
// Methods
const loadMaterials = async () => { /* ... */ }
// Lifecycle
onMounted(async () => {
await loadMaterials()
})
// Grouped return object
return {
// Data
materials,
// Loading states
loading,
// Methods
loadMaterials
}
}JSDoc 注释
typescript
/**
* Material management composable
*
* Handles loading and managing material data including
* categories and material items.
*
* @param config - Designer configuration
* @returns Material state and methods
*/
export function useMaterial(config: DesignerConfig) {
// Implementation
}错误处理
标准模式
typescript
try {
await someAsyncOperation()
} catch (error) {
console.error('Failed to perform operation:', error)
// Set fallback state
materials.value = []
hasMore.value = false
}不要抛出错误(composables)
typescript
// ✅ 推荐
const loadMaterials = async () => {
try {
const data = await fetch('/api/materials')
return data
} catch (error) {
console.error('Load failed:', error)
return { records: [], total: 0, page: 1, pageSize: 20 }
}
}
// ❌ 避免(除非必要)
const loadMaterials = async () => {
const data = await fetch('/api/materials')
return data // 可能抛出错误
}导入组织
按以下顺序组织导入:
typescript
// 1. Vue core imports
import { ref, computed, onMounted } from 'vue'
// 2. Third-party libraries
import axios from 'axios'
// 3. Local utilities/composables
import { useMaterial } from '@/composables/useMaterial'
// 4. Local components
import MaterialPanel from '@/components/MaterialPanel.vue'
// 5. Type imports
import type { DesignerConfig, MaterialItem } from '@/types'
// 6. CSS/SCSS imports
import '@/styles/index.scss'路径别名
使用 @/ 别名访问 src 目录:
typescript
// ✅ 推荐
import { Config } from '@/utils/config'
// ❌ 避免
import { Config } from '../../../utils/config'最佳实践
1. 避免 any 类型
typescript
// ✅ 推荐
function process(data: MaterialItem): void {
// ...
}
// ❌ 避免
function process(data: any): void {
// ...
}2. 使用 const 和 let
typescript
// ✅ 推荐
const MAX_SIZE = 100
let currentIndex = 0
// ❌ 避免
var MAX_SIZE = 1003. 清理资源
typescript
onUnmounted(() => {
// Clean up event listeners
window.removeEventListener('resize', handleResize)
// Clear timers
clearTimeout(timer)
// Unsubscribe
subscription?.unsubscribe()
})4. 使用 computed 而非 watch
typescript
// ✅ 推荐
const filteredMaterials = computed(() => {
return materials.value.filter(m => m.categoryId === currentCategoryId.value)
})
// ❌ 避免(除非必要)
watch([materials, currentCategoryId], () => {
filteredMaterials.value = materials.value.filter(m =>
m.categoryId === currentCategoryId.value
)
})5. 具名导出 vs 默认导出
typescript
// ✅ Vue 组件: 默认导出
export default defineComponent({
name: 'MaterialPanel'
})
// ✅ 工具函数: 具名导出
export function loadUEditor() { }
export function processStyles() { }CSS/SCSS 规范
类名前缀
scss
// Page builder sections
.pb-section { }
.pb-section-active { }
// UEditor Plus Designer components
.upd-toolbar { }
.upd-panel { }Scoped 样式
组件样式使用 scoped:
vue
<style scoped>
.material-panel {
/* Component-specific styles */
}
</style>全局样式
全局样式放在 src/styles/:
scss
// src/styles/index.scss
.pb-section {
position: relative;
cursor: pointer;
}提交规范
Commit 消息格式
<type>(<scope>): <subject>
<body>
<footer>Type 类型
feat: 新功能fix: 修复 bugdocs: 文档更新style: 代码格式(不影响代码运行)refactor: 重构test: 测试chore: 构建过程或辅助工具的变动
示例
feat(material): add material search functionality
Add search input in material panel to filter materials by keywords.
The search is debounced by 300ms to improve performance.
Closes #123代码审查检查清单
- [ ] 所有公共 API 都有 TypeScript 类型定义
- [ ] 所有导出的函数/接口都有 JSDoc 注释
- [ ] 没有使用
any类型(除非必要) - [ ] 错误处理得当
- [ ] 资源清理(event listeners, timers)
- [ ] 遵循命名规范
- [ ] 导入顺序正确
- [ ] 运行
npm run type-check无错误