米塞斯行为引擎 - 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