SSXZ Web 是一个基于 Vue 3 的单页面应用(SPA),采用了现代化的前端架构设计。整体架构可以分为以下几个层次:
选择原因:
项目中的应用:
<script setup> 语法简化组件定义选择原因:
项目中的应用:
@ 指向 src 目录)选择原因:
项目中的应用:
选择原因:
项目中的应用:
选择原因:
项目中的应用:
选择原因:
项目中的应用:
选择原因:
项目中的应用:
选择原因:
项目中的应用:
d:\Projects\sxc\web/
├── .env # 环境变量配置
├── .env.development # 开发环境变量
├── .env.production # 生产环境变量
├── .gitattributes # Git 属性配置
├── .gitignore # Git 忽略文件配置
├── .prettierrc.json # Prettier 代码格式化配置
├── API接口文档.md # API 接口文档
├── README.md # 项目说明文档
├── package.json # 项目依赖和脚本配置
├── tsconfig.json # TypeScript 配置
├── tsconfig.app.json # 应用 TypeScript 配置
├── tsconfig.node.json # Node.js TypeScript 配置
├── vite.config.ts # Vite 构建配置
├── cert/ # 证书目录
├── public/ # 静态资源目录
└── src/ # 源代码目录
这个目录包含了所有与后端 API 交互的模块,按功能领域划分:
basic.ts - 基础信息接口,如网站配置、点赞/踩操作category.ts - 服务分类接口,获取服务分类信息commonProblem.ts - 常见问题接口,获取 FAQ 列表media.ts - 媒体处理接口,语音转文字、文字转语音message.ts - 消息相关接口,发送消息、获取消息历史opinion.ts - 意见反馈接口,提交用户反馈service.ts - 服务导航接口,获取服务项目信息每个模块都封装了特定领域的 API 调用,使用 Axios 发送 HTTP 请求,并处理响应数据。
这个目录包含了项目的静态资源:
base.css - 基础样式,包含 CSS 变量定义、全局重置样式等fonts.css - 字体定义,引入 PingFang SC 字体main.css - 主样式文件,包含全局样式和组件样式这个目录包含了所有可复用的 Vue 组件,按功能划分:
SsCommonProblem.vue - 常见问题组件,展示 FAQ 列表SsFooter.vue - 页脚组件,显示版权和链接信息SsGridEntrance.vue - 网格入口组件,展示服务入口网格SsHeader.vue - 页头组件,显示导航和 logoSsHeadline.vue - 标题组件,显示页面标题SsHotline.vue - 热线组件,显示联系电话SsInputBox.vue - 输入框组件,提供文本输入功能SsNavigation.vue - 导航组件,提供页面导航SsOpinion.vue - 意见反馈组件,收集用户反馈SsPanel.vue - 面板组件,提供内容面板SsRecording.vue - 录音组件,提供音频录制功能SsService.vue - 服务组件,展示服务信息特别值得注意的是 ss_chat/ 子目录,它包含了聊天相关的所有组件:
SsChat.vue - 主聊天组件,整合聊天功能components/ - 聊天子组件目录
SsChatHistory.vue - 聊天历史组件,显示历史消息SsChatInput.vue - 聊天输入组件,提供消息输入功能SsChatMessage.vue - 聊天消息组件,显示单条消息SsChatReply.vue - 聊天回复组件,显示 AI 回复SsChatSendMessage.vue - 发送消息组件,处理消息发送hooks/ - 聊天相关钩子
useEventSource.ts - EventSource 钩子,处理 SSE 连接这个目录包含了可复用的组合式函数(Hooks):
useClipboard.ts - 剪贴板操作钩子,提供复制到剪贴板功能这个目录包含了 Vue 的全局插件:
globalMethods.ts - 全局方法插件,注册全局可用的工具函数这个目录包含了路由配置:
index.ts - 路由配置文件,定义页面路由这个目录包含了 Pinia 状态管理:
global.ts - 全局状态,存储应用级别的状态这个目录包含了 TypeScript 类型定义文件:
api.d.ts - API 相关类型定义global.d.ts - 全局类型定义markdown-it-*.d.ts - Markdown-it 插件类型定义string.extensions.d.ts - 字符串扩展类型定义这个目录包含了各种工具函数:
EventSourceWrapper.ts - EventSource 封装,处理 SSE 连接PCMAudioPlayer.ts - PCM 音频播放器,处理音频播放common.ts - 通用工具函数,提供图片 URL 构造、空值检查等功能formatDate.ts - 日期格式化工具,提供日期格式化功能parseMarkdown.ts - Markdown 解析工具,处理 Markdown 渲染request.ts - 请求封装,封装 Axios 请求string.extensions.ts - 字符串扩展,提供字符串处理方法useDeviceDetection.ts - 设备检测,检测移动设备这个目录包含了页面级组件:
HomeView.vue - 主页视图,应用的主页面这个目录包含了不需要经过构建处理的静态资源:
favicon.ico - 网站图标fonts/ - 字体文件,包含 PingFang SC 字体的各种字重images/ - 图片资源,包含应用中使用的各种图片让我们选择 useClipboard 这个简单的功能模块进行详细分析,它位于 src/hooks/useClipboard.ts。
useClipboard 是一个自定义的组合式函数(Hook),用于提供复制文本到剪贴板的功能。这个功能在聊天应用中很常见,用户可能需要复制 AI 的回复或自己的消息。
让我们先查看这个文件的完整内容:
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
export function useClipboard() {
const copied = ref(false)
const copyToClipboard = async (text: string) => {
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text)
} else {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
copied.value = true
ElMessage.success('复制成功')
setTimeout(() => {
copied.value = false
}, 2000)
} catch (error) {
ElMessage.error('复制失败')
console.error('复制失败:', error)
}
}
return {
copied,
copyToClipboard
}
}
导入依赖:
import { ref } from 'vue'
import { ElMessage } from 'element-plus'
ref,用于创建响应式引用ElMessage,用于显示提示消息状态定义:
const copied = ref(false)
copied,表示是否已复制false核心功能实现:
const copyToClipboard = async (text: string) => {
try {
// 主要逻辑
} catch (error) {
// 错误处理
}
}
copyToClipboard,接收要复制的文本复制逻辑:
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text)
} else {
// 降级方案
const textArea = document.createElement('textarea')
textArea.value = text
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
document.execCommand('copy')
document.body.removeChild(textArea)
}
navigator.clipboard)且在安全上下文中(window.isSecureContext)navigator.clipboard.writeText() 方法复制文本成功处理:
copied.value = true
ElMessage.success('复制成功')
setTimeout(() => {
copied.value = false
}, 2000)
copied 状态为 truecopied 状态重置为 false错误处理:
ElMessage.error('复制失败')
console.error('复制失败:', error)
返回接口:
return {
copied,
copyToClipboard
}
copied 状态和 copyToClipboard 函数,供组件使用这个 Hook 在组件中的使用方式如下:
<template>
<div>
<button @click="handleCopy">复制文本</button>
<span v-if="copied">已复制!</span>
</div>
</template>
<script setup>
import { useClipboard } from '@/hooks/useClipboard'
const { copied, copyToClipboard } = useClipboard()
const handleCopy = () => {
copyToClipboard('要复制的文本内容')
}
</script>
兼容性处理:同时支持现代 Clipboard API 和降级方案,确保在各种浏览器环境下都能工作
状态管理:提供 copied 状态,方便 UI 根据复制状态进行变化
用户反馈:使用 Element Plus 的消息组件提供操作反馈
错误处理:捕获并处理可能的错误,提供友好的错误提示
自动重置:复制状态在 2 秒后自动重置,避免状态一直保持为 true
封装性:将复杂的剪贴板操作封装为简单的函数,组件调用非常简洁
这个简单的 Hook 展示了 Vue 3 Composition API 的几个重要概念:
ref 创建响应式状态这种模式在项目中广泛使用,使得代码更加模块化、可维护和可测试。
项目广泛使用了 Vue 3 的 Composition API,这是一种组合模式的体现:
// 在组件中使用组合式函数
import { useClipboard } from '@/hooks/useClipboard'
import { useDeviceDetection } from '@/utils/useDeviceDetection'
export default defineComponent({
setup() {
const { copied, copyToClipboard } = useClipboard()
const { isMobile } = useDeviceDetection()
return {
copied,
copyToClipboard,
isMobile
}
}
})
优点:
在 API 模块中,使用了工厂模式创建请求实例:
// src/utils/request.ts
import axios from 'axios'
const request = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL,
timeout: 10000
})
// 添加请求拦截器
request.interceptors.request.use(config => {
// 添加认证信息
const timestamp = new Date().getTime()
const sign = md5(`${timestamp}${import.meta.env.VITE_API_SECRET}`)
config.headers['h-timestamp'] = timestamp
config.headers['h-sign'] = sign
return config
})
export default request
优点:
全局状态管理使用了单例模式:
// src/stores/global.ts
import { defineStore } from 'pinia'
export const useGlobalStore = defineStore('global', {
state: () => ({
userInfo: null,
systemConfig: {}
}),
actions: {
setUserInfo(info) {
this.userInfo = info
}
}
})
优点:
在 Markdown 解析中使用了策略模式:
// src/utils/parseMarkdown.ts
import MarkdownIt from 'markdown-it'
import subscript from 'markdown-it-sub'
import superscript from 'markdown-it-sup'
import footnote from 'markdown-it-footnote'
import taskLists from 'markdown-it-task-lists'
const md = new MarkdownIt({
html: true,
linkify: true,
typographer: true
})
.use(subscript)
.use(superscript)
.use(footnote)
.use(taskLists)
优点:
在 EventSource 封装中使用了观察者模式:
// src/utils/EventSourceWrapper.ts
export class EventSourceWrapper {
private eventSource: EventSource | null = null
private listeners: { [key: string]: Function[] } = {}
on(event: string, callback: Function) {
if (!this.listeners[event]) {
this.listeners[event] = []
}
this.listeners[event].push(callback)
}
emit(event: string, data: any) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data))
}
}
}
优点:
单一职责原则:
每个组件只负责一个功能,如 SsChatInput 只负责输入,SsChatMessage 只负责消息显示。
<!-- SsChatInput.vue - 只负责输入功能 -->
<template>
<div class="chat-input">
<textarea v-model="inputText" @keydown.enter="handleEnter"></textarea>
<button @click="sendMessage">发送</button>
</div>
</template>
<script setup>
const inputText = ref('')
const emit = defineEmits(['send'])
const sendMessage = () => {
emit('send', inputText.value)
inputText.value = ''
}
</script>
可复用性: 组件设计考虑复用场景,通过 props 和 slots 提供灵活性。
<!-- SsPanel.vue - 可复用的面板组件 -->
<template>
<div class="panel">
<div class="panel-header" v-if="$slots.header">
<slot name="header"></slot>
</div>
<div class="panel-body">
<slot></slot>
</div>
<div class="panel-footer" v-if="$slots.footer">
<slot name="footer"></slot>
</div>
</div>
</template>
模块化状态: 按功能模块划分状态,避免单一巨大状态对象。
// 聊天状态
export const useChatStore = defineStore('chat', {
state: () => ({
messages: [],
isLoading: false
}),
actions: {
addMessage(message) {
this.messages.push(message)
}
}
})
// 用户状态
export const useUserStore = defineStore('user', {
state: () => ({
profile: null,
preferences: {}
}),
actions: {
updateProfile(profile) {
this.profile = profile
}
}
})
状态规范化: 使用规范化结构存储数据,避免数据冗余。
state: () => ({
messages: {
byId: {},
allIds: []
}
})
统一错误处理: 在请求拦截器中统一处理错误。
// src/utils/request.ts
request.interceptors.response.use(
response => {
return response.data
},
error => {
if (error.response) {
switch (error.response.status) {
case 401:
// 处理未授权
break
case 404:
// 处理未找到
break
default:
// 处理其他错误
}
}
return Promise.reject(error)
}
)
请求取消: 实现请求取消功能,避免组件卸载后仍然处理响应。
// 在组件中
import { onUnmounted } from 'vue'
let cancelToken
const fetchData = () => {
cancelToken = axios.CancelToken.source()
request.get('/api/data', {
cancelToken: cancelToken.token
})
}
onUnmounted(() => {
if (cancelToken) {
cancelToken.cancel('组件卸载,取消请求')
}
})
按功能组织: 代码按功能模块组织,而不是按文件类型。
src/
├── features/
│ ├── chat/
│ │ ├── components/
│ │ ├── hooks/
│ │ ├── services/
│ │ └── types/
│ └── user/
│ ├── components/
│ ├── hooks/
│ ├── services/
│ └── types/
工具函数分类: 工具函数按功能分类,便于查找和维护。
src/utils/
├── date/ # 日期相关工具
├── format/ # 格式化工具
├── validation/ # 验证工具
└── dom/ # DOM 操作工具
懒加载组件: 使用动态导入实现组件懒加载。
const SsChatReply = defineAsyncComponent(() => import('./SsChatReply.vue'))
列表虚拟化: 对于长列表使用虚拟滚动技术。
<template>
<VirtualList :items="messages" :itemHeight="80">
<template #default="{ item }">
<SsChatMessage :message="item" />
</template>
</VirtualList>
</template>
防抖和节流: 对频繁触发的事件使用防抖和节流。
import { debounce } from 'lodash-es'
const handleInput = debounce((value) => {
// 处理输入
}, 300)
XSS 防护: 使用 DOMPurify 清理用户输入。
import DOMPurify from 'dompurify'
const sanitizedContent = DOMPurify.sanitize(userInput)
CSRF 防护: 在请求中添加 CSRF 令牌。
request.interceptors.request.use(config => {
config.headers['X-CSRF-TOKEN'] = getCsrfToken()
return config
})
敏感信息保护: 避免在前端存储敏感信息。
// 不推荐
localStorage.setItem('password', password)
// 推荐
const token = getTemporaryToken() // 短期有效的令牌
核心概念:
推荐资源:
类型系统:
实践项目:
推荐资源:
构建工具:
代码质量:
推荐资源:
Pinia 深入:
其他状态管理方案:
推荐资源:
渲染性能:
加载性能:
监控与调试:
推荐资源:
安全基础:
实践应用:
推荐资源:
贡献开源项目:
个人项目:
技术分享:
初级阶段(1-3个月):
中级阶段(3-6个月):
高级阶段(6-12个月):
专家阶段(1年以上):
书籍:
在线课程:
社区和博客:
实践平台:
通过循序渐进的学习和实践,你将能够从新手成长为一名熟练的前端开发者,不仅能够理解和维护现有项目,还能够独立设计和开发高质量的前端应用。记住,持续学习和实践是成为优秀前端开发者的关键。