开发指南
前端开发
技术栈
- Vue 3 Composition API - 使用现代化的组合式 API 编写组件
- TypeScript - 严格模式,确保类型安全
- Element Plus - 基于 Material Design 的 UI 组件库
- Tailwind CSS - 原子化 CSS 框架
- Nuxt 4 - 全栈框架,提供 SSR 和 API 能力
组件开发
创建组件
vue
<!-- app/components/MyComponent.vue -->
<script setup lang="ts">
// 使用 TypeScript 类型
interface Props {
title: string
count?: number
}
// 定义 Props
const props = withDefaults(defineProps<Props>(), {
count: 0
})
// 定义 Emits
const emit = defineEmits<{
update: [value: number]
delete: []
}>()
// 响应式数据
const message = ref('Hello World')
const items = ref<string[]>([])
// 计算属性
const doubledCount = computed(() => props.count * 2)
// 方法
const handleClick = () => {
emit('update', props.count + 1)
}
// 生命周期
onMounted(() => {
console.log('组件已挂载')
})
</script>
<template>
<div class="my-component">
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<el-button @click="handleClick">
Count: {{ count }}
</el-button>
</div>
</template>
<style scoped lang="css">
.my-component {
/* 样式 */
}
</style>使用组合式函数
typescript
// app/composables/useCounter.ts
import { ref, computed } from 'vue'
export function useCounter(initialValue = 0) {
const count = ref(initialValue)
const doubledCount = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
const reset = () => count.value = initialValue
return {
count,
doubledCount,
increment,
decrement,
reset
}
}vue
<!-- 在组件中使用 -->
<script setup lang="ts">
const { count, increment, decrement, reset } = useCounter(0)
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<button @click="decrement">-</button>
<button @click="reset">Reset</button>
</div>
</template>API 请求封装
typescript
// app/composables/useApi.ts
export const useApi = () => {
const config = useRuntimeConfig()
const request = async <T>(url: string, options?: RequestInit): Promise<T> => {
try {
const response = await fetch(url, {
...options,
headers: {
'Content-Type': 'application/json',
...options?.headers
}
})
const data = await response.json()
if (!response.ok || !data.success) {
throw new Error(data.message || '请求失败')
}
return data.data
} catch (error) {
console.error('API 请求错误:', error)
throw error
}
}
return { request }
}页面开发
vue
<!-- app/pages/articles/[slug].vue -->
<script setup lang="ts">
// 获取路由参数
const route = useRoute()
const slug = route.params.slug
// 获取文章数据
const { data: article, pending, error } = await useFetch(`/api/articles/${slug}`)
// SEO 优化
useSeoMeta({
title: article.value?.title || '文章详情',
description: article.value?.summary,
ogTitle: article.value?.title,
ogImage: article.value?.cover_image
})
</script>
<template>
<div>
<div v-if="pending">加载中...</div>
<div v-else-if="error">加载失败</div>
<article v-else-if="article">
<h1>{{ article.title }}</h1>
<p>{{ article.summary }}</p>
<div v-html="article.content"></div>
</article>
</div>
</template>后端开发
API 路由开发
Nuxt 3 使用文件系统路由,API 文件放在 server/api/ 目录下。
typescript
// server/api/hello.get.ts
export default defineEventHandler(async (event) => {
return {
success: true,
message: 'Hello World'
}
})带参数的 API
typescript
// server/api/articles/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
// 获取文章
const article = await ArticleModel.findById(id)
if (!article) {
throw createError({
statusCode: 404,
statusMessage: '文章不存在'
})
}
return {
success: true,
data: article
}
})POST 请求
typescript
// server/api/articles/create.post.ts
export default defineEventHandler(async (event) => {
try {
// 读取请求体
const body = await readBody(event)
// 获取当前用户(需要认证中间件)
const user = event.context.user
// 参数验证
if (!body.title || !body.content) {
return {
success: false,
message: '标题和内容不能为空'
}
}
// 创建文章
const article = await ArticleModel.create({
...body,
author_id: user.userId
})
return {
success: true,
data: article,
message: '创建成功'
}
} catch (error) {
console.error('创建文章失败:', error)
return {
success: false,
message: '创建失败'
}
}
})中间件开发
typescript
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
// 跳过登录相关路径
if (['/api/auth/login'].includes(event.path)) {
return
}
// 获取 Token
const cookies = parseCookies(event)
const token = cookies.auth_token
if (token) {
const payload = verifyToken(token)
if (payload) {
// 验证用户状态
const user = await User.getSafeUserById(payload.userId)
if (user && user.status == 'active') {
event.context.user = user
return
}
}
}
// 认证失败
if (event.path.startsWith('/api/admin/')) {
throw createError({
statusCode: 401,
message: '无权限访问管理API'
})
}
})数据模型开发
typescript
// server/models/Article.ts
import { db } from 'server/database/config'
export const ArticleModel = {
// 查找所有文章
async findAll(limit: number = 20, offset: number = 0) {
const [rows] = await db.query(`
SELECT a.*, c.name as category_name
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
ORDER BY a.created_at DESC
LIMIT ? OFFSET ?
`, [limit, offset])
return rows
},
// 根据 ID 查找文章
async findById(id: number) {
const [rows] = await db.query(`
SELECT a.*, c.name as category_name
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
WHERE a.id = ?
`, [id])
return rows[0]
},
// 创建文章
async create(data: any) {
const [result] = await db.query(`
INSERT INTO articles (title, content, summary, author_id, category_id)
VALUES (?, ?, ?, ?, ?)
`, [data.title, data.content, data.summary, data.author_id, data.category_id])
return result.insertId
},
// 更新文章
async update(id: number, data: any) {
const [result] = await db.query(`
UPDATE articles
SET title = ?, content = ?, summary = ?, category_id = ?, updated_at = NOW()
WHERE id = ?
`, [data.title, data.content, data.summary, data.category_id, id])
return result.affectedRows > 0
},
// 删除文章
async delete(id: number) {
const [result] = await db.query('DELETE FROM articles WHERE id = ?', [id])
return result.affectedRows > 0
}
}数据库操作
使用 MySQL 插件
typescript
import { db } from 'server/database/config'
export default defineEventHandler(async (event) => {
// 查询
const [users] = await db.query('SELECT * FROM users WHERE status = ?', ['active'])
// 插入
const [result] = await db.query(
'INSERT INTO users (username, email, password) VALUES (?, ?, ?)',
['test', 'test@example.com', 'hashed_password']
)
// 更新
await db.query('UPDATE users SET nickname = ? WHERE id = ?', ['New Name', userId])
// 删除
await db.query('DELETE FROM users WHERE id = ?', [userId])
return { success: true }
})使用事务
typescript
import { db } from 'server/database/config'
export default defineEventHandler(async (event) => {
const connection = await db.getConnection()
try {
await connection.beginTransaction()
// 执行多个操作
await connection.query('INSERT INTO articles (...) VALUES (...)', [...])
await connection.query('UPDATE categories SET count = count + 1 WHERE id = ?', [categoryId])
await connection.commit()
return { success: true }
} catch (error) {
await connection.rollback()
throw error
} finally {
connection.release()
}
})代码规范
TypeScript 规范
typescript
// ✅ 好的做法
interface User {
id: number
username: string
email: string
}
const getUser = async (id: number): Promise<User> => {
// ...
}
// ❌ 避免
const getUser = async (id) => {
// 缺少类型注解
}命名规范
| 类型 | 规范 | 示例 |
|---|---|---|
| 组件 | PascalCase | UserProfile.vue |
| 工具函数 | camelCase | formatDate() |
| 常量 | UPPER_SNAKE_CASE | API_BASE_URL |
| 接口/类型 | PascalCase | UserProfile |
| 文件名 | kebab-case | user-profile.ts |
注释规范
typescript
/**
* 用户认证服务
* 提供用户登录、注册、Token 验证等功能
*/
export class AuthService {
/**
* 用户登录
* @param username 用户名
* @param password 密码
* @returns 登录结果
*/
static async login(username: string, password: string): Promise<LoginResult> {
// ...
}
}调试技巧
开发工具
bash
# 类型检查
pnpm run type-check
# 代码检查
pnpm run lint
# 自动修复
pnpm run lint:fix日志调试
typescript
export default defineEventHandler(async (event) => {
console.log('请求路径:', event.path)
console.log('请求参数:', getQuery(event))
console.log('请求体:', await readBody(event))
// ...
})测试
TODO: 添加测试框架和示例