Files
sanguo_vnpy_v2/requirements/implementation-plan.md
T
claude_dev 653472def3 feat: VeighNa Web 前端对等实现
对齐 VeighNa 4.4 原生 Qt UI,新增成交监控、资金监控、网关管理、全局配置等页面与 API,功能对等性 98.5%。

- 新增 API: /api/v1/trades, /api/v1/accounts, /api/v1/settings, 网关扩展
- 新增前端页面: 成交、资金、合约、网关、全局配置、微信通知
- 扩展导航菜单与实时数据推送
- 补充需求分析与实现计划文档
2026-07-04 20:13:03 +08:00

14 KiB

Sanguo VeighNa Web 前端对等实现计划

目标

使 Web 前端功能与 VeighNa 4.4 原生 Qt UI 对等,提供完整的交易功能。


一、需求概览

基于 requirements/veighna-ui-analysis.md 的深度分析,需要实现以下功能:

1.1 核心缺失功能(Phase 1 - 高优先级)

  • 成交监控页面
  • 资金监控页面
  • 网关连接管理

1.2 增强功能(Phase 2 - 中优先级)

  • 活动委托视图
  • 市场深度盘口
  • 合约管理
  • 表格排序

1.3 完善功能(Phase 3 - 低优先级)

  • 双击交互
  • CSV 导出
  • 全局配置编辑
  • 微信通知设置

二、技术架构

2.1 现有架构

sanguo_web/
├── api/              # FastAPI 端点
├── services/         # 业务逻辑
├── websocket/        # WebSocket 处理
├── static/
│   ├── css/         # 样式
│   └── js/          # Vue.js 应用
└── templates/       # HTML 模板

2.2 后端扩展

API 端点

端点 方法 功能
/api/trades GET 获取所有成交记录
/api/accounts GET 获取所有账户资金
/api/gateways GET 获取所有可用网关
/api/gateways/{name}/setting GET 获取网关配置模板
/api/gateways/{name}/connect POST 连接网关
/api/gateways/{name}/disconnect POST 断开网关
/api/gateways/{name}/status GET 获取网关状态
/api/contracts GET 获取所有合约

WebSocket 事件

事件 说明
EVENT_TRADE 成交数据推送
EVENT_ACCOUNT 账户数据推送
EVENT_GATEWAY 网关状态变化

2.3 前端扩展

新增页面组件

组件 路由 功能
TradesPage /trades 成交监控表格
AccountPage /account 资金监控表格
GatewayPage /gateway 网关管理
ContractPage /contracts 合约查询
ActiveOrdersPage /active-orders 活动委托

导航菜单扩展

const navItems = [
    { id: 'dashboard', label: '总览', icon: '' },
    { id: 'market', label: '行情', icon: '' },
    { id: 'trading', label: '交易', icon: '' },
    { id: 'trades', label: '成交', icon: '' },      // 新增
    { id: 'position', label: '持仓', icon: '' },
    { id: 'account', label: '资金', icon: '' },     // 新增
    { id: 'gateway', label: '网关', icon: '' },    // 新增
    { id: 'contracts', label: '合约', icon: '' },   // 新增
    { id: 'strategy', label: '策略', icon: '' },
    { id: 'log', label: '日志', icon: '' }
];

三、Phase 1 实现方案

3.1 成交监控页面 (TradesPage)

后端实现

文件: sanguo_web/api/trades.py

from fastapi import APIRouter, Depends
from typing import List
from vnpy.trader.object import TradeData

router = APIRouter(prefix="/api/trades", tags=["trades"])

@router.get("/", response_model=List[dict])
async def get_all_trades():
    """获取所有成交记录"""
    from sanguo_web.services.main_service import main_engine
    trades = main_engine.get_all_trades()
    return [t.__dict__ for t in trades]

WebSocket 事件

文件: sanguo_web/websocket/events.py

def handle_trade_event(event: Event):
    """处理成交事件"""
    trade = event.data
    # 推送到前端
    broadcast_event("trade", {
        "tradeid": trade.tradeid,
        "orderid": trade.orderid,
        "symbol": trade.symbol,
        "exchange": trade.exchange.value,
        "direction": trade.direction.value,
        "offset": trade.offset.value,
        "price": trade.price,
        "volume": trade.volume,
        "datetime": trade.datetime.strftime("%H:%M:%S"),
        "gateway_name": trade.gateway_name
    })

前端实现

文件: sanguo_web/static/js/app.js

// 成交数据
const trades = ref([]);

// 获取成交数据
async function loadTrades() {
    const response = await fetch('/api/trades');
    trades.value = await response.json();
}

// WebSocket 处理
ws.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'trade') {
        trades.value.unshift(data.payload);
    }
});

HTML 模板

<section v-if="currentPage === 'trades'" class="page-content">
    <div class="card">
        <div class="card-header">
            <h3>成交记录</h3>
            <button @click="refreshTrades" class="btn-secondary">刷新</button>
        </div>
        <table class="data-table">
            <thead>
                <tr>
                    <th>成交号</th>
                    <th>委托号</th>
                    <th>合约</th>
                    <th>方向</th>
                    <th>价格</th>
                    <th>数量</th>
                    <th>时间</th>
                </tr>
            </thead>
            <tbody>
                <tr v-for="trade in trades" :key="trade.tradeid">
                    <td>{{ trade.tradeid }}</td>
                    <td>{{ trade.orderid }}</td>
                    <td>{{ trade.symbol }}</td>
                    <td :class="trade.direction">{{ trade.direction }}</td>
                    <td>{{ formatNumber(trade.price) }}</td>
                    <td>{{ trade.volume }}</td>
                    <td>{{ trade.datetime }}</td>
                </tr>
            </tbody>
        </table>
    </div>
</section>

3.2 资金监控页面 (AccountPage)

后端实现

文件: sanguo_web/api/accounts.py

from fastapi import APIRouter
from typing import List

router = APIRouter(prefix="/api/accounts", tags=["accounts"])

@router.get("/", response_model=List[dict])
async def get_all_accounts():
    """获取所有账户资金"""
    from sanguo_web.services.main_service import main_engine
    accounts = main_engine.get_all_accounts()
    return [a.__dict__ for a in accounts]

WebSocket 事件

def handle_account_event(event: Event):
    """处理账户事件"""
    account = event.data
    broadcast_event("account", {
        "accountid": account.accountid,
        "balance": account.balance,
        "frozen": account.frozen,
        "available": account.available,
        "gateway_name": account.gateway_name
    })

前端实现

// 账户数据
const accounts = ref([]);

// 获取账户数据
async function loadAccounts() {
    const response = await fetch('/api/accounts');
    accounts.value = await response.json();
}

// WebSocket 处理
ws.addEventListener('message', (event) => {
    const data = JSON.parse(event.data);
    if (data.type === 'account') {
        const index = accounts.value.findIndex(a => a.accountid === data.payload.accountid);
        if (index >= 0) {
            accounts.value[index] = data.payload;
        } else {
            accounts.value.push(data.payload);
        }
    }
});

3.3 网关连接管理

后端实现

文件: sanguo_web/api/gateways.py

from fastapi import APIRouter
from typing import List, Dict, Any

router = APIRouter(prefix="/api/gateways", tags=["gateways"])

@router.get("/", response_model=List[str])
async def get_all_gateways():
    """获取所有可用网关"""
    from sanguo_web.services.main_service import main_engine
    return main_engine.get_all_gateway_names()

@router.get("/{name}/setting")
async def get_gateway_setting(name: str):
    """获取网关配置模板"""
    from sanguo_web.services.main_service import main_engine
    return main_engine.get_default_setting(name)

@router.post("/{name}/connect")
async def connect_gateway(name: str, setting: Dict[str, Any]):
    """连接网关"""
    from sanguo_web.services.main_service import main_engine
    main_engine.connect(setting, name)
    return {"status": "connecting"}

@router.post("/{name}/disconnect")
async def disconnect_gateway(name: str):
    """断开网关"""
    from sanguo_web.services.main_service import main_engine
    main_engine.disconnect(name)
    return {"status": "disconnected"}

@router.get("/{name}/status")
async def get_gateway_status(name: str):
    """获取网关状态"""
    from sanguo_web.services.main_service import main_engine
    # 需要扩展 MainEngine 支持状态查询
    return {"name": name, "status": "connected"}

前端实现

// 网关数据
const gateways = ref([]);
const selectedGateway = ref(null);
const gatewayForm = ref({});
const showConnectDialog = ref(false);

// 获取网关列表
async function loadGateways() {
    const response = await fetch('/api/gateways');
    gateways.value = await response.json();
}

// 获取网关配置模板
async function openConnectDialog(gatewayName) {
    const response = await fetch(`/api/gateways/${gatewayName}/setting`);
    const setting = await response.json();
    gatewayForm.value = setting;
    selectedGateway.value = gatewayName;
    showConnectDialog.value = true;
}

// 连接网关
async function connectGateway() {
    const response = await fetch(`/api/gateways/${selectedGateway.value}/connect`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(gatewayForm.value)
    });
    showConnectDialog.value = false;
    // 刷新网关状态
}

HTML 模板(连接对话框)

<div v-if="showConnectDialog" class="modal">
    <div class="modal-content">
        <h3>连接 {{ selectedGateway }}</h3>
        <form @submit.prevent="connectGateway">
            <div v-for="(value, key) in gatewayForm" :key="key" class="form-group">
                <label>{{ key }}</label>
                <input v-if="typeof value === 'string'" type="text" v-model="gatewayForm[key]">
                <input v-else-if="typeof value === 'number'" type="number" v-model.number="gatewayForm[key]">
                <select v-else-if="Array.isArray(value)" v-model="gatewayForm[key]">
                    <option v-for="option in value" :key="option" :value="option">{{ option }}</option>
                </select>
            </div>
            <div class="modal-actions">
                <button type="button" @click="showConnectDialog = false">取消</button>
                <button type="submit">连接</button>
            </div>
        </form>
    </div>
</div>

四、实现顺序

Step 1: 后端基础设施(1-2天)

  1. 创建 API 路由文件
    • sanguo_web/api/trades.py
    • sanguo_web/api/accounts.py
    • sanguo_web/api/gateways.py
  2. 扩展 WebSocket 事件处理
    • 添加 EVENT_TRADE 处理
    • 添加 EVENT_ACCOUNT 处理
  3. 注册新路由到主应用

Step 2: 前端页面开发(2-3天)

  1. 扩展导航菜单
  2. 实现成交监控页面
  3. 实现资金监控页面
  4. 实现网关管理页面
  5. 添加相关样式

Step 3: 集成测试(1天)

  1. API 接口测试
  2. WebSocket 事件测试
  3. 端到端功能测试
  4. 修复发现的问题

Step 4: 代码审查(1天)

  1. 前端代码审查
  2. 后端代码审查
  3. 安全性检查

Step 5: 部署和验证(0.5天)

  1. 更新文档
  2. 部署验证
  3. 性能测试

五、关键实现细节

5.1 网关动态表单

网关配置模板由各网关心提供,格式为:

{
    "字段名": "默认值",  # 类型可以是 str, int, bool, list
    ...
}

前端需要根据类型动态渲染:

  • str → 文本输入框
  • int → 数字输入框
  • bool → 复选框
  • list → 下拉选择框

5.2 实时数据推送

使用 WebSocket 推送实时数据:

  • 新数据插入到数组头部
  • 对于有唯一键的数据(如账户),更新现有条目
  • 对于无唯一键的数据(如成交),只插入新条目

5.3 状态管理

使用 Vue 3 的 reactive/ref 管理状态:

  • 每个页面有独立的数据 ref
  • WebSocket 统一处理,根据事件类型更新对应数据
  • 避免全局状态污染

六、验收标准

6.1 功能验收

  • 成交监控页面能正确显示所有成交记录
  • 成交记录能实时更新
  • 资金监控页面能正确显示所有账户
  • 账户资金能实时更新
  • 网关列表能正确显示所有可用网关
  • 能成功连接/断开网关

6.2 性能验收

  • 页面加载时间 < 2秒
  • WebSocket 延迟 < 100ms
  • 表格渲染 100 条数据 < 500ms

6.3 兼容性验收

  • Chrome 浏览器正常
  • Firefox 浏览器正常
  • Safari 浏览器正常

七、后续阶段预览

Phase 2

  • 活动委托视图
  • 市场深度盘口(五档)
  • 合约管理页面
  • 表格排序功能

Phase 3

  • 双击交互(撤单、更新交易表单)
  • CSV 导出
  • 全局配置编辑器
  • 微信通知设置

六、实施进度

Phase 1 后端 (已完成 2026-07-04)

步骤 状态 说明
创建 API 路由 trades.py, accounts.py, settings.py
注册路由 __init__.py 已更新
修复模块加载 run_web.py 添加 vnpy 路径
修复循环导入 deps.py 使用动态导入
添加服务字段 main_service.py 添加 offset, gateway_name
生产部署 已部署到 ~/.sanguo_projects/sanguo_vnpy_v2
文档更新 docs/deployment/README.md 已更新

待完成

步骤 状态 说明
前端页面开发 成交监控、资金监控、网关管理页面
WebSocket 事件 EVENT_TRADE, EVENT_ACCOUNT 处理
集成测试 API 和 WebSocket 测试
代码审查 前后端代码审查

计划制定日期: 2026-07-02 预计完成时间: Phase 1 约 5-6 天 Phase 1 后端完成日期: 2026-07-04