feat: add quick trade panel and strategy creation enhancements in indicator analysis

This commit is contained in:
dienakdz
2026-04-07 07:44:43 +07:00
parent cb49789bf4
commit 0818028b4c
2 changed files with 162 additions and 1 deletions
@@ -68,6 +68,15 @@
{{ priceChange > 0 ? '+' : '' }}{{ priceChange.toFixed(2) }}%
</span>
</div>
<a-button
v-if="isCryptoMarket"
class="qt-header-btn"
size="small"
@click="openQuickTrade"
>
<a-icon type="thunderbolt" theme="filled" />
{{ tt('quickTrade.openPanel', 'Quick Trade') }}
</a-button>
</div>
</div>
</div>
@@ -210,6 +219,13 @@
@click.stop="handlePublishIndicator(indicator)"
/>
</a-tooltip>
<a-tooltip :title="tt('trading-assistant.createStrategy', 'Create Strategy')">
<a-icon
type="rocket"
class="action-icon create-strategy-icon"
@click.stop="handleCreateStrategyFromIndicator(indicator)"
/>
</a-tooltip>
</div>
</div>
<span class="card-desc">{{ indicator.description || '' }}</span>
@@ -230,6 +246,15 @@
<a-icon :type="purchasedSectionCollapsed ? 'right' : 'down'" class="collapse-icon" />
<span>{{ $t('dashboard.indicator.section.purchased') }} ({{ purchasedIndicators.length }})</span>
</div>
<a-button
type="link"
size="small"
icon="shop"
class="buy-indicator-btn"
@click.stop="goToIndicatorMarket"
>
{{ $t('menu.dashboard.community') }}
</a-button>
</div>
<div v-show="!purchasedSectionCollapsed" class="section-content custom-scrollbar">
<div
@@ -460,6 +485,17 @@
@cancel="showBacktestRunViewer = false; selectedBacktestRun = null"
/>
<quick-trade-panel
:visible="showQuickTrade"
:symbol="qtSymbol"
:preset-side="qtSide"
:preset-price="qtPrice"
source="indicator"
market-type="swap"
@close="showQuickTrade = false"
@update:symbol="qtSymbol = $event"
/>
<!-- 发布到社区弹窗 -->
<a-modal
:title="publishIndicator && publishIndicator.publish_to_community ? $t('dashboard.indicator.publish.editTitle') : $t('dashboard.indicator.publish.title')"
@@ -658,6 +694,7 @@ import KlineChart from '@/views/indicator-analysis/components/KlineChart.vue'
import BacktestModal from '@/views/indicator-analysis/components/BacktestModal.vue'
import BacktestHistoryDrawer from '@/views/indicator-analysis/components/BacktestHistoryDrawer.vue'
import BacktestRunViewer from '@/views/indicator-analysis/components/BacktestRunViewer.vue'
import QuickTradePanel from '@/components/QuickTradePanel.vue'
export default {
name: 'DashboardIndicator',
@@ -666,7 +703,8 @@ export default {
KlineChart,
BacktestModal,
BacktestHistoryDrawer,
BacktestRunViewer
BacktestRunViewer,
QuickTradePanel
},
computed: {
...mapState({
@@ -717,10 +755,20 @@ export default {
const currentMarket = ref('')
const currentPrice = ref('--')
const priceChange = ref(0)
const showQuickTrade = ref(false)
const qtSymbol = ref('')
const qtSide = ref('')
const qtPrice = ref(0)
const priceChangeClass = computed(() => {
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 {
@@ -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') {