rebuild(stage-5): establish market data gateway and charts

This commit is contained in:
leefer
2026-07-30 02:35:42 +08:00
parent 40ad5d6836
commit cf0ab7026f
45 changed files with 2701 additions and 46 deletions
+6 -1
View File
@@ -4,6 +4,7 @@ import { onBeforeUnmount, onMounted } from "vue";
import DialogHost from "../../shared/components/DialogHost.vue";
import ToastHost from "../../shared/components/ToastHost.vue";
import { useUiStore } from "../../shared/stores/ui";
import { useMarketStore } from "../../shared/stores/market";
import DesktopSidebar from "./DesktopSidebar.vue";
import MarketStrip from "./MarketStrip.vue";
import MobileNav from "./MobileNav.vue";
@@ -11,6 +12,7 @@ import StatusBar from "./StatusBar.vue";
import TopBar from "./TopBar.vue";
const ui = useUiStore();
const market = useMarketStore();
function globalShortcut(event: KeyboardEvent): void {
if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "k") return;
@@ -22,7 +24,10 @@ function globalShortcut(event: KeyboardEvent): void {
ui.openDialog("search");
}
onMounted(() => window.addEventListener("keydown", globalShortcut));
onMounted(() => {
window.addEventListener("keydown", globalShortcut);
void market.load();
});
onBeforeUnmount(() => window.removeEventListener("keydown", globalShortcut));
</script>
+51 -11
View File
@@ -1,28 +1,68 @@
<script setup lang="ts">
import { ref } from "vue";
import { computed, ref } from "vue";
import { useMarketStore } from "../../shared/stores/market";
const expanded = ref(false);
const cells = ["市场情绪", "涨停", "跌停", "炸板", "封板率", "两市成交", "数据日期"];
const market = useMarketStore();
const values = computed(() => market.summary?.values ?? {});
const cells = computed(() => [
["市场情绪", display("temperature")],
["涨停", display("limit_up")],
["跌停", display("limit_down")],
["炸板", display("broken")],
["封板率", percent("seal_rate")],
["两市成交", amount("amount")],
["数据日期", observedTime()],
]);
function display(key: string): string {
const value = values.value[key];
return typeof value === "number" || typeof value === "string" ? String(value) : "";
}
function percent(key: string): string {
const value = values.value[key];
return typeof value === "number" ? `${value.toFixed(1)}%` : "";
}
function amount(key: string): string {
const value = values.value[key];
return typeof value === "number" ? `${(value / 100_000_000).toFixed(2)} 亿` : "";
}
function observedTime(): string {
const observed = market.summary?.context.observed_at;
if (!observed) return "";
return new Intl.DateTimeFormat("zh-CN", {
timeZone: "Asia/Shanghai",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
}).format(new Date(observed));
}
</script>
<template>
<section class="market-strip" aria-label="市场摘要">
<div class="market-strip-row">
<span class="market-emotion"><span class="emotion-dot" aria-hidden="true"></span>市场情绪</span>
<span>涨停</span>
<span>跌停</span>
<span>炸板</span>
<span>封板率</span>
<span>两市成交</span>
<span class="faint">等待最新行情</span>
<span>涨停 {{ display("limit_up") }}</span>
<span>跌停 {{ display("limit_down") }}</span>
<span>炸板 {{ display("broken") }}</span>
<span>封板率 {{ percent("seal_rate") }}</span>
<span>两市成交 {{ amount("amount") }}</span>
<span class="faint">{{ market.loading ? "正在读取行情" : observedTime() || market.summary?.context.message || market.error }}</span>
<button class="market-toggle" type="button" :aria-expanded="expanded" @click="expanded = !expanded">
{{ expanded ? "收起详情" : "展开详情" }}
</button>
</div>
<div v-if="expanded" class="market-details">
<div v-for="cell in cells" :key="cell" class="market-cell">
<div class="market-cell-label">{{ cell }}</div>
<div class="market-cell-value"></div>
<div v-for="cell in cells" :key="cell[0]" class="market-cell">
<div class="market-cell-label">{{ cell[0] }}</div>
<div class="market-cell-value">{{ cell[1] }}</div>
</div>
</div>
</section>
+2 -1
View File
@@ -17,6 +17,7 @@ const menuRoot = ref<HTMLElement | null>(null);
const title = computed(() => {
if (route.name === "system") return "系统管理";
if (route.name === "entity-detail") return "行情详情";
return findWorkspace(String(route.params.workspace ?? "emotion"))?.title ?? "小白复盘";
});
@@ -38,7 +39,7 @@ function changeDate(event: Event): void {
!Number.isNaN(parsed.valueOf()) &&
parsed.toISOString().slice(0, 10) === value
) {
market.selectedDate = value;
void market.selectDate(value);
return;
}
input.value = market.selectedDate;