34 KiB
34 KiB
健身房管理系统前端技术架构详细设计文档
文档编号: GYM-FE-ARCH-001
版本: v1.0
日期: 2026-03-04
作者: 张翔
状态: 正式发布
文档修订历史
| 版本 | 日期 | 作者 | 修订内容 |
|---|---|---|---|
| v1.0 | 2026-03-04 | 张翔 | 创建前端技术架构详细设计 |
参考文档
- 《健身房管理系统基础版产品设计文档》 GYM-PRD-BASIC-001
- 《健身房管理系统基础版业务概要设计文档》 GYM-B-HLD-BASIC-001
- 《健身房管理系统基础版业务详细设计文档》 GYM-B-LLD-BASIC-001
- Vue 3 官方文档
- uniapp 官方文档
- TypeScript 官方文档
一、架构概述
1.1 架构目标
- 跨平台覆盖:支持小程序、App、PC多端,满足不同用户角色需求
- 高性能:首屏加载时间 < 2s,交互响应时间 < 100ms
- 高安全性:符合金融级安全标准,保障用户数据和交易安全
- 高可维护性:代码结构清晰,模块化设计,便于团队协作
- 高扩展性:支持订阅模块动态加载,适应业务快速变化
1.2 客户端架构
graph LR
subgraph 前端客户端架构
A[会员小程序 uniapp+Vue3<br/>• 会员注册/登录<br/>• 课程预约<br/>• 扫码签到<br/>• 会员卡管理<br/>• 个人中心<br/>• 消息通知<br/>• 数据统计]
B[教练端App uniapp+Vue3<br/>• 课程管理<br/>• 排班管理<br/>• 会员管理<br/>• 签到管理<br/>• 数据统计<br/>• 消息通知<br/>• 个人中心]
C[管理后台PC Vue3+Vite+Element Plus<br/>• 会员管理<br/>• 课程管理<br/>• 预约管理<br/>• 签到管理<br/>• 财务管理<br/>• 数据统计<br/>• 系统管理<br/>• 订阅管理]
end
1.3 技术栈选型
1.3.1 核心框架
| 技术 | 版本 | 用途 | 选型理由 |
|---|---|---|---|
| Vue 3 | 3.4+ | 前端框架 | Composition API、响应式系统、生态成熟 |
| uniapp | 3.0+ | 跨平台框架 | 一次开发多端部署、性能优秀、生态完善 |
| TypeScript | 5.0+ | 类型系统 | 类型安全、开发体验、代码可维护性 |
| Vite | 5.0+ | 构建工具 | 快速热更新、优化的构建性能、插件生态 |
1.3.2 状态管理
| 技术 | 版本 | 用途 | 选型理由 |
|---|---|---|---|
| Pinia | 2.1+ | 状态管理 | Vue3官方推荐、API简洁、TypeScript支持好、模块化设计 |
1.3.3 UI组件库
| 技术 | 版本 | 用途 | 选型理由 |
|---|---|---|---|
| Element Plus | 2.5+ | PC端UI组件 | 功能完善、设计规范、TypeScript支持、文档齐全 |
| uni-ui | 1.5+ | 小程序/App UI组件 | 官方组件库、跨平台兼容、性能优化 |
1.3.4 工具库
| 技术 | 版本 | 用途 | 选型理由 |
|---|---|---|---|
| Vue Router | 4.2+ | 路由管理 | Vue官方路由、动态路由、路由守卫 |
| Axios | 1.6+ | HTTP客户端 | 拦截器、请求取消、TypeScript支持 |
| Day.js | 1.11+ | 日期处理 | 轻量级、API友好、国际化支持 |
| Lodash-es | 4.17+ | 工具函数 | 按需加载、性能优化、功能完善 |
1.3.5 开发工具
| 技术 | 版本 | 用途 | 选型理由 |
|---|---|---|---|
| ESLint | 8.56+ | 代码检查 | 规则完善、插件生态、自动修复 |
| Prettier | 3.1+ | 代码格式化 | 统一风格、配置简单、编辑器集成 |
| Husky | 8.0+ | Git钩子 | 提交前检查、自动化流程 |
| Commitlint | 18.4+ | 提交规范 | 规范提交信息、自动化生成Changelog |
二、架构设计
2.1 分层架构
flowchart TB
subgraph 前端分层架构
A[表现层 Presentation Layer<br/>• 页面组件<br/>• 业务组件<br/>• 基础组件<br/>• 布局组件]
B[状态管理层 State Management Layer<br/>• 全局状态<br/>• 模块状态<br/>• 组件状态<br/>• 持久化状态]
C[业务逻辑层 Business Logic Layer<br/>• Composables<br/>• Hooks<br/>• Utils<br/>• Validators]
D[数据访问层 Data Access Layer<br/>• API Service<br/>• WebSocket<br/>• Cache<br/>• Storage]
E[基础设施层 Infrastructure Layer<br/>• 路由<br/>• 拦截器<br/>• 错误处理<br/>• 日志<br/>• 监控]
A --> B
B --> C
C --> D
D --> E
end
2.2 模块划分
2.2.1 会员端模块
会员端 (Member Mini Program)
├── 认证模块 (Auth)
│ ├── 登录/注册
│ ├── 手机号验证
│ ├── 微信授权
│ └── 忘记密码
├── 会员模块 (Member)
│ ├── 个人信息
│ ├── 会员卡管理
│ ├── 权益管理
│ └── 等级体系
├── 预约模块 (Booking)
│ ├── 团课列表
│ ├── 课程详情
│ ├── 预约操作
│ └── 预约记录
├── 签到模块 (CheckIn)
│ ├── 扫码签到
│ ├── 签到记录
│ └── 签到统计
├── 消息模块 (Message)
│ ├── 系统通知
│ ├── 预约提醒
│ └── 消息中心
└── 个人中心 (Profile)
├── 设置
├── 帮助
└── 关于
2.2.2 教练端模块
教练端 (Coach App)
├── 认证模块 (Auth)
│ ├── 登录
│ └── 权限验证
├── 课程模块 (Course)
│ ├── 课程管理
│ ├── 排班管理
│ └── 课程统计
├── 会员模块 (Member)
│ ├── 会员列表
│ ├── 会员详情
│ └── 会员跟进
├── 签到模块 (CheckIn)
│ ├── 签到管理
│ ├── 代签操作
│ └── 签到统计
├── 数据模块 (Data)
│ ├── 课程数据
│ ├── 会员数据
│ └── 收入数据
└── 个人中心 (Profile)
├── 个人信息
├── 设置
└── 帮助
2.2.3 管理后台模块
管理后台 (Admin PC)
├── 认证模块 (Auth)
│ ├── 登录
│ ├── 权限管理
│ └── 角色管理
├── 会员模块 (Member)
│ ├── 会员管理
│ ├── 会员卡管理
│ ├── 权益管理
│ └── 等级管理
├── 课程模块 (Course)
│ ├── 课程管理
│ ├── 时段管理
│ └── 排班管理
├── 预约模块 (Booking)
│ ├── 预约管理
│ ├── 候补管理
│ └── 预约统计
├── 签到模块 (CheckIn)
│ ├── 签到记录
│ ├── 设备管理
│ └── 签到统计
├── 财务模块 (Finance)
│ ├── 订单管理
│ ├── 收入统计
│ └── 财务报表
├── 数据模块 (Data)
│ ├── 数据统计
│ ├── 数据分析
│ └── 报表导出
├── 系统模块 (System)
│ ├── 用户管理
│ ├── 角色权限
│ ├── 系统配置
│ └── 操作日志
└── 订阅模块 (Subscription)
├── 订阅管理
├── 模块管理
└── 计费管理
2.3 目录结构
2.3.1 会员端/教练端目录结构
src/
├── api/ # API接口
│ ├── modules/
│ │ ├── auth.ts
│ │ ├── member.ts
│ │ ├── booking.ts
│ │ └── checkin.ts
│ └── request.ts # Axios封装
├── assets/ # 静态资源
│ ├── images/
│ ├── icons/
│ └── styles/
├── components/ # 组件
│ ├── base/ # 基础组件
│ ├── business/ # 业务组件
│ └── layout/ # 布局组件
├── composables/ # Composables
│ ├── useAuth.ts
│ ├── useBooking.ts
│ └── useCheckIn.ts
├── config/ # 配置
│ ├── app.config.ts
│ └── env.config.ts
├── hooks/ # Hooks
│ ├── useRequest.ts
│ └── useWebSocket.ts
├── pages/ # 页面
│ ├── index/
│ ├── auth/
│ ├── member/
│ ├── booking/
│ └── checkin/
├── stores/ # 状态管理
│ ├── auth.ts
│ ├── member.ts
│ ├── booking.ts
│ └── checkin.ts
├── types/ # 类型定义
│ ├── api.ts
│ ├── models.ts
│ └── index.ts
├── utils/ # 工具函数
│ ├── validator.ts
│ ├── formatter.ts
│ └── storage.ts
├── App.vue
└── main.ts
2.3.2 管理后台目录结构
src/
├── api/ # API接口
│ ├── modules/
│ │ ├── auth.ts
│ │ ├── member.ts
│ │ ├── course.ts
│ │ ├── booking.ts
│ │ ├── checkin.ts
│ │ ├── finance.ts
│ │ └── system.ts
│ └── request.ts # Axios封装
├── assets/ # 静态资源
│ ├── images/
│ ├── icons/
│ └── styles/
├── components/ # 组件
│ ├── base/ # 基础组件
│ ├── business/ # 业务组件
│ └── layout/ # 布局组件
├── composables/ # Composables
│ ├── useAuth.ts
│ ├── usePermission.ts
│ └── useTable.ts
├── config/ # 配置
│ ├── app.config.ts
│ └── env.config.ts
├── directives/ # 自定义指令
│ ├── permission.ts
│ └── loading.ts
├── hooks/ # Hooks
│ ├── useRequest.ts
│ └── useWebSocket.ts
├── layouts/ # 布局
│ ├── BasicLayout.vue
│ └── BlankLayout.vue
├── router/ # 路由
│ ├── index.ts
│ └── modules/
│ ├── auth.ts
│ ├── member.ts
│ ├── course.ts
│ └── system.ts
├── stores/ # 状态管理
│ ├── auth.ts
│ ├── permission.ts
│ ├── app.ts
│ └── user.ts
├── types/ # 类型定义
│ ├── api.ts
│ ├── models.ts
│ └── index.ts
├── utils/ # 工具函数
│ ├── validator.ts
│ ├── formatter.ts
│ ├── storage.ts
│ └── permission.ts
├── views/ # 页面
│ ├── auth/
│ ├── member/
│ ├── course/
│ ├── booking/
│ ├── checkin/
│ ├── finance/
│ ├── data/
│ ├── system/
│ └── subscription/
├── App.vue
└── main.ts
三、数据流设计
3.1 单向数据流
┌─────────────────────────────────────────────────────────────────────────┐
│ 单向数据流设计 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户 │ ──▶│ 组件 │ ──▶│ Store │ ──▶│ API │ │
│ │ 操作 │ │ Action │ │ State │ │ Service │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ │ │ │ ▼ │
│ │ │ │ ┌─────────┐ │
│ │ │ │ │ 后端API │ │
│ │ │ │ └─────────┘ │
│ │ │ │ │ │
│ │ │ │ ▼ │
│ │ │ │ ┌─────────┐ │
│ │ │ │ │ 数据库 │ │
│ │ │ │ └─────────┘ │
│ │ │ │ │ │
│ │ │ │ ▼ │
│ │ │ │ ┌─────────┐ │
│ │ │ │ │ 返回数据 │ │
│ │ │ │ └─────────┘ │
│ │ │ │ │ │
│ │ │ ▼ │ │
│ │ │ ┌─────────┐ │ │
│ │ │ │ 更新 │◀─────────┘ │
│ │ │ │ State │ │
│ │ │ └─────────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌─────────────────────────┐ │
│ │ │ 重新渲染组件 │ │
│ │ └─────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────┐ │
│ │ 更新UI显示 │ │
│ └─────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
3.2 状态管理策略
3.2.1 状态分类
| 状态类型 | 存储位置 | 持久化 | 示例 |
|---|---|---|---|
| 全局状态 | Pinia Store | 是 | 用户信息、权限、配置 |
| 模块状态 | Pinia Store | 否 | 当前页面数据、表单数据 |
| 组件状态 | Component State | 否 | 弹窗显示、加载状态 |
| 临时状态 | Component State | 否 | 输入框值、选择项 |
3.2.2 状态持久化
// stores/auth.ts
import { defineStore } from 'pinia'
import { ref } from 'vue'
export const useAuthStore = defineStore('auth', () => {
const token = ref<string>('')
const user = ref<User | null>(null)
// 持久化到localStorage
const $persist = () => {
localStorage.setItem('auth_token', token.value)
localStorage.setItem('auth_user', JSON.stringify(user.value))
}
const login = async (credentials: LoginRequest) => {
const response = await api.login(credentials)
token.value = response.token
user.value = response.user
$persist()
}
const logout = () => {
token.value = ''
user.value = null
localStorage.removeItem('auth_token')
localStorage.removeItem('auth_user')
}
return { token, user, login, logout }
})
3.3 实时数据处理
3.3.1 WebSocket连接管理
// hooks/useWebSocket.ts
import { ref, onUnmounted } from 'vue'
export function useWebSocket(url: string) {
const ws = ref<WebSocket | null>(null)
const connected = ref(false)
const messageHandler = ref<(data: any) => void>(() => {})
const connect = () => {
ws.value = new WebSocket(url)
ws.value.onopen = () => {
connected.value = true
startHeartbeat()
}
ws.value.onmessage = (event) => {
const data = JSON.parse(event.data)
messageHandler.value(data)
}
ws.value.onclose = () => {
connected.value = false
reconnect()
}
ws.value.onerror = (error) => {
console.error('WebSocket error:', error)
}
}
const disconnect = () => {
if (ws.value) {
ws.value.close()
ws.value = null
}
}
const send = (data: any) => {
if (ws.value && connected.value) {
ws.value.send(JSON.stringify(data))
}
}
const reconnect = () => {
setTimeout(() => {
connect()
}, 3000)
}
const startHeartbeat = () => {
setInterval(() => {
send({ type: 'ping' })
}, 30000)
}
onUnmounted(() => {
disconnect()
})
return {
connected,
connect,
disconnect,
send,
onMessage: (handler: (data: any) => void) => {
messageHandler.value = handler
}
}
}
3.3.2 数据更新优化
// composables/useRealTimeData.ts
import { ref, computed } from 'vue'
import { useWebSocket } from '@/hooks/useWebSocket'
export function useRealTimeData<T>(initialData: T, wsUrl: string) {
const data = ref<T>(initialData)
const { connected, connect, send, onMessage } = useWebSocket(wsUrl)
const updateData = (newData: Partial<T>) => {
data.value = { ...data.value, ...newData }
}
const subscribe = (channel: string) => {
onMessage((message) => {
if (message.channel === channel) {
// 增量更新,减少重渲染
updateData(message.data)
}
})
}
return {
data,
connected,
connect,
subscribe
}
}
四、组件设计
4.1 组件分类
4.1.1 基础组件
| 组件名称 | 功能 | 适用端 |
|---|---|---|
| Button | 按钮 | 全端 |
| Input | 输入框 | 全端 |
| Select | 选择器 | 全端 |
| DatePicker | 日期选择器 | 全端 |
| Modal | 弹窗 | 全端 |
| Loading | 加载 | 全端 |
| Empty | 空状态 | 全端 |
| ErrorPage | 错误页 | 全端 |
4.1.2 业务组件
| 组件名称 | 功能 | 适用端 |
|---|---|---|
| MemberCard | 会员卡展示 | 全端 |
| CourseCard | 课程卡片 | 全端 |
| BookingCard | 预约卡片 | 全端 |
| CheckInCode | 签到码 | 会员端/教练端 |
| QRCodeScanner | 二维码扫描 | 会员端/教练端 |
| DataChart | 数据图表 | 管理后台 |
| DataTable | 数据表格 | 管理后台 |
4.1.3 布局组件
| 组件名称 | 功能 | 适用端 |
|---|---|---|
| PageLayout | 页面布局 | 全端 |
| TabBar | 底部导航 | 会员端/教练端 |
| Sidebar | 侧边栏 | 管理后台 |
| Header | 顶部导航 | 管理后台 |
| Footer | 底部 | 管理后台 |
4.2 组件设计原则
4.2.1 单一职责原则
每个组件只负责一个功能,保持组件的简洁和可复用性。
<!-- Bad: 职责过多 -->
<template>
<div>
<input v-model="form.name" />
<input v-model="form.phone" />
<button @click="submit">提交</button>
<div v-if="showChart">
<chart :data="chartData" />
</div>
</div>
</template>
<!-- Good: 职责单一 -->
<template>
<member-form :model="form" @submit="handleSubmit" />
<data-chart v-if="showChart" :data="chartData" />
</template>
4.2.2 Props设计
// components/base/Button.vue
interface ButtonProps {
type?: 'primary' | 'secondary' | 'danger'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
loading?: boolean
block?: boolean
}
const props = withDefaults(defineProps<ButtonProps>(), {
type: 'primary',
size: 'medium',
disabled: false,
loading: false,
block: false
})
4.2.3 事件设计
// components/base/DatePicker.vue
const emit = defineEmits<{
change: [value: Date]
confirm: [value: Date]
cancel: []
}>()
// 使用
emit('change', new Date())
emit('confirm', new Date())
emit('cancel')
4.3 组件复用策略
4.3.1 跨端组件
使用uniapp的条件编译实现跨端组件:
<template>
<view class="button">
<!-- #ifdef MP-WEIXIN -->
<button open-type="getUserInfo" @getuserinfo="handleGetUserInfo">
{{ text }}
</button>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<button @click="handleClick">
{{ text }}
</button>
<!-- #endif -->
</view>
</template>
4.3.2 业务组件封装
<!-- components/business/CourseCard.vue -->
<template>
<view class="course-card" @click="handleClick">
<image :src="course.coverImage" mode="aspectFill" />
<view class="course-info">
<text class="course-name">{{ course.name }}</text>
<text class="course-time">{{ course.startTime }}</text>
<text class="course-capacity">
{{ course.bookedCount }}/{{ course.capacity }}
</text>
</view>
</view>
</template>
<script setup lang="ts">
interface Course {
id: number
name: string
coverImage: string
startTime: string
bookedCount: number
capacity: number
}
interface Props {
course: Course
}
const props = defineProps<Props>()
const emit = defineEmits<{
click: [course: Course]
}>()
const handleClick = () => {
emit('click', props.course)
}
</script>
五、路由设计
5.1 路由结构
5.1.1 会员端路由
// router/index.ts
const routes = [
{
path: '/pages/index/index',
name: 'Home',
meta: { title: '首页' }
},
{
path: '/pages/auth/login',
name: 'Login',
meta: { title: '登录' }
},
{
path: '/pages/member/profile',
name: 'Profile',
meta: { title: '个人中心', requiresAuth: true }
},
{
path: '/pages/booking/list',
name: 'BookingList',
meta: { title: '课程列表', requiresAuth: true }
},
{
path: '/pages/booking/detail',
name: 'BookingDetail',
meta: { title: '课程详情', requiresAuth: true }
},
{
path: '/pages/checkin/scan',
name: 'CheckInScan',
meta: { title: '扫码签到', requiresAuth: true }
}
]
5.1.2 管理后台路由
// router/index.ts
const routes = [
{
path: '/login',
name: 'Login',
component: () => import('@/views/auth/Login.vue'),
meta: { title: '登录' }
},
{
path: '/',
component: () => import('@/layouts/BasicLayout.vue'),
redirect: '/dashboard',
children: [
{
path: 'dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/Index.vue'),
meta: { title: '仪表盘', requiresAuth: true }
},
{
path: 'member',
name: 'Member',
redirect: '/member/list',
meta: { title: '会员管理', requiresAuth: true, permission: 'member:view' },
children: [
{
path: 'list',
name: 'MemberList',
component: () => import('@/views/member/List.vue'),
meta: { title: '会员列表' }
},
{
path: 'detail/:id',
name: 'MemberDetail',
component: () => import('@/views/member/Detail.vue'),
meta: { title: '会员详情' }
}
]
}
]
}
]
5.2 路由守卫
5.2.1 认证守卫
// router/guards/auth.ts
import { useAuthStore } from '@/stores/auth'
router.beforeEach((to, from, next) => {
const authStore = useAuthStore()
if (to.meta.requiresAuth && !authStore.token) {
next('/login')
} else {
next()
}
})
5.2.2 权限守卫
// router/guards/permission.ts
import { usePermissionStore } from '@/stores/permission'
router.beforeEach((to, from, next) => {
const permissionStore = usePermissionStore()
if (to.meta.permission && !permissionStore.hasPermission(to.meta.permission as string)) {
next('/403')
} else {
next()
}
})
5.3 动态路由
// router/dynamic.ts
import { usePermissionStore } from '@/stores/permission'
export function setupDynamicRoutes() {
const permissionStore = usePermissionStore()
const routes = permissionStore.generateRoutes()
routes.forEach(route => {
router.addRoute(route)
})
}
六、API设计
6.1 Axios封装
// api/request.ts
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { useAuthStore } from '@/stores/auth'
class Request {
private instance: AxiosInstance
constructor(config: AxiosRequestConfig) {
this.instance = axios.create(config)
this.setupInterceptors()
}
private setupInterceptors() {
// 请求拦截器
this.instance.interceptors.request.use(
(config) => {
const authStore = useAuthStore()
if (authStore.token) {
config.headers.Authorization = `Bearer ${authStore.token}`
}
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
this.instance.interceptors.response.use(
(response: AxiosResponse) => {
const { code, data, message } = response.data
if (code === 200) {
return data
} else {
return Promise.reject(new Error(message))
}
},
(error) => {
if (error.response?.status === 401) {
const authStore = useAuthStore()
authStore.logout()
window.location.href = '/login'
}
return Promise.reject(error)
}
)
}
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.get(url, config)
}
public post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.post(url, data, config)
}
public put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
return this.instance.put(url, data, config)
}
public delete<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
return this.instance.delete(url, config)
}
}
export default new Request({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
6.2 API模块化
// api/modules/member.ts
import request from '../request'
export interface Member {
id: number
name: string
phone: string
avatar?: string
level: number
exp: number
}
export interface MemberListParams {
page: number
pageSize: number
keyword?: string
level?: number
}
export interface MemberListResponse {
list: Member[]
total: number
}
export const memberApi = {
// 获取会员列表
getList: (params: MemberListParams) => {
return request.get<MemberListResponse>('/member/list', { params })
},
// 获取会员详情
getDetail: (id: number) => {
return request.get<Member>(`/member/${id}`)
},
// 创建会员
create: (data: Partial<Member>) => {
return request.post<Member>('/member', data)
},
// 更新会员
update: (id: number, data: Partial<Member>) => {
return request.put<Member>(`/member/${id}`, data)
},
// 删除会员
delete: (id: number) => {
return request.delete(`/member/${id}`)
}
}
七、性能优化
7.1 加载性能优化
7.1.1 代码分割
// 路由懒加载
const routes = [
{
path: '/member/list',
component: () => import('@/views/member/List.vue')
}
]
7.1.2 资源优化
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router', 'pinia'],
'element-plus': ['element-plus'],
'utils': ['lodash-es', 'dayjs']
}
}
}
}
})
7.2 运行时性能优化
7.2.1 虚拟滚动
<template>
<virtual-list
:data="memberList"
:item-size="80"
:buffer="10"
>
<template #default="{ item }">
<member-item :member="item" />
</template>
</virtual-list>
</template>
7.2.2 防抖节流
// utils/debounce.ts
export function debounce<T extends (...args: any[]) => any>(
fn: T,
delay: number
): (...args: Parameters<T>) => void {
let timer: ReturnType<typeof setTimeout>
return function(this: any, ...args: Parameters<T>) {
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
// 使用
const handleSearch = debounce((keyword: string) => {
searchMembers(keyword)
}, 300)
7.3 渲染性能优化
7.3.1 减少重渲染
<template>
<member-item
v-for="member in memberList"
:key="member.id"
:member="member"
@click="handleMemberClick"
/>
</template>
<script setup lang="ts">
import { shallowRef } from 'vue'
// 使用shallowRef减少响应式开销
const memberList = shallowRef<Member[]>([])
</script>
7.3.2 计算属性缓存
const filteredMembers = computed(() => {
return memberList.value.filter(member =>
member.name.includes(searchKeyword.value)
)
})
八、安全设计
8.1 XSS防护
// utils/sanitize.ts
import DOMPurify from 'dompurify'
export function sanitizeHtml(html: string): string {
return DOMPurify.sanitize(html)
}
// 使用
const safeHtml = sanitizeHtml(userInput)
8.2 CSRF防护
// api/request.ts
instance.interceptors.request.use((config) => {
const csrfToken = getCookie('csrf_token')
if (csrfToken) {
config.headers['X-CSRF-Token'] = csrfToken
}
return config
})
8.3 数据加密
// utils/crypto.ts
import CryptoJS from 'crypto-js'
const SECRET_KEY = 'your-secret-key'
export function encrypt(text: string): string {
return CryptoJS.AES.encrypt(text, SECRET_KEY).toString()
}
export function decrypt(ciphertext: string): string {
const bytes = CryptoJS.AES.decrypt(ciphertext, SECRET_KEY)
return bytes.toString(CryptoJS.enc.Utf8)
}
8.4 CSP策略
<!-- index.html -->
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;">
九、测试策略
9.1 单元测试
// components/__tests__/Button.spec.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import Button from '@/components/base/Button.vue'
describe('Button', () => {
it('renders text correctly', () => {
const wrapper = mount(Button, {
slots: {
default: 'Click me'
}
})
expect(wrapper.text()).toBe('Click me')
})
it('emits click event', async () => {
const wrapper = mount(Button)
await wrapper.trigger('click')
expect(wrapper.emitted('click')).toBeTruthy()
})
})
9.2 E2E测试
// e2e/booking.spec.ts
import { test, expect } from '@playwright/test'
test('user can book a course', async ({ page }) => {
await page.goto('/booking/list')
await page.click('[data-testid="course-card"]:first-child')
await page.click('[data-testid="book-button"]')
await expect(page.locator('[data-testid="success-message"]')).toBeVisible()
})
十、监控与日志
10.1 错误监控
// utils/sentry.ts
import * as Sentry from '@sentry/vue'
export function setupSentry(app: App) {
Sentry.init({
app,
dsn: import.meta.env.VITE_SENTRY_DSN,
environment: import.meta.env.MODE,
tracesSampleRate: 1.0,
beforeSend(event) {
// 过滤敏感信息
if (event.request) {
delete event.request.cookies
}
return event
}
})
}
10.2 性能监控
// utils/analytics.ts
import { onCLS, onFID, onLCP } from 'web-vitals'
export function setupPerformanceMonitoring() {
onCLS((metric) => {
console.log('CLS:', metric.value)
})
onFID((metric) => {
console.log('FID:', metric.value)
})
onLCP((metric) => {
console.log('LCP:', metric.value)
})
}
十一、部署策略
11.1 构建配置
// vite.config.ts
export default defineConfig({
base: '/',
build: {
outDir: 'dist',
assetsDir: 'assets',
sourcemap: false,
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true
}
}
}
})
11.2 环境配置
// .env.development
VITE_API_BASE_URL=http://localhost:8080/api
VITE_APP_TITLE=健身房管理系统(开发环境)
// .env.production
VITE_API_BASE_URL=https://api.example.com/api
VITE_APP_TITLE=健身房管理系统
十二、总结
本文档详细描述了健身房管理系统的前端技术架构,包括:
- 技术栈选型:Vue3 + uniapp + TypeScript + Pinia + Vite
- 架构设计:分层架构、模块划分、目录结构
- 数据流设计:单向数据流、状态管理、实时数据处理
- 组件设计:组件分类、设计原则、复用策略
- 路由设计:路由结构、路由守卫、动态路由
- API设计:Axios封装、API模块化
- 性能优化:加载性能、运行时性能、渲染性能
- 安全设计:XSS防护、CSRF防护、数据加密、CSP策略
- 测试策略:单元测试、E2E测试
- 监控与日志:错误监控、性能监控
- 部署策略:构建配置、环境配置
该架构设计充分考虑了跨平台需求、性能优化、安全性、可维护性和可扩展性,为健身房管理系统的前端开发提供了完整的技术指导。