前端开发规范 (Frontend Development)
定位:Vue 3 + TypeScript 企业级前端开发标准,确保类型安全、可维护性。
技术选型
| 层级 |
技术栈 |
版本要求 |
选型理由 |
| 构建工具 |
Vite |
≥ 5.0 |
热更新秒级体验,比 Webpack 快 |
| 框架 |
Vue 3 |
≥ 3.3 |
Composition API、Vapor Mode 前景好 |
| 类型系统 |
TypeScript |
≥ 5.0 |
必须开启 strict 模式 |
| 状态管理 |
Pinia |
≥ 2.0 |
体积仅 1.5KB,TS 支持好 |
| 路由 |
Vue Router |
≥ 4.0 |
类型安全的路由配置 |
| UI 框架 (后端) |
Element Plus |
≥ 2.0 |
企业级组件库,完善生态 |
| UI 框架 (前端) |
Vant 4 |
≥ 4.0 |
轻量移动端组件库,针对 H5 端优化 |
| 样式框架 (前端) |
UnoCSS |
≥ 0.60 |
原子化 CSS,按需生成,极致性能 |
| 动画库 |
GSAP |
≥ 3.0 |
高性能 JS 动画,时间线控制,ScrollTrigger 等插件 |
| 3D 渲染 |
Three.js |
≥ 0.160 |
WebGL 3D 图形库,场景/相机/材质/光照完备体系 |
| 包管理 |
pnpm |
≥ 8.0 |
磁盘空间高效、依赖管理严格 |
⚠️ 注意:Vuex 5 开发基本停滞,新项目不推荐使用
项目结构
src/
├── api/ # API 接口层
│ ├── modules/ # 按业务模块拆分
│ │ ├── user.ts
│ │ └── order.ts
│ └── index.ts
├── components/ # 全局通用组件
│ ├── base/ # 基础组件 (BaseInput, BaseTable)
│ └── business/ # 业务通用组件
├── composables/ # 组合式函数(带响应式逻辑)
│ ├── useAuth.ts
│ └── useRequest.ts
├── utils/ # 纯工具函数(无响应式)
├── stores/ # Pinia 状态管理
│ ├── modules/
│ └── index.ts
├── types/ # 全局类型定义
│ ├── api.d.ts
│ └── global.d.ts
├── views/ # 页面组件(按业务模块拆分)
├── router/ # 路由配置
└── env.d.ts # .vue 文件类型声明
关键原则
| 目录 |
职责 |
composables/ |
响应式逻辑(如 useAuth、useRequest) |
utils/ |
纯函数(如 formatDate、debounce) |
types/ |
全局类型,组件内部类型可写在组件文件内 |
命名规范
| 类型 |
规范 |
示例 |
| 组合式函数 |
use + 功能名 |
useAuth、useRequest |
| Store |
功能名 + Store |
useUserStore |
| 组件 |
多词命名 (PascalCase) |
UserProfile、OrderList |
| 路由模块 |
功能域命名 |
userRoutes |
| 文件名 |
kebab-case |
user-profile.vue |
TypeScript 规范
tsconfig.json 关键配置
{
"compilerOptions": {
"strict": true,
"moduleResolution": "bundler",
"paths": { "@/*": ["./src/*"] }
}
}
Props 类型定义
interface Props {
title: string
count?: number
items: string[]
}
// 带默认值
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
})
Emits 类型定义(Vue 3.3+)
const emit = defineEmits<{
update: [value: string]
delete: [id: number]
}>()
.vue 文件类型声明
// src/env.d.ts
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
状态管理(Pinia)
推荐写法:Composition API 风格
export const useUserStore = defineStore('user', () => {
// state
const token = ref<string>('')
const userInfo = ref<UserInfo | null>(null)
// getters
const isLoggedIn = computed(() => !!token.value)
// actions(直接修改 state,无需 mutations)
const setToken = (newToken: string) => {
token.value = newToken
}
return { token, userInfo, isLoggedIn, setToken }
})
使用注意事项
// ❌ 错误:解构后失去响应式
const { userName, isLoggedIn } = userStore
// ✅ 正确:使用 storeToRefs 解构响应式属性
const { userName, isLoggedIn } = storeToRefs(userStore)
// ✅ actions 可以直接解构(普通函数)
const { logout, fetchUserInfo } = userStore
路由配置
declare module 'vue-router' {
interface RouteMeta {
title?: string
requiresAuth?: boolean
roles?: string[]
}
}
权限守卫
export function setupAuthGuard(router: Router) {
router.beforeEach((to, from, next) => {
const userStore = useUserStore()
if (!to.meta.requiresAuth) return next()
if (!userStore.isLoggedIn) {
return next({ path: '/login', query: { redirect: to.fullPath } })
}
next()
})
}
UI 框架规范
框架选择指南
| 场景 |
推荐框架 |
说明 |
| 后端管理系统 |
Element Plus |
表单、表格、弹窗等企业级组件 |
| 前台用户界面 |
UnoCSS |
灵活定制、原子化样式 |
| H5 移动端 |
Vant 4 |
轻量移动端组件库,针对 H5 端优化 |
| 混合项目 |
按需组合 |
按端类型选择(PC 端 Element Plus / H5 端 Vant 4) |
Element Plus(后端 UI)
适用于:后台管理系统、数据看板、表单密集型页面
按需引入
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
export default {
plugins: [
AutoImport({ resolvers: [ElementPlusResolver()] }),
Components({ resolvers: [ElementPlusResolver()] })
]
}
布局规范
| 规则 |
说明 |
| 全屏容器 |
width: 100%, min-height: 100vh, margin: 0 |
| 盒模型 |
全局启用 box-sizing: border-box |
| 滚动策略 |
严禁表格内固定高度滚动,使用原生页面级滚动 |
| 栅格系统 |
必须使用 el-row / el-col (:span="24") |
表格规范
<!-- 必须开启 stripe 属性 -->
<el-table :data="tableData" stripe>
<el-table-column prop="name" label="名称" />
</el-table>
Vant 4(H5 前端 UI)
适用于:移动端 H5 页面、微信内嵌页、移动端表单/列表/轻交互
安装配置
// vite.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from 'unplugin-vue-components/resolvers'
export default {
plugins: [
AutoImport({ resolvers: [VantResolver()] }),
Components({ resolvers: [VantResolver()] })
]
}
核心组件
<template>
<!-- 导航栏 -->
<van-nav-bar title="标题" left-text="返回" right-text="按钮" />
<!-- 表单 -->
<van-form @submit="onSubmit">
<van-field v-model="username" name="用户名" label="用户名" placeholder="请输入用户名" />
<van-field v-model="password" type="password" name="密码" label="密码" placeholder="请输入密码" />
<div style="margin: 16px;">
<van-button round block type="primary" native-type="submit">提交</van-button>
</div>
</van-form>
<!-- 单元格列表 -->
<van-cell-group>
<van-cell title="设置" is-link />
<van-cell title="关于" is-link />
</van-cell-group>
<!-- 弹出层 -->
<van-popup v-model:show="showPopup" position="bottom" round>
<div class="p-4">底部弹出内容</div>
</van-popup>
</template>
H5 适配(Viewport + rem/px)
// 方案一:postcss-pxtorem(推荐)
pnpm add -D postcss-pxtorem
// postcss.config.js
export default {
plugins: {
'postcss-pxtorem': {
rootValue: 37.5, // Vant 设计稿 375
propList: ['*'],
selectorBlackList: ['.norem']
}
}
}
使用规范
| 规则 |
说明 |
| 按需引入 |
使用 unplugin-vue-components 自动按需加载,禁止全局引入 |
| 主题定制 |
通过 CSS 变量覆盖 --van-primary-color 等 |
| 设计稿 |
Vant 基于 375px 设计稿,使用 postcss-pxtorem 适配 |
| 与 Element Plus 共存 |
同一项目按端分离,H5 路由页面仅用 Vant,PC 路由页面仅用 Element Plus |
UnoCSS(前端 UI)
适用于:用户前台、营销页面、高度定制化界面
UnoCSS 是原子化 CSS 引擎,相比 Tailwind CSS 无需解析、按需生成、无核心工具集,构建性能更高。
安装配置
// vite.config.ts
import UnoCSS from 'unocss/vite'
export default {
plugins: [UnoCSS()]
}
// uno.config.ts
import { defineConfig, presetUno, presetAttributify } from 'unocss'
export default defineConfig({
presets: [
presetUno(),
presetAttributify() // 支持属性模式:<button uno-text="red">
],
shortcuts: [
['btn', 'px-4 py-2 rounded-lg font-medium transition-colors'],
['btn-primary', 'btn bg-blue-500 text-white hover:bg-blue-600'],
],
theme: {
colors: {
primary: '#3b82f6',
secondary: '#64748b',
}
}
})
// main.ts
import 'virtual:uno.css'
核心用法
<template>
<!-- 布局 -->
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow">
<h1 class="text-2xl font-bold text-gray-900">标题</h1>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
按钮
</button>
</div>
<!-- 响应式 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<div class="p-4 bg-gray-100">卡片</div>
</div>
<!-- 使用 shortcuts -->
<button class="btn-primary">提交</button>
</template>
常用工具类(与 Tailwind CSS 兼容)
| 类别 |
示例 |
| 布局 |
flex, grid, items-center, justify-between |
| 间距 |
p-4, m-2, gap-4, space-x-2 |
| 尺寸 |
w-full, h-screen, max-w-xl |
| 文字 |
text-lg, font-bold, text-gray-500 |
| 背景 |
bg-white, bg-blue-500, bg-opacity-50 |
| 边框 |
border, rounded-lg, shadow-md |
| 响应式 |
md:flex, lg:grid-cols-3 |
自定义快捷方式
// uno.config.ts
export default defineConfig({
shortcuts: [
// 静态快捷方式
['btn-primary', 'px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600'],
// 动态快捷方式
[/^btn-(.*)$/, ([, c]) => `bg-${c}-500 text-white px-4 py-2 rounded hover:bg-${c}-600`],
]
})
与 Vue 组件结合
<script setup lang="ts">
const props = withDefaults(defineProps<{
variant?: 'primary' | 'secondary'
}>(), {
variant: 'primary'
})
</script>
<template>
<button
class="px-4 py-2 rounded-lg text-white transition-colors"
:class="{
'bg-blue-500 hover:bg-blue-600': variant === 'primary',
'bg-gray-500 hover:bg-gray-600': variant === 'secondary'
}"
>
<slot />
</button>
</template>
GSAP(动画库)
适用于:页面滚动动画、元素进入/离开过渡、复杂时间线动画、SVG 动画
安装配置
基础用法
import gsap from 'gsap'
// 带动画的 ref
const boxRef = ref<HTMLElement>()
onMounted(() => {
gsap.from(boxRef.value, {
duration: 1,
x: -100,
opacity: 0,
ease: 'power2.out'
})
})
pnpm add gsap @gsap/scrolltrigger
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
onMounted(() => {
gsap.from('.card', {
scrollTrigger: '.card',
y: 50,
opacity: 0,
duration: 0.8,
stagger: 0.2
})
})
使用规范
| 规则 |
说明 |
| 清理 |
onUnmounted 中使用 gsap.killTweensOf(ref) 清理动画 |
| 响应式 |
动画参数使用 ref 绑定,避免硬编码数值 |
| 性能 |
尽量使用 transform 和 opacity,避免触发重排的属性 |
Three.js(3D 渲染)
适用于:3D 产品展示、数据可视化、交互式场景、品牌动效
安装配置
pnpm add three
pnpm add -D @types/three
基础场景
import * as THREE from 'three'
const containerRef = ref<HTMLElement>()
onMounted(() => {
// 场景 / 相机 / 渲染器
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(75, containerRef.value!.clientWidth / containerRef.value!.clientHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
renderer.setSize(containerRef.value!.clientWidth, containerRef.value!.clientHeight)
containerRef.value!.appendChild(renderer.domElement)
// 添加几何体
const geometry = new THREE.BoxGeometry()
const material = new THREE.MeshStandardMaterial({ color: 0x3b82f6 })
const cube = new THREE.Mesh(geometry, material)
scene.add(cube)
// 光照
const light = new THREE.DirectionalLight(0xffffff, 1)
light.position.set(5, 5, 5)
scene.add(light)
camera.position.z = 5
// 渲染循环
const animate = () => {
requestAnimationFrame(animate)
cube.rotation.x += 0.01
cube.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()
// 响应式
window.addEventListener('resize', () => {
camera.aspect = containerRef.value!.clientWidth / containerRef.value!.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(containerRef.value!.clientWidth, containerRef.value!.clientHeight)
})
})
<template>
<div ref="containerRef" class="w-full h-96" />
</template>
使用规范
| 规则 |
说明 |
| 清理 |
onUnmounted 中调用 renderer.dispose() 释放 GPU 资源 |
| 按需加载 |
Three.js 体积大,使用动态 import() 按路由懒加载 |
| 性能 |
控制几何体面数,使用 requestAnimationFrame 而非 setInterval |
常见陷阱
文件上传重置
handleFileChange(e: Event) {
// 处理逻辑...
e.target.value = '' // 必须重置,否则重复选择同一文件不触发
}
Markdown 渲染
const renderMarkdown = (text: string): string => {
if (!text) return ''
let html = text.replace(/\n/g, '<br>')
html = html.replace(/<br>- /g, '<br><span class="md-list-item">• </span>')
html = html.replace(/`([^`]+)`/g, '<code class="md-code">$1</code>')
return html
}
工程化配置
ESLint 9(Flat Config)
// eslint.config.js
export default [
js.configs.recommended,
...vue.configs['flat/recommended'],
{
files: ['**/*.{ts,tsx,vue}'],
languageOptions: {
parser: vueParser,
parserOptions: { parser: tsParser }
}
}
]
自动导入
// vite.config.ts
AutoImport({
imports: ['vue', 'vue-router', 'pinia'],
dts: 'src/auto-imports.d.ts'
})
配置后可直接使用 ref()、computed() 无需手动 import。
沉淀协议
| 场景 |
行动 |
| 新组件模式重复 ≥ 3 次 |
提取到 components/base/ |
| API 调用模式重复 ≥ 3 次 |
封装到 composables/useRequest.ts |
| 样式规范固化 |
更新本文档 |
多项目共用依赖 (Monorepo 模式)
适用场景:web/ 目录下多个 Vue 3 项目共用一套依赖,避免重复安装。
目录结构
web/
├── node_modules/ # 共用依赖 (Vue 3, Pinia, Vue Router 等)
├── package.json # 共用依赖声明
├── pnpm-workspace.yaml # pnpm 工作区配置
├── .vue-global/ # (可选) 全局类型声明、共享组件
│ └── types/
│ └── global.d.ts
├── project-a/ # 独立项目 A
│ ├── package.json # 项目特有依赖
│ ├── vite.config.ts
│ └── src/
└── project-b/ # 独立项目 B
├── package.json
├── vite.config.ts
└── src/
pnpm 工作区配置
# web/pnpm-workspace.yaml
packages:
- 'project-a'
- 'project-b'
共用依赖 package.json
{
"name": "aiasme-web-workspace",
"private": true,
"dependencies": {
"vue": "^3.4.0",
"vue-router": "^4.3.0",
"pinia": "^2.1.0",
"element-plus": "^2.6.0"
},
"devDependencies": {
"vite": "^5.4.0",
"typescript": "^5.4.0",
"@vitejs/plugin-vue": "^5.0.0"
}
}
子项目 package.json
{
"name": "project-a",
"version": "1.0.0",
"dependencies": {
"vue": "workspace:*"
},
"devDependencies": {
"vite": "workspace:*",
"typescript": "workspace:*"
}
}
使用规范
| 规则 |
说明 |
| 共用依赖位置 |
web/package.json 声明 Vue 3 核心生态库 |
| 项目特有依赖 |
子项目 package.json 仅声明业务特有依赖 |
| 版本锁定 |
使用 workspace:* 引用工作区版本,确保一致 |
| 安装命令 |
在 web/ 目录执行 pnpm install |
| 独立运行 |
子项目可独立 pnpm dev,自动使用共享依赖 |
vite.config.ts 配置
// web/project-a/vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
// 确保解析到工作区根目录的 node_modules
resolve: {
dedupe: ['vue', 'vue-router', 'pinia']
}
})
新建 Web 项目流程
- 在
web/ 下创建项目目录
- 创建
package.json,使用 workspace:* 引用共享依赖
- 配置
vite.config.ts,添加 dedupe 防止重复打包
- 在
web/ 根目录执行 pnpm install
禁止事项
| 禁止 |
原因 |
| 子项目独立安装 Vue 3 |
导致版本冲突、包体积膨胀 |
省略 dedupe 配置 |
Vite 可能重复打包 Vue |
| 不同项目使用不同 Vue 版本 |
破坏共享依赖一致性 |