Frontend Development Standards
Reference sources: Vue Official Style Guide, Element Plus best practices
UI Style Constraints
Strictly Prohibited (Common AI Style)
- ❌ Blue-purple neon gradients, glowing outlines, glassmorphism
- ❌ Large-area gradients, excessive decorative geometric shapes
- ❌ Cyberpunk style, dark tech style, AI-style UI
- ❌ Using emoji in UI copy
Admin System (Default Style)
| Element |
Requirement |
| Theme |
Use the component library's default theme |
| Color Scheme |
Primarily black, white, and gray + 1 accent color |
| Motion Effects |
Restrained, keep only necessary interaction feedback |
Tech Stack
| Tier |
Vue (Preferred) |
React (Alternative) |
| Framework |
Vue 3 + TypeScript |
React 18 + TypeScript |
| Build |
Vite |
Vite |
| Routing |
Vue Router 4 |
React Router 6 |
| State |
Pinia |
Zustand |
| UI Library |
Element Plus |
Ant Design |
Vue Coding Standards
Component Basics
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import type { User } from '@/types'
// Props & Emits
const props = defineProps<{ userId: number }>()
const emit = defineEmits<{ (e: 'update', value: string): void }>()
// 响应式状态
const loading = ref(false)
const user = ref<User | null>(null)
// 计算属性
const displayName = computed(() => user.value?.name ?? '未知用户')
// 生命周期
onMounted(async () => { await fetchUser() })
// 方法
async function fetchUser() {
loading.value = true
try {
user.value = await api.getUser(props.userId)
} finally {
loading.value = false
}
}
</script>
<template>
<div class="user-card">
<h3>{{ displayName }}</h3>
</div>
</template>
<style scoped>
.user-card { padding: 16px; }
</style>
Naming Conventions
| Type |
Convention |
Example |
| Component Files |
PascalCase.vue |
UserCard.vue |
| Composables |
useXxx.ts |
useAuth.ts |
| Store |
useXxxStore.ts |
useUserStore.ts |
State Management (Pinia)
// stores/user.ts
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const token = ref<string>('')
const isLoggedIn = computed(() => !!token.value)
async function login(username: string, password: string) {
const res = await api.login(username, password)
token.value = res.token
user.value = res.user
}
return { user, token, isLoggedIn, login }
})
Interaction State Handling
States That Must Be Handled: loading、empty、error、disabled、submitting
<template>
<el-skeleton v-if="loading" :rows="5" animated />
<el-result v-else-if="error" icon="error" :title="error">
<template #extra>
<el-button @click="fetchData">重试</el-button>
</template>
</el-result>
<el-empty v-else-if="list.length === 0" description="暂无数据" />
<template v-else>
<!-- 正常内容 -->
</template>
</template>
TypeScript Standards
// types/user.ts
export interface User {
id: number
username: string
role: 'admin' | 'user'
}
export interface ApiResponse<T = unknown> {
code: number
message: string
data: T
}
API Call Type Safety
| Rule |
Description |
| ✅ API utility functions support generics |
get<T>(url): Promise<T> instead of returning unknown |
| ✅ Specify generics or assertions at the call site |
get<UserInfo>(url) or data as typeof ref.value |
❌ Prohibit as any bypassing |
Masks type issues, causing pitfalls in later maintenance |
// ❌ get() 返回 unknown,赋值报 TS2322
const data = await get('/contact/config')
contact.value = data
// ❌ as any 绕过
contact.value = data as any
// ✅ 泛型约束(推荐)
const data = await get<ContactConfig>('/contact/config')
contact.value = data
// ✅ 类型断言(最小改动)
contact.value = data as typeof contact.value
Performance Optimization
| Scenario |
Solution |
| Large Lists |
Virtual scrolling |
| Routing |
Lazy loading () => import() |
| Computation |
Use computed caching |
| Large Data |
Use shallowRef |
// 路由懒加载
const routes = [
{ path: '/dashboard', component: () => import('@/views/Dashboard.vue') }
]
// 请求防抖
import { useDebounceFn } from '@vueuse/core'
const debouncedSearch = useDebounceFn((keyword) => api.search(keyword), 300)
Directory Structure
src/
├── assets/
│ └── styles/ # 全局/共享样式
├── api/ # API 请求
├── components/ # 通用组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia stores
├── types/ # TypeScript 类型
├── utils/ # 工具函数
├── views/ # 页面组件
├── App.vue
└── main.ts
Style Management Standards
| Rule |
Description |
❌ Prohibit writing large blocks of styles in .vue blocks must not exceed 20 lines |
<style> blocks must not exceed 20 lines |
❌ Prohibit writing large blocks of inline styles in .tsx style objects/CSS-in-JS must not exceed 20 lines |
Style objects/CSS-in-JS must not exceed 20 lines |
✅ Extract shared styles to src/assets/styles/ |
Split files by module |
| ✅ Keep only minimal styles inside components |
Vue: scoped fine-tuning; React: className references |
src/assets/styles/
├── variables.scss # 变量(颜色、间距、字号)
├── common.scss # 通用样式
└── [module].scss # 按模块拆分
Request Body Completeness Standards
| Rule |
Description |
| ❌ Prohibit omitting UI optional fields from the API |
All fields selected/entered by the user must be included in the request body |
| ✅ Submission functions correspond one-to-one with form fields |
Use TypeScript interfaces to constrain the request body |
// ❌ UI 有支付方式选择器,但请求体没传 payMethod
const payMethod = ref<'wechat' | 'points' | 'mixed'>('wechat')
async function createOrder() {
await api.createOrder({
items: orderItems.value,
addressId: selectedAddress.value.id,
// payMethod 忘记传了!支付方式选择 UI 形同虚设
})
}
// ✅ 请求体与 UI 表单字段对应
interface CreateOrderRequest {
items: OrderItem[]
addressId: number
payMethod: 'wechat' | 'points' | 'mixed' // 类型约束确保不遗漏
}
async function createOrder() {
const request: CreateOrderRequest = {
items: orderItems.value,
addressId: selectedAddress.value.id,
payMethod: payMethod.value, // TypeScript 会提示缺少字段
}
await api.createOrder(request)
}
API Error Handling Standards
| Rule |
Description |
| ❌ Silently ignoring non-success responses is prohibited |
res.code !== 200 the user must be prompted |
| ✅ Unified error prompt |
Non-success responses are unified with message.error prompt |
| ✅ Network exceptions must also be handled |
try/catch catch request exceptions |
// ❌ 只处理成功,非 200 静默忽略
const res = await api.getList(params)
if (res.code === 200) {
list.value = res.data
}
// ✅ 成功 + 失败都处理
try {
const res = await api.getList(params)
if (res.code === 200) {
list.value = res.data
} else {
message.error(res.message || '加载失败')
}
} catch (e) {
message.error('网络异常,请稍后重试')
}
Type reuse conventions
| Rule |
Description |
| ❌ Defining the same interface repeatedly in multiple files is prohibited |
PageResponse、BaseResult etc. |
✅ Common types are uniformly placed in @/types/common.ts |
global exports, referenced everywhere |
// ❌ 每个 api 文件都定义一遍
// api/user.ts
interface PageResponse<T> { list: T[]; total: number }
// api/order.ts
interface PageResponse<T> { list: T[]; total: number } // 重复
// ✅ 统一定义,各处引用
// types/common.ts
export interface PageResponse<T> {
list: T[]
total: number
}
// api/user.ts
import type { PageResponse } from '@/types/common'
Detailed reference
| File |
Content |
references/frontend-style.md |
UI style, Vue 3 conventions, Pinia, API encapsulation, performance optimization |
references/miniapp-pitfalls.md |
uni-app pitfalls: page stack read-only, Storage cleanup timing, lifecycle double triggering, frontend validation mirroring |
references/date-time.md |
dayjs/date-fns date addition and subtraction, billing period calculation, month-end alignment prohibited |
📋 This reply follows:frontend-dev - [specific section]