fix
This commit is contained in:
262
.cursor/rules/common/code-style.mdc
Normal file
262
.cursor/rules/common/code-style.mdc
Normal file
@ -0,0 +1,262 @@
|
||||
---
|
||||
description: Code style and naming conventions
|
||||
---
|
||||
|
||||
# 代码风格和约定规则
|
||||
|
||||
## 📝 通用代码风格
|
||||
|
||||
### 文件命名
|
||||
- **Go文件**: `camelCase.go` (例: `uploadPhotoHandler.go`)
|
||||
- **TypeScript**: `kebab-case.tsx` 或 `PascalCase.tsx` (组件)
|
||||
- **API文件**: `kebab-case.api` (例: `photo.api`)
|
||||
- **配置文件**: `kebab-case.yaml/.json`
|
||||
|
||||
### 注释规范
|
||||
```go
|
||||
// ✅ Go - 函数注释
|
||||
// UploadPhoto 上传照片到服务器
|
||||
// 支持JPEG、PNG、GIF、WebP格式,最大10MB
|
||||
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) {
|
||||
// 实现逻辑
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ✅ TypeScript - 接口注释
|
||||
/**
|
||||
* 照片数据接口
|
||||
* @interface Photo
|
||||
*/
|
||||
interface Photo {
|
||||
/** 照片唯一标识符 */
|
||||
id: string
|
||||
/** 照片标题 */
|
||||
title: string
|
||||
/** 文件名 */
|
||||
filename: string
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 命名约定
|
||||
|
||||
### 变量命名
|
||||
```go
|
||||
// ✅ Go - 驼峰命名
|
||||
var photoID string
|
||||
var userList []User
|
||||
var maxFileSize int64 = 10 * 1024 * 1024 // 10MB
|
||||
|
||||
// ❌ 避免
|
||||
var photo_id string
|
||||
var PhotoId string
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ✅ TypeScript - 驼峰命名
|
||||
const photoList: Photo[] = []
|
||||
const isLoading = false
|
||||
const handlePhotoUpload = () => {}
|
||||
|
||||
// ✅ 常量 - 大写下划线
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024
|
||||
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL
|
||||
```
|
||||
|
||||
### 函数命名
|
||||
- **动词开头**: `getPhotoList`, `uploadPhoto`, `deleteCategory`
|
||||
- **布尔值**: `isVisible`, `hasPermission`, `canEdit`
|
||||
- **事件处理**: `handleClick`, `onPhotoSelect`, `onUploadSuccess`
|
||||
|
||||
## 🛡️ 错误处理
|
||||
|
||||
### Go 错误处理
|
||||
```go
|
||||
// ✅ 标准错误处理
|
||||
func (l *UploadPhotoLogic) UploadPhoto(req *types.UploadPhotoRequest) (*types.UploadPhotoResponse, error) {
|
||||
if !file.IsImageFile(req.File) {
|
||||
return nil, errorx.NewDefaultError("不支持的文件类型")
|
||||
}
|
||||
|
||||
photoID, err := l.savePhoto(req)
|
||||
if err != nil {
|
||||
logx.Errorf("保存照片失败: %v", err)
|
||||
return nil, errorx.NewDefaultError("照片保存失败")
|
||||
}
|
||||
|
||||
return &types.UploadPhotoResponse{
|
||||
Id: photoID,
|
||||
// ...
|
||||
}, nil
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript 错误处理
|
||||
```typescript
|
||||
// ✅ 异步操作错误处理
|
||||
try {
|
||||
const response = await api.post<UploadResponse>('/photos', formData)
|
||||
return response.data
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const message = error.response?.data?.msg || '上传失败'
|
||||
throw new Error(message)
|
||||
}
|
||||
throw error
|
||||
}
|
||||
```
|
||||
|
||||
## 📦 导入组织
|
||||
|
||||
### Go 导入顺序
|
||||
```go
|
||||
import (
|
||||
// 标准库
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
// 第三方库
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
|
||||
// 本地包
|
||||
"photography/internal/logic/photo"
|
||||
"photography/internal/svc"
|
||||
"photography/internal/types"
|
||||
)
|
||||
```
|
||||
|
||||
### TypeScript 导入顺序
|
||||
```typescript
|
||||
// React 相关
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
// 第三方库
|
||||
import axios from 'axios'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
// UI 组件
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
|
||||
// 本地模块
|
||||
import { api } from '@/lib/api'
|
||||
import { Photo } from '@/types/api'
|
||||
import { useAuthStore } from '@/stores/authStore'
|
||||
```
|
||||
|
||||
## 🎨 CSS/样式约定
|
||||
|
||||
### Tailwind CSS 类名顺序
|
||||
```tsx
|
||||
// ✅ 推荐顺序:布局 → 尺寸 → 间距 → 颜色 → 其他
|
||||
<div className="flex flex-col w-full h-full p-4 bg-white border rounded-lg shadow-md">
|
||||
<img
|
||||
className="w-full h-48 object-cover rounded-md"
|
||||
src={photo.thumbnail}
|
||||
alt={photo.title}
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 响应式设计
|
||||
```tsx
|
||||
// ✅ 移动优先响应式
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{photos.map(photo => (
|
||||
<PhotoCard key={photo.id} photo={photo} />
|
||||
))}
|
||||
</div>
|
||||
```
|
||||
|
||||
## 🔧 类型定义
|
||||
|
||||
### TypeScript 接口规范
|
||||
```typescript
|
||||
// ✅ 明确的接口定义
|
||||
interface PhotoCardProps {
|
||||
photo: Photo
|
||||
onEdit?: (id: string) => void
|
||||
onDelete?: (id: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
// ✅ API 响应类型
|
||||
interface ApiResponse<T> {
|
||||
code: number
|
||||
msg: string
|
||||
data: T
|
||||
}
|
||||
|
||||
type PhotoListResponse = ApiResponse<{
|
||||
photos: Photo[]
|
||||
total: number
|
||||
page: number
|
||||
limit: number
|
||||
}>
|
||||
```
|
||||
|
||||
### Go 结构体规范
|
||||
```go
|
||||
// ✅ 结构体标签完整
|
||||
type Photo struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
Title string `json:"title" db:"title"`
|
||||
Description string `json:"description" db:"description"`
|
||||
Filename string `json:"filename" db:"filename"`
|
||||
Thumbnail string `json:"thumbnail" db:"thumbnail"`
|
||||
CategoryID string `json:"category_id" db:"category_id"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 性能约定
|
||||
|
||||
### 避免性能陷阱
|
||||
```typescript
|
||||
// ✅ 使用 useMemo 避免重复计算
|
||||
const expensiveValue = useMemo(() => {
|
||||
return photos.filter(photo => photo.category_id === selectedCategory)
|
||||
}, [photos, selectedCategory])
|
||||
|
||||
// ✅ 使用 useCallback 避免重复渲染
|
||||
const handlePhotoSelect = useCallback((id: string) => {
|
||||
setSelectedPhoto(photos.find(p => p.id === id))
|
||||
}, [photos])
|
||||
```
|
||||
|
||||
## 🔒 安全约定
|
||||
|
||||
### 输入验证
|
||||
```go
|
||||
// ✅ 后端输入验证
|
||||
if req.Title == "" {
|
||||
return nil, errorx.NewDefaultError("照片标题不能为空")
|
||||
}
|
||||
|
||||
if len(req.Title) > 100 {
|
||||
return nil, errorx.NewDefaultError("照片标题不能超过100个字符")
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// ✅ 前端输入验证
|
||||
const validatePhoto = (data: PhotoFormData): string[] => {
|
||||
const errors: string[] = []
|
||||
|
||||
if (!data.title.trim()) {
|
||||
errors.push('标题不能为空')
|
||||
}
|
||||
|
||||
if (data.title.length > 100) {
|
||||
errors.push('标题不能超过100个字符')
|
||||
}
|
||||
|
||||
return errors
|
||||
}
|
||||
```
|
||||
|
||||
遵循这些约定可以保持代码的一致性和可维护性。
|
||||
Reference in New Issue
Block a user