mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 16:54:20 +08:00
refactor: 移除异动雷达 — 数据源盘中几乎无记录,功能不达预期
- 删除 /radar 页面与导航、看板「异动雷达」卡片、fetchUnusual 封装 - 移除 /mac/unusual HTTP 端点(MAC 协议层 get_unusual 与 mock 保留, 属协议覆盖面);路线图 ③ 标记取消
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
| ⑨ | **市场宽度分时** `/sentiment` | 指数新高但上涨家数背离的顶部信号 | 依赖 ① 的采样器 | ✅ 第三批(随①) |
|
| ⑨ | **市场宽度分时** `/sentiment` | 指数新高但上涨家数背离的顶部信号 | 依赖 ① 的采样器 | ✅ 第三批(随①) |
|
||||||
| ⑥ | **板块资金日历** | 哪天钱涌向了哪个板块 | board summary 主力净额逐日采样 | ⏳ 后续批次 |
|
| ⑥ | **板块资金日历** | 哪天钱涌向了哪个板块 | board summary 主力净额逐日采样 | ⏳ 后续批次 |
|
||||||
| ⑤ | **板块相关性热力图** `/hotspots` | 哪些板块同涨同跌(抱团 vs 分散) | 热点滚动已缓存的 60 日涨跌矩阵求两两相关 | ✅ 第四批(热点滚动页内「相关性」视图) |
|
| ⑤ | **板块相关性热力图** `/hotspots` | 哪些板块同涨同跌(抱团 vs 分散) | 热点滚动已缓存的 60 日涨跌矩阵求两两相关 | ✅ 第四批(热点滚动页内「相关性」视图) |
|
||||||
| ③ | **异动雷达时间线** `/radar` | 异动密度骤增 = 盘面转折点 | `/mac/unusual` 现成,纯前端 | ✅ 第四批 |
|
| ③ | ~~异动雷达时间线~~ | ~~异动密度骤增 = 盘面转折点~~ | `/mac/unusual` 盘中数据源质量差(盘中几乎无记录),**整项取消**(页面/导航/端点已移除) | ❌ 已取消 |
|
||||||
| ⑧ | **量能仪表盘** `/sentiment` | 放量/缩量(两市累计成交 vs 5日同期均值) | 指数 5 分钟线现成 | ✅ 第四批(并入市场情绪页) |
|
| ⑧ | **量能仪表盘** `/sentiment` | 放量/缩量(两市累计成交 vs 5日同期均值) | 指数 5 分钟线现成 | ✅ 第四批(并入市场情绪页) |
|
||||||
| ⑩ | **AI 盘面早报/复盘** | 把以上所有数据"自动读"给你听 | LLM 管道 + ai-history 归档现成 | ⏳ 收尾(必须做) |
|
| ⑩ | **AI 盘面早报/复盘** | 把以上所有数据"自动读"给你听 | LLM 管道 + ai-history 归档现成 | ⏳ 收尾(必须做) |
|
||||||
|
|
||||||
|
|||||||
@@ -55,15 +55,3 @@ async def auction(
|
|||||||
"""获取集合竞价数据。"""
|
"""获取集合竞价数据。"""
|
||||||
df = await client.get_auction(market=market_value_from_str(market), code=code)
|
df = await client.get_auction(market=market_value_from_str(market), code=code)
|
||||||
return _df_resp(df)
|
return _df_resp(df)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/mac/unusual", response_model=DataFrameResponse)
|
|
||||||
async def unusual(
|
|
||||||
market: str = Query(..., description="市场: SZ, SH"),
|
|
||||||
start: int = Query(0, ge=0, description="分页起始位置"),
|
|
||||||
count: int = Query(50, ge=1, le=500, description="返回数量"),
|
|
||||||
client: Any = Depends(get_mac_client),
|
|
||||||
) -> DataFrameResponse:
|
|
||||||
"""获取市场异动行情数据。"""
|
|
||||||
df = await client.get_unusual(market=market_value_from_str(market), start=start, count=count)
|
|
||||||
return _df_resp(df)
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ const sseLabel: Record<string, string> = {
|
|||||||
<RouterLink to="/calendar" active-class="active">大盘日历</RouterLink>
|
<RouterLink to="/calendar" active-class="active">大盘日历</RouterLink>
|
||||||
<RouterLink to="/limitup" active-class="active">涨停生态</RouterLink>
|
<RouterLink to="/limitup" active-class="active">涨停生态</RouterLink>
|
||||||
<RouterLink to="/sentiment" active-class="active">市场情绪</RouterLink>
|
<RouterLink to="/sentiment" active-class="active">市场情绪</RouterLink>
|
||||||
<RouterLink to="/radar" active-class="active">异动雷达</RouterLink>
|
|
||||||
<RouterLink to="/watchlist" active-class="active">自选行情</RouterLink>
|
<RouterLink to="/watchlist" active-class="active">自选行情</RouterLink>
|
||||||
<RouterLink to="/ccpm" active-class="active">期货持仓排名</RouterLink>
|
<RouterLink to="/ccpm" active-class="active">期货持仓排名</RouterLink>
|
||||||
<div class="nav-group">分析</div>
|
<div class="nav-group">分析</div>
|
||||||
|
|||||||
+10
-9
@@ -6,6 +6,7 @@ import type {
|
|||||||
BacktestRequest,
|
BacktestRequest,
|
||||||
BacktestResult,
|
BacktestResult,
|
||||||
Bar,
|
Bar,
|
||||||
|
BoardFundDay,
|
||||||
BoardOverviewResp,
|
BoardOverviewResp,
|
||||||
BoardRow,
|
BoardRow,
|
||||||
Category,
|
Category,
|
||||||
@@ -610,15 +611,6 @@ export async function fetchBoardHotspot(
|
|||||||
return body.data
|
return body.data
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 市场异动流(火箭发射/大笔买入/封涨停板/打开跌停板/快速反弹等)。 */
|
|
||||||
export async function fetchUnusual(market: 'SH' | 'SZ', count = 50): Promise<Record<string, unknown>[]> {
|
|
||||||
const params = new URLSearchParams({ market, count: String(count) })
|
|
||||||
const resp = await fetch(`${BASE}/mac/unusual?${params}`)
|
|
||||||
if (!resp.ok) await throwError(resp)
|
|
||||||
const body = (await resp.json()) as DataFrameResponse
|
|
||||||
return body.data
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 排行榜(MAC 排行行情)。
|
/** 排行榜(MAC 排行行情)。
|
||||||
* MAC 协议价格列是 close(无 price/change_pct),这里统一归一化为
|
* MAC 协议价格列是 close(无 price/change_pct),这里统一归一化为
|
||||||
* price/change_pct,渲染端不再做多候选探测。sortBy 对应后端 SortType。 */
|
* price/change_pct,渲染端不再做多候选探测。sortBy 对应后端 SortType。 */
|
||||||
@@ -865,6 +857,15 @@ export async function fetchHotspotCorrelation(
|
|||||||
return body.data
|
return body.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 行业主力净流入逐日排行(FundFlowSampler 每交易日 14:45 后采样,需积累)。 */
|
||||||
|
export async function fetchBoardFundHistory(days = 15): Promise<BoardFundDay[]> {
|
||||||
|
const params = new URLSearchParams({ days: String(days) })
|
||||||
|
const resp = await fetch(`${BASE}/market/board-fund/history?${params}`)
|
||||||
|
if (!resp.ok) await throwError(resp)
|
||||||
|
const body = (await resp.json()) as { data: { count: number; days: BoardFundDay[] } }
|
||||||
|
return body.data.days
|
||||||
|
}
|
||||||
|
|
||||||
/** 当日情绪分钟曲线(采样器逐分钟落库;date=0 表示尚无采样)。 */
|
/** 当日情绪分钟曲线(采样器逐分钟落库;date=0 表示尚无采样)。 */
|
||||||
export async function fetchSentimentToday(): Promise<SentimentTodayResp> {
|
export async function fetchSentimentToday(): Promise<SentimentTodayResp> {
|
||||||
const resp = await fetch(`${BASE}/market/sentiment/today`)
|
const resp = await fetch(`${BASE}/market/sentiment/today`)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import LlmHistoryView from './views/LlmHistoryView.vue'
|
|||||||
import LlmSettingsView from './views/LlmSettingsView.vue'
|
import LlmSettingsView from './views/LlmSettingsView.vue'
|
||||||
import OptimizeView from './views/OptimizeView.vue'
|
import OptimizeView from './views/OptimizeView.vue'
|
||||||
import PortfolioView from './views/PortfolioView.vue'
|
import PortfolioView from './views/PortfolioView.vue'
|
||||||
import RadarView from './views/RadarView.vue'
|
|
||||||
import SentimentView from './views/SentimentView.vue'
|
import SentimentView from './views/SentimentView.vue'
|
||||||
import ServerSettingsView from './views/ServerSettingsView.vue'
|
import ServerSettingsView from './views/ServerSettingsView.vue'
|
||||||
import SignalRadarView from './views/SignalRadarView.vue'
|
import SignalRadarView from './views/SignalRadarView.vue'
|
||||||
@@ -39,8 +38,6 @@ const routes = [
|
|||||||
{ path: '/limitup', name: 'limitup', component: LimitUpView },
|
{ path: '/limitup', name: 'limitup', component: LimitUpView },
|
||||||
// 市场情绪(宽度分时 + 涨停温度计;采样器盘中逐分钟积累)
|
// 市场情绪(宽度分时 + 涨停温度计;采样器盘中逐分钟积累)
|
||||||
{ path: '/sentiment', name: 'sentiment', component: SentimentView },
|
{ path: '/sentiment', name: 'sentiment', component: SentimentView },
|
||||||
// 异动雷达(沪深异动流时间线:封板/炸板/大笔买入…)
|
|
||||||
{ path: '/radar', name: 'radar', component: RadarView },
|
|
||||||
{ path: '/backtest', name: 'backtest', component: BacktestView },
|
{ path: '/backtest', name: 'backtest', component: BacktestView },
|
||||||
{ path: '/portfolio', name: 'portfolio', component: PortfolioView },
|
{ path: '/portfolio', name: 'portfolio', component: PortfolioView },
|
||||||
{ path: '/optimize', name: 'optimize', component: OptimizeView },
|
{ path: '/optimize', name: 'optimize', component: OptimizeView },
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import {
|
|||||||
fetchIndexBars,
|
fetchIndexBars,
|
||||||
fetchMarketStat,
|
fetchMarketStat,
|
||||||
fetchRankList,
|
fetchRankList,
|
||||||
fetchUnusual,
|
|
||||||
fetchMinute,
|
fetchMinute,
|
||||||
formatError,
|
formatError,
|
||||||
} from '../api'
|
} from '../api'
|
||||||
@@ -310,43 +309,6 @@ function barWidth(pct: number, list: BoardRow[]): string {
|
|||||||
return `${Math.min(100, (Math.abs(pct) / maxAbs) * 100)}%`
|
return `${Math.min(100, (Math.abs(pct) / maxAbs) * 100)}%`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── 异动事件流(轮询 60s) ───────────────────────────────────────────────────
|
|
||||||
|
|
||||||
interface UnusualRow {
|
|
||||||
time: string
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
desc: string
|
|
||||||
value: string
|
|
||||||
market: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const unusualRows = ref<UnusualRow[]>([])
|
|
||||||
const unusualError = ref('')
|
|
||||||
|
|
||||||
function normalizeUnusual(rows: Record<string, unknown>[], market: 'SH' | 'SZ'): UnusualRow[] {
|
|
||||||
return rows.map((r) => ({
|
|
||||||
time: String(r.time ?? '').slice(0, 8),
|
|
||||||
code: String(r.code ?? ''),
|
|
||||||
name: String(r.name ?? ''),
|
|
||||||
desc: String(r.desc ?? ''),
|
|
||||||
value: String(r.value ?? ''),
|
|
||||||
market,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadUnusual() {
|
|
||||||
unusualError.value = ''
|
|
||||||
try {
|
|
||||||
const [sh, sz] = await Promise.all([fetchUnusual('SH', 60), fetchUnusual('SZ', 60)])
|
|
||||||
const merged = [...normalizeUnusual(sh, 'SH'), ...normalizeUnusual(sz, 'SZ')]
|
|
||||||
merged.sort((a, b) => (a.time < b.time ? 1 : a.time > b.time ? -1 : 0))
|
|
||||||
unusualRows.value = merged.slice(0, 80)
|
|
||||||
} catch (e) {
|
|
||||||
unusualError.value = formatError(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 排行榜(涨幅/跌幅/成交额/换手 四 tab,轮询 60s) ───────────────────────
|
// ── 排行榜(涨幅/跌幅/成交额/换手 四 tab,轮询 60s) ───────────────────────
|
||||||
|
|
||||||
const gainers = ref<RankRow[]>([])
|
const gainers = ref<RankRow[]>([])
|
||||||
@@ -442,7 +404,6 @@ const sessionLabel = computed(() => {
|
|||||||
function refreshAll() {
|
function refreshAll() {
|
||||||
loadStat()
|
loadStat()
|
||||||
loadBoards()
|
loadBoards()
|
||||||
loadUnusual()
|
|
||||||
loadRanks()
|
loadRanks()
|
||||||
loadIdxSparks()
|
loadIdxSparks()
|
||||||
loadIndexContext()
|
loadIndexContext()
|
||||||
@@ -458,7 +419,6 @@ function tickIfActive() {
|
|||||||
|
|
||||||
function slowTickIfActive() {
|
function slowTickIfActive() {
|
||||||
if (autoPaused.value) return
|
if (autoPaused.value) return
|
||||||
loadUnusual()
|
|
||||||
loadRanks()
|
loadRanks()
|
||||||
loadBoards()
|
loadBoards()
|
||||||
loadIdxSparks()
|
loadIdxSparks()
|
||||||
@@ -731,29 +691,6 @@ function openBoard(code: string | undefined, name: string | undefined) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 异动事件流 -->
|
|
||||||
<div class="card unusual-card">
|
|
||||||
<h3>异动雷达</h3>
|
|
||||||
<div v-if="unusualError" class="err">{{ unusualError }}</div>
|
|
||||||
<table v-else class="qtable">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>时间</th>
|
|
||||||
<th style="text-align: left">股票</th>
|
|
||||||
<th style="text-align: left">异动</th>
|
|
||||||
<th>数值</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="(r, i) in unusualRows" :key="i" @click="openDialog(r.code, r.name, r.market)">
|
|
||||||
<td class="dim">{{ r.time }}</td>
|
|
||||||
<td style="text-align: left">{{ r.name }}</td>
|
|
||||||
<td style="text-align: left"><span class="tag">{{ r.desc }}</span></td>
|
|
||||||
<td class="mono">{{ r.value }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StockDialog
|
<StockDialog
|
||||||
@@ -873,10 +810,6 @@ function openBoard(code: string | undefined, name: string | undefined) {
|
|||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
.unusual-card {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
max-height: 320px;
|
|
||||||
}
|
|
||||||
.dist-card {
|
.dist-card {
|
||||||
min-height: 220px;
|
min-height: 220px;
|
||||||
}
|
}
|
||||||
@@ -1078,10 +1011,6 @@ function openBoard(code: string | undefined, name: string | undefined) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 涨跌幅榜 */
|
/* 涨跌幅榜 */
|
||||||
.unusual-card tr,
|
|
||||||
.card table tr {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 异动 */
|
/* 异动 */
|
||||||
.tag {
|
.tag {
|
||||||
|
|||||||
@@ -1,306 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
// 异动雷达(/radar):沪深异动流时间线(封板/炸板/大笔买入/火箭发射…)
|
|
||||||
// + 每分钟异动密度柱。异动流为交易所最近交易日的盘中记录;15s 轮询、
|
|
||||||
// 交易时段外仅手动刷新;类型筛选 chips;单击行直达个股弹窗。
|
|
||||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
||||||
|
|
||||||
import echarts from '../echarts-setup'
|
|
||||||
import { fetchUnusual, formatError } from '../api'
|
|
||||||
import StockDialog from '../components/StockDialog.vue'
|
|
||||||
|
|
||||||
type UnusualRow = {
|
|
||||||
index: number
|
|
||||||
market: number
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
time: string
|
|
||||||
desc: string
|
|
||||||
value: string
|
|
||||||
unusual_type: number
|
|
||||||
mkt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = ref<UnusualRow[]>([])
|
|
||||||
const loading = ref(false)
|
|
||||||
const error = ref('')
|
|
||||||
const lastRefresh = ref('')
|
|
||||||
const activeType = ref('全部')
|
|
||||||
|
|
||||||
let timer = 0
|
|
||||||
|
|
||||||
async function load() {
|
|
||||||
loading.value = rows.value.length === 0
|
|
||||||
error.value = ''
|
|
||||||
try {
|
|
||||||
const [sh, sz] = await Promise.all([fetchUnusual('SH', 300), fetchUnusual('SZ', 300)])
|
|
||||||
rows.value = [...sh, ...sz]
|
|
||||||
.map((r) => ({ ...(r as unknown as UnusualRow), mkt: Number(r.market) === 1 ? 'SH' : 'SZ' }))
|
|
||||||
.sort((a, b) => b.time.localeCompare(a.time))
|
|
||||||
lastRefresh.value = new Date().toLocaleTimeString('zh-CN', { hour12: false })
|
|
||||||
renderDensity()
|
|
||||||
} catch (e) {
|
|
||||||
error.value = formatError(e)
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const types = computed(() => {
|
|
||||||
const count = new Map<string, number>()
|
|
||||||
for (const r of rows.value) count.set(r.desc, (count.get(r.desc) ?? 0) + 1)
|
|
||||||
return ['全部', ...[...count.entries()].sort((a, b) => b[1] - a[1]).map(([t]) => t)]
|
|
||||||
})
|
|
||||||
|
|
||||||
const filtered = computed(() =>
|
|
||||||
activeType.value === '全部' ? rows.value : rows.value.filter((r) => r.desc === activeType.value),
|
|
||||||
)
|
|
||||||
|
|
||||||
function typeCount(t: string): number {
|
|
||||||
return t === '全部' ? rows.value.length : (rows.value.filter((r) => r.desc === t).length ?? 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── 密度图(每分钟异动条数) ─────────────────────────────────────────────────
|
|
||||||
|
|
||||||
const densityEl = ref<HTMLDivElement>()
|
|
||||||
let densityChart: echarts.ECharts | null = null
|
|
||||||
|
|
||||||
function renderDensity() {
|
|
||||||
if (!densityEl.value) return
|
|
||||||
const perMinute = new Map<string, number>()
|
|
||||||
for (const r of rows.value) {
|
|
||||||
const m = r.time.slice(0, 5)
|
|
||||||
perMinute.set(m, (perMinute.get(m) ?? 0) + 1)
|
|
||||||
}
|
|
||||||
const x = [...perMinute.keys()].sort()
|
|
||||||
const y = x.map((k) => perMinute.get(k) ?? 0)
|
|
||||||
densityChart ??= echarts.init(densityEl.value, 'dark')
|
|
||||||
densityChart.setOption(
|
|
||||||
{
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
tooltip: { trigger: 'axis' },
|
|
||||||
grid: { left: 40, top: 8, bottom: 24, right: 10 },
|
|
||||||
xAxis: { type: 'category', data: x, axisLabel: { fontSize: 9 } },
|
|
||||||
yAxis: { type: 'value', name: '条/分', nameTextStyle: { fontSize: 9 } },
|
|
||||||
series: [
|
|
||||||
{
|
|
||||||
type: 'bar',
|
|
||||||
data: y,
|
|
||||||
itemStyle: { color: '#f5a623' },
|
|
||||||
barMaxWidth: 6,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function onResize() {
|
|
||||||
densityChart?.resize()
|
|
||||||
}
|
|
||||||
|
|
||||||
function tick() {
|
|
||||||
const now = new Date()
|
|
||||||
const day = now.getDay()
|
|
||||||
if (day === 0 || day === 6 || document.hidden) return
|
|
||||||
const m = now.getHours() * 60 + now.getMinutes()
|
|
||||||
if ((m >= 555 && m <= 690) || (m >= 780 && m <= 905)) load()
|
|
||||||
}
|
|
||||||
|
|
||||||
const stockDlg = ref<{ market: string; code: string; name: string } | null>(null)
|
|
||||||
|
|
||||||
function openStock(r: UnusualRow) {
|
|
||||||
stockDlg.value = { market: r.mkt, code: r.code, name: r.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
load()
|
|
||||||
timer = window.setInterval(tick, 15_000)
|
|
||||||
window.addEventListener('resize', onResize)
|
|
||||||
})
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.clearInterval(timer)
|
|
||||||
window.removeEventListener('resize', onResize)
|
|
||||||
densityChart?.dispose()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="radar-view">
|
|
||||||
<div class="view-head">
|
|
||||||
<h2>异动雷达</h2>
|
|
||||||
<span class="dim head-sub">沪深异动流 · 最近交易日盘中记录</span>
|
|
||||||
<span class="tb-spacer"></span>
|
|
||||||
<span v-if="lastRefresh" class="dim refresh-ts">{{ lastRefresh }}</span>
|
|
||||||
<button class="manual-refresh" @click="load">↻ 刷新</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="error" class="err card">
|
|
||||||
加载失败:{{ error }}
|
|
||||||
<button @click="load">重试</button>
|
|
||||||
</div>
|
|
||||||
<div v-else-if="loading" class="loading">异动流加载中…</div>
|
|
||||||
|
|
||||||
<template v-else>
|
|
||||||
<div class="card chart-card">
|
|
||||||
<div ref="densityEl" class="density-chart"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="chips">
|
|
||||||
<button
|
|
||||||
v-for="t in types"
|
|
||||||
:key="t"
|
|
||||||
class="chip-s"
|
|
||||||
:class="{ on: activeType === t }"
|
|
||||||
@click="activeType = t"
|
|
||||||
>
|
|
||||||
{{ t }} <span class="mono dim">{{ typeCount(t) }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card list-card">
|
|
||||||
<div v-for="r in filtered" :key="`${r.mkt}${r.code}${r.time}${r.index}`" class="u-row" @click="openStock(r)">
|
|
||||||
<span class="mono u-time">{{ r.time }}</span>
|
|
||||||
<span class="u-name">{{ r.name }}</span>
|
|
||||||
<span class="mono dim u-code">{{ r.mkt }}·{{ r.code }}</span>
|
|
||||||
<span class="u-desc" :class="{ hot: r.desc.includes('涨停') || r.desc.includes('买'), cold: r.desc.includes('跌') }">{{ r.desc }}</span>
|
|
||||||
<span class="mono dim u-val">{{ r.value }}</span>
|
|
||||||
</div>
|
|
||||||
<div v-if="filtered.length === 0" class="empty dim">暂无对应类型的异动记录</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<StockDialog
|
|
||||||
v-if="stockDlg"
|
|
||||||
:market="stockDlg.market"
|
|
||||||
:code="stockDlg.code"
|
|
||||||
:name="stockDlg.name"
|
|
||||||
@close="stockDlg = null"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.radar-view {
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.view-head,
|
|
||||||
.err,
|
|
||||||
.loading,
|
|
||||||
.chart-card,
|
|
||||||
.chips {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.view-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.view-head h2 {
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.head-sub {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.tb-spacer {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.refresh-ts {
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 11.5px;
|
|
||||||
}
|
|
||||||
.manual-refresh {
|
|
||||||
font-size: 12px;
|
|
||||||
padding: 4px 10px;
|
|
||||||
}
|
|
||||||
.err {
|
|
||||||
color: var(--up);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
.loading {
|
|
||||||
padding: 40px 0;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-dim);
|
|
||||||
}
|
|
||||||
.chart-card {
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
.density-chart {
|
|
||||||
height: 110px;
|
|
||||||
}
|
|
||||||
.chips {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 6px;
|
|
||||||
}
|
|
||||||
.chip-s {
|
|
||||||
padding: 3px 10px;
|
|
||||||
font-size: 11.5px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: var(--bg-elevated);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.chip-s.on {
|
|
||||||
border-color: var(--accent);
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
.list-card {
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
.u-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 5px 14px;
|
|
||||||
font-size: 12.5px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.u-row:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
.u-row:hover {
|
|
||||||
background: var(--bg-elevated);
|
|
||||||
}
|
|
||||||
.u-time {
|
|
||||||
font-size: 11.5px;
|
|
||||||
color: var(--text-dim);
|
|
||||||
width: 62px;
|
|
||||||
}
|
|
||||||
.u-name {
|
|
||||||
font-weight: 600;
|
|
||||||
width: 110px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.u-code {
|
|
||||||
font-size: 11px;
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
.u-desc {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.u-desc.hot {
|
|
||||||
color: var(--up);
|
|
||||||
}
|
|
||||||
.u-desc.cold {
|
|
||||||
color: var(--down);
|
|
||||||
}
|
|
||||||
.u-val {
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
.empty {
|
|
||||||
padding: 24px 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user