76 lines
3.6 KiB
Vue
76 lines
3.6 KiB
Vue
<script setup lang="ts">
|
||
import { computed, onBeforeUnmount, onMounted } from 'vue';
|
||
|
||
import { useMarketSyncStore } from '@/stores/marketSync';
|
||
|
||
const store = useMarketSyncStore();
|
||
|
||
onMounted(async () => {
|
||
await store.fetchStatus();
|
||
store.pollIfRunning();
|
||
});
|
||
onBeforeUnmount(() => store.stopPolling());
|
||
|
||
/** "2026-09-01T00:00:00" / "2026-09-01" -> "2026年09月01日";无数据显示 — */
|
||
function fmtDate(s?: string | null): string {
|
||
if (!s) return '—';
|
||
const d = s.slice(0, 10);
|
||
const [y, m, day] = d.split('-');
|
||
if (!y || !m || !day) return d;
|
||
return `${y}年${m}月${day}日`;
|
||
}
|
||
|
||
const latestDate = computed(() => store.syncStatus?.last_trade_date ?? null);
|
||
const running = computed(() => !!store.syncStatus?.running);
|
||
const syncing = computed(() => !!store.syncStatus?.total_days);
|
||
// 后端任务内错误(如 daily_basic 权限受限)与请求级错误都展示
|
||
const errText = computed(() => store.error || store.syncStatus?.error || null);
|
||
const progressPct = computed(() => {
|
||
const s = store.syncStatus;
|
||
if (!s?.total_days) return 0;
|
||
return Math.min(100, ((s.done_days ?? 0) / s.total_days) * 100);
|
||
});
|
||
</script>
|
||
|
||
<template>
|
||
<div class="mb-12 flex flex-wrap items-center gap-x-5 gap-y-3 rounded-xl border border-[#26272E] bg-[#101014] px-5 py-4">
|
||
<!-- 最新更新日期 -->
|
||
<div class="flex items-center gap-3">
|
||
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-blue-500/15 text-blue-300">
|
||
<svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||
<rect x="3" y="4" width="18" height="18" rx="2" />
|
||
<path d="M16 2v4M8 2v4M3 10h18" />
|
||
</svg>
|
||
</span>
|
||
<div>
|
||
<div class="text-xs text-[#6B7280]">A股日线数据更新至</div>
|
||
<div class="text-base font-semibold tabular-nums text-[#E5E7EB]">{{ fmtDate(latestDate) }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<span class="hidden flex-1 sm:block"></span>
|
||
|
||
<!-- 同步中:进度条 -->
|
||
<div v-if="running" class="flex min-w-[220px] flex-1 items-center gap-2">
|
||
<svg class="h-4 w-4 shrink-0 animate-spin text-blue-500" viewBox="0 0 24 24" fill="none"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" /></svg>
|
||
<span class="whitespace-nowrap text-[13px] text-[#A8AFB8]">
|
||
{{ store.syncStatus?.step || '同步中…' }}<template v-if="syncing">({{ store.syncStatus?.done_days }}/{{ store.syncStatus?.total_days }})</template>
|
||
</span>
|
||
<span v-if="syncing" class="h-1.5 flex-1 overflow-hidden rounded-full bg-[#26272E]">
|
||
<span class="block h-full rounded-full bg-blue-500 transition-all" :style="{ width: progressPct + '%' }" />
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 空闲:手动同步按钮 -->
|
||
<button v-else type="button" class="btn-primary shrink-0" @click="store.startSync()">
|
||
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12a9 9 0 11-2.6-6.4M21 3v6h-6" /></svg>
|
||
同步A股当日数据
|
||
</button>
|
||
|
||
<!-- 错误提示 -->
|
||
<div v-if="errText" class="w-full text-sm text-amber-400">
|
||
<svg class="mr-1 inline h-4 w-4 align-[-3px]" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.3 3.9L1.8 18a2 2 0 001.7 3h17a2 2 0 001.7-3L13.7 3.9a2 2 0 00-3.4 0z" /><path d="M12 9v4M12 17h.01" /></svg>
|
||
{{ errText }}
|
||
</div>
|
||
</div>
|
||
</template> |