复盘隐藏定时入口并清理相关代码

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-06 12:35:03 +08:00
parent 331ff3067f
commit 4384f5fa00
+4 -197
View File
@@ -9,10 +9,10 @@
import { useCallback, useEffect, useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { motion, AnimatePresence } from 'framer-motion'
import { motion } from 'framer-motion'
import {
BookOpenCheck, RefreshCw, Sparkles, Trash2, History, ChevronRight, AlertTriangle,
Database, Wand2, Copy, Download, Clock, X, Check,
Database, Wand2, Copy, Download,
} from 'lucide-react'
import { api, type OverviewMarket, type AiReviewReport } from '@/lib/api'
@@ -22,7 +22,6 @@ import { fmtBigNum } from '@/lib/format'
import { PageHeader } from '@/components/PageHeader'
import { MarkdownRenderer } from '@/components/financials/MarkdownRenderer'
import { toast } from '@/components/Toast'
import { usePreferences } from '@/lib/useSharedQueries'
import { useReviewState } from '@/lib/useReviewStore'
import {
startReviewGeneration, resetReview, isReviewGenerating,
@@ -99,45 +98,8 @@ export function Review() {
onError: () => { /* request() 已 toast */ },
})
// ===== 定时复盘 =====
const [showSchedule, setShowSchedule] = useState(false)
const prefs = usePreferences()
const reviewSched = prefs.data?.review_schedule ?? { enabled: false, hour: 15, minute: 10 }
const feishuConfigured = !!((prefs.data as any)?.feishu_webhook_url)
// 推送渠道是独立的顶层偏好(多选), 与定时 / 实时行情无关, 常驻可单独设置
// []=不推送, ['feishu']=飞书(微信开发中, 仅占位)
const reviewPushChannels = prefs.data?.review_push_channels ?? []
// 弹窗内的本地草稿: 开关和时间都在本地改, 点「保存」才真正提交(避免开关一拨就关弹窗)
const [draft, setDraft] = useState(reviewSched)
const openSchedule = useCallback(() => {
setDraft(reviewSched) // 每次打开同步最新服务端值
setShowSchedule(true)
}, [reviewSched])
const reviewMut = useMutation({
mutationFn: ({ enabled, hour, minute }: { enabled: boolean; hour: number; minute: number }) =>
api.updateReviewSchedule(enabled, hour, minute),
onSuccess: (_data, vars) => {
qc.invalidateQueries({ queryKey: QK.preferences })
setShowSchedule(false)
toast(vars.enabled ? '已开启定时复盘' : '已关闭定时复盘', 'success')
},
onError: () => { /* request() 已 toast */ },
})
// 推送渠道(多选): 独立常驻, 即时生效(勾选渠道即开关, 改了立刻提交)
const pushMut = useMutation({
mutationFn: (channels: string[]) => api.updateReviewPush(channels),
onSuccess: (_data, vars) => {
qc.invalidateQueries({ queryKey: QK.preferences })
toast(vars.length === 0 ? '已关闭复盘推送' : '已更新复盘推送渠道', 'success')
},
onError: () => { /* request() 已 toast */ },
})
const togglePushChannel = useCallback((ch: string) => {
const next = reviewPushChannels.includes(ch)
? reviewPushChannels.filter(c => c !== ch)
: [...reviewPushChannels, ch]
pushMut.mutate(next)
}, [reviewPushChannels, pushMut])
// 自动滚动到报告底部(streaming 时)
useEffect(() => {
@@ -236,18 +198,6 @@ export function Review() {
>
<RefreshCw className={cn('h-3 w-3', marketQuery.isFetching && 'animate-spin')} />
</button>
<button
onClick={openSchedule}
className={cn(
'inline-flex items-center gap-1 rounded-btn border px-2 py-1 text-[11px] transition-colors',
reviewSched.enabled
? 'border-accent/40 bg-accent/10 text-accent hover:bg-accent/20'
: 'border-border bg-elevated text-secondary hover:text-foreground',
)}
title={reviewSched.enabled ? `定时复盘已开启 · 每日 ${String(reviewSched.hour).padStart(2,'0')}:${String(reviewSched.minute).padStart(2,'0')}` : '定时复盘'}
>
<Clock className="h-3 w-3" />
</button>
<button
onClick={generate}
disabled={isGenerating}
@@ -344,149 +294,6 @@ export function Review() {
</div>
</div>
{/* ===== 定时复盘设置弹窗 ===== */}
<AnimatePresence>
{showSchedule && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
onClick={() => setShowSchedule(false)}
>
<motion.div
initial={{ scale: 0.96, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.96, opacity: 0 }}
className="w-full max-w-md rounded-card border border-border bg-surface p-5 shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-accent" />
<h3 className="text-sm font-medium text-foreground"></h3>
</div>
<button
onClick={() => setShowSchedule(false)}
className="rounded p-1 text-muted transition-colors hover:bg-elevated hover:text-foreground"
>
<X className="h-4 w-4" />
</button>
</div>
<p className="mb-4 text-[11px] leading-relaxed text-muted">
,,
;
</p>
{/* 开关(只改本地草稿, 不提交) */}
<label className="flex items-center justify-between rounded-btn bg-elevated/40 px-3 py-2.5">
<span className="text-xs text-foreground"></span>
<button
type="button"
role="switch"
aria-checked={draft.enabled}
onClick={() => setDraft(d => ({ ...d, enabled: !d.enabled }))}
className={cn(
'relative inline-flex h-5 w-9 shrink-0 items-center rounded-full transition-colors',
draft.enabled ? 'bg-accent' : 'bg-border',
)}
>
<span className={cn('inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform', draft.enabled ? 'translate-x-[18px]' : 'translate-x-1')} />
</button>
</label>
{/* 时间设置(仅开启时可编辑, 本地草稿) */}
{draft.enabled && (
<div className="mt-3 flex items-center gap-2 rounded-btn bg-elevated/40 px-3 py-2.5">
<span className="text-[11px] text-muted"></span>
<input
type="number" min={0} max={23} value={draft.hour}
onChange={e => setDraft(d => ({ ...d, hour: Math.max(0, Math.min(23, Number(e.target.value))) }))}
className="w-12 px-1.5 py-1 rounded-btn bg-base border border-border text-xs font-mono text-foreground text-center focus:outline-none focus:border-accent/50"
/>
<span className="text-xs text-muted">:</span>
<input
type="number" min={0} max={59} value={draft.minute}
onChange={e => setDraft(d => ({ ...d, minute: Math.max(0, Math.min(59, Number(e.target.value))) }))}
className="w-12 px-1.5 py-1 rounded-btn bg-base border border-border text-xs font-mono text-foreground text-center focus:outline-none focus:border-accent/50"
/>
<span className="text-[10px] text-muted/70"> 15:00 · </span>
</div>
)}
{/* 推送渠道(多选, 独立常驻, 与定时无关, 即时生效) */}
<div className="mt-3 rounded-btn bg-elevated/40 px-3 py-2.5">
<div className="flex items-center justify-between">
<span className="text-xs text-foreground"></span>
<span className="text-[10px] text-muted/70">{reviewPushChannels.length === 0 ? '未开启' : `${reviewPushChannels.length} 个渠道`}</span>
</div>
<div className="mt-2 space-y-1.5">
{/* 飞书(可用, 多选) */}
<button
type="button"
disabled={pushMut.isPending}
onClick={() => togglePushChannel('feishu')}
className={cn(
'flex w-full items-center gap-2 rounded-btn border px-2.5 py-1.5 text-left transition-colors disabled:opacity-50',
reviewPushChannels.includes('feishu')
? 'border-accent/40 bg-accent/10'
: 'border-border/60 bg-base/40 hover:bg-base/60',
)}
>
<span className={cn('flex h-3 w-3 shrink-0 items-center justify-center rounded border', reviewPushChannels.includes('feishu') ? 'border-accent bg-accent text-white' : 'border-border')}>
{reviewPushChannels.includes('feishu') && <Check className="h-2.5 w-2.5" />}
</span>
<span className="text-[11px] text-foreground"></span>
<span className="text-[9px] text-muted"></span>
<span className={cn('ml-auto text-[9px]', feishuConfigured ? 'text-emerald-500' : 'text-warning')}>
{feishuConfigured ? '已配置' : '未配置'}
</span>
</button>
{/* 微信(开发中, 占位不可选) */}
<div className="flex items-center gap-2 rounded-btn border border-border/40 bg-base/20 px-2.5 py-1.5 opacity-60">
<span className="flex h-3 w-3 shrink-0 items-center justify-center rounded border border-border" />
<span className="text-[11px] text-secondary"></span>
<span className="text-[9px] text-muted">/</span>
<span className="ml-auto rounded bg-muted/10 px-1 py-px text-[9px] text-muted"></span>
</div>
</div>
<p className="mt-1.5 text-[10px] leading-relaxed text-muted/70">
Webhook
{reviewPushChannels.includes('feishu') && !feishuConfigured && (
<Link to="/settings?tab=monitoring" className="ml-1 text-accent hover:underline" onClick={() => setShowSchedule(false)}>
</Link>
)}
</p>
</div>
{!draft.enabled && (
<p className="mt-3 text-[10px] text-muted/70">
当前: 已关闭
</p>
)}
{/* 操作区: 取消 + 保存(统一提交开关+时间) */}
<div className="mt-5 flex justify-end gap-2">
<button
onClick={() => setShowSchedule(false)}
className="rounded-btn bg-elevated px-4 py-1.5 text-xs text-secondary transition-colors hover:text-foreground"
>
</button>
<button
onClick={() => reviewMut.mutate({ enabled: draft.enabled, hour: draft.hour, minute: draft.minute })}
disabled={reviewMut.isPending}
className="inline-flex items-center gap-1.5 rounded-btn bg-accent px-4 py-1.5 text-xs font-medium text-white transition-colors hover:bg-accent/90 disabled:opacity-50"
>
{reviewMut.isPending ? '保存中…' : '保存'}
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
</>
)
}