chore: initialize lhbfx project and documentation
This commit is contained in:
188
frontend/src/App.vue
Normal file
188
frontend/src/App.vue
Normal file
@ -0,0 +1,188 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, shallowRef } from 'vue'
|
||||
|
||||
import AppHero from './components/AppHero.vue'
|
||||
import HomeControlScreen from './components/HomeControlScreen.vue'
|
||||
import StockDetailScreen from './components/StockDetailScreen.vue'
|
||||
import TraderDetailScreen from './components/TraderDetailScreen.vue'
|
||||
import WarningCenterScreen from './components/WarningCenterScreen.vue'
|
||||
import { useDashboardData } from './composables/useDashboardData'
|
||||
import type { WarningItem } from './types'
|
||||
|
||||
const dashboard = useDashboardData()
|
||||
|
||||
type PageKey = 'home' | 'trader' | 'stock' | 'warning'
|
||||
|
||||
const currentPage = shallowRef<PageKey>('home')
|
||||
const selectedTraderId = computed(() => dashboard.selectedTraderId.value)
|
||||
|
||||
const navItems: Array<{ key: PageKey; label: string }> = [
|
||||
{ key: 'home', label: '首页总控台' },
|
||||
{ key: 'trader', label: '游资详情' },
|
||||
{ key: 'stock', label: '个股详情' },
|
||||
{ key: 'warning', label: '预警中心' },
|
||||
]
|
||||
|
||||
function pageFromHash(): PageKey {
|
||||
const hash = window.location.hash.replace(/^#\/?/, '')
|
||||
if (hash === 'trader' || hash === 'stock' || hash === 'warning') return hash
|
||||
return 'home'
|
||||
}
|
||||
|
||||
function syncPageFromHash() {
|
||||
currentPage.value = pageFromHash()
|
||||
}
|
||||
|
||||
function navigate(page: PageKey) {
|
||||
const nextHash = `#/${page}`
|
||||
if (window.location.hash !== nextHash) {
|
||||
window.location.hash = nextHash
|
||||
}
|
||||
currentPage.value = page
|
||||
}
|
||||
|
||||
async function handleSelectTrader(traderId: number) {
|
||||
await dashboard.selectTrader(traderId)
|
||||
navigate('trader')
|
||||
}
|
||||
|
||||
async function handleSelectStock(stockCode: string) {
|
||||
await dashboard.selectStock(stockCode)
|
||||
navigate('stock')
|
||||
}
|
||||
|
||||
async function handleSelectWarningInCenter(warning: WarningItem) {
|
||||
await dashboard.selectWarning(warning)
|
||||
}
|
||||
|
||||
async function handleSelectTradeDateRange(payload: { dateFrom: string; dateTo: string }) {
|
||||
await dashboard.selectTradeDateRange(payload.dateFrom, payload.dateTo)
|
||||
}
|
||||
|
||||
function handleUpdateTraderFilter(traderName: string) {
|
||||
dashboard.selectedTraderFilter.value = traderName
|
||||
void dashboard.loadActions()
|
||||
}
|
||||
|
||||
function handleUpdateActionFilter(actionFilter: 'all' | 'buy' | 'sell' | 'net_buy' | 'net_sell') {
|
||||
dashboard.selectedActionFilter.value = actionFilter
|
||||
}
|
||||
|
||||
async function handleFollowStock(payload: {
|
||||
stock_code: string
|
||||
stock_name: string
|
||||
source_trade_date: string | null
|
||||
source_trader_name: string | null
|
||||
}) {
|
||||
await dashboard.addToWatchlist(payload)
|
||||
}
|
||||
|
||||
async function handleUnfollowStock(stockCode: string) {
|
||||
await dashboard.removeFromWatchlist(stockCode)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
syncPageFromHash()
|
||||
window.addEventListener('hashchange', syncPageFromHash)
|
||||
void dashboard.initialize()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('hashchange', syncPageFromHash)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="page-shell">
|
||||
<div v-if="dashboard.errorMessage.value" class="error-banner">
|
||||
数据加载失败:{{ dashboard.errorMessage.value }}
|
||||
</div>
|
||||
|
||||
<AppHero
|
||||
:summary="dashboard.summary.value"
|
||||
:status="dashboard.status.value"
|
||||
:nav-items="navItems"
|
||||
:current-page="currentPage"
|
||||
@navigate="navigate"
|
||||
/>
|
||||
|
||||
<div v-if="dashboard.isBooting.value" class="loading-state">
|
||||
正在加载龙虎榜、游资和预警数据...
|
||||
</div>
|
||||
|
||||
<section v-else class="page-stage">
|
||||
<HomeControlScreen
|
||||
v-if="currentPage === 'home'"
|
||||
:trade-dates="dashboard.availableTradeDates.value"
|
||||
:selected-date-from="dashboard.selectedDateFrom.value"
|
||||
:selected-date-to="dashboard.selectedDateTo.value"
|
||||
:traders="dashboard.traders.value"
|
||||
:selected-trader-filter="dashboard.selectedTraderFilter.value"
|
||||
:selected-action-filter="dashboard.selectedActionFilter.value"
|
||||
:watched-actions="dashboard.watchedActionRows.value"
|
||||
:candidate-actions="dashboard.candidateActionRows.value"
|
||||
:watchlist="dashboard.watchlistStocksForDisplay.value"
|
||||
:metrics="dashboard.watchlistMetrics.value"
|
||||
@select-trade-date-range="handleSelectTradeDateRange"
|
||||
@update-trader-filter="handleUpdateTraderFilter"
|
||||
@update-action-filter="handleUpdateActionFilter"
|
||||
@select-stock="handleSelectStock"
|
||||
@follow-stock="handleFollowStock"
|
||||
@unfollow-stock="handleUnfollowStock"
|
||||
/>
|
||||
|
||||
<TraderDetailScreen
|
||||
v-else-if="currentPage === 'trader'"
|
||||
:traders="dashboard.traders.value"
|
||||
:trader-detail="dashboard.traderDetail.value"
|
||||
:selected-trader-id="selectedTraderId"
|
||||
@select-trader="handleSelectTrader"
|
||||
@select-stock="handleSelectStock"
|
||||
/>
|
||||
|
||||
<StockDetailScreen
|
||||
v-else-if="currentPage === 'stock'"
|
||||
:stock-detail="dashboard.stockDetail.value"
|
||||
:active-warning="dashboard.activeWarning.value"
|
||||
/>
|
||||
|
||||
<WarningCenterScreen
|
||||
v-else
|
||||
:warnings="dashboard.warnings.value"
|
||||
:traders="dashboard.traders.value"
|
||||
:active-warning="dashboard.activeWarning.value"
|
||||
@select-warning="handleSelectWarningInCenter"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page-shell {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
height: calc(100vh - 12px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-state,
|
||||
.error-banner {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: var(--color-muted);
|
||||
}
|
||||
|
||||
.error-banner {
|
||||
border-color: rgba(255, 93, 93, 0.2);
|
||||
color: #ffb4b4;
|
||||
background: rgba(255, 93, 93, 0.08);
|
||||
}
|
||||
|
||||
.page-stage {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user