From 0818028b4c89f5506135b875b21e33089bcb4119 Mon Sep 17 00:00:00 2001 From: dienakdz Date: Tue, 7 Apr 2026 07:44:43 +0700 Subject: [PATCH] feat: add quick trade panel and strategy creation enhancements in indicator analysis --- .../src/views/indicator-analysis/index.vue | 116 +++++++++++++++++- .../src/views/trading-assistant/index.vue | 47 +++++++ 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/frontend_vue/src/views/indicator-analysis/index.vue b/frontend_vue/src/views/indicator-analysis/index.vue index 8543537..e6ba7bd 100644 --- a/frontend_vue/src/views/indicator-analysis/index.vue +++ b/frontend_vue/src/views/indicator-analysis/index.vue @@ -68,6 +68,15 @@ {{ priceChange > 0 ? '+' : '' }}{{ priceChange.toFixed(2) }}% + + + {{ tt('quickTrade.openPanel', 'Quick Trade') }} + @@ -210,6 +219,13 @@ @click.stop="handlePublishIndicator(indicator)" /> + + + {{ indicator.description || '' }} @@ -230,6 +246,15 @@ {{ $t('dashboard.indicator.section.purchased') }} ({{ purchasedIndicators.length }}) + + {{ $t('menu.dashboard.community') }} +
+ + { return priceChange.value > 0 ? 'color-up' : priceChange.value < 0 ? 'color-down' : '' }) + const isCryptoMarket = computed(() => currentMarket.value === 'Crypto') + + const tt = (key, fallback, params) => { + const translated = proxy.$t(key, params) + return translated !== key ? translated : fallback + } const timeframe = ref('1D') const activeIndicators = ref([]) @@ -799,6 +847,14 @@ export default { priceChange.value = change } + const openQuickTrade = () => { + if (!isCryptoMarket.value || !currentSymbol.value) return + qtSymbol.value = currentSymbol.value + qtSide.value = '' + qtPrice.value = Number(String(currentPrice.value || '').replace(/,/g, '')) || 0 + showQuickTrade.value = true + } + // 处理图表重试事件 const handleChartRetry = () => { // KlineChart 组件会自己处理重试,这里可以添加额外的逻辑 @@ -1647,6 +1703,22 @@ export default { showBacktestHistoryDrawer.value = true } + const handleCreateStrategyFromIndicator = (indicator) => { + if (!indicator || !indicator.id) return + const query = { + open: 'from-indicator', + indicator_id: String(indicator.id) + } + if (currentMarket.value) query.market = currentMarket.value + if (currentSymbol.value) query.symbol = currentSymbol.value + if (timeframe.value) query.timeframe = timeframe.value + proxy.$router.push({ path: '/trading-assistant', query }) + } + + const goToIndicatorMarket = () => { + proxy.$router.push('/indicator-community') + } + const handleViewBacktestRun = (run) => { selectedBacktestRun.value = run showBacktestRunViewer.value = true @@ -1968,6 +2040,7 @@ export default { return { userId, + tt, klineChart, searchSymbol, symbolSuggestions, @@ -1979,6 +2052,12 @@ currentMarket, currentPrice, priceChange, priceChangeClass, + isCryptoMarket, + showQuickTrade, + qtSymbol, + qtSide, + qtPrice, + openQuickTrade, timeframe, loadingWatchlist, activeIndicators, @@ -2042,6 +2121,8 @@ getMarketColor, showBacktestRunViewer, selectedBacktestRun, handleViewBacktestRun, + handleCreateStrategyFromIndicator, + goToIndicatorMarket, // 发布到社区相关 showPublishModal, publishIndicator, @@ -2196,6 +2277,18 @@ getMarketColor, gap: 24px; } +.qt-header-btn { + display: inline-flex; + align-items: center; + gap: 6px; + height: 32px; + padding: 0 12px; + border-radius: 999px; + border-color: rgba(24, 144, 255, 0.24); + color: #1890ff; + background: rgba(24, 144, 255, 0.08); +} + .symbol-info { display: flex; flex-direction: column; @@ -2579,6 +2672,13 @@ getMarketColor, } } + &.create-strategy-icon { + color: #fa8c16; + &:hover { + color: #ffa940; + } + } + &.status-icon { &.status-normal { color: #52c41a; @@ -2995,6 +3095,12 @@ getMarketColor, color: #f6465d; } } + + .qt-header-btn { + color: #40a9ff; + background: rgba(24, 144, 255, 0.14); + border-color: rgba(24, 144, 255, 0.28); + } } } } @@ -3198,6 +3304,14 @@ getMarketColor, color: #d3adf7; } } + + &.create-strategy-icon { + color: #faad14; + + &:hover { + color: #ffc53d; + } + } &.backtest-history-icon { color: #5cdbd3; &:hover { diff --git a/frontend_vue/src/views/trading-assistant/index.vue b/frontend_vue/src/views/trading-assistant/index.vue index 4794caf..6121958 100644 --- a/frontend_vue/src/views/trading-assistant/index.vue +++ b/frontend_vue/src/views/trading-assistant/index.vue @@ -1999,6 +1999,7 @@ export default { mounted () { this.loadStrategies() this.loadUserNotificationSettings() + this.handleRouteCreateStrategyPrefill() }, beforeDestroy () { this.stopEquityPolling() @@ -2591,6 +2592,52 @@ export default { this.loadExchangeCredentials() }) }, + async handleRouteCreateStrategyPrefill () { + const query = (this.$route && this.$route.query) || {} + if (query.open !== 'from-indicator') { + return + } + + this.handleCreateStrategy() + await this.$nextTick() + await Promise.all([ + this.loadIndicators(), + this.loadWatchlist(), + this.loadExchangeCredentials() + ]) + + const indicatorId = query.indicator_id ? String(query.indicator_id) : '' + const market = query.market || 'Crypto' + const symbol = query.symbol || '' + const timeframe = query.timeframe || '1D' + const fullSymbol = symbol ? (String(symbol).includes(':') ? String(symbol) : `${market}:${symbol}`) : '' + + this.selectedMarketCategory = market + if (fullSymbol) { + this.handleMultiSymbolChange([fullSymbol]) + } + + const fieldValues = { + timeframe + } + if (indicatorId) { + fieldValues.indicator_id = indicatorId + } + this.form.setFieldsValue(fieldValues) + + if (indicatorId) { + await this.handleIndicatorChange(indicatorId) + const indicator = this.availableIndicators.find(ind => String(ind.id) === indicatorId) + if (indicator) { + const strategyName = fullSymbol ? `${indicator.name} ${symbol}` : indicator.name + this.form.setFieldsValue({ + strategy_name: strategyName + }) + } + } + + this.$router.replace({ path: this.$route.path, query: {} }).catch(() => {}) + }, handleEditStrategy (strategy) { // 如果策略正在运行,提示用户先停止 if (strategy.status === 'running') {