@@ -31,6 +31,7 @@ const HomePage: React.FC = () => {
3131 const [ marketReviewReport , setMarketReviewReport ] = useState < string | null > ( null ) ;
3232 const [ marketReviewReportCopied , setMarketReviewReportCopied ] = useState ( false ) ;
3333 const marketReviewPollTimer = useRef < number | null > ( null ) ;
34+ const dashboardScrollRef = useRef < HTMLElement | null > ( null ) ;
3435
3536 const stopMarketReviewPolling = useCallback ( ( ) => {
3637 if ( marketReviewPollTimer . current !== null ) {
@@ -39,6 +40,20 @@ const HomePage: React.FC = () => {
3940 }
4041 } , [ ] ) ;
4142
43+ const scrollMarketReviewFeedbackIntoView = useCallback ( ( ) => {
44+ const scrollContainer = dashboardScrollRef . current ;
45+ if ( ! scrollContainer ) {
46+ return ;
47+ }
48+
49+ if ( typeof scrollContainer . scrollTo === 'function' ) {
50+ scrollContainer . scrollTo ( { top : 0 , behavior : 'smooth' } ) ;
51+ return ;
52+ }
53+
54+ scrollContainer . scrollTop = 0 ;
55+ } , [ ] ) ;
56+
4257 useEffect ( ( ) => stopMarketReviewPolling , [ stopMarketReviewPolling ] ) ;
4358 const [ setupStatus , setSetupStatus ] = useState < SetupStatusResponse | null > ( null ) ;
4459
@@ -187,6 +202,7 @@ const HomePage: React.FC = () => {
187202 title : '大盘复盘已超时' ,
188203 message : '任务长时间未返回最终结果,请在任务列表/历史中查看。' ,
189204 } ) ;
205+ scrollMarketReviewFeedbackIntoView ( ) ;
190206 return false ;
191207 }
192208
@@ -219,6 +235,7 @@ const HomePage: React.FC = () => {
219235 message : marketReviewText ? '大盘复盘任务已完成,结果如下:' : '大盘复盘任务已完成,结果已生成并按配置推送。' ,
220236 } ) ;
221237 setMarketReviewError ( null ) ;
238+ scrollMarketReviewFeedbackIntoView ( ) ;
222239 return false ;
223240 }
224241
@@ -237,6 +254,7 @@ const HomePage: React.FC = () => {
237254 } ) ,
238255 ) ;
239256 setMarketReviewNotice ( null ) ;
257+ scrollMarketReviewFeedbackIntoView ( ) ;
240258 return false ;
241259 }
242260
@@ -247,6 +265,7 @@ const HomePage: React.FC = () => {
247265 title : '大盘复盘状态异常' ,
248266 message : `收到未知任务状态:${ status . status } ` ,
249267 } ) ;
268+ scrollMarketReviewFeedbackIntoView ( ) ;
250269 return false ;
251270 } catch ( err : unknown ) {
252271 const parsed = getParsedApiError ( err ) ;
@@ -255,6 +274,7 @@ const HomePage: React.FC = () => {
255274 setMarketReviewReport ( null ) ;
256275 setMarketReviewError ( parsed ) ;
257276 setMarketReviewNotice ( null ) ;
277+ scrollMarketReviewFeedbackIntoView ( ) ;
258278 return false ;
259279 }
260280 return true ;
@@ -273,32 +293,35 @@ const HomePage: React.FC = () => {
273293 } , intervalMs ) ;
274294 }
275295 } ,
276- [ stopMarketReviewPolling ] ,
296+ [ scrollMarketReviewFeedbackIntoView , stopMarketReviewPolling ] ,
277297 ) ;
278298
279299 const handleTriggerMarketReview = useCallback ( async ( ) => {
280300 setIsSubmittingMarketReview ( true ) ;
281301 setMarketReviewNotice ( null ) ;
282302 setMarketReviewError ( null ) ;
283303 setMarketReviewReport ( null ) ;
304+ scrollMarketReviewFeedbackIntoView ( ) ;
284305 try {
285306 const result = await analysisApi . triggerMarketReview ( { sendNotification : notify } ) ;
286307 setMarketReviewNotice ( {
287308 variant : 'success' ,
288309 title : '大盘复盘已提交' ,
289310 message : result . message ,
290311 } ) ;
312+ scrollMarketReviewFeedbackIntoView ( ) ;
291313
292314 if ( result . taskId ) {
293315 await pollMarketReviewStatus ( result . taskId ) ;
294316 }
295317 } catch ( err : unknown ) {
296318 setMarketReviewError ( getParsedApiError ( err ) ) ;
297319 setMarketReviewNotice ( null ) ;
320+ scrollMarketReviewFeedbackIntoView ( ) ;
298321 } finally {
299322 setIsSubmittingMarketReview ( false ) ;
300323 }
301- } , [ notify , pollMarketReviewStatus ] ) ;
324+ } , [ notify , pollMarketReviewStatus , scrollMarketReviewFeedbackIntoView ] ) ;
302325
303326 const handleCopyMarketReviewReport = useCallback ( ( ) => {
304327 if ( ! marketReviewReport ) {
@@ -479,48 +502,6 @@ const HomePage: React.FC = () => {
479502 </ div >
480503 ) : null }
481504
482- { marketReviewNotice ? (
483- < div className = "px-3 pb-2 md:px-4" >
484- < InlineAlert
485- variant = { marketReviewNotice . variant }
486- title = { marketReviewNotice . title }
487- message = { marketReviewNotice . message }
488- className = "rounded-xl px-3 py-2 text-xs shadow-none"
489- />
490- </ div >
491- ) : null }
492-
493- { marketReviewError ? (
494- < div className = "px-3 pb-2 md:px-4" >
495- < ApiErrorAlert
496- error = { marketReviewError }
497- className = "mb-1"
498- onDismiss = { ( ) => setMarketReviewError ( null ) }
499- />
500- </ div >
501- ) : null }
502-
503- { marketReviewReport ? (
504- < div className = "px-3 pb-2 md:px-4" >
505- < div className = "rounded-xl border border-subtle bg-surface/70 px-3 py-3 text-xs text-secondary-text shadow-sm" >
506- < div className = "mb-2 flex items-center justify-between gap-2" >
507- < p className = "font-semibold text-foreground" > 大盘复盘报告</ p >
508- < button
509- type = "button"
510- className = "home-surface-button h-7 rounded-md px-3 py-1 text-xs text-foreground"
511- disabled = { marketReviewReportCopied }
512- onClick = { ( ) => void handleCopyMarketReviewReport ( ) }
513- >
514- { marketReviewReportCopied ? '已复制' : '复制' }
515- </ button >
516- </ div >
517- < pre className = "max-h-64 overflow-x-auto overflow-y-auto whitespace-pre-wrap break-words rounded-lg bg-background px-3 py-2 leading-relaxed" >
518- { marketReviewReport }
519- </ pre >
520- </ div >
521- </ div >
522- ) : null }
523-
524505 < div className = "flex-1 flex min-h-0 overflow-hidden" >
525506 < div className = "hidden min-h-0 w-64 shrink-0 flex-col overflow-hidden pl-4 pb-4 md:flex lg:w-72" >
526507 { sidebarContent }
@@ -538,7 +519,54 @@ const HomePage: React.FC = () => {
538519 </ div >
539520 ) : null }
540521
541- < section className = "flex-1 min-w-0 min-h-0 overflow-x-auto overflow-y-auto px-3 pb-4 md:px-6 touch-pan-y" >
522+ < section
523+ ref = { dashboardScrollRef }
524+ data-testid = "home-dashboard-scroll"
525+ className = "flex-1 min-w-0 min-h-0 overflow-x-auto overflow-y-auto px-3 pb-4 md:px-6 touch-pan-y"
526+ >
527+ { marketReviewNotice ? (
528+ < div className = "mb-3" >
529+ < InlineAlert
530+ variant = { marketReviewNotice . variant }
531+ title = { marketReviewNotice . title }
532+ message = { marketReviewNotice . message }
533+ className = "rounded-xl px-3 py-2 text-xs shadow-none"
534+ />
535+ </ div >
536+ ) : null }
537+
538+ { marketReviewError ? (
539+ < div className = "mb-3" >
540+ < ApiErrorAlert
541+ error = { marketReviewError }
542+ className = "mb-1"
543+ onDismiss = { ( ) => setMarketReviewError ( null ) }
544+ />
545+ </ div >
546+ ) : null }
547+
548+ { marketReviewReport ? (
549+ < div className = "mb-3 rounded-xl border border-subtle bg-surface/70 px-3 py-3 text-xs text-secondary-text shadow-sm" >
550+ < div className = "mb-2 flex items-center justify-between gap-2" >
551+ < p className = "font-semibold text-foreground" > 大盘复盘报告</ p >
552+ < button
553+ type = "button"
554+ className = "home-surface-button h-7 rounded-md px-3 py-1 text-xs text-foreground"
555+ disabled = { marketReviewReportCopied }
556+ onClick = { ( ) => void handleCopyMarketReviewReport ( ) }
557+ >
558+ { marketReviewReportCopied ? '已复制' : '复制' }
559+ </ button >
560+ </ div >
561+ < pre
562+ data-testid = "market-review-report"
563+ className = "overflow-x-auto whitespace-pre-wrap break-words rounded-lg bg-background px-3 py-2 leading-relaxed"
564+ >
565+ { marketReviewReport }
566+ </ pre >
567+ </ div >
568+ ) : null }
569+
542570 { error ? (
543571 < ApiErrorAlert
544572 error = { error }
0 commit comments