米塞斯行为引擎 - PWA 前端架构设计

版本:1.0 更新日期:2026-01-27


1. 技术栈选择

技术 选择 理由
框架 Vue 3 + Vite 轻量、快速、中文生态好
UI 库 Vant 4 移动端UI库,体验好
状态管理 Pinia Vue 官方推荐,简单
路由 Vue Router 4 SPA 路由
HTTP Axios 请求封装
PWA vite-plugin-pwa 自动生成 SW
图表 ECharts 路径可视化
音频 Web Audio API 语音录制/播放

2. 项目结构

mises-pwa/
├── public/
│   ├── favicon.ico
│   ├── icons/                    # PWA 图标
│   │   ├── icon-72x72.png
│   │   ├── icon-96x96.png
│   │   ├── icon-128x128.png
│   │   ├── icon-144x144.png
│   │   ├── icon-152x152.png
│   │   ├── icon-192x192.png
│   │   ├── icon-384x384.png
│   │   └── icon-512x512.png
│   └── robots.txt
│
├── src/
│   ├── main.js                   # 入口文件
│   ├── App.vue                   # 根组件
│   │
│   ├── api/                      # API 封装
│   │   ├── index.js              # axios 配置
│   │   ├── mcp.js                # MCP 分析接口
│   │   ├── voice.js              # 语音接口
│   │   ├── user.js               # 用户接口
│   │   └── history.js            # 历史记录接口
│   │
│   ├── views/                    # 页面
│   │   ├── Home.vue              # 首页(对话主界面)
│   │   ├── Chat.vue              # 对话详情
│   │   ├── Paths.vue             # 路径可视化
│   │   ├── History.vue           # 历史记录
│   │   ├── Profile.vue           # 用户画像
│   │   ├── Settings.vue          # 设置
│   │   └── About.vue             # 关于
│   │
│   ├── components/               # 组件
│   │   ├── common/               # 通用组件
│   │   │   ├── AppHeader.vue
│   │   │   ├── AppTabbar.vue
│   │   │   └── Loading.vue
│   │   │
│   │   ├── chat/                 # 聊天组件
│   │   │   ├── MessageList.vue   # 消息列表
│   │   │   ├── MessageBubble.vue # 消息气泡
│   │   │   ├── InputBar.vue      # 输入栏
│   │   │   ├── VoiceRecorder.vue # 语音录制
│   │   │   ├── VoicePlayer.vue   # 语音播放
│   │   │   └── QuickActions.vue  # 快捷按钮
│   │   │
│   │   ├── paths/                # 路径组件
│   │   │   ├── PathCard.vue      # 路径卡片
│   │   │   ├── PathTree.vue      # 路径树形图
│   │   │   └── FeasibilityBar.vue # 可行性进度条
│   │   │
│   │   └── profile/              # 画像组件
│   │       ├── ValueRadar.vue    # 价值观雷达图
│   │       ├── InsightCard.vue   # 洞察卡片
│   │       └── PatternList.vue   # 行为模式列表
│   │
│   ├── stores/                   # Pinia 状态
│   │   ├── user.js               # 用户状态
│   │   ├── chat.js               # 聊天状态
│   │   ├── session.js            # 会话状态
│   │   └── settings.js           # 设置状态
│   │
│   ├── composables/              # 组合式函数
│   │   ├── useVoice.js           # 语音录制/播放
│   │   ├── useAudio.js           # 音频处理
│   │   ├── useDevice.js          # 设备信息
│   │   └── useNotification.js    # 通知
│   │
│   ├── router/                   # 路由配置
│   │   └── index.js
│   │
│   ├── styles/                   # 全局样式
│   │   ├── variables.scss        # 变量
│   │   ├── reset.scss            # 重置
│   │   └── global.scss           # 全局样式
│   │
│   └── utils/                    # 工具函数
│       ├── storage.js            # 本地存储
│       ├── device.js             # 设备ID
│       ├── format.js             # 格式化
│       └── constants.js          # 常量
│
├── index.html
├── vite.config.js                # Vite 配置
├── package.json
└── README.md

3. PWA 配置

3.1 manifest.json

{
  "name": "米塞斯行为引擎",
  "short_name": "米塞斯",
  "description": "AI决策分析助手,帮你从不适中找到方向",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0f0f1a",
  "theme_color": "#00d4aa",
  "orientation": "portrait",
  "icons": [
    {
      "src": "/icons/icon-72x72.png",
      "sizes": "72x72",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-96x96.png",
      "sizes": "96x96",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-128x128.png",
      "sizes": "128x128",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-144x144.png",
      "sizes": "144x144",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-152x152.png",
      "sizes": "152x152",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-192x192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/icon-384x384.png",
      "sizes": "384x384",
      "type": "image/png"
    },
    {
      "src": "/icons/icon-512x512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ],
  "screenshots": [
    {
      "src": "/screenshots/chat.png",
      "sizes": "390x844",
      "type": "image/png",
      "form_factor": "narrow"
    }
  ],
  "categories": ["productivity", "lifestyle"],
  "lang": "zh-CN"
}

3.2 vite.config.js (PWA 配置)

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'

export default defineConfig({
  plugins: [
    vue(),
    VitePWA({
      registerType: 'autoUpdate',
      includeAssets: ['favicon.ico', 'robots.txt', 'icons/*.png'],
      manifest: {
        name: '米塞斯行为引擎',
        short_name: '米塞斯',
        description: 'AI决策分析助手',
        theme_color: '#00d4aa',
        background_color: '#0f0f1a',
        display: 'standalone',
        icons: [
          { src: '/icons/icon-192x192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icons/icon-512x512.png', sizes: '512x512', type: 'image/png' }
        ]
      },
      workbox: {
        // 缓存策略
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/mbe\.hi-maker\.com\/api\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 100,
                maxAgeSeconds: 60 * 60 // 1小时
              }
            }
          },
          {
            urlPattern: /\.(png|jpg|jpeg|svg|gif)$/,
            handler: 'CacheFirst',
            options: {
              cacheName: 'image-cache',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 60 * 60 * 24 * 30 // 30天
              }
            }
          }
        ]
      }
    })
  ],
  server: {
    proxy: {
      '/api': 'https://mbe.hi-maker.com',
      '/mcp': 'https://mbe.hi-maker.com'
    }
  }
})

4. 页面设计

4.1 首页 (Home.vue) - 对话主界面

┌─────────────────────────────────────┐
│  🧠 米塞斯行为引擎        ● 在线    │  ← Header
├─────────────────────────────────────┤
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🤖 你好,我是米塞斯         │   │  ← 欢迎消息
│  │    基于行为学理论帮你分析    │   │
│  └─────────────────────────────┘   │
│                                     │
│        ┌──────────────────┐         │
│        │ 我想聊聊工作压力  │         │  ← 用户消息
│        └──────────────────┘         │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🤖 我感受到你的压力...      │   │  ← AI回复
│  │    ▶ [语音播放]             │   │
│  │                             │   │
│  │    检测到的不适:           │   │
│  │    • 工作强度过大           │   │
│  │    • 成就感缺失             │   │
│  └─────────────────────────────┘   │
│                                     │
├─────────────────────────────────────┤
│ [工作压力] [家庭关系] [职业选择]    │  ← 快捷按钮
├─────────────────────────────────────┤
│  ┌───────────────────────────┐ 🎙️  │  ← 输入区
│  │ 输入你的困惑...           │     │
│  └───────────────────────────┘ ➤   │
├─────────────────────────────────────┤
│   🏠      📊      📜      👤       │  ← 底部导航
│  首页    路径    历史    我的      │
└─────────────────────────────────────┘

4.2 路径页面 (Paths.vue) - 可视化决策路径

┌─────────────────────────────────────┐
│  ← 返回     决策路径分析            │
├─────────────────────────────────────┤
│                                     │
│  当前愿望:获得工作意义感           │
│                                     │
│  ┌─────────────────────────────┐   │
│  │      🎯                      │   │
│  │       │                      │   │
│  │   ┌───┴───┐                 │   │  ← 路径树
│  │   │       │                 │   │
│  │  🅰️      🅱️      🅲️         │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🅰️ 内部探索                  │   │  ← 路径卡片
│  │                             │   │
│  │ 在现公司寻找更有意义的项目   │   │
│  │                             │   │
│  │ 可行性: ████████░░ 85%      │   │
│  │                             │   │
│  │ 为什么适合你:               │   │
│  │ 你过去内部调岗成功过         │   │
│  │                             │   │
│  │ 第一步:下周找HR聊聊        │   │
│  │                             │   │
│  │        [选择此路径]          │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🅱️ 副业试水          75%    │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🅲️ 跳槽使命公司       50%    │   │
│  └─────────────────────────────┘   │
│                                     │
└─────────────────────────────────────┘

4.3 用户画像 (Profile.vue)

┌─────────────────────────────────────┐
│  ← 返回        我的画像             │
├─────────────────────────────────────┤
│                                     │
│         ┌─────────┐                 │
│         │  👤     │                 │
│         │ 用户123 │                 │
│         └─────────┘                 │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 🎯 核心价值观                │   │
│  │                             │   │
│  │     家庭 ████████░░ 8       │   │
│  │     事业 ███████░░░ 7       │   │
│  │     健康 ██████░░░░ 6       │   │
│  │     自由 █████░░░░░ 5       │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ ⏱️ 米塞斯参数               │   │
│  │                             │   │
│  │ 时间偏好: 偏长期 (0.3)      │   │
│  │ 风险承受: 中等保守 (0.4)    │   │
│  └─────────────────────────────┘   │
│                                     │
│  ┌─────────────────────────────┐   │
│  │ 💡 AI洞察                   │   │
│  │                             │   │
│  │ • 工作压力常在季度末爆发     │   │
│  │ • 事业 vs 家人 长期冲突     │   │
│  │ • 倾向渐进式改变            │   │
│  └─────────────────────────────┘   │
│                                     │
└─────────────────────────────────────┘

5. 核心代码示例

5.1 API 封装 (api/index.js)

import axios from 'axios'
import { useUserStore } from '@/stores/user'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_BASE || 'https://mbe.hi-maker.com',
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json'
  }
})

// 请求拦截器
api.interceptors.request.use(config => {
  const userStore = useUserStore()
  if (userStore.deviceId) {
    config.headers['X-Device-ID'] = userStore.deviceId
  }
  return config
})

// 响应拦截器
api.interceptors.response.use(
  response => response.data,
  error => {
    console.error('API Error:', error)
    return Promise.reject(error)
  }
)

export default api

5.2 MCP 分析接口 (api/mcp.js)

import api from './index'

export const mcpApi = {
  // 米塞斯分析
  analyze(userInput, deviceId) {
    return api.post('/mcp/analyze', {
      user_input: userInput,
      device_id: deviceId
    })
  },
  
  // 反馈
  feedback(deviceId, actionResult, satisfaction) {
    return api.post('/mcp/feedback', {
      device_id: deviceId,
      action_result: actionResult,
      satisfaction: satisfaction
    })
  }
}

5.3 语音组合式函数 (composables/useVoice.js)

import { ref } from 'vue'

export function useVoice() {
  const isRecording = ref(false)
  const audioBlob = ref(null)
  const duration = ref(0)
  
  let mediaRecorder = null
  let audioChunks = []
  let startTime = 0
  let timerInterval = null
  
  // 开始录音
  async function startRecording() {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: {
          sampleRate: 16000,
          channelCount: 1,
          echoCancellation: true,
          noiseSuppression: true
        }
      })
      
      audioChunks = []
      mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'audio/webm;codecs=opus'
      })
      
      mediaRecorder.ondataavailable = (e) => {
        if (e.data.size > 0) {
          audioChunks.push(e.data)
        }
      }
      
      mediaRecorder.onstop = () => {
        stream.getTracks().forEach(track => track.stop())
        audioBlob.value = new Blob(audioChunks, { type: 'audio/webm' })
      }
      
      mediaRecorder.start(100)
      isRecording.value = true
      startTime = Date.now()
      
      // 更新时长
      timerInterval = setInterval(() => {
        duration.value = Math.floor((Date.now() - startTime) / 1000)
      }, 1000)
      
      return true
    } catch (err) {
      console.error('录音失败:', err)
      return false
    }
  }
  
  // 停止录音
  function stopRecording() {
    if (mediaRecorder && mediaRecorder.state !== 'inactive') {
      mediaRecorder.stop()
    }
    isRecording.value = false
    clearInterval(timerInterval)
  }
  
  // 播放音频
  function playAudio(base64Data) {
    const audio = new Audio('data:audio/mpeg;base64,' + base64Data)
    audio.play()
    return audio
  }
  
  return {
    isRecording,
    audioBlob,
    duration,
    startRecording,
    stopRecording,
    playAudio
  }
}

5.4 聊天状态 (stores/chat.js)

import { defineStore } from 'pinia'
import { mcpApi } from '@/api/mcp'

export const useChatStore = defineStore('chat', {
  state: () => ({
    messages: [],
    isLoading: false,
    currentSession: null,
    paths: []
  }),
  
  actions: {
    // 发送消息
    async sendMessage(text, deviceId) {
      // 添加用户消息
      this.messages.push({
        id: Date.now(),
        type: 'user',
        content: text,
        time: new Date()
      })
      
      this.isLoading = true
      
      try {
        const response = await mcpApi.analyze(text, deviceId)
        
        // 添加AI回复
        this.messages.push({
          id: Date.now() + 1,
          type: 'bot',
          content: response.speech_output,
          audioBase64: response.audio_base64,
          session: response.session,
          analysis: response.analysis,
          time: new Date()
        })
        
        // 更新会话状态
        if (response.session) {
          this.currentSession = response.session
        }
        
        // 更新路径
        if (response.paths) {
          this.paths = response.paths
        }
        
        return response
      } catch (error) {
        this.messages.push({
          id: Date.now() + 1,
          type: 'bot',
          content: '抱歉,服务暂时不可用',
          isError: true,
          time: new Date()
        })
        throw error
      } finally {
        this.isLoading = false
      }
    },
    
    // 清空消息
    clearMessages() {
      this.messages = []
      this.currentSession = null
      this.paths = []
    }
  },
  
  // 持久化(可选)
  persist: {
    key: 'mbe-chat',
    storage: localStorage,
    paths: ['messages']
  }
})

6. 部署方案

6.1 构建命令

# 安装依赖
npm install

# 开发模式
npm run dev

# 生产构建
npm run build

# 预览构建结果
npm run preview

6.2 部署到 MBE 服务

可以将构建后的 dist 目录集成到 FastAPI 中:

# src/api/pwa.py
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles

router = APIRouter()

# 挂载 PWA 静态文件
app.mount("/app", StaticFiles(directory="pwa/dist", html=True), name="pwa")

6.3 Nginx 配置(独立部署)

server {
    listen 443 ssl http2;
    server_name mbe.hi-maker.com;
    
    root /var/www/mises-pwa/dist;
    index index.html;
    
    # PWA 前端
    location / {
        try_files $uri $uri/ /index.html;
    }
    
    # API 代理
    location /api {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
    
    location /mcp {
        proxy_pass http://localhost:8000;
        proxy_set_header Host $host;
    }
    
    # Service Worker 不缓存
    location /sw.js {
        add_header Cache-Control "no-cache";
        proxy_cache_bypass $http_pragma;
    }
}

7. 开发计划

阶段 内容 工作量
Phase 1 项目初始化 + PWA配置 + 基础组件 2天
Phase 2 对话主界面 + 语音录制/播放 3天
Phase 3 路径可视化页面 2天
Phase 4 用户画像 + 历史记录 2天
Phase 5 设置页 + 离线支持 + 测试 2天
Phase 6 部署 + 优化 1天

总计:约 12 天


8. 效果预览

安装到桌面

用户访问 PWA 后,浏览器会提示"添加到主屏幕":

┌──────────────────────────────┐
│                              │
│  添加到主屏幕                │
│                              │
│  🧠 米塞斯                   │
│                              │
│  [取消]         [添加]       │
│                              │
└──────────────────────────────┘

安装后,用户可以像原生 APP 一样从桌面图标启动。


文档版本:1.0 | 最后更新:2026-01-27