Search K
Appearance
👍欢迎大家积极投稿交流👍
如果文档内容陈旧或者链接失效,请发现后及时同步,我将尽快修改
👇微信👇

Appearance
本项目基于 UniApp + Vue3 + TypeScript + Vite 技术栈,采用现代化的JavaScript/TypeScript开发方式。本规范旨在确保代码的类型安全、可维护性和团队协作效率。
// .prettierrc.cjs
module.exports = {
printWidth: 100, // 行宽100字符
tabWidth: 2, // 缩进2个空格
useTabs: false, // 使用空格而非tab
semi: false, // 不使用分号
singleQuote: true, // 使用单引号
quoteProps: 'as-needed', // 对象属性按需引号
jsxSingleQuote: true, // JSX使用单引号
trailingComma: 'es5', // ES5尾随逗号
bracketSpacing: true, // 对象括号间空格
arrowParens: 'avoid', // 箭头函数参数括号
endOfLine: 'auto', // 行尾符号自动
}// ✅ 正确示例
import type { Ref } from 'vue'
import { ref, computed, watch, onMounted } from 'vue'
import { useUserStore } from '@/store/user'
import { getUserInfo } from '@/api/user'
import type { UserInfo } from '@/types/user'
interface Props {
userId: string
showAvatar?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showAvatar: true,
})
const userStore = useUserStore()
const userInfo: Ref<UserInfo | null> = ref(null)
const loading = ref(false)
const displayName = computed(() => {
return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
})
const fetchUserInfo = async () => {
try {
loading.value = true
const response = await getUserInfo(props.userId)
userInfo.value = response.data
} catch (error) {
console.error('获取用户信息失败:', error)
} finally {
loading.value = false
}
}
watch(
() => props.userId,
newUserId => {
if (newUserId) {
fetchUserInfo()
}
},
{ immediate: true }
)
onMounted(() => {
console.log('组件已挂载')
})// ✅ 推荐:使用明确的类型定义
interface UserInfo {
id: string
username: string
nickname?: string
avatar?: string
email: string
phone?: string
createdAt: string
updatedAt: string
}
// ✅ 推荐:使用联合类型
type UserStatus = 'active' | 'inactive' | 'pending' | 'banned'
// ✅ 推荐:使用泛型
interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
}
// ✅ 推荐:使用工具类型
type CreateUserRequest = Omit<UserInfo, 'id' | 'createdAt' | 'updatedAt'>
type UpdateUserRequest = Partial<Pick<UserInfo, 'nickname' | 'avatar' | 'email'>>// ✅ 推荐:组件Props类型定义
interface ComponentProps {
title: string
visible?: boolean
size?: 'small' | 'medium' | 'large'
onConfirm?: (data: any) => void
onCancel?: () => void
}
// ✅ 推荐:组件Emits类型定义
interface ComponentEmits {
(e: 'update:visible', visible: boolean): void
(e: 'confirm', data: any): void
(e: 'cancel'): void
}
// ✅ 推荐:Ref类型定义
const userList: Ref<UserInfo[]> = ref([])
const currentUser: Ref<UserInfo | null> = ref(null)
const loading: Ref<boolean> = ref(false)// ✅ 推荐:明确区分类型导入和值导入
import type { UserInfo, ApiResponse } from '@/types/user'
import type { ComponentProps } from '@/types/component'
import { ref, computed } from 'vue'
import { getUserInfo } from '@/api/user'
// ✅ 推荐:导出类型
export type { UserInfo, ApiResponse }
export interface ComponentInstance {
refresh: () => void
reset: () => void
}<script setup lang="ts">
// 1. 类型导入
import type { Ref } from 'vue'
import type { UserInfo } from '@/types/user'
// 2. 依赖导入
import { ref, computed, watch, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/store/user'
// 3. Props 定义
interface Props {
userId: string
showActions?: boolean
}
const props = withDefaults(defineProps<Props>(), {
showActions: true,
})
// 4. Emits 定义
interface Emits {
(e: 'user-loaded', user: UserInfo): void
(e: 'error', error: Error): void
}
const emit = defineEmits<Emits>()
// 5. 响应式数据
const userInfo: Ref<UserInfo | null> = ref(null)
const loading = ref(false)
const error: Ref<Error | null> = ref(null)
// 6. 计算属性
const displayName = computed(() => {
return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
})
const isVip = computed(() => {
return userInfo.value?.vipLevel && userInfo.value.vipLevel > 0
})
// 7. 方法定义
const fetchUserInfo = async () => {
try {
loading.value = true
error.value = null
const response = await getUserInfo(props.userId)
userInfo.value = response.data
emit('user-loaded', response.data)
} catch (err) {
error.value = err as Error
emit('error', err as Error)
console.error('获取用户信息失败:', err)
} finally {
loading.value = false
}
}
const handleRefresh = () => {
fetchUserInfo()
}
// 8. 监听器
watch(
() => props.userId,
(newUserId, oldUserId) => {
if (newUserId && newUserId !== oldUserId) {
fetchUserInfo()
}
},
{ immediate: true }
)
// 9. 生命周期
onMounted(() => {
console.log('用户信息组件已挂载')
})
// 10. 暴露给父组件
defineExpose({
refresh: handleRefresh,
userInfo: readonly(userInfo),
loading: readonly(loading),
})
</script>
<template>
<view class="user-info-container">
<!-- 模板内容 -->
</view>
</template>// composables/useUserInfo.ts
import type { Ref } from 'vue'
import { ref, computed } from 'vue'
import type { UserInfo } from '@/types/user'
import { getUserInfo } from '@/api/user'
interface UseUserInfoOptions {
immediate?: boolean
onSuccess?: (user: UserInfo) => void
onError?: (error: Error) => void
}
export function useUserInfo(userId: Ref<string> | string, options: UseUserInfoOptions = {}) {
const { immediate = false, onSuccess, onError } = options
const userInfo: Ref<UserInfo | null> = ref(null)
const loading = ref(false)
const error: Ref<Error | null> = ref(null)
const displayName = computed(() => {
return userInfo.value?.nickname || userInfo.value?.username || '未知用户'
})
const fetchUserInfo = async () => {
const id = unref(userId)
if (!id) return
try {
loading.value = true
error.value = null
const response = await getUserInfo(id)
userInfo.value = response.data
onSuccess?.(response.data)
} catch (err) {
error.value = err as Error
onError?.(err as Error)
} finally {
loading.value = false
}
}
const refresh = () => fetchUserInfo()
const reset = () => {
userInfo.value = null
error.value = null
loading.value = false
}
// 监听userId变化
watch(
() => unref(userId),
newId => {
if (newId) {
fetchUserInfo()
} else {
reset()
}
},
{ immediate }
)
return {
userInfo: readonly(userInfo),
loading: readonly(loading),
error: readonly(error),
displayName,
fetchUserInfo,
refresh,
reset,
}
}// store/user.ts
import { defineStore } from 'pinia'
import type { UserInfo, LoginRequest, LoginResponse } from '@/types/user'
import { login, getUserProfile, logout } from '@/api/auth'
import { removeToken, setToken } from '@/utils/auth'
interface UserState {
userInfo: UserInfo | null
token: string | null
isLoggedIn: boolean
permissions: string[]
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
userInfo: null,
token: null,
isLoggedIn: false,
permissions: [],
}),
getters: {
// ✅ 推荐:使用箭头函数定义getter
displayName: (state): string => {
return state.userInfo?.nickname || state.userInfo?.username || '未知用户'
},
isVip: (state): boolean => {
return Boolean(state.userInfo?.vipLevel && state.userInfo.vipLevel > 0)
},
// ✅ 推荐:带参数的getter
hasPermission: state => {
return (permission: string): boolean => {
return state.permissions.includes(permission)
}
},
},
actions: {
// ✅ 推荐:异步action使用async/await
async login(loginData: LoginRequest): Promise<LoginResponse> {
try {
const response = await login(loginData)
const { token, userInfo } = response.data
this.token = token
this.userInfo = userInfo
this.isLoggedIn = true
setToken(token)
return response
} catch (error) {
console.error('登录失败:', error)
throw error
}
},
async fetchUserProfile(): Promise<void> {
try {
const response = await getUserProfile()
this.userInfo = response.data
} catch (error) {
console.error('获取用户信息失败:', error)
throw error
}
},
async logout(): Promise<void> {
try {
await logout()
} catch (error) {
console.error('退出登录失败:', error)
} finally {
this.resetUserState()
}
},
resetUserState(): void {
this.userInfo = null
this.token = null
this.isLoggedIn = false
this.permissions = []
removeToken()
},
updateUserInfo(userInfo: Partial<UserInfo>): void {
if (this.userInfo) {
this.userInfo = { ...this.userInfo, ...userInfo }
}
},
},
// ✅ 推荐:使用持久化插件
persist: {
key: 'user-store',
storage: uni.getStorageSync
? {
getItem: (key: string) => uni.getStorageSync(key),
setItem: (key: string, value: string) => uni.setStorageSync(key, value),
removeItem: (key: string) => uni.removeStorageSync(key),
}
: localStorage,
paths: ['userInfo', 'token', 'isLoggedIn'],
},
})// http/alova.ts
import { createAlova } from 'alova'
import adapterUniapp from '@alova/adapter-uniapp'
import type { AlovaGenerics } from 'alova'
// ✅ 推荐:定义通用的API响应类型
export interface ApiResponse<T = any> {
code: number
message: string
data: T
success: boolean
timestamp: number
}
// ✅ 推荐:创建Alova实例
export const alovaInstance = createAlova({
baseURL: import.meta.env.VITE_API_BASE_URL,
statesHook: adapterUniapp.VueHook,
requestAdapter: adapterUniapp.uniappRequestAdapter(),
// 全局请求拦截器
beforeRequest(method) {
const token = uni.getStorageSync('token')
if (token) {
method.config.headers = {
...method.config.headers,
Authorization: `Bearer ${token}`,
}
}
},
// 全局响应拦截器
responded: {
onSuccess: async response => {
const data = response.data as ApiResponse
if (data.success) {
return data
} else {
// 处理业务错误
throw new Error(data.message || '请求失败')
}
},
onError: error => {
console.error('请求错误:', error)
// 处理HTTP错误
if (error.status === 401) {
// 处理未授权
uni.navigateTo({ url: '/pages/login/login' })
} else if (error.status === 403) {
// 处理权限不足
uni.showToast({ title: '权限不足', icon: 'none' })
}
throw error
},
},
})// api/user.ts
import type { Method } from 'alova'
import { alovaInstance } from '@/http/alova'
import type { ApiResponse } from '@/http/alova'
import type { UserInfo, LoginRequest, LoginResponse, UserListParams } from '@/types/user'
// ✅ 推荐:用户登录
export const login = (data: LoginRequest): Method<AlovaGenerics<any, any, LoginResponse>> => {
return alovaInstance.Post<ApiResponse<LoginResponse>>('/auth/login', data)
}
// ✅ 推荐:获取用户信息
export const getUserInfo = (userId: string): Method<AlovaGenerics<any, any, UserInfo>> => {
return alovaInstance.Get<ApiResponse<UserInfo>>(`/users/${userId}`)
}
// ✅ 推荐:获取用户列表(分页)
export const getUserList = (
params: UserListParams
): Method<AlovaGenerics<any, any, { list: UserInfo[]; total: number }>> => {
return alovaInstance.Get<ApiResponse<{ list: UserInfo[]; total: number }>>('/users', {
params,
})
}
// ✅ 推荐:更新用户信息
export const updateUserInfo = (
userId: string,
data: Partial<UserInfo>
): Method<AlovaGenerics<any, any, UserInfo>> => {
return alovaInstance.Put<ApiResponse<UserInfo>>(`/users/${userId}`, data)
}
// ✅ 推荐:删除用户
export const deleteUser = (userId: string): Method<AlovaGenerics<any, any, boolean>> => {
return alovaInstance.Delete<ApiResponse<boolean>>(`/users/${userId}`)
}// utils/index.ts
import type { PageMetaDatum } from '@uni-helper/vite-plugin-uni-pages'
// ✅ 推荐:类型安全的工具函数
export function parseUrlToObj(url: string): { path: string; query: Record<string, string> } {
const [path, queryStr] = url.split('?')
if (!queryStr) {
return { path, query: {} }
}
const query: Record<string, string> = {}
queryStr.split('&').forEach(item => {
const [key, value] = item.split('=')
if (key && value !== undefined) {
query[key] = ensureDecodeURIComponent(value)
}
})
return { path, query }
}
export function ensureDecodeURIComponent(url: string): string {
if (url.startsWith('%')) {
return ensureDecodeURIComponent(decodeURIComponent(url))
}
return url
}
// ✅ 推荐:泛型工具函数
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout | null = null
return function executedFunction(...args: Parameters<T>) {
const later = () => {
timeout = null
func(...args)
}
if (timeout) {
clearTimeout(timeout)
}
timeout = setTimeout(later, wait)
}
}
export function throttle<T extends (...args: any[]) => any>(
func: T,
limit: number
): (...args: Parameters<T>) => void {
let inThrottle: boolean = false
return function executedFunction(...args: Parameters<T>) {
if (!inThrottle) {
func(...args)
inThrottle = true
setTimeout(() => (inThrottle = false), limit)
}
}
}
// ✅ 推荐:类型守卫函数
export function isString(value: unknown): value is string {
return typeof value === 'string'
}
export function isNumber(value: unknown): value is number {
return typeof value === 'number' && !isNaN(value)
}
export function isObject(value: unknown): value is Record<string, any> {
return value !== null && typeof value === 'object' && !Array.isArray(value)
}// utils/error.ts
export class AppError extends Error {
public readonly code: string
public readonly statusCode: number
constructor(message: string, code: string = 'UNKNOWN_ERROR', statusCode: number = 500) {
super(message)
this.name = 'AppError'
this.code = code
this.statusCode = statusCode
}
}
export class ValidationError extends AppError {
constructor(message: string, field?: string) {
super(message, 'VALIDATION_ERROR', 400)
this.name = 'ValidationError'
}
}
export class NetworkError extends AppError {
constructor(message: string = '网络连接失败') {
super(message, 'NETWORK_ERROR', 0)
this.name = 'NetworkError'
}
}
// ✅ 推荐:错误处理函数
export function handleError(error: unknown): void {
console.error('应用错误:', error)
if (error instanceof AppError) {
uni.showToast({
title: error.message,
icon: 'none',
duration: 2000,
})
} else if (error instanceof Error) {
uni.showToast({
title: error.message || '操作失败',
icon: 'none',
duration: 2000,
})
} else {
uni.showToast({
title: '未知错误',
icon: 'none',
duration: 2000,
})
}
}
// ✅ 推荐:异步错误包装
export async function safeAsync<T>(
asyncFn: () => Promise<T>,
fallback?: T
): Promise<[T | null, Error | null]> {
try {
const result = await asyncFn()
return [result, null]
} catch (error) {
return [fallback || null, error as Error]
}
}/**
* 获取用户信息
* @param userId - 用户ID
* @param options - 可选配置
* @param options.includeProfile - 是否包含详细资料
* @param options.cache - 是否使用缓存
* @returns Promise<UserInfo> 用户信息
* @throws {ValidationError} 当用户ID无效时抛出
* @throws {NetworkError} 当网络请求失败时抛出
* @example
* ```typescript
* const user = await getUserInfo('123', { includeProfile: true })
* console.log(user.nickname)
* ```
*/
export async function getUserInfo(
userId: string,
options: {
includeProfile?: boolean
cache?: boolean
} = {}
): Promise<UserInfo> {
// 实现代码
}
/**
* 用户信息接口
* @interface UserInfo
*/
export interface UserInfo {
/** 用户ID */
id: string
/** 用户名 */
username: string
/** 昵称 */
nickname?: string
/** 头像URL */
avatar?: string
/** 邮箱地址 */
email: string
/** 手机号码 */
phone?: string
/** VIP等级 */
vipLevel: number
/** 创建时间 */
createdAt: string
/** 更新时间 */
updatedAt: string
}// ✅ 推荐:路由懒加载
const routes = [
{
path: '/user',
component: () => import('@/pages/user/index.vue'),
},
{
path: '/profile',
component: () => import('@/pages/profile/index.vue'),
},
]
// ✅ 推荐:组件懒加载
const LazyComponent = defineAsyncComponent(() => import('@/components/heavy-component.vue'))
// ✅ 推荐:条件导入
const loadChartLibrary = async () => {
if (process.env.NODE_ENV === 'development') {
const { Chart } = await import('chart.js')
return Chart
}
return null
}// ✅ 推荐:使用shallowRef优化大对象
const largeData = shallowRef<LargeDataType>({})
// ✅ 推荐:使用markRaw标记不需要响应式的对象
const staticConfig = markRaw({
apiEndpoints: {
user: '/api/users',
order: '/api/orders',
},
})
// ✅ 推荐:使用computed缓存计算结果
const expensiveComputed = computed(() => {
return heavyCalculation(props.data)
})
// ✅ 推荐:使用watchEffect优化副作用
watchEffect(() => {
if (userStore.isLoggedIn) {
fetchUserData()
}
})// __tests__/utils/index.test.ts
import { describe, it, expect } from 'vitest'
import { parseUrlToObj, debounce } from '@/utils/index'
describe('parseUrlToObj', () => {
it('should parse URL with query parameters', () => {
const result = parseUrlToObj('/pages/user?id=123&name=test')
expect(result).toEqual({
path: '/pages/user',
query: { id: '123', name: 'test' },
})
})
it('should handle URL without query parameters', () => {
const result = parseUrlToObj('/pages/user')
expect(result).toEqual({
path: '/pages/user',
query: {},
})
})
})
describe('debounce', () => {
it('should debounce function calls', async () => {
let callCount = 0
const debouncedFn = debounce(() => {
callCount++
}, 100)
debouncedFn()
debouncedFn()
debouncedFn()
expect(callCount).toBe(0)
await new Promise(resolve => setTimeout(resolve, 150))
expect(callCount).toBe(1)
})
})项目使用的主要ESLint规则:
// eslint.config.mjs
export default uniHelper({
unocss: true,
vue: true,
rules: {
'no-console': 'off', // 允许console
'no-unused-vars': 'off', // 关闭未使用变量检查
'vue/no-unused-refs': 'off', // 关闭未使用ref检查
'unused-imports/no-unused-vars': 'off', // 关闭未使用导入检查
'ts/no-empty-object-type': 'off', // 允许空对象类型
'vue/block-order': [
'error',
{
// Vue SFC块顺序
order: [['script', 'template'], 'style'],
},
],
},
})anyref、reactive、computed等响应式API