30 KiB
30 KiB
情绪博物馆Web端技术方案
技术选型说明
WebSocket通信方案选择
本方案选择 STOMP + 原生WebSocket 而非 Socket.io 的原因:
- 后端集成优势: 项目后端使用Spring Boot,STOMP是Spring WebSocket的原生支持协议
- 标准化协议: STOMP是标准的消息传递协议,不依赖特定实现
- 消息队列支持: 天然支持点对点和发布订阅模式,适合聊天和通知场景
- 轻量级: 相比Socket.io更轻量,减少前端包体积
- Token认证支持: 原生WebSocket支持在握手时传递自定义请求头进行Token认证
技术栈版本策略
- 稳定性优先: 选择经过验证的稳定版本
- 生态兼容: 确保各组件间良好兼容
- 长期支持: 优先选择有LTS支持的版本
- 性能考虑: 新版本的性能优化和bug修复
1. 核心技术栈(推荐版本)
1.1 前端框架
- Vue.js:
3.4.21(最新稳定版) - TypeScript:
5.4.2(最新稳定版) - Vite:
5.1.6(最新稳定版,更好的构建性能)
1.2 UI框架与样式
- Element Plus:
2.6.1(最新稳定版,更好的Vue3支持) - Tailwind CSS:
3.4.1(最新稳定版) - @tailwindcss/forms:
0.5.7(表单样式增强) - @tailwindcss/typography:
0.5.10(文本排版增强)
1.3 状态管理与路由
- Pinia:
2.1.7(保持现有版本,稳定可靠) - Vue Router:
4.3.0(最新稳定版) - @pinia/nuxt:
0.5.1(如果需要SSR支持)
1.4 HTTP客户端与实时通信
- Axios:
1.6.8(最新稳定版) - @stomp/stompjs:
7.1.1(WebSocket通信,与Spring Boot后端集成,支持Token认证)
1.5 数据可视化
- ECharts:
5.5.0(最新稳定版) - vue-echarts:
6.7.3(Vue3专用ECharts组件) - @antv/g2:
5.1.15(备选图表库,更现代化)
1.6 工具库
- Day.js:
1.11.10(最新稳定版) - Lodash-es:
4.17.21(ES模块版本) - Zod:
3.22.4(数据验证) - VueUse:
10.9.0(Vue组合式API工具集)
1.7 开发工具
- @vitejs/plugin-vue:
5.0.4 - @vue/tsconfig:
0.5.1 - vue-tsc:
2.0.6 - unplugin-auto-import:
0.17.5(自动导入) - unplugin-vue-components:
0.26.0(组件自动导入)
2. 新增推荐技术栈
2.1 表单处理
- @vuelidate/core:
2.0.3(表单验证) - @vuelidate/validators:
2.0.4 - vue-hooks-form:
0.8.6(表单状态管理)
2.2 动画与交互
- @vueuse/motion:
2.0.0(动画库) - vue-toastification:
2.0.0-rc.5(通知组件) - nprogress:
0.2.0(页面加载进度条)
2.3 文件处理
- vue-upload-component:
3.1.4(文件上传) - cropperjs:
1.6.1(图片裁剪) - file-saver:
2.0.5(文件下载)
2.4 富文本编辑
- @tiptap/vue-3:
2.2.4(现代富文本编辑器) - @tiptap/starter-kit:
2.2.4 - @tiptap/extension-image:
2.2.4
2.5 PWA支持
- vite-plugin-pwa:
0.19.2(PWA支持) - workbox-window:
7.0.0(Service Worker管理)
3. 开发工具与代码质量
3.1 代码规范
- ESLint:
8.57.0 - @vue/eslint-config-typescript:
12.0.0 - @vue/eslint-config-prettier:
9.0.0 - Prettier:
3.2.5 - lint-staged:
15.2.2 - husky:
9.0.11
3.2 测试框架
- Vitest:
1.4.0(单元测试) - @vue/test-utils:
2.4.5(Vue组件测试) - jsdom:
24.0.0(DOM环境模拟) - Cypress:
13.7.1(E2E测试)
3.3 构建优化
- rollup-plugin-visualizer:
5.12.0(构建分析) - vite-plugin-compression:
0.5.1(Gzip压缩) - vite-plugin-mock:
3.0.1(Mock数据)
4. 项目结构设计
src/
├── api/ # API接口定义
│ ├── auth.ts # 认证相关接口
│ ├── chat.ts # 聊天相关接口
│ ├── diary.ts # 日记相关接口
│ └── user.ts # 用户相关接口
├── assets/ # 静态资源
│ ├── images/ # 图片资源
│ ├── icons/ # 图标资源
│ └── styles/ # 全局样式
├── components/ # 公共组件
│ ├── common/ # 通用组件
│ ├── forms/ # 表单组件
│ ├── charts/ # 图表组件
│ └── layout/ # 布局组件
├── composables/ # 组合式API
│ ├── useAuth.ts # 认证逻辑
│ ├── useChat.ts # 聊天逻辑
│ ├── useWebSocket.ts # WebSocket逻辑
│ └── useApi.ts # API调用逻辑
├── config/ # 配置文件
│ ├── env.ts # 环境配置
│ ├── constants.ts # 常量定义
│ └── routes.ts # 路由配置
├── layouts/ # 页面布局
│ ├── DefaultLayout.vue # 默认布局
│ ├── AuthLayout.vue # 认证布局
│ └── ChatLayout.vue # 聊天布局
├── pages/ # 页面组件
│ ├── auth/ # 认证页面
│ ├── chat/ # 聊天页面
│ ├── diary/ # 日记页面
│ └── dashboard/ # 仪表盘页面
├── stores/ # 状态管理
│ ├── auth.ts # 认证状态
│ ├── chat.ts # 聊天状态
│ ├── user.ts # 用户状态
│ └── app.ts # 应用状态
├── types/ # 类型定义
│ ├── api.ts # API类型
│ ├── user.ts # 用户类型
│ ├── chat.ts # 聊天类型
│ └── global.d.ts # 全局类型
├── utils/ # 工具函数
│ ├── request.ts # HTTP请求工具
│ ├── websocket.ts # WebSocket工具
│ ├── storage.ts # 存储工具
│ ├── validation.ts # 验证工具
│ └── format.ts # 格式化工具
└── views/ # 页面视图
├── Home.vue # 首页
├── Login.vue # 登录页
├── Chat.vue # 聊天页
└── Dashboard.vue # 仪表盘
5. 核心功能实现方案
5.1 认证系统
- JWT Token管理: 使用Pinia存储,自动刷新机制
- 路由守卫: 基于Vue Router的权限控制
- 第三方登录: 支持微信、QQ、GitHub等
- 验证码: 图形验证码 + 短信验证码
5.2 实时通信
- STOMP协议: 基于@stomp/stompjs,与Spring Boot WebSocket集成
- 原生WebSocket: 支持Token认证,无需降级方案
- 连接管理: 自动重连、心跳检测、连接状态监控
- 消息队列: 支持点对点和发布订阅模式
- Token认证: 在WebSocket握手时传递Authorization头部
- 消息类型: 文本、图片、表情、文件、系统通知
- 离线处理: 离线消息缓存和同步机制
5.3 数据可视化
- 情绪趋势图: 基于ECharts的时间序列图
- 情绪雷达图: 多维度情绪分析
- 成长轨迹: 交互式时间轴
- 数据导出: 支持PDF、Excel导出
5.4 响应式设计
- 移动端适配: 基于Tailwind CSS的响应式布局
- 触摸手势: 支持滑动、缩放等手势操作
- PWA支持: 离线缓存、桌面安装
- 性能优化: 虚拟滚动、懒加载
6. 环境配置优化
6.1 多环境配置
// config/env.ts
export const envConfigs = {
local: {
name: '本地环境',
apiBaseUrl: 'http://localhost:19089/api',
wsBaseUrl: 'ws://localhost:19089',
uploadUrl: 'http://localhost:19089/api/upload',
debug: true,
mock: false
},
dev: {
name: '开发环境',
apiBaseUrl: 'https://dev-api.emotion-museum.com/api',
wsBaseUrl: 'wss://dev-api.emotion-museum.com',
uploadUrl: 'https://dev-api.emotion-museum.com/api/upload',
debug: true,
mock: false
},
test: {
name: '测试环境',
apiBaseUrl: 'https://test-api.emotion-museum.com/api',
wsBaseUrl: 'wss://test-api.emotion-museum.com',
uploadUrl: 'https://test-api.emotion-museum.com/api/upload',
debug: false,
mock: false
},
prod: {
name: '生产环境',
apiBaseUrl: 'https://api.emotion-museum.com/api',
wsBaseUrl: 'wss://api.emotion-museum.com',
uploadUrl: 'https://api.emotion-museum.com/api/upload',
debug: false,
mock: false
}
}
6.2 构建优化配置
// vite.config.ts
export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router', 'pinia', '@vueuse/core'],
dts: true
}),
Components({
resolvers: [ElementPlusResolver()],
dts: true
}),
VitePWA({
registerType: 'autoUpdate',
workbox: {
globPatterns: ['**/*.{js,css,html,ico,png,svg}']
}
})
],
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['vue', 'vue-router', 'pinia'],
elementPlus: ['element-plus'],
echarts: ['echarts'],
utils: ['axios', 'dayjs', 'lodash-es']
}
}
}
}
})
7. 部署方案
7.1 Docker部署
# Dockerfile
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
7.2 CI/CD配置
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- run: npm ci
- run: npm run build
- run: npm run test
- name: Deploy to server
run: |
# 部署脚本
7.3 Nginx配置
# nginx.conf
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# 处理Vue Router的history模式
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# API代理
location /api/ {
proxy_pass http://backend:19089/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# WebSocket代理
location /ws/ {
proxy_pass http://backend:19089/ws/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
8. 性能优化策略
8.1 代码分割
- 路由级别懒加载: 使用动态import()
- 组件级别动态导入: 按需加载大型组件
- 第三方库按需加载: Tree-shaking优化
- 图片懒加载: Intersection Observer API
8.2 缓存策略
- HTTP缓存配置: 静态资源长期缓存
- Service Worker缓存: 离线访问支持
- 本地存储优化: IndexedDB存储大量数据
- CDN加速: 静态资源CDN分发
8.3 监控与分析
- Sentry:
7.108.0(错误监控) - Google Analytics:
gtag(用户行为分析) - Web Vitals:
3.5.2(性能指标监控) - Bundle Analyzer: 构建分析工具
8.4 性能指标
- 首屏加载时间: < 2秒
- 交互响应时间: < 100ms
- 代码分割: 单个chunk < 250KB
- 图片优化: WebP格式,响应式图片
9. 安全考虑
9.1 前端安全
- XSS防护: 内容安全策略(CSP)
- CSRF防护: Token验证
- 敏感信息加密: 本地存储加密
- 依赖安全: 定期更新依赖包
9.2 API安全
- Token自动刷新: JWT令牌管理
- 请求签名验证: HMAC签名
- 接口限流: 防止恶意请求
- 数据加密传输: HTTPS强制
9.3 内容安全策略
<!-- CSP配置 -->
<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:;
connect-src 'self' wss: https:;">
10. 开发规范
10.1 代码规范
- 命名规范:
- 组件: PascalCase (UserProfile.vue)
- 文件: kebab-case (user-profile.ts)
- 变量: camelCase (userName)
- 常量: UPPER_SNAKE_CASE (API_BASE_URL)
10.2 Git提交规范
feat: 新功能
fix: 修复bug
docs: 文档更新
style: 代码格式调整
refactor: 代码重构
test: 测试相关
chore: 构建工具或辅助工具的变动
10.3 组件开发规范
<template>
<!-- 模板内容 -->
</template>
<script setup lang="ts">
// 导入
import { ref, computed, onMounted } from 'vue'
import type { User } from '@/types/user'
// Props定义
interface Props {
user: User
readonly?: boolean
}
const props = withDefaults(defineProps<Props>(), {
readonly: false
})
// Emits定义
interface Emits {
update: [user: User]
delete: [id: string]
}
const emit = defineEmits<Emits>()
// 响应式数据
const loading = ref(false)
// 计算属性
const displayName = computed(() => {
return props.user.nickname || props.user.username
})
// 生命周期
onMounted(() => {
// 初始化逻辑
})
</script>
<style scoped>
/* 组件样式 */
</style>
10.4 API接口规范
// api/user.ts
import type { User, UserProfile, UpdateUserRequest } from '@/types/user'
import { request } from '@/utils/request'
export const userApi = {
// 获取用户信息
getProfile(): Promise<UserProfile> {
return request.get('/user/profile')
},
// 更新用户信息
updateProfile(data: UpdateUserRequest): Promise<User> {
return request.put('/user/profile', data)
},
// 上传头像
uploadAvatar(file: File): Promise<{ url: string }> {
const formData = new FormData()
formData.append('avatar', file)
return request.post('/user/avatar/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
})
}
}
10.5 WebSocket实现方案
为什么选择STOMP而不是Socket.io?
- 后端集成: 项目后端使用Spring Boot,STOMP是Spring WebSocket的标准协议
- 消息队列: STOMP天然支持消息队列模式,适合聊天和通知场景
- 标准化: STOMP是标准协议,不依赖特定实现
- 轻量级: 相比Socket.io更轻量,减少包体积
WebSocket工具类实现
// utils/websocket.ts
import { Client } from '@stomp/stompjs'
import { envConfig } from '@/config/env'
export class WebSocketService {
private client: Client
private connected = false
private reconnectAttempts = 0
private maxReconnectAttempts = 5
private currentToken = ''
constructor() {
this.client = new Client({
// 使用原生WebSocket,支持Token认证
brokerURL: `${envConfig.wsBaseUrl}/ws`,
// 心跳检测
heartbeatIncoming: 4000,
heartbeatOutgoing: 4000,
// 重连配置
reconnectDelay: 5000,
// 调试模式
debug: envConfig.debug ? console.log : undefined,
onConnect: () => {
this.connected = true
this.reconnectAttempts = 0
console.log('WebSocket连接成功')
},
onDisconnect: () => {
this.connected = false
console.log('WebSocket连接断开')
},
onStompError: (frame) => {
console.error('STOMP错误:', frame)
this.handleReconnect()
},
// WebSocket连接前的配置
beforeConnect: () => {
// 在WebSocket握手时添加Token
if (this.currentToken) {
this.client.configure({
connectHeaders: {
Authorization: `Bearer ${this.currentToken}`,
'X-Requested-With': 'XMLHttpRequest'
}
})
}
}
})
}
// 连接WebSocket
connect(token: string) {
this.currentToken = token
this.client.configure({
connectHeaders: {
Authorization: `Bearer ${token}`,
'X-Requested-With': 'XMLHttpRequest'
}
})
this.client.activate()
}
// 更新Token(用于Token刷新场景)
updateToken(newToken: string) {
this.currentToken = newToken
if (this.connected) {
// 断开当前连接
this.disconnect()
// 使用新Token重新连接
setTimeout(() => {
this.connect(newToken)
}, 1000)
}
}
// 断开连接
disconnect() {
this.client.deactivate()
}
// 订阅消息
subscribe(destination: string, callback: (message: any) => void) {
if (!this.connected) {
console.warn('WebSocket未连接')
return
}
return this.client.subscribe(destination, (message) => {
try {
const data = JSON.parse(message.body)
callback(data)
} catch (error) {
console.error('消息解析失败:', error)
}
})
}
// 发送消息
send(destination: string, body: any) {
if (!this.connected) {
console.warn('WebSocket未连接,消息将被缓存')
// 这里可以实现消息缓存逻辑
return
}
this.client.publish({
destination,
body: JSON.stringify(body)
})
}
// 处理重连
private handleReconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++
setTimeout(() => {
console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
this.client.activate()
}, 5000 * this.reconnectAttempts)
}
}
}
聊天功能使用示例
// composables/useChat.ts
import { ref, onMounted, onUnmounted } from 'vue'
import { WebSocketService } from '@/utils/websocket'
import { useAuthStore } from '@/stores/auth'
export function useChat() {
const authStore = useAuthStore()
const wsService = new WebSocketService()
const messages = ref<any[]>([])
const isConnected = ref(false)
// 连接WebSocket
const connect = () => {
if (authStore.token) {
wsService.connect(authStore.token)
// 订阅个人消息
wsService.subscribe(`/user/${authStore.user.id}/queue/messages`, (message) => {
messages.value.push(message)
})
// 订阅聊天室消息
wsService.subscribe('/topic/chat', (message) => {
messages.value.push(message)
})
isConnected.value = true
}
}
// 发送消息
const sendMessage = (content: string, type: 'text' | 'image' = 'text') => {
const message = {
content,
type,
timestamp: Date.now(),
userId: authStore.user.id
}
wsService.send('/app/chat.send', message)
}
// 组件挂载时连接
onMounted(() => {
connect()
})
// 组件卸载时断开连接
onUnmounted(() => {
wsService.disconnect()
})
return {
messages,
isConnected,
sendMessage,
connect
}
}
10.6 WebSocket Token认证详解
Token传递方式
原生WebSocket + STOMP协议支持Token认证:
- WebSocket握手时传递 (推荐)
// 在WebSocket连接建立时传递Token
brokerURL: `${envConfig.wsBaseUrl}/ws`,
connectHeaders: {
Authorization: `Bearer ${token}`,
'X-Requested-With': 'XMLHttpRequest'
}
- URL参数传递 (备选方案)
// 如果后端不支持连接头部,可以通过URL传递
brokerURL: `${envConfig.wsBaseUrl}/ws?token=${token}`
为什么移除SockJS?
- ❌ SockJS不支持在WebSocket握手时传递自定义请求头
- ❌ 无法在连接建立时进行Token认证
- ❌ 只能通过URL参数传递Token,安全性较低
- ✅ 原生WebSocket完全支持Token认证
- ✅ 现代浏览器WebSocket支持度已经很好
WebSocket方案对比
| 特性 | 原生WebSocket + STOMP | SockJS + STOMP | Socket.io |
|---|---|---|---|
| Token认证 | ✅ 支持请求头传递 | ❌ 不支持请求头 | ✅ 支持 |
| 浏览器兼容 | ✅ 现代浏览器完全支持 | ✅ 兼容性最好 | ✅ 兼容性好 |
| 包体积 | ✅ 最小 | ⚠️ 中等 | ❌ 最大 |
| Spring Boot集成 | ✅ 原生支持 | ✅ 原生支持 | ❌ 需要额外配置 |
| 标准化 | ✅ 标准协议 | ✅ 标准协议 | ❌ 私有协议 |
| 安全性 | ✅ 握手时认证 | ⚠️ URL参数认证 | ✅ 握手时认证 |
最终选择:原生WebSocket + STOMP
- 🎯 安全性优先: 支持在握手时进行Token认证
- 🎯 性能最优: 无额外协议层,直接使用WebSocket
- 🎯 标准化: 基于标准STOMP协议
- 🎯 轻量级: 最小的包体积
- 🎯 兼容性: 现代浏览器支持度已经足够
Token刷新处理
// stores/auth.ts - 认证状态管理
import { defineStore } from 'pinia'
import { ref, watch } from 'vue'
import { WebSocketService } from '@/utils/websocket'
export const useAuthStore = defineStore('auth', () => {
const token = ref<string>('')
const user = ref<any>(null)
const wsService = ref<WebSocketService | null>(null)
// 监听Token变化,自动更新WebSocket连接
watch(token, (newToken, oldToken) => {
if (newToken && newToken !== oldToken && wsService.value) {
wsService.value.updateToken(newToken)
}
})
// 登录
const login = async (credentials: LoginRequest) => {
try {
const response = await authApi.login(credentials)
token.value = response.token
user.value = response.user
// 登录成功后建立WebSocket连接
if (!wsService.value) {
wsService.value = new WebSocketService()
}
wsService.value.connect(token.value)
return response
} catch (error) {
throw error
}
}
// 登出
const logout = () => {
// 断开WebSocket连接
if (wsService.value) {
wsService.value.disconnect()
wsService.value = null
}
token.value = ''
user.value = null
}
// Token刷新
const refreshToken = async () => {
try {
const response = await authApi.refreshToken()
token.value = response.token
// watch会自动处理WebSocket重连
return response
} catch (error) {
// Token刷新失败,执行登出
logout()
throw error
}
}
return {
token,
user,
wsService,
login,
logout,
refreshToken
}
})
后端WebSocket安全配置
对应的Spring Boot后端配置示例:
// WebSocketConfig.java
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
config.setUserDestinationPrefix("/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*");
// 移除.withSockJS(),使用原生WebSocket支持Token认证
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(new AuthChannelInterceptor());
}
}
// AuthChannelInterceptor.java - Token验证拦截器
@Component
public class AuthChannelInterceptor implements ChannelInterceptor {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String token = accessor.getFirstNativeHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
String jwt = token.substring(7);
// 验证JWT Token
if (jwtTokenProvider.validateToken(jwt)) {
String userId = jwtTokenProvider.getUserIdFromToken(jwt);
accessor.setUser(new StompPrincipal(userId));
} else {
throw new IllegalArgumentException("Invalid token");
}
} else {
throw new IllegalArgumentException("Missing token");
}
}
return message;
}
}
错误处理和重连机制
// 增强的WebSocket错误处理
export class WebSocketService {
private tokenExpiredCallback?: () => void
constructor(onTokenExpired?: () => void) {
this.tokenExpiredCallback = onTokenExpired
// ... 其他初始化代码
}
private handleStompError(frame: any) {
console.error('STOMP错误:', frame)
// 检查是否是Token相关错误
if (frame.headers && frame.headers.message) {
const errorMessage = frame.headers.message.toLowerCase()
if (errorMessage.includes('unauthorized') ||
errorMessage.includes('invalid token') ||
errorMessage.includes('token expired')) {
console.warn('Token认证失败,触发重新登录')
this.tokenExpiredCallback?.()
return
}
}
// 其他错误进行重连
this.handleReconnect()
}
}
// 在应用中使用
const authStore = useAuthStore()
const wsService = new WebSocketService(() => {
// Token过期回调
authStore.logout()
router.push('/login')
})
安全最佳实践
- Token验证: 每次WebSocket连接都验证Token有效性
- 权限控制: 基于用户角色限制订阅和发送权限
- 连接限制: 限制单用户的并发连接数
- 消息加密: 敏感消息内容加密传输
- 审计日志: 记录WebSocket连接和消息日志
这个方案完全支持Token认证,并且提供了完整的Token生命周期管理,包括刷新、过期处理和安全重连机制。
11. 测试策略
11.1 单元测试
// tests/components/UserProfile.test.ts
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserProfile from '@/components/UserProfile.vue'
describe('UserProfile', () => {
it('renders user information correctly', () => {
const user = {
id: '1',
username: 'testuser',
nickname: 'Test User',
avatar: 'https://example.com/avatar.jpg'
}
const wrapper = mount(UserProfile, {
props: { user }
})
expect(wrapper.text()).toContain('Test User')
expect(wrapper.find('img').attributes('src')).toBe(user.avatar)
})
})
11.2 E2E测试
// cypress/e2e/auth.cy.ts
describe('Authentication', () => {
it('should login successfully', () => {
cy.visit('/login')
cy.get('[data-cy=username]').type('testuser')
cy.get('[data-cy=password]').type('password123')
cy.get('[data-cy=login-btn]').click()
cy.url().should('include', '/dashboard')
cy.get('[data-cy=user-menu]').should('be.visible')
})
})
11.3 测试配置
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./tests/setup.ts']
},
resolve: {
alias: {
'@': '/src'
}
}
})
12. 国际化支持
12.1 i18n配置
- vue-i18n:
9.10.2(国际化支持) - @intlify/unplugin-vue-i18n:
4.0.0(构建时优化)
// i18n/index.ts
import { createI18n } from 'vue-i18n'
import zh from './locales/zh.json'
import en from './locales/en.json'
export const i18n = createI18n({
legacy: false,
locale: 'zh',
fallbackLocale: 'en',
messages: {
zh,
en
}
})
12.2 语言文件结构
// i18n/locales/zh.json
{
"common": {
"confirm": "确认",
"cancel": "取消",
"save": "保存",
"delete": "删除"
},
"auth": {
"login": "登录",
"register": "注册",
"logout": "退出登录"
},
"chat": {
"sendMessage": "发送消息",
"typing": "正在输入...",
"offline": "离线"
}
}
13. 移动端适配
13.1 响应式断点
/* tailwind.config.js */
module.exports = {
theme: {
screens: {
'xs': '475px',
'sm': '640px',
'md': '768px',
'lg': '1024px',
'xl': '1280px',
'2xl': '1536px',
}
}
}
13.2 移动端优化
- 触摸优化: 44px最小触摸目标
- 手势支持: 滑动、缩放、长按
- 性能优化: 虚拟滚动、图片懒加载
- 离线支持: Service Worker缓存
13.3 PWA配置
// vite.config.ts PWA配置
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png'],
manifest: {
name: '情绪博物馆',
short_name: '情绪博物馆',
description: '记录情绪,分享心情的温暖空间',
theme_color: '#4A90E2',
background_color: '#ffffff',
display: 'standalone',
icons: [
{
src: 'pwa-192x192.png',
sizes: '192x192',
type: 'image/png'
},
{
src: 'pwa-512x512.png',
sizes: '512x512',
type: 'image/png'
}
]
}
})
14. 总结
本技术方案基于现代化的Vue3生态系统,采用TypeScript提供类型安全,使用Vite构建工具提升开发体验。方案涵盖了:
14.1 技术优势
- 现代化技术栈: Vue3 + TypeScript + Vite
- 完整的工具链: 从开发到部署的全流程支持
- 性能优化: 代码分割、懒加载、缓存策略
- 开发体验: 热更新、自动导入、类型检查
14.2 可扩展性
- 模块化设计: 清晰的项目结构和职责分离
- 组件化开发: 可复用的UI组件库
- 插件系统: 支持功能扩展和第三方集成
- 国际化支持: 多语言适配能力
14.3 维护性
- 代码规范: ESLint + Prettier统一代码风格
- 测试覆盖: 单元测试 + E2E测试
- 文档完善: 组件文档和API文档
- 版本管理: 语义化版本控制
14.4 部署方案
- 多环境支持: local/dev/test/prod环境配置
- 容器化部署: Docker + Nginx部署方案
- CI/CD流程: 自动化构建和部署
- 监控告警: 错误监控和性能分析
这个技术方案能够很好地支持情绪博物馆Web端的所有功能需求,同时具备良好的可维护性和扩展性,为项目的长期发展奠定坚实基础。