794 lines
25 KiB
HTML
794 lines
25 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>交易信号 - Stock Agent</title>
|
|
<link rel="stylesheet" href="/static/css/style.css">
|
|
<style>
|
|
/* 防止横向滚动 */
|
|
html, body {
|
|
overflow-x: hidden;
|
|
max-width: 100vw;
|
|
}
|
|
|
|
/* 覆盖全局 #app 样式 */
|
|
#app {
|
|
height: auto;
|
|
display: block;
|
|
align-items: initial;
|
|
justify-content: initial;
|
|
padding: 0;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.signals-page {
|
|
min-height: 100vh;
|
|
background: var(--bg-primary);
|
|
padding: 20px;
|
|
}
|
|
|
|
.signals-container {
|
|
max-width: 1400px;
|
|
min-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
/* 固定顶部区域 */
|
|
.sticky-header {
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 100;
|
|
background: var(--bg-primary);
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.signals-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 20px;
|
|
padding: 10px 0 20px 0;
|
|
border-bottom: 1px solid var(--border);
|
|
background: var(--bg-primary);
|
|
}
|
|
|
|
.signals-title {
|
|
font-size: 24px;
|
|
font-weight: 300;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.signals-title span {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.refresh-btn {
|
|
padding: 8px 16px;
|
|
background: transparent;
|
|
border: 1px solid var(--accent);
|
|
color: var(--accent);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.refresh-btn:hover {
|
|
background: var(--accent);
|
|
color: var(--bg-primary);
|
|
}
|
|
|
|
/* 统计卡片 */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 24px;
|
|
font-weight: 300;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.stat-value.positive {
|
|
color: #00ff41;
|
|
}
|
|
|
|
.stat-value.negative {
|
|
color: #ff4444;
|
|
}
|
|
|
|
/* 标签页 */
|
|
.tabs {
|
|
display: flex;
|
|
gap: 0;
|
|
margin-bottom: 20px;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.tab {
|
|
padding: 12px 24px;
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
cursor: pointer;
|
|
border-bottom: 2px solid transparent;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.tab:hover {
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.tab.active {
|
|
color: var(--accent);
|
|
border-bottom-color: var(--accent);
|
|
}
|
|
|
|
/* 信号卡片网格 */
|
|
.signals-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
|
|
.signal-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 20px;
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.signal-card:hover {
|
|
border-color: var(--accent);
|
|
box-shadow: 0 4px 12px rgba(0, 200, 150, 0.1);
|
|
}
|
|
|
|
.signal-card.buy {
|
|
border-left: 3px solid #00ff41;
|
|
}
|
|
|
|
.signal-card.sell {
|
|
border-left: 3px solid #ff4444;
|
|
}
|
|
|
|
.signal-card.hold {
|
|
border-left: 3px solid var(--text-secondary);
|
|
}
|
|
|
|
.signal-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.signal-symbol-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.signal-symbol {
|
|
font-size: 20px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.signal-type-badge {
|
|
font-size: 11px;
|
|
padding: 3px 8px;
|
|
background: var(--bg-tertiary);
|
|
color: var(--text-secondary);
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.signal-action-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
|
|
.signal-action {
|
|
padding: 6px 16px;
|
|
border-radius: 2px;
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.signal-action.buy {
|
|
background: rgba(0, 255, 65, 0.1);
|
|
color: #00ff41;
|
|
}
|
|
|
|
.signal-action.sell {
|
|
background: rgba(255, 68, 68, 0.1);
|
|
color: #ff4444;
|
|
}
|
|
|
|
.signal-action.hold {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.signal-grade {
|
|
display: inline-block;
|
|
padding: 4px 10px;
|
|
border-radius: 2px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.signal-grade.A {
|
|
background: linear-gradient(135deg, rgba(255, 215, 0, 0.2), rgba(255, 179, 0, 0.2));
|
|
color: gold;
|
|
}
|
|
|
|
.signal-grade.B {
|
|
background: linear-gradient(135deg, rgba(192, 192, 192, 0.2), rgba(160, 160, 160, 0.2));
|
|
color: silver;
|
|
}
|
|
|
|
.signal-grade.C {
|
|
background: linear-gradient(135deg, rgba(205, 127, 50, 0.2), rgba(160, 82, 45, 0.2));
|
|
color: #cd7f32;
|
|
}
|
|
|
|
.signal-grade.D {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* 置信度条 */
|
|
.confidence-section {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.confidence-label {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.confidence-value {
|
|
color: var(--accent);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.confidence-bar {
|
|
height: 4px;
|
|
background: var(--bg-tertiary);
|
|
border-radius: 2px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.confidence-fill {
|
|
height: 100%;
|
|
background: linear-gradient(90deg, var(--accent) 0%, #00ff41 100%);
|
|
transition: width 0.3s;
|
|
}
|
|
|
|
/* 价格信息 */
|
|
.price-section {
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
gap: 12px;
|
|
padding: 12px 0;
|
|
margin-bottom: 12px;
|
|
border-top: 1px solid var(--border);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.price-item {
|
|
text-align: center;
|
|
}
|
|
|
|
.price-label {
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.price-value {
|
|
font-size: 14px;
|
|
color: var(--text-primary);
|
|
font-family: monospace;
|
|
}
|
|
|
|
/* 信号详情 */
|
|
.signal-details {
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.detail-row {
|
|
display: flex;
|
|
margin-bottom: 6px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.detail-label {
|
|
color: var(--text-secondary);
|
|
min-width: 70px;
|
|
}
|
|
|
|
.detail-value {
|
|
color: var(--text-primary);
|
|
flex: 1;
|
|
}
|
|
|
|
/* 分析理由 */
|
|
.signal-reason {
|
|
background: var(--bg-tertiary);
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.reason-label {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.reason-text {
|
|
font-size: 13px;
|
|
color: var(--text-primary);
|
|
line-height: 1.5;
|
|
}
|
|
|
|
/* 时间戳 */
|
|
.signal-time {
|
|
font-size: 11px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* 空状态 */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state svg {
|
|
width: 48px;
|
|
height: 48px;
|
|
margin-bottom: 16px;
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* 加载状态 */
|
|
.loading {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
/* 响应式设计 */
|
|
@media (max-width: 768px) {
|
|
.signals-page {
|
|
padding: 10px;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
.signals-container {
|
|
min-width: auto;
|
|
max-width: 100%;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* 取消顶部固定 */
|
|
.sticky-header {
|
|
position: static;
|
|
}
|
|
|
|
.signals-header {
|
|
flex-direction: column;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
}
|
|
|
|
.signals-title {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.signals-title span {
|
|
display: block;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 10px;
|
|
}
|
|
|
|
.stat-card {
|
|
padding: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 16px;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.tabs {
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.tab {
|
|
padding: 10px 16px;
|
|
font-size: 13px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.signals-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.price-section {
|
|
grid-template-columns: 1fr;
|
|
gap: 8px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.signals-title {
|
|
font-size: 16px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 16px;
|
|
}
|
|
}
|
|
|
|
/* 等级统计 */
|
|
.grade-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 16px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.grade-stat-card {
|
|
background: var(--bg-secondary);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
}
|
|
|
|
.grade-stat-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.grade-stat-title {
|
|
font-size: 14px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.grade-stat-count {
|
|
font-size: 20px;
|
|
font-weight: 300;
|
|
color: var(--accent);
|
|
}
|
|
|
|
.grade-stat-details {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.grade-stat-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
color: var(--text-secondary);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<div class="signals-page">
|
|
<div class="signals-container">
|
|
<!-- 固定顶部区域 -->
|
|
<div class="sticky-header">
|
|
<!-- 头部 -->
|
|
<div class="signals-header">
|
|
<h1 class="signals-title">交易信号中心 <span>| Trading Signals</span></h1>
|
|
<button class="refresh-btn" @click="loadSignals">刷新</button>
|
|
</div>
|
|
|
|
<!-- 统计卡片 -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-label">加密货币信号</div>
|
|
<div class="stat-value">{{ stats.crypto.total }}</div>
|
|
<div class="stat-label" style="margin-top: 6px;">最近24小时: {{ stats.crypto.recent_24h }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">美股信号</div>
|
|
<div class="stat-value">{{ stats.stock.total }}</div>
|
|
<div class="stat-label" style="margin-top: 6px;">最近24小时: {{ stats.stock.recent_24h }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">总信号数</div>
|
|
<div class="stat-value">{{ stats.total }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">买入信号</div>
|
|
<div class="stat-value positive">{{ stats.crypto.buy + stats.stock.buy }}</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-label">卖出信号</div>
|
|
<div class="stat-value negative">{{ stats.crypto.sell + stats.stock.sell }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 等级统计 -->
|
|
<div class="grade-stats" v-if="Object.keys(stats.grades).length > 0">
|
|
<div class="grade-stat-card" v-for="(count, grade) in stats.grades" :key="grade">
|
|
<div class="grade-stat-header">
|
|
<span class="grade-stat-title">
|
|
<span class="signal-grade" :class="grade">{{ grade }}</span> 级信号
|
|
</span>
|
|
<span class="grade-stat-count">{{ count }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 标签页 -->
|
|
<div class="tabs">
|
|
<button class="tab" :class="{ active: currentTab === 'crypto' }" @click="switchTab('crypto')">
|
|
加密货币 ({{ cryptoSignals.length }})
|
|
</button>
|
|
<button class="tab" :class="{ active: currentTab === 'stock' }" @click="switchTab('stock')">
|
|
美股 ({{ stockSignals.length }})
|
|
</button>
|
|
<button class="tab" :class="{ active: currentTab === 'all' }" @click="switchTab('all')">
|
|
全部信号 ({{ allSignals.length }})
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 信号列表 -->
|
|
<div v-if="loading" class="loading">加载中...</div>
|
|
<div v-else-if="currentSignals.length === 0" class="empty-state">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<path d="M12 6v6l4 2"/>
|
|
</svg>
|
|
<p>暂无信号</p>
|
|
</div>
|
|
<div v-else class="signals-grid">
|
|
<div v-for="signal in currentSignals" :key="signal.id" class="signal-card" :class="signal.action">
|
|
<!-- 信号头部 -->
|
|
<div class="signal-header">
|
|
<div class="signal-symbol-group">
|
|
<span class="signal-symbol">{{ signal.symbol }}</span>
|
|
<span class="signal-type-badge">{{ signal.signal_type === 'crypto' ? '加密货币' : '美股' }}</span>
|
|
</div>
|
|
<div class="signal-action-group">
|
|
<span class="signal-action" :class="signal.action">
|
|
{{ signal.action === 'buy' ? '做多' : signal.action === 'sell' ? '做空' : '持有' }}
|
|
</span>
|
|
<span class="signal-grade" :class="signal.grade">{{ signal.grade }}级</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 置信度 -->
|
|
<div class="confidence-section">
|
|
<div class="confidence-label">
|
|
<span>置信度</span>
|
|
<span class="confidence-value">{{ signal.confidence }}%</span>
|
|
</div>
|
|
<div class="confidence-bar">
|
|
<div class="confidence-fill" :style="{ width: signal.confidence + '%' }"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 价格信息 -->
|
|
<div class="price-section" v-if="signal.entry_price || signal.stop_loss || signal.take_profit">
|
|
<div class="price-item" v-if="signal.entry_price">
|
|
<div class="price-label">入场价</div>
|
|
<div class="price-value">${{ signal.entry_price?.toFixed(2) }}</div>
|
|
</div>
|
|
<div class="price-item" v-if="signal.stop_loss">
|
|
<div class="price-label">止损</div>
|
|
<div class="price-value">${{ signal.stop_loss?.toFixed(2) }}</div>
|
|
</div>
|
|
<div class="price-item" v-if="signal.take_profit">
|
|
<div class="price-label">止盈</div>
|
|
<div class="price-value">${{ signal.take_profit?.toFixed(2) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 信号详情 -->
|
|
<div class="signal-details" v-if="signal.signal_type_detail || signal.entry_type || signal.position_size">
|
|
<div class="detail-row" v-if="signal.signal_type_detail">
|
|
<span class="detail-label">周期:</span>
|
|
<span class="detail-value">{{ getSignalTypeText(signal.signal_type_detail) }}</span>
|
|
</div>
|
|
<div class="detail-row" v-if="signal.entry_type">
|
|
<span class="detail-label">入场:</span>
|
|
<span class="detail-value">{{ getEntryTypeText(signal.entry_type) }}</span>
|
|
</div>
|
|
<div class="detail-row" v-if="signal.position_size">
|
|
<span class="detail-label">仓位:</span>
|
|
<span class="detail-value">{{ getPositionSizeText(signal.position_size) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 分析理由 -->
|
|
<div class="signal-reason" v-if="signal.reason">
|
|
<div class="reason-label">分析理由</div>
|
|
<div class="reason-text">{{ signal.reason }}</div>
|
|
</div>
|
|
|
|
<!-- 时间戳 -->
|
|
<div class="signal-time">
|
|
{{ formatTime(signal.timestamp || signal.created_at) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
|
<script>
|
|
const { createApp } = Vue;
|
|
|
|
createApp({
|
|
data() {
|
|
return {
|
|
currentTab: 'crypto',
|
|
loading: true,
|
|
cryptoSignals: [],
|
|
stockSignals: [],
|
|
allSignals: [],
|
|
stats: {
|
|
crypto: { total: 0, buy: 0, sell: 0, recent_24h: 0 },
|
|
stock: { total: 0, buy: 0, sell: 0, recent_24h: 0 },
|
|
grades: {},
|
|
total: 0
|
|
},
|
|
refreshInterval: null
|
|
};
|
|
},
|
|
computed: {
|
|
currentSignals() {
|
|
if (this.currentTab === 'crypto') return this.cryptoSignals;
|
|
if (this.currentTab === 'stock') return this.stockSignals;
|
|
return this.allSignals;
|
|
}
|
|
},
|
|
methods: {
|
|
switchTab(tab) {
|
|
this.currentTab = tab;
|
|
},
|
|
|
|
async loadSignals() {
|
|
this.loading = true;
|
|
try {
|
|
await Promise.all([
|
|
this.loadCryptoSignals(),
|
|
this.loadStockSignals(),
|
|
this.loadStats()
|
|
]);
|
|
} catch (e) {
|
|
console.error('加载信号失败:', e);
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
async loadCryptoSignals() {
|
|
const response = await fetch('/api/signals/crypto?limit=50&days=7');
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
this.cryptoSignals = data.signals || [];
|
|
}
|
|
},
|
|
|
|
async loadStockSignals() {
|
|
const response = await fetch('/api/signals/stock?limit=50&days=7');
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
this.stockSignals = data.signals || [];
|
|
}
|
|
},
|
|
|
|
async loadStats() {
|
|
const response = await fetch('/api/signals/stats?days=7');
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
this.stats = {
|
|
crypto: data.crypto || { total: 0, buy: 0, sell: 0, recent_24h: 0 },
|
|
stock: data.stock || { total: 0, buy: 0, sell: 0, recent_24h: 0 },
|
|
grades: data.grades || {},
|
|
total: data.total || 0
|
|
};
|
|
}
|
|
},
|
|
|
|
formatTime(timeStr) {
|
|
if (!timeStr) return '-';
|
|
const date = new Date(timeStr);
|
|
return date.toLocaleString('zh-CN', {
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
},
|
|
|
|
getSignalTypeText(type) {
|
|
const map = {
|
|
'short_term': '短期',
|
|
'medium_term': '中期',
|
|
'long_term': '长期'
|
|
};
|
|
return map[type] || type;
|
|
},
|
|
|
|
getEntryTypeText(type) {
|
|
const map = {
|
|
'market': '市价',
|
|
'limit': '限价'
|
|
};
|
|
return map[type] || type;
|
|
},
|
|
|
|
getPositionSizeText(size) {
|
|
const map = {
|
|
'light': '轻仓',
|
|
'medium': '中仓',
|
|
'heavy': '重仓'
|
|
};
|
|
return map[size] || size;
|
|
}
|
|
},
|
|
mounted() {
|
|
this.loadSignals();
|
|
// 每30秒自动刷新
|
|
this.refreshInterval = setInterval(() => {
|
|
this.loadSignals();
|
|
}, 30000);
|
|
},
|
|
beforeUnmount() {
|
|
if (this.refreshInterval) {
|
|
clearInterval(this.refreshInterval);
|
|
}
|
|
}
|
|
}).mount('#app');
|
|
</script>
|
|
</body>
|
|
</html>
|