Files
st/docs/Vue重构方案.md

16 KiB
Raw Blame History

web-app Vue 3 重构方案

📋 方案概述

将 web-app 从传统的 jQuery 项目重构为 Vue 3 现代化单页应用SPA

🎯 重构目标

技术目标

  • 使用 Vue 3 + Vite + TypeScript
  • 组件化、模块化开发
  • 统一前后端技术栈(与管理后台一致)
  • 优化性能和用户体验
  • 便于维护和扩展

业务目标

  • 保留所有原有功能
  • 改善 UI/UX 设计
  • 适配移动端
  • 支持主题切换
  • 国际化支持

🏗️ 技术栈选择

核心技术

{
  "框架": "Vue 3 (Composition API)",
  "构建工具": "Vite 5",
  "语言": "TypeScript",
  "状态管理": "Pinia",
  "路由": "Vue Router 4",
  "HTTP": "Axios",
  "WebSocket": "原生 WebSocket / Socket.io-client"
}

UI 框架选择

方案一Element Plus推荐

# 优点
✅ 与管理后台统一web/ 使用的就是 Element Plus
✅ 组件丰富,开箱即用
✅ 中文文档完善
✅ 支持深色主题
✅ TypeScript 支持好

# 缺点
⚠️ 组件较重(如果只需要简单 UI可能过度设计

方案二Naive UI

# 优点
✅ 轻量级,性能好
✅ TypeScript 原生支持
✅ 组件设计现代
✅ 支持深色主题

# 缺点
⚠️ 与管理后台不统一

方案三:自定义 UI参考 auth.html

# 优点
✅ 完全自定义,独特设计
✅ 轻量级
✅ 可以复用已有的 auth.css 样式

# 缺点
⚠️ 需要自己实现所有组件
⚠️ 开发时间较长

推荐Element Plus + 自定义样式

  • 使用 Element Plus 的基础组件(表单、按钮、对话框等)
  • 自定义主题和关键页面样式
  • 兼顾开发效率和设计独特性

📂 项目结构

新项目结构(推荐)

web-app-vue/                    # 新建的 Vue 项目
├── public/
│   ├── favicon.ico
│   └── img/                    # 静态图片资源
├── src/
│   ├── api/                    # API 接口
│   │   ├── auth.ts            # 认证接口
│   │   ├── character.ts       # 角色接口
│   │   ├── chat.ts            # 对话接口
│   │   └── index.ts           # axios 配置
│   ├── assets/                 # 资源文件
│   │   ├── styles/            # 全局样式
│   │   │   ├── index.scss
│   │   │   ├── variables.scss
│   │   │   └── dark-theme.scss
│   │   └── images/
│   ├── components/             # 通用组件
│   │   ├── common/            # 基础组件
│   │   │   ├── AppHeader.vue
│   │   │   ├── AppSidebar.vue
│   │   │   └── Loading.vue
│   │   ├── chat/              # 对话组件
│   │   │   ├── ChatMessage.vue
│   │   │   ├── ChatInput.vue
│   │   │   ├── MessageSwipe.vue
│   │   │   └── VoiceInput.vue
│   │   ├── character/         # 角色组件
│   │   │   ├── CharacterCard.vue
│   │   │   ├── CharacterList.vue
│   │   │   ├── CharacterEditor.vue
│   │   │   └── CharacterImport.vue
│   │   └── settings/          # 设置组件
│   │       ├── AIConfig.vue
│   │       ├── ThemeConfig.vue
│   │       └── UserProfile.vue
│   ├── composables/           # 组合式函数
│   │   ├── useAuth.ts         # 认证相关
│   │   ├── useChat.ts         # 对话相关
│   │   ├── useCharacter.ts    # 角色相关
│   │   └── useWebSocket.ts    # WebSocket
│   ├── layouts/               # 布局组件
│   │   ├── DefaultLayout.vue  # 默认布局
│   │   ├── AuthLayout.vue     # 认证页布局
│   │   └── ChatLayout.vue     # 对话页布局
│   ├── router/                # 路由配置
│   │   ├── index.ts
│   │   └── guards.ts          # 路由守卫
│   ├── stores/                # Pinia 状态管理
│   │   ├── auth.ts            # 用户认证
│   │   ├── chat.ts            # 对话状态
│   │   ├── character.ts       # 角色管理
│   │   ├── settings.ts        # 设置
│   │   └── index.ts
│   ├── types/                 # TypeScript 类型
│   │   ├── api.d.ts
│   │   ├── character.d.ts
│   │   ├── chat.d.ts
│   │   └── user.d.ts
│   ├── utils/                 # 工具函数
│   │   ├── request.ts         # HTTP 请求封装
│   │   ├── storage.ts         # 本地存储
│   │   ├── websocket.ts       # WebSocket 封装
│   │   ├── format.ts          # 格式化
│   │   └── validate.ts        # 验证
│   ├── views/                 # 页面视图
│   │   ├── auth/              # 认证页
│   │   │   ├── Login.vue
│   │   │   └── Register.vue
│   │   ├── home/              # 主页
│   │   │   └── Index.vue
│   │   ├── character/         # 角色页
│   │   │   ├── List.vue
│   │   │   ├── Detail.vue
│   │   │   └── Create.vue
│   │   ├── chat/              # 对话页
│   │   │   └── Index.vue
│   │   ├── settings/          # 设置页
│   │   │   └── Index.vue
│   │   └── user/              # 用户中心
│   │       └── Profile.vue
│   ├── App.vue                # 根组件
│   └── main.ts                # 入口文件
├── .env.development           # 开发环境配置
├── .env.production            # 生产环境配置
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
└── README.md

与现有项目的关系

st/
├── server/                # Go 后端(不变)
├── web/                   # Vue 管理后台(不变)
├── web-app/               # 旧项目(保留备份)
└── web-app-vue/           # 新的 Vue 前台(新建)
    └── ...

🚀 实施步骤

阶段一项目初始化1-2 天)

1.1 创建 Vue 项目

# 使用 Vite 创建项目
npm create vite@latest web-app-vue -- --template vue-ts

cd web-app-vue
npm install

1.2 安装依赖

# UI 框架
npm install element-plus

# 路由和状态管理
npm install vue-router@4 pinia

# HTTP 和工具
npm install axios
npm install @vueuse/core  # Vue 常用组合式函数

# 开发依赖
npm install -D sass
npm install -D unplugin-vue-components unplugin-auto-import

1.3 配置 Vite

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: [
    vue(),
    AutoImport({
      resolvers: [ElementPlusResolver()],
      imports: ['vue', 'vue-router', 'pinia'],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
  server: {
    port: 3000,
    proxy: {
      '/app': {
        target: 'http://localhost:8888',
        changeOrigin: true,
      },
    },
  },
})

1.4 基础配置

// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import router from './router'
import App from './App.vue'
import './assets/styles/index.scss'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(router)
app.use(ElementPlus)
app.mount('#app')

阶段二核心功能开发5-7 天)

2.1 认证模块1 天)

  • 登录页面
  • 注册页面
  • Token 管理
  • 路由守卫

2.2 角色管理2 天)

  • 角色列表
  • 角色详情
  • 角色创建/编辑
  • 角色导入/导出
  • 收藏功能

2.3 对话功能3 天)

  • 对话界面
  • 消息发送
  • 消息渲染
  • Swipe 功能
  • WebSocket 实时通信
  • 流式输出

2.4 设置页面1 天)

  • AI 配置
  • 主题设置
  • 用户偏好

阶段三高级功能3-5 天)

3.1 AI 功能

  • 多模型支持
  • Prompt 管理
  • World Info
  • 向量记忆

3.2 UI/UX 优化

  • 响应式布局
  • 移动端适配
  • 主题切换
  • 动画效果

3.3 文件管理

  • 头像上传
  • 文件管理
  • 图片预览

阶段四测试和优化2-3 天)

  • 单元测试
  • E2E 测试
  • 性能优化
  • 打包优化
  • 部署配置

📦 关键组件示例

1. 认证 Store

// src/stores/auth.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { login, register, getUserInfo } from '@/api/auth'
import type { LoginRequest, User } from '@/types/user'

export const useAuthStore = defineStore('auth', () => {
  const token = ref<string>(localStorage.getItem('st_access_token') || '')
  const user = ref<User | null>(null)

  const isLoggedIn = computed(() => !!token.value)

  async function handleLogin(data: LoginRequest) {
    const res = await login(data)
    token.value = res.data.token
    user.value = res.data.user
    localStorage.setItem('st_access_token', res.data.token)
    localStorage.setItem('st_user_info', JSON.stringify(res.data.user))
  }

  async function handleLogout() {
    token.value = ''
    user.value = null
    localStorage.removeItem('st_access_token')
    localStorage.removeItem('st_user_info')
  }

  async function fetchUserInfo() {
    const res = await getUserInfo()
    user.value = res.data
  }

  return {
    token,
    user,
    isLoggedIn,
    handleLogin,
    handleLogout,
    fetchUserInfo,
  }
})

2. HTTP 请求封装

// src/utils/request.ts
import axios from 'axios'
import { ElMessage } from 'element-plus'
import { useAuthStore } from '@/stores/auth'

const request = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8888',
  timeout: 10000,
})

// 请求拦截器
request.interceptors.request.use(
  (config) => {
    const authStore = useAuthStore()
    if (authStore.token) {
      config.headers['x-token'] = authStore.token
    }
    return config
  },
  (error) => {
    return Promise.reject(error)
  }
)

// 响应拦截器
request.interceptors.response.use(
  (response) => {
    const res = response.data
    if (res.code !== 0) {
      ElMessage.error(res.msg || '请求失败')
      return Promise.reject(new Error(res.msg || '请求失败'))
    }
    return res
  },
  (error) => {
    ElMessage.error(error.message || '网络错误')
    return Promise.reject(error)
  }
)

export default request

3. 角色卡片组件

<!-- src/components/character/CharacterCard.vue -->
<template>
  <div class="character-card" @click="handleClick">
    <div class="card-header">
      <img :src="character.avatar" :alt="character.name" class="avatar" />
      <el-icon v-if="isFavorite" class="favorite-icon">
        <Star />
      </el-icon>
    </div>
    <div class="card-body">
      <h3 class="character-name">{{ character.name }}</h3>
      <p class="character-description">{{ character.description }}</p>
    </div>
    <div class="card-footer">
      <el-tag v-for="tag in character.tags" :key="tag" size="small">
        {{ tag }}
      </el-tag>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import type { Character } from '@/types/character'

interface Props {
  character: Character
}

const props = defineProps<Props>()
const emit = defineEmits<{
  click: [character: Character]
}>()

const isFavorite = computed(() => props.character.isFavorite)

function handleClick() {
  emit('click', props.character)
}
</script>

<style scoped lang="scss">
.character-card {
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
  transition: all 0.3s;
  background: var(--el-bg-color);
  
  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
  }
  
  .card-header {
    position: relative;
    
    .avatar {
      width: 100%;
      height: 200px;
      object-fit: cover;
    }
    
    .favorite-icon {
      position: absolute;
      top: 12px;
      right: 12px;
      color: #f59e0b;
      font-size: 24px;
    }
  }
  
  .card-body {
    padding: 16px;
    
    .character-name {
      font-size: 18px;
      font-weight: bold;
      margin-bottom: 8px;
    }
    
    .character-description {
      color: var(--el-text-color-secondary);
      font-size: 14px;
      line-height: 1.6;
      display: -webkit-box;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      overflow: hidden;
    }
  }
  
  .card-footer {
    padding: 0 16px 16px;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
}
</style>

🔄 迁移策略

渐进式迁移(推荐)

  1. Phase 1: 新功能使用 Vue

    • 保留旧 web-app用于稳定运行
    • 新功能在 web-app-vue 开发
    • 通过路由切换新旧页面
  2. Phase 2: 逐步迁移

    • 按模块迁移(认证 → 角色 → 对话 → 设置)
    • 每个模块迁移后充分测试
    • 确保功能完整
  3. Phase 3: 完全替换

    • 所有功能迁移完成
    • 彻底删除旧代码
    • web-app-vue 改名为 web-app

一次性重写(不推荐,风险高)

除非你有充足的时间和人力,否则不建议一次性重写整个项目。

📊 与现有项目的集成

Go 后端配置

// server/initialize/router.go

// 前台 Vue 应用静态文件
webAppVuePath := "../web-app-vue/dist"
if _, err := os.Stat(webAppVuePath); err == nil {
    Router.Static("/assets", webAppVuePath+"/assets")
    Router.StaticFile("/", webAppVuePath+"/index.html")
    global.GVA_LOG.Info("前台 Vue 应用已启动: " + webAppVuePath)
}

环境配置

# .env.development
VITE_API_BASE_URL=http://localhost:8888
VITE_WS_URL=ws://localhost:8888

# .env.production
VITE_API_BASE_URL=https://your-domain.com
VITE_WS_URL=wss://your-domain.com

🎯 开发优先级

P0必须

  1. 用户认证(登录/注册)
  2. 角色列表和详情
  3. 基本对话功能
  4. AI 配置

P1重要

  1. 角色编辑和创建
  2. 消息 Swipe
  3. 文件上传
  4. 设置页面

P2可选

  1. World Info
  2. 向量记忆
  3. 高级 AI 功能
  4. 主题切换

📝 注意事项

1. 保持功能一致性

  • 确保所有原有功能都能在 Vue 版本中实现
  • 参考旧版的交互逻辑
  • 不要遗漏边界情况

2. 性能优化

  • 使用虚拟滚动(角色列表、消息列表)
  • 图片懒加载
  • 路由懒加载
  • 打包分析和优化

3. 移动端适配

  • 响应式设计
  • 触摸手势支持
  • 移动端专用布局

4. 国际化

  • 使用 vue-i18n
  • 支持多语言切换
  • 本地化日期和数字

📚 学习资源

🎉 总结

为什么要重构?

  1. 代码质量:从混乱的 jQuery 代码到清晰的组件化代码
  2. 开发效率热更新、TypeScript、自动导入
  3. 性能提升Vite 构建、按需加载、虚拟滚动
  4. 易于维护:模块化、类型安全、单一职责
  5. 技术统一:与管理后台使用相同技术栈
  6. 未来发展:易于扩展新功能

建议的时间线

  • 快速原型1 周):核心功能,可以开始使用
  • MVP 版本2-3 周):主要功能完成
  • 完整版本4-6 周):所有功能迁移完成
  • 优化打磨持续性能优化、bug 修复

更新日期: 2026-02-10
版本: v1.0.0