@@ -8,7 +8,13 @@ import {
88 type WeatherForecast ,
99 type WeatherRealtime ,
1010} from "../api" ;
11- import { Check , PencilLine } from "lucide-react" ;
11+ import {
12+ ArrowDown ,
13+ ArrowUp ,
14+ Check ,
15+ GripVertical ,
16+ PencilLine ,
17+ } from "lucide-react" ;
1218import {
1319 getHomeCards ,
1420 moveHomeCard ,
@@ -133,6 +139,9 @@ export function HomePage({
133139 const isInteractiveDragTarget = ( target : HTMLElement ) =>
134140 Boolean ( target . closest ( "a, button, input, textarea, select, label" ) ) ;
135141
142+ const isDragHandleTarget = ( target : HTMLElement ) =>
143+ Boolean ( target . closest ( "[data-home-drag-handle]" ) ) ;
144+
136145 const handlePointerDown = (
137146 event : PointerEvent < HTMLDivElement > ,
138147 cardId : HomeCardId ,
@@ -142,10 +151,11 @@ export function HomePage({
142151 const target = event . target as HTMLElement ;
143152 if (
144153 event . pointerType === "mouse" ||
145- isInteractiveDragTarget ( target )
154+ ! isDragHandleTarget ( target )
146155 ) {
147156 return ;
148157 }
158+ event . preventDefault ( ) ;
149159 event . currentTarget . setPointerCapture ( event . pointerId ) ;
150160 setPointerDrag ( {
151161 cardId,
@@ -205,7 +215,12 @@ export function HomePage({
205215 ) => {
206216 if ( ! isEditing ) return ;
207217 const target = event . target as HTMLElement ;
208- if ( event . button !== 0 || isInteractiveDragTarget ( target ) ) return ;
218+ if (
219+ event . button !== 0 ||
220+ ( isInteractiveDragTarget ( target ) && ! isDragHandleTarget ( target ) )
221+ ) {
222+ return ;
223+ }
209224 const startX = event . clientX ;
210225 const startY = event . clientY ;
211226 let isActive = false ;
@@ -254,6 +269,20 @@ export function HomePage({
254269 window . addEventListener ( "mouseup" , handleMouseUp ) ;
255270 } ;
256271
272+ const moveCardByStep = (
273+ cardId : HomeCardId ,
274+ column : HomeCardColumn ,
275+ step : - 1 | 1 ,
276+ ) => {
277+ const index = homeCardLayout [ column ] . indexOf ( cardId ) ;
278+ const nextIndex = index + step ;
279+ if ( index < 0 || nextIndex < 0 || nextIndex >= homeCardLayout [ column ] . length ) {
280+ return ;
281+ }
282+ const insertIndex = step > 0 ? index + 2 : nextIndex ;
283+ setHomeCardLayout ( moveHomeCard ( homeCardLayout , cardId , column , insertIndex ) ) ;
284+ } ;
285+
257286 const renderHomeCard = ( cardId : HomeCardId ) => {
258287 if ( cardId === "daily" ) return < DailyCard state = { daily } /> ;
259288 if ( cardId === "hot" ) {
@@ -316,6 +345,8 @@ export function HomePage({
316345 const isDragging = draggedCard ?. cardId === card . id ;
317346 const isDropTarget =
318347 dropTarget ?. column === column && dropTarget . index === index ;
348+ const canMoveUp = index > 0 ;
349+ const canMoveDown = index >= 0 && index < homeCardLayout [ column ] . length - 1 ;
319350 return (
320351 < div
321352 className = { `home-card-slot ${ isDragging ? "is-dragging" : "" } ${
@@ -335,6 +366,37 @@ export function HomePage({
335366 handleMouseDown ( event , card . id , column )
336367 }
337368 >
369+ { isEditing && (
370+ < div className = "home-card-edit-controls" >
371+ < button
372+ type = "button"
373+ className = "home-card-drag-handle"
374+ aria-label = { `拖动${ card . label } 排序` }
375+ title = "拖动排序"
376+ data-home-drag-handle
377+ >
378+ < GripVertical size = { 16 } />
379+ </ button >
380+ < button
381+ type = "button"
382+ aria-label = { `${ card . label } 上移` }
383+ title = "上移"
384+ disabled = { ! canMoveUp }
385+ onClick = { ( ) => moveCardByStep ( card . id , column , - 1 ) }
386+ >
387+ < ArrowUp size = { 15 } />
388+ </ button >
389+ < button
390+ type = "button"
391+ aria-label = { `${ card . label } 下移` }
392+ title = "下移"
393+ disabled = { ! canMoveDown }
394+ onClick = { ( ) => moveCardByStep ( card . id , column , 1 ) }
395+ >
396+ < ArrowDown size = { 15 } />
397+ </ button >
398+ </ div >
399+ ) }
338400 { renderHomeCard ( card . id ) }
339401 </ div >
340402 ) ;
0 commit comments