mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
merge: 同步 main (辐射本地读 + 传输压缩/网络设置) 到全量分钟分支
冲突两处均为 minute-refresh status 字段两侧各自新增, 两者全保留 (healthy + provider/provider_effective/repair_only)。
This commit is contained in:
@@ -131,7 +131,7 @@ export function ActiveJobCard({ job }: { job: PipelineJob }) {
|
||||
{job.error.includes('超时自动取消') && (
|
||||
<div className="mt-1 text-[10px] text-muted">
|
||||
判定依据是「无进度」而非总时长, 任务只要仍在推进就不会被中断;
|
||||
若网络环境较慢可在 设置 → 超时设置 中调大停滞阈值。
|
||||
若网络环境较慢可在 设置 → 网络设置 中调大停滞阈值。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1614,6 +1614,8 @@ export interface Preferences {
|
||||
financial_data_provider?: string
|
||||
data_source_job_timeout_s: number
|
||||
data_source_long_job_timeout_s: number
|
||||
minute_batch_compress: boolean
|
||||
daily_batch_compress: boolean
|
||||
realtime_pull_stock?: boolean
|
||||
realtime_pull_etf?: boolean
|
||||
pipeline_pull_a_share: boolean
|
||||
@@ -1788,8 +1790,23 @@ export const api = {
|
||||
data_source_job_timeout_s: dataSourceJobTimeoutS,
|
||||
data_source_long_job_timeout_s: dataSourceLongJobTimeoutS,
|
||||
}),
|
||||
|
||||
},
|
||||
),
|
||||
|
||||
/** 分时批量响应 gzip 压缩开关 (网络设置) — 逐请求即时生效 */
|
||||
updateMinuteBatchCompress: (enabled: boolean) =>
|
||||
request<Pick<Preferences, 'minute_batch_compress'>>('/api/settings/preferences/minute-batch-compress', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ minute_batch_compress: enabled }),
|
||||
}),
|
||||
|
||||
/** 日K批量响应 gzip 压缩开关 (与分时独立) — 逐请求即时生效 */
|
||||
updateDailyBatchCompress: (enabled: boolean) =>
|
||||
request<Pick<Preferences, 'daily_batch_compress'>>('/api/settings/preferences/daily-batch-compress', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ daily_batch_compress: enabled }),
|
||||
}),
|
||||
updateMinuteSync: (enabled: boolean, days: number, segmentDays?: number) =>
|
||||
request<Preferences>('/api/settings/preferences/minute-sync', {
|
||||
method: 'PUT',
|
||||
@@ -1806,6 +1823,8 @@ export const api = {
|
||||
available: boolean
|
||||
enabled?: boolean
|
||||
running?: boolean
|
||||
/** 读侧 freshness: 本地分区正被服务持续写入 (前端据此切本地读/解除截断) */
|
||||
healthy?: boolean
|
||||
provider?: string
|
||||
provider_effective?: string
|
||||
repair_only?: boolean
|
||||
|
||||
@@ -438,6 +438,14 @@ export function Screener() {
|
||||
)
|
||||
// 分时图依赖分钟K批量数据 (kline.minute.batch), 无数据时开了列也不拉
|
||||
const caps = useCapabilities()
|
||||
// 全量分钟服务健康 (freshness 契约): 健康时本地分区按配置间隔持续落盘,
|
||||
// 分时读本地不受批量上限约束 → 不截断 + prefer_local; 与监控设置页共享缓存
|
||||
const refreshStatus = useQuery({
|
||||
queryKey: ['minute-refresh-status'],
|
||||
queryFn: api.minuteRefreshStatus,
|
||||
refetchInterval: 15000,
|
||||
})
|
||||
const fullMinuteHealthy = !!refreshStatus.data?.healthy
|
||||
const hasMinuteBatch = !!caps.data?.capabilities?.['kline.minute.batch']
|
||||
const intradayVisible = !!intradayColumn && hasMinuteBatch && intradayChartVisible
|
||||
|
||||
@@ -456,11 +464,11 @@ export function Screener() {
|
||||
() => displayRows.map((r: any) => r.symbol),
|
||||
[displayRows],
|
||||
)
|
||||
const intradayTruncated = intradayVisible && allIntradaySymbols.length > minuteBatchCap
|
||||
// 截断到 batch 上限, 一次请求 = 一次数据源调用
|
||||
// 拉模型 (走批量接口) 才截断到 batch 上限; 全量分钟健康时读本地分区无上限
|
||||
const intradayTruncated = intradayVisible && !fullMinuteHealthy && allIntradaySymbols.length > minuteBatchCap
|
||||
const intradaySymbols = useMemo(
|
||||
() => intradayTruncated ? allIntradaySymbols.slice(0, minuteBatchCap) : allIntradaySymbols,
|
||||
[allIntradaySymbols, intradayTruncated, minuteBatchCap],
|
||||
[allIntradaySymbols, intradayTruncated, minuteBatchCap, fullMinuteHealthy],
|
||||
)
|
||||
const intradayRequestSymbols = useMemo(
|
||||
() => [...new Set(intradaySymbols)].sort(),
|
||||
@@ -471,7 +479,7 @@ export function Screener() {
|
||||
const minuteBatch = useQuery({
|
||||
queryKey: QK.minuteBatch(intradaySymbolsKey),
|
||||
// 增量轮询: 读缓存以最后一根为 since 只拉新增, 本地合并为完整序列
|
||||
queryFn: () => fetchMinuteBatchIncremental(qc, QK.minuteBatch(intradaySymbolsKey), intradayRequestSymbols),
|
||||
queryFn: () => fetchMinuteBatchIncremental(qc, QK.minuteBatch(intradaySymbolsKey), intradayRequestSymbols, fullMinuteHealthy),
|
||||
enabled: intradayVisible && intradayRequestSymbols.length > 0,
|
||||
staleTime: 10_000,
|
||||
placeholderData: previousData => previousData,
|
||||
|
||||
@@ -36,7 +36,7 @@ const TABS: readonly TabDef[] = [
|
||||
{ key: 'monitoring', label: '实时监控', icon: Radio, panel: SettingsMonitoringPanel },
|
||||
{ key: 'ext-pages', label: '扩展页面', icon: BarChart3, panel: SettingsExtPagesPanel },
|
||||
{ key: 'signals', label: '信号库', icon: Zap, panel: SettingsCustomSignalsPanel },
|
||||
{ key: 'timeout', label: '超时设置', icon: Clock3, panel: SettingsTimeoutPanel },
|
||||
{ key: 'timeout', label: '网络设置', icon: Clock3, panel: SettingsTimeoutPanel },
|
||||
{ key: 'menus', label: '菜单设置', icon: SlidersHorizontal, panel: SettingsMenuSettingsPanel },
|
||||
{ key: 'system', label: '系统设置', icon: Settings2, panel: SettingsSystemPanel },
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
/**
|
||||
* 数据任务超时配置卡片 — 从 DataSources 抽出, 放在系统设置页。
|
||||
* 网络设置面板内容 — 任务停滞超时配置 + 分时批量传输压缩开关。
|
||||
* 菜单/Tab 名为「网络设置」(Settings.tsx), 卡片内超时区块标题保持「超时设置」。
|
||||
*/
|
||||
import { useState } from 'react'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
@@ -54,6 +55,47 @@ export function JobTimeoutCard() {
|
||||
const timeoutValuesChanged = regularTimeout !== currentRegularTimeout
|
||||
|| longTimeout !== currentLongTimeout
|
||||
|
||||
const minuteBatchCompress = prefs.data?.minute_batch_compress ?? true
|
||||
const dailyBatchCompress = prefs.data?.daily_batch_compress ?? true
|
||||
// 总开关显示: 任一子开即亮, 全关才灭; 点击 = 全开/全关 (批量写两个子项)
|
||||
const compressAnyOn = minuteBatchCompress || dailyBatchCompress
|
||||
const toggleCompress = useMutation({
|
||||
mutationFn: (enabled: boolean) => api.updateMinuteBatchCompress(enabled),
|
||||
onSuccess: (saved) => {
|
||||
qc.setQueryData<Preferences>(QK.preferences, current => (
|
||||
current ? { ...current, ...saved } : current
|
||||
))
|
||||
toast(saved.minute_batch_compress ? '分时压缩已开启' : '分时压缩已关闭', 'success')
|
||||
},
|
||||
onError: (e: Error) => toast(`保存失败: ${e.message}`, 'error'),
|
||||
})
|
||||
const toggleDailyCompress = useMutation({
|
||||
mutationFn: (enabled: boolean) => api.updateDailyBatchCompress(enabled),
|
||||
onSuccess: (saved) => {
|
||||
qc.setQueryData<Preferences>(QK.preferences, current => (
|
||||
current ? { ...current, ...saved } : current
|
||||
))
|
||||
toast(saved.daily_batch_compress ? '日K压缩已开启' : '日K压缩已关闭', 'success')
|
||||
},
|
||||
onError: (e: Error) => toast(`保存失败: ${e.message}`, 'error'),
|
||||
})
|
||||
const toggleAllCompress = useMutation({
|
||||
mutationFn: async (enabled: boolean) => {
|
||||
const [a, b] = await Promise.all([
|
||||
api.updateMinuteBatchCompress(enabled),
|
||||
api.updateDailyBatchCompress(enabled),
|
||||
])
|
||||
return { ...a, ...b }
|
||||
},
|
||||
onSuccess: (saved) => {
|
||||
qc.setQueryData<Preferences>(QK.preferences, current => (
|
||||
current ? { ...current, ...saved } : current
|
||||
))
|
||||
toast(saved.minute_batch_compress ? '传输压缩已全部开启' : '传输压缩已全部关闭', 'success')
|
||||
},
|
||||
onError: (e: Error) => toast(`保存失败: ${e.message}`, 'error'),
|
||||
})
|
||||
|
||||
const saveJobTimeouts = useMutation({
|
||||
mutationFn: () => api.updateDataSourceJobTimeouts(regularTimeout, longTimeout),
|
||||
onSuccess: (saved) => {
|
||||
@@ -152,6 +194,74 @@ export function JobTimeoutCard() {
|
||||
<span className="block text-[10px] text-muted/60 mt-1.5">默认 30 分钟无进度,最小 1 分钟</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="mt-3 pt-3 border-t border-border space-y-3">
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<span className="block text-xs font-medium text-foreground">数据传输压缩</span>
|
||||
<span className="block text-[10px] text-muted mt-0.5 leading-relaxed">
|
||||
大数据接口(分时、日K)启用 gzip 压缩,响应可缩至约 1/8,公网访问明显更快;本机或内网可关闭以节省服务端 CPU。任一子项开启时总开关为开,点击总开关一键全开/全关,子项可单独微调,立即生效。
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => toggleAllCompress.mutate(!compressAnyOn)}
|
||||
disabled={toggleAllCompress.isPending}
|
||||
className={`shrink-0 relative h-5 w-9 rounded-full transition-colors disabled:opacity-40 ${
|
||||
compressAnyOn ? 'bg-accent' : 'bg-elevated'
|
||||
}`}
|
||||
title={compressAnyOn ? '全部关闭' : '全部开启'}
|
||||
>
|
||||
<span className={`absolute top-0.5 h-4 w-4 rounded-full bg-white shadow transition-all ${
|
||||
compressAnyOn ? 'left-[1.125rem]' : 'left-0.5'
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="ml-1 space-y-2 border-l-2 border-border/60 pl-3">
|
||||
<CompressToggleRow
|
||||
label="分时数据压缩"
|
||||
desc="分时批量接口(自选/策略分时图,千只标的 MB 级响应)"
|
||||
enabled={minuteBatchCompress}
|
||||
pending={toggleCompress.isPending}
|
||||
onToggle={() => toggleCompress.mutate(!minuteBatchCompress)}
|
||||
/>
|
||||
<CompressToggleRow
|
||||
label="日K数据压缩"
|
||||
desc="日K批量接口(自选/策略日K列,千只标的 MB 级响应)"
|
||||
enabled={dailyBatchCompress}
|
||||
pending={toggleDailyCompress.isPending}
|
||||
onToggle={() => toggleDailyCompress.mutate(!dailyBatchCompress)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function CompressToggleRow({ label, desc, enabled, pending, onToggle }: {
|
||||
label: string
|
||||
desc: string
|
||||
enabled: boolean
|
||||
pending: boolean
|
||||
onToggle: () => void
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<div className="min-w-0">
|
||||
<span className="block text-[11px] font-medium text-secondary">{label}</span>
|
||||
<span className="block text-[10px] text-muted/80 mt-0.5 leading-relaxed">{desc}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={onToggle}
|
||||
disabled={pending}
|
||||
className={`shrink-0 relative h-4 w-7 rounded-full transition-colors disabled:opacity-40 ${
|
||||
enabled ? 'bg-accent' : 'bg-elevated'
|
||||
}`}
|
||||
title={enabled ? '点击关闭' : '点击开启'}
|
||||
>
|
||||
<span className={`absolute top-0.5 h-3 w-3 rounded-full bg-white shadow transition-all ${
|
||||
enabled ? 'left-[0.875rem]' : 'left-0.5'
|
||||
}`} />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user