Files
tick-stock-panel/frontend/src/components/data/RepairDailyPanel.tsx
T
shy3130 6fb46b100a feat(ui): 能力徽章与数据源页重构——路由门控、中立文案、详情卡重排
- 侧栏数据源徽章: 六能力方格(按注册序) + 悬浮路由卡(逐格同色对应,
  fixed 定位逃逸侧栏裁剪, 渲染后按实际高度钳制视口防遮挡)
- 各页能力门控切换为矩阵 usable 视角: Data/Financials/Monitor 缺能力时
  提示并引导数据源配置; 门控判定不再绑定 TickFlow 套餐
- 设置页数据源区: 能力路由卡(点标签切换, 乐观更新)、TickFlow 详情改为
  左 API Key 右能力档位表、检测档位收进头部徽章集群(+/重检测)、
  可用功能收进悬停图标、订阅档位面板移除
- 插件详情与 TickFlow 同构: 能力适配表(服务中/已适配/未就绪/—) +
  Key 区申请话术嵌官网链接(manifest homepage)
- 能力层文案中立化: 通用界面不出现档位/订阅词汇, 能力不可用警示按
  usable 判定(TickFlow 档位不足与源未就绪同待遇)
- API Key 输入框与保存按钮改为一行左右布局
2026-08-30 19:05:22 +08:00

110 lines
4.0 KiB
TypeScript

import { useState } from 'react'
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { Loader2 } from 'lucide-react'
import { api } from '@/lib/api'
import { QK } from '@/lib/queryKeys'
import { MissingCapChip } from '@/lib/capability-labels'
import { DatePicker } from '@/components/DatePicker'
function pad(n: number) { return String(n).padStart(2, '0') }
function todayStr() {
const d = new Date()
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
/** 往前推 N 天 */
function daysAgo(n: number): string {
const d = new Date()
d.setDate(d.getDate() - n)
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
// hasCap: 日K批量能力当前是否可用 (路由矩阵判定, 生效源含插件/自定义源)
export function RepairDailyPanel({ hasCap, isRunning, latestDate, onStart }: {
hasCap: boolean
isRunning: boolean
latestDate: string | null
onStart: () => void
}) {
const qc = useQueryClient()
const hasBatchCap = hasCap
// 默认起始日期: 最新数据往前推 30 天 (兼顾补缺口 + 复核近期数据, 成本不高)
const [startDate, setStartDate] = useState(daysAgo(30))
const repair = useMutation({
mutationFn: () => api.repairDaily(startDate),
onSuccess: () => {
onStart()
qc.invalidateQueries({ queryKey: QK.pipelineJobs })
},
})
const today = todayStr()
const canSubmit = hasBatchCap && !isRunning && !repair.isPending && startDate <= today
const gapDays = latestDate
? Math.max(0, Math.round((Date.parse(today) - Date.parse(latestDate)) / 86400000))
: null
return (
<div className="space-y-3">
<div className="rounded-card bg-accent/8 border border-accent/20 p-3 space-y-1.5">
<div className="text-xs text-foreground">
当数据出现缺口时(漏跑、停服),从这里重拉选定区间到今天的全部数据并重算。
</div>
<div className="text-[10px] text-muted leading-relaxed">
完整复用盘后管道流程 (A股日K · 除权因子 · 指标重算 · 指数),新数据按 (个股, 日期) 覆盖旧值,不会重复,也无需先清除。
</div>
</div>
{/* 起始日期 — 用户主要操作,放上面 */}
<div className="flex items-center justify-between gap-2">
<span className="text-xs text-secondary">起始日期</span>
<DatePicker
value={startDate}
onChange={setStartDate}
max={today}
align="right"
buttonClassName="font-mono text-xs"
/>
</div>
{/* 本地最新数据 — 参考信息,放下面 */}
<div className="flex items-center justify-between gap-2">
<span className="text-xs text-muted">本地最新数据</span>
<span className="font-mono text-xs text-secondary">
{latestDate ?? '—'}
{gapDays !== null && gapDays > 0 && (
<span className="ml-2 text-warning/90">已落后 {gapDays} </span>
)}
</span>
</div>
<div className="text-[10px] text-muted -mt-1">
将重拉 <span className="font-mono text-secondary">{startDate}</span>
{' → '}<span className="font-mono text-secondary">{today}</span>(今天) A股日K · 除权 · 指数并重算指标
</div>
<button
onClick={() => repair.mutate()}
disabled={!canSubmit}
className="w-full inline-flex items-center justify-center gap-1.5 px-3 py-1.5 rounded-btn bg-accent/90 text-base text-xs font-medium hover:bg-accent disabled:opacity-40 disabled:pointer-events-none transition-colors duration-150"
>
{repair.isPending ? (
<>
<Loader2 className="h-3 w-3 animate-spin" />
请求中…
</>
) : (
<>开始修正</>
)}
</button>
{!hasBatchCap && (
<span className="block text-center">
<MissingCapChip capKey="kline.daily.batch" />
</span>
)}
</div>
)
}