Files
sanguo_vnpy_v2/sanguo_web/static/js/app.js
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

1133 lines
38 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Sanguo VeighNa Web 应用
* Vue.js 3 单页应用
*/
const { createApp, ref, computed, onMounted, onUnmounted, watch, nextTick } = Vue;
createApp({
setup() {
// ============================================
// 状态定义
// ============================================
// 认证状态
const isLoggedIn = ref(false);
const currentUser = ref({ username: '' });
const loginForm = ref({ username: '', password: '' });
const loginError = ref('');
const isLoading = ref(false);
// 当前页面
const currentPage = ref('dashboard');
// 导航菜单
const navItems = [
{ id: 'dashboard', label: '总览', icon: '' },
{ id: 'market', label: '行情', icon: '' },
{ id: 'trading', label: '交易', icon: '' },
{ id: 'active_orders', label: '活动委托', icon: '' },
{ id: 'trades', label: '成交', icon: '' },
{ id: 'position', label: '持仓', icon: '' },
{ id: 'account', label: '资金', icon: '' },
{ id: 'contracts', label: '合约', icon: '' },
{ id: 'gateway', label: '网关', icon: '' },
{ id: 'strategy', label: '策略', icon: '' },
{ id: 'global_settings', label: '全局配置', icon: '' },
{ id: 'wechat', label: '微信通知', icon: '' },
{ id: 'log', label: '日志', icon: '' }
];
// WebSocket 连接状态
const wsConnected = ref(false);
// 系统状态
const systemStatus = ref({
vn_ready: false,
main_engine: false,
trading_engine: false
});
// 网关状态
const gatewayStatus = ref({ online: false });
// 行情数据
const marketSearch = ref('');
const contracts = ref([]);
const ticks = ref([]);
const subscribedSymbols = ref(new Set());
// 账户数据
const accounts = ref([]);
// 持仓数据
const positions = ref([]);
// 订单数据
const allOrders = ref([]);
const activeOrders = ref([]);
// 策略数据
const strategies = ref([]);
// 成交数据
const trades = ref([]);
// 所有账户数据
const allAccounts = ref([]);
// 网关数据
const gateways = ref([]);
const selectedGateway = ref(null);
const gatewayForm = ref({});
const showConnectDialog = ref(false);
const isConnecting = ref(false);
// 日志数据
const logs = ref([]);
const autoScroll = ref(true);
const logLevelFilter = ref('');
const logsContainer = ref(null);
// 合约数据
const allContracts = ref([]);
const contractSearch = ref('');
const contractFilter = ref({ field: '', value: '' });
// 选中的合约(用于市场深度盘口)
const selectedTick = ref(null);
// 全局配置
const globalSettings = ref({});
const globalSettingsLoading = ref(false);
const globalSettingsError = ref('');
const isSavingSettings = ref(false);
// 下单表单
const orderForm = ref({
symbol: '',
direction: 'buy',
order_type: 'limit',
price: 0,
volume: 1
});
const orderError = ref('');
const isOrdering = ref(false);
// ============================================
// 计算属性
// ============================================
const currentPageTitle = computed(() => {
const item = navItems.find(i => i.id === currentPage.value);
return item ? item.label : 'Sanguo VeighNa';
});
const filteredTicks = computed(() => {
if (!marketSearch.value) {
return ticks.value;
}
const search = marketSearch.value.toLowerCase();
return ticks.value.filter(t =>
t.vt_symbol?.toLowerCase().includes(search)
);
});
const recentLogs = computed(() => {
return logs.value.slice(-5);
});
const filteredLogs = computed(() => {
if (!logLevelFilter.value) {
return logs.value;
}
return logs.value.filter(l => l.level === logLevelFilter.value);
});
// 表格排序状态
const tableSort = ref({
field: '',
order: 'asc' // 'asc' or 'desc'
});
// 合约过滤
const filteredContracts = computed(() => {
let result = allContracts.value;
// 搜索过滤
if (contractSearch.value) {
const search = contractSearch.value.toLowerCase();
result = result.filter(c =>
c.vt_symbol?.toLowerCase().includes(search) ||
c.symbol?.toLowerCase().includes(search) ||
c.name?.toLowerCase().includes(search) ||
c.exchange?.toLowerCase().includes(search)
);
}
// 字段过滤
if (contractFilter.value.field && contractFilter.value.value) {
result = result.filter(c =>
String(c[contractFilter.value.field])?.toLowerCase().includes(contractFilter.value.value.toLowerCase())
);
}
return result;
});
const totalPnL = computed(() => {
return positions.value.reduce((sum, p) => sum + (p.pnl || 0), 0);
});
// ============================================
// 格式化函数
// ============================================
const formatNumber = (num) => {
if (num === null || num === undefined) return '-';
return Number(num).toFixed(2);
};
const formatPercent = (num) => {
if (num === null || num === undefined) return '-';
return (Number(num) * 100).toFixed(2) + '%';
};
const formatTime = (timestamp) => {
if (!timestamp) return '-';
const date = new Date(timestamp);
return date.toLocaleString('zh-CN');
};
const getPriceClass = (current, previous) => {
if (current === null || current === undefined) return '';
if (previous === null || previous === undefined) return '';
if (current > previous) return 'price-up';
if (current < previous) return 'price-down';
return 'price-flat';
};
const getPnLClass = (pnl) => {
if (pnl === null || pnl === undefined) return '';
if (pnl > 0) return 'pnl-positive';
if (pnl < 0) return 'pnl-negative';
return '';
};
const getDirectionClass = (direction) => {
if (!direction) return '';
const dir = direction.toLowerCase();
if (dir === 'long' || dir === 'buy') return 'buy';
if (dir === 'short' || dir === 'sell') return 'sell';
return '';
};
// ============================================
// 成交相关
// ============================================
const refreshTrades = async () => {
try {
trades.value = await api.getLatestTrades(50);
} catch (e) {
console.error('Failed to refresh trades:', e);
}
};
// ============================================
// 资金相关
// ============================================
const refreshAllAccounts = async () => {
try {
allAccounts.value = await api.getAllAccounts();
} catch (e) {
console.error('Failed to refresh accounts:', e);
}
};
// ============================================
// 网关相关
// ============================================
const refreshGateways = async () => {
try {
gateways.value = await api.getAllGateways();
} catch (e) {
console.error('Failed to refresh gateways:', e);
}
};
const openConnectDialog = async (gatewayName) => {
try {
const setting = await api.getGatewaySetting(gatewayName);
gatewayForm.value = { ...setting };
selectedGateway.value = gatewayName;
showConnectDialog.value = true;
} catch (e) {
console.error('Failed to get gateway setting:', e);
}
};
const closeConnectDialog = () => {
showConnectDialog.value = false;
selectedGateway.value = null;
gatewayForm.value = {};
};
const connectGateway = async () => {
isConnecting.value = true;
try {
await api.connectGateway(selectedGateway.value, gatewayForm.value);
closeConnectDialog();
await refreshGateways();
addLog({
level: 'INFO',
message: `网关 ${selectedGateway.value} 连接请求已发送`
});
} catch (e) {
addLog({
level: 'ERROR',
message: `连接网关失败: ${e.message || '未知错误'}`
});
} finally {
isConnecting.value = false;
}
};
const disconnectGateway = async (gatewayName) => {
try {
await api.disconnectGateway(gatewayName);
await refreshGateways();
addLog({
level: 'INFO',
message: `网关 ${gatewayName} 已断开连接`
});
} catch (e) {
addLog({
level: 'ERROR',
message: `断开网关失败: ${e.message || '未知错误'}`
});
}
};
const getGatewayStatusClass = (gateway) => {
if (gateway.connected) return 'status-online';
return 'status-offline';
};
const getGatewayStatusText = (gateway) => {
if (gateway.connected) return '已连接';
return '未连接';
};
const getFormFieldClass = (fieldName) => {
// 如果字段名包含"密码",返回密码类型
if (fieldName.includes('密码') || fieldName.toLowerCase().includes('password')) {
return 'password';
}
return 'text';
};
const isFormFieldPassword = (fieldName) => {
return fieldName.includes('密码') || fieldName.toLowerCase().includes('password');
};
// ============================================
// 行情相关
// ============================================
const isSubscribed = (symbol) => {
return subscribedSymbols.value.has(symbol);
};
const subscribeSymbol = (symbol) => {
if (subscribedSymbols.value.has(symbol)) {
// 取消订阅
subscribedSymbols.value.delete(symbol);
wsClient.unsubscribeSymbol([symbol]);
} else {
// 订阅
subscribedSymbols.value.add(symbol);
wsClient.subscribeSymbol([symbol]);
}
};
const refreshMarket = async () => {
try {
ticks.value = await api.getTicks();
} catch (e) {
console.error('Failed to refresh market data:', e);
}
};
// ============================================
// 双击交互处理
// ============================================
const handleTickDoubleClick = (tick) => {
// 双击行情记录 → 更新交易表单
orderForm.value.symbol = tick.vt_symbol || tick.symbol;
// 如果有最新价,更新价格
if (tick.last_price) {
orderForm.value.price = tick.last_price;
}
// 切换到交易页面
currentPage.value = 'trading';
addLog({
level: 'INFO',
message: `已从行情选择合约: ${tick.vt_symbol}`
});
};
const handleOrderDoubleClick = (order) => {
// 双击委托/活动委托 → 撤单
if (canCancel(order.status)) {
cancelOrder(order.order_id);
} else {
addLog({
level: 'WARNING',
message: `订单 ${order.order_id} 状态为 ${order.status},无法撤销`
});
}
};
const handlePositionDoubleClick = (position) => {
// 双击持仓记录 → 更新交易表单(反向平仓)
orderForm.value.symbol = position.vt_symbol || `${position.symbol}.${position.exchange}`;
// 反向方向
if (position.direction?.toLowerCase() === 'long' || position.direction?.toLowerCase() === 'buy') {
orderForm.value.direction = 'sell';
} else {
orderForm.value.direction = 'buy';
}
// 设置平仓数量
orderForm.value.volume = position.volume - (position.frozen || 0);
// 切换到交易页面
currentPage.value = 'trading';
addLog({
level: 'INFO',
message: `已从持仓选择合约: ${position.symbol},准备平仓`
});
};
// ============================================
// CSV 导出功能
// ============================================
const exportTable = (tableType) => {
let data = [];
let filename = '';
let headers = [];
switch (tableType) {
case 'market':
data = filteredTicks.value;
filename = 'market_data';
headers = ['合约', '最新价', '买价', '卖价', '成交量'];
break;
case 'orders':
data = allOrders.value;
filename = 'orders';
headers = ['订单ID', '合约', '方向', '价格', '数量', '已成交', '状态'];
break;
case 'active_orders':
data = activeOrders.value;
filename = 'active_orders';
headers = ['订单ID', '合约', '方向', '价格', '数量', '已成交', '状态', '时间'];
break;
case 'positions':
data = positions.value;
filename = 'positions';
headers = ['合约', '交易所', '方向', '数量', '可用', '均价', '盈亏', '盈亏率'];
break;
case 'trades':
data = trades.value;
filename = 'trades';
headers = ['成交号', '委托号', '合约', '方向', '开平', '价格', '数量', '时间', '接口'];
break;
case 'accounts':
data = allAccounts.value;
filename = 'accounts';
headers = ['账号', '余额', '冻结', '可用', '接口'];
break;
case 'contracts':
data = filteredContracts.value;
filename = 'contracts';
headers = ['本地代码', '代码', '交易所', '名称', '分类', '乘数', '跳动点', '最小量'];
break;
default:
return;
}
// 生成 CSV 内容
let csv = headers.join(',') + '\n';
data.forEach(row => {
const values = [];
switch (tableType) {
case 'market':
values.push(row.vt_symbol, row.last_price, row.bid_price_1, row.ask_price_1, row.volume);
break;
case 'orders':
values.push(row.order_id, row.symbol, row.direction, row.price, row.volume, row.traded, row.status);
break;
case 'active_orders':
values.push(row.order_id, row.symbol, row.direction, row.price, row.volume, row.traded, row.status, formatTime(row.time));
break;
case 'positions':
values.push(row.symbol, row.exchange, row.direction, row.volume, row.volume - (row.frozen || 0), row.price, row.pnl, formatPercent(row.pnl_ratio));
break;
case 'trades':
values.push(row.tradeid, row.orderid, row.symbol, row.direction, row.offset, row.price, row.volume, row.datetime, row.gateway_name);
break;
case 'accounts':
values.push(row.accountid, row.balance, row.frozen, row.available, row.gateway_name);
break;
case 'contracts':
values.push(row.vt_symbol, row.symbol, row.exchange, row.name, row.product, row.size, row.pricetick, row.min_volume);
break;
}
csv += values.map(v => `"${v}"`).join(',') + '\n';
});
// 创建 Blob 并触发下载
const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
link.setAttribute('href', url);
link.setAttribute('download', `${filename}_${timestamp}.csv`);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
addLog({
level: 'INFO',
message: `已导出 ${filename} 数据,共 ${data.length} 条记录`
});
};
// ============================================
// 交易相关
// ============================================
const canCancel = (status) => {
return ['submitted', 'pending', 'partial_filled'].includes(
status?.toLowerCase()
);
};
const sendOrder = async () => {
orderError.value = '';
isOrdering.value = true;
try {
const result = await api.sendOrder({
symbol: orderForm.value.symbol.split('.')[0],
exchange: orderForm.value.symbol.split('.')[1] || 'SIM',
direction: orderForm.value.direction,
order_type: orderForm.value.order_type,
volume: orderForm.value.volume,
price: orderForm.value.order_type === 'limit' ? orderForm.value.price : null
});
// 刷新订单列表
await refreshOrders();
// 重置表单
orderForm.value = {
symbol: '',
direction: 'buy',
order_type: 'limit',
price: 0,
volume: 1
};
} catch (e) {
orderError.value = e.message || '下单失败';
} finally {
isOrdering.value = false;
}
};
const cancelOrder = async (orderId) => {
try {
await api.cancelOrder(orderId);
await refreshOrders();
} catch (e) {
console.error('Failed to cancel order:', e);
}
};
const refreshOrders = async () => {
try {
const [all, active] = await Promise.all([
api.getOrders(),
api.getActiveOrders()
]);
allOrders.value = all;
activeOrders.value = active;
} catch (e) {
console.error('Failed to refresh orders:', e);
}
};
// ============================================
// 持仓相关
// ============================================
const refreshPositions = async () => {
try {
positions.value = await api.getPositions();
} catch (e) {
console.error('Failed to refresh positions:', e);
}
};
// ============================================
// 策略相关
// ============================================
const refreshStrategies = async () => {
try {
strategies.value = await api.getStrategies();
} catch (e) {
console.error('Failed to refresh strategies:', e);
}
};
const startStrategy = async (name) => {
try {
await api.startStrategy(name);
await refreshStrategies();
} catch (e) {
console.error('Failed to start strategy:', e);
}
};
const stopStrategy = async (name) => {
try {
await api.stopStrategy(name);
await refreshStrategies();
} catch (e) {
console.error('Failed to stop strategy:', e);
}
};
// ============================================
// 全局配置相关
// ============================================
const refreshGlobalSettings = async () => {
globalSettingsLoading.value = true;
globalSettingsError.value = '';
try {
const settings = await api.getGlobalSettings();
globalSettings.value = settings;
} catch (e) {
globalSettingsError.value = e.message || '加载全局配置失败';
console.error('Failed to load global settings:', e);
} finally {
globalSettingsLoading.value = false;
}
};
const saveGlobalSettings = async () => {
isSavingSettings.value = true;
try {
await api.updateGlobalSettings(globalSettings.value);
addLog({
level: 'INFO',
message: '全局配置已保存,重启后生效'
});
} catch (e) {
addLog({
level: 'ERROR',
message: `保存全局配置失败: ${e.message || '未知错误'}`
});
} finally {
isSavingSettings.value = false;
}
};
const getSettingType = (value) => {
if (value === null || value === undefined) return 'null';
const type = typeof value;
if (type === 'object') return Array.isArray(value) ? 'array' : 'object';
return type;
};
// ============================================
// 日志相关
// ============================================
const addLog = (logData) => {
logs.value.push({
time: logData.time || new Date().toISOString(),
level: logData.level || 'INFO',
message: logData.message || ''
});
// 限制日志数量
if (logs.value.length > 1000) {
logs.value = logs.value.slice(-1000);
}
// 自动滚动
if (autoScroll.value) {
nextTick(() => {
if (logsContainer.value) {
logsContainer.value.scrollTop = logsContainer.value.scrollHeight;
}
});
}
};
const clearLogs = () => {
logs.value = [];
};
// ============================================
// 排序功能
// ============================================
const sortTable = (field) => {
if (tableSort.value.field === field) {
// 切换排序方向
tableSort.value.order = tableSort.value.order === 'asc' ? 'desc' : 'asc';
} else {
// 新字段,默认升序
tableSort.value.field = field;
tableSort.value.order = 'asc';
}
};
const getSortIcon = (field) => {
if (tableSort.value.field !== field) {
return '⇅';
}
return tableSort.value.order === 'asc' ? '↑' : '↓';
};
// 排序数据
const sortData = (data, field, order) => {
if (!field) return data;
const sorted = [...data].sort((a, b) => {
const aVal = a[field];
const bVal = b[field];
if (aVal === null || aVal === undefined) return 1;
if (bVal === null || bVal === undefined) return -1;
if (aVal === bVal) return 0;
// 数字比较
if (typeof aVal === 'number' && typeof bVal === 'number') {
return order === 'asc' ? aVal - bVal : bVal - aVal;
}
// 字符串比较
const aStr = String(aVal).toLowerCase();
const bStr = String(bVal).toLowerCase();
if (order === 'asc') {
return aStr < bStr ? -1 : 1;
} else {
return aStr > bStr ? -1 : 1;
}
});
return sorted;
};
// ============================================
// 合约相关
// ============================================
const refreshContracts = async () => {
try {
const data = await api.getContracts();
allContracts.value = data;
} catch (e) {
console.error('Failed to refresh contracts:', e);
}
};
const selectTick = (tick) => {
selectedTick.value = tick;
};
// ============================================
// 认证相关
// ============================================
const login = async () => {
loginError.value = '';
isLoading.value = true;
try {
await api.login(loginForm.value.username, loginForm.value.password);
isLoggedIn.value = true;
currentUser.value = { username: loginForm.value.username };
// 连接 WebSocket
if (api.token) {
wsClient.connect(api.token);
}
// 加载初始数据(不阻塞登录成功)
loadInitialData().catch(err => {
console.warn('部分数据加载失败:', err);
addLog({
level: 'WARNING',
message: '部分功能数据加载失败,请检查服务连接'
});
});
} catch (e) {
loginError.value = e.message || '登录失败';
throw e;
} finally {
isLoading.value = false;
}
};
const logout = async () => {
try {
await api.logout();
} catch (e) {
console.error('Logout error:', e);
} finally {
isLoggedIn.value = false;
currentUser.value = { username: '' };
wsClient.disconnect();
}
};
// ============================================
// 初始数据加载
// ============================================
const loadInitialData = async () => {
try {
// 并行加载所有数据
const [
sysInfo,
contractsData,
accountsData,
positionsData,
ordersData,
strategiesData,
tradesData,
allAccountsData,
gatewaysData
] = await Promise.all([
api.getSystemInfo().catch(() => null),
api.getContracts().catch(() => []),
api.getAccounts().catch(() => []),
api.getPositions().catch(() => []),
api.getOrders().catch(() => []),
api.getStrategies().catch(() => []),
api.getLatestTrades(50).catch(() => []),
api.getAllAccounts().catch(() => []),
api.getAllGateways().catch(() => [])
]);
// 加载全局配置(不阻塞其他数据)
refreshGlobalSettings().catch(() => {
console.warn('Failed to load global settings');
});
// 更新系统状态
if (sysInfo) {
systemStatus.value = {
vn_ready: sysInfo.vn_ready || false,
main_engine: sysInfo.main_engine || false,
trading_engine: sysInfo.trading_engine || false
};
}
contracts.value = contractsData;
allContracts.value = contractsData;
// 更新系统状态
if (sysInfo) {
systemStatus.value = {
vn_ready: sysInfo.vn_ready || false,
main_engine: sysInfo.main_engine || false,
trading_engine: sysInfo.trading_engine || false
};
}
contracts.value = contractsData;
accounts.value = accountsData;
positions.value = positionsData;
allOrders.value = ordersData;
activeOrders.value = ordersData.filter(o =>
['submitted', 'pending', 'partial_filled'].includes(o.status?.toLowerCase())
);
strategies.value = strategiesData;
trades.value = tradesData;
allAccounts.value = allAccountsData;
gateways.value = gatewaysData;
// 获取行情数据
const ticksData = await api.getTicks().catch(() => []);
ticks.value = ticksData;
// 订阅 WebSocket 消息
wsClient.subscribe(['tick', 'order', 'trade', 'position', 'account', 'log', 'gateway']);
} catch (e) {
console.error('Failed to load initial data:', e);
}
};
// ============================================
// WebSocket 消息处理
// ============================================
const setupWebSocketHandlers = () => {
// 连接状态
wsClient.on('connected', () => {
wsConnected.value = true;
});
wsClient.on('disconnected', () => {
wsConnected.value = false;
});
// 行情推送
wsClient.on('tick', (data) => {
// 更新或添加行情数据
const index = ticks.value.findIndex(t => t.vt_symbol === data.vt_symbol);
if (index >= 0) {
ticks.value[index] = data;
} else {
ticks.value.push(data);
}
// 如果是选中的合约,更新选中数据
if (selectedTick.value && selectedTick.value.vt_symbol === data.vt_symbol) {
selectedTick.value = data;
}
});
// 订单推送
wsClient.on('order', (data) => {
// 更新订单列表
const index = allOrders.value.findIndex(o => o.order_id === data.order_id);
if (index >= 0) {
allOrders.value[index] = data;
} else {
allOrders.value.push(data);
}
// 更新活动订单
if (['submitted', 'pending', 'partial_filled'].includes(data.status?.toLowerCase())) {
const activeIndex = activeOrders.value.findIndex(o => o.order_id === data.order_id);
if (activeIndex >= 0) {
activeOrders.value[activeIndex] = data;
} else {
activeOrders.value.push(data);
}
} else {
activeOrders.value = activeOrders.value.filter(o => o.order_id !== data.order_id);
}
});
// 成交推送
wsClient.on('trade', (data) => {
// 成交记录插入到数组头部
trades.value.unshift(data);
// 限制数量
if (trades.value.length > 100) {
trades.value = trades.value.slice(0, 100);
}
});
// 持仓推送
wsClient.on('position', (data) => {
const index = positions.value.findIndex(
p => p.symbol === data.symbol && p.exchange === data.exchange
);
if (index >= 0) {
positions.value[index] = data;
} else {
positions.value.push(data);
}
});
// 账户推送
wsClient.on('account', (data) => {
// 更新 dashboard 中的账户
if (accounts.value.length > 0) {
accounts.value[0] = data;
} else {
accounts.value.push(data);
}
// 更新资金监控页面中的账户
const index = allAccounts.value.findIndex(a => a.accountid === data.accountid);
if (index >= 0) {
allAccounts.value[index] = data;
} else {
allAccounts.value.push(data);
}
});
// 网关状态推送
wsClient.on('gateway', (data) => {
const index = gateways.value.findIndex(g => g.name === data.name);
if (index >= 0) {
gateways.value[index] = data;
} else {
gateways.value.push(data);
}
});
// 日志推送
wsClient.on('log', (data) => {
addLog(data);
});
// 合约推送
wsClient.on('contract', (data) => {
// 更新合约列表
const index = allContracts.value.findIndex(c => c.vt_symbol === data.vt_symbol);
if (index >= 0) {
allContracts.value[index] = data;
} else {
allContracts.value.push(data);
}
});
};
// ============================================
// 生命周期
// ============================================
onMounted(() => {
// 检查是否已登录
if (api.isTokenValid()) {
isLoggedIn.value = true;
const tokenPayload = JSON.parse(atob(api.token.split('.')[1]));
currentUser.value = { username: tokenPayload.sub };
// 连接 WebSocket
wsClient.connect(api.token);
// 加载初始数据
loadInitialData();
}
// 设置 WebSocket 处理器
setupWebSocketHandlers();
});
onUnmounted(() => {
wsClient.disconnect();
});
// ============================================
// 返回
// ============================================
return {
// 状态
isLoggedIn,
currentUser,
loginForm,
loginError,
isLoading,
currentPage,
navItems,
wsConnected,
systemStatus,
gatewayStatus,
marketSearch,
contracts,
ticks,
accounts,
positions,
allOrders,
activeOrders,
strategies,
trades,
allAccounts,
gateways,
selectedGateway,
gatewayForm,
showConnectDialog,
isConnecting,
logs,
autoScroll,
logLevelFilter,
logsContainer,
orderForm,
orderError,
isOrdering,
allContracts,
contractSearch,
contractFilter,
selectedTick,
tableSort,
globalSettings,
globalSettingsLoading,
globalSettingsError,
isSavingSettings,
// 计算属性
currentPageTitle,
filteredTicks,
recentLogs,
filteredLogs,
totalPnL,
filteredContracts,
// 方法
login,
logout,
formatNumber,
formatPercent,
formatTime,
getPriceClass,
getPnLClass,
getDirectionClass,
isSubscribed,
subscribeSymbol,
refreshMarket,
canCancel,
sendOrder,
cancelOrder,
refreshOrders,
refreshPositions,
refreshStrategies,
startStrategy,
stopStrategy,
refreshTrades,
refreshAllAccounts,
refreshGateways,
openConnectDialog,
closeConnectDialog,
connectGateway,
disconnectGateway,
getGatewayStatusClass,
getGatewayStatusText,
getFormFieldClass,
isFormFieldPassword,
clearLogs,
sortTable,
getSortIcon,
sortData,
refreshContracts,
selectTick,
handleTickDoubleClick,
handleOrderDoubleClick,
handlePositionDoubleClick,
exportTable,
refreshGlobalSettings,
saveGlobalSettings,
getSettingType
};
}
}).mount('#app');