fix: 修复拖拽排序持久化 + 恢复 package.json catalog 引用
- 引入 @dnd-kit/helpers,使用 move() 替代手工 splice 排序逻辑 - 恢复 apps/server/package.json 中所有依赖的 catalog: 引用 - 简化 ORPC client,移除 experimental_defaults,改用 MutationCache - route loaders 改用 fetchQuery 确保数据刷新
This commit is contained in:
@@ -25,6 +25,7 @@
|
||||
"dependencies": {
|
||||
"@base-ui/react": "catalog:",
|
||||
"@dnd-kit/dom": "catalog:",
|
||||
"@dnd-kit/helpers": "catalog:",
|
||||
"@dnd-kit/react": "catalog:",
|
||||
"@fontsource-variable/geist": "catalog:",
|
||||
"@orpc/client": "catalog:",
|
||||
|
||||
@@ -24,69 +24,4 @@ const getORPCClient = createIsomorphicFn()
|
||||
|
||||
const client: RouterClient = getORPCClient()
|
||||
|
||||
export const orpc = createTanstackQueryUtils(client, {
|
||||
experimental_defaults: {
|
||||
bookmarks: {
|
||||
category: {
|
||||
create: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
reorder: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
bookmark: {
|
||||
create: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
update: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
remove: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
reorder: {
|
||||
mutationOptions: {
|
||||
onSuccess: (_, __, ___, ctx) => {
|
||||
ctx.client.invalidateQueries({ queryKey: orpc.bookmarks.category.list.key() })
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export const orpc = createTanstackQueryUtils(client)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { move } from '@dnd-kit/helpers'
|
||||
import { DragDropProvider } from '@dnd-kit/react'
|
||||
import { useSortable } from '@dnd-kit/react/sortable'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import * as icons from 'lucide-react'
|
||||
import { ExternalLink, GripVertical, Pencil, Plus, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -114,45 +115,33 @@ const SortableBookmarkItem = ({
|
||||
|
||||
export const BookmarkManager = ({ category }: BookmarkManagerProps) => {
|
||||
const [items, setItems] = useState(category.bookmarks)
|
||||
const queryClient = useQueryClient()
|
||||
const reorderBookmarks = useMutation(orpc.bookmarks.bookmark.reorder.mutationOptions())
|
||||
|
||||
useEffect(() => {
|
||||
if (!reorderBookmarks.isPending) {
|
||||
setItems(category.bookmarks)
|
||||
}
|
||||
}, [category.bookmarks, reorderBookmarks.isPending])
|
||||
setItems(category.bookmarks)
|
||||
}, [category.bookmarks])
|
||||
|
||||
const handleDragEnd: NonNullable<React.ComponentProps<typeof DragDropProvider>['onDragEnd']> = (event) => {
|
||||
if (event.canceled) {
|
||||
return
|
||||
}
|
||||
if (event.canceled) return
|
||||
|
||||
const sourceId = event.operation.source?.id
|
||||
const targetId = event.operation.target?.id
|
||||
if (!sourceId || !targetId || sourceId === targetId) {
|
||||
return
|
||||
}
|
||||
|
||||
const sourceIndex = items.findIndex((item) => item.id === sourceId)
|
||||
const targetIndex = items.findIndex((item) => item.id === targetId)
|
||||
if (sourceIndex === -1 || targetIndex === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
const reordered = [...items]
|
||||
const [moved] = reordered.splice(sourceIndex, 1)
|
||||
if (!moved) {
|
||||
return
|
||||
}
|
||||
|
||||
reordered.splice(targetIndex, 0, moved)
|
||||
const reordered = move(items, event)
|
||||
const previousItems = items
|
||||
setItems(reordered)
|
||||
|
||||
reorderBookmarks.mutate(
|
||||
reordered.map((item, index) => ({ id: item.id, orderId: index })),
|
||||
{
|
||||
onSuccess: () => toast.success('书签顺序已更新'),
|
||||
onError: () => toast.error('操作失败'),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: orpc.bookmarks.category.list.queryOptions().queryKey,
|
||||
})
|
||||
toast.success('书签顺序已更新')
|
||||
},
|
||||
onError: () => {
|
||||
setItems(previousItems)
|
||||
toast.error('操作失败')
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { move } from '@dnd-kit/helpers'
|
||||
import { DragDropProvider } from '@dnd-kit/react'
|
||||
import { useSortable } from '@dnd-kit/react/sortable'
|
||||
import { useMutation } from '@tanstack/react-query'
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { GripVertical, MoreHorizontal, Pencil, Plus, Trash2 } from 'lucide-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
@@ -111,45 +112,33 @@ const SortableCategoryItem = ({
|
||||
|
||||
export const CategoryManager = ({ categories, selectedCategoryId, onSelectCategory }: CategoryManagerProps) => {
|
||||
const [items, setItems] = useState(categories)
|
||||
const queryClient = useQueryClient()
|
||||
const reorderCategories = useMutation(orpc.bookmarks.category.reorder.mutationOptions())
|
||||
|
||||
useEffect(() => {
|
||||
if (!reorderCategories.isPending) {
|
||||
setItems(categories)
|
||||
}
|
||||
}, [categories, reorderCategories.isPending])
|
||||
setItems(categories)
|
||||
}, [categories])
|
||||
|
||||
const handleDragEnd: NonNullable<React.ComponentProps<typeof DragDropProvider>['onDragEnd']> = (event) => {
|
||||
if (event.canceled) {
|
||||
return
|
||||
}
|
||||
if (event.canceled) return
|
||||
|
||||
const sourceId = event.operation.source?.id
|
||||
const targetId = event.operation.target?.id
|
||||
if (!sourceId || !targetId || sourceId === targetId) {
|
||||
return
|
||||
}
|
||||
|
||||
const sourceIndex = items.findIndex((item) => item.id === sourceId)
|
||||
const targetIndex = items.findIndex((item) => item.id === targetId)
|
||||
if (sourceIndex === -1 || targetIndex === -1) {
|
||||
return
|
||||
}
|
||||
|
||||
const reordered = [...items]
|
||||
const [moved] = reordered.splice(sourceIndex, 1)
|
||||
if (!moved) {
|
||||
return
|
||||
}
|
||||
|
||||
reordered.splice(targetIndex, 0, moved)
|
||||
const reordered = move(items, event)
|
||||
const previousItems = items
|
||||
setItems(reordered)
|
||||
|
||||
reorderCategories.mutate(
|
||||
reordered.map((item, index) => ({ id: item.id, orderId: index })),
|
||||
{
|
||||
onSuccess: () => toast.success('分类顺序已更新'),
|
||||
onError: () => toast.error('操作失败'),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({
|
||||
queryKey: orpc.bookmarks.category.list.queryOptions().queryKey,
|
||||
})
|
||||
toast.success('分类顺序已更新')
|
||||
},
|
||||
onError: () => {
|
||||
setItems(previousItems)
|
||||
toast.error('操作失败')
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
import { MutationCache, QueryClient } from '@tanstack/react-query'
|
||||
import { createRouter } from '@tanstack/react-router'
|
||||
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
||||
import type { RouterContext } from './routes/__root'
|
||||
@@ -6,6 +6,20 @@ import { routeTree } from './routeTree.gen'
|
||||
|
||||
export const getRouter = () => {
|
||||
const queryClient = new QueryClient({
|
||||
mutationCache: new MutationCache({
|
||||
onSuccess: (_data, _variables, _context, mutation) => {
|
||||
const key = mutation.options.mutationKey
|
||||
if (Array.isArray(key) && Array.isArray(key[0]) && key[0].length > 0) {
|
||||
const module = key[0][0]
|
||||
queryClient.invalidateQueries({
|
||||
predicate: (query) => {
|
||||
const qk = query.queryKey
|
||||
return Array.isArray(qk) && Array.isArray(qk[0]) && qk[0][0] === module
|
||||
},
|
||||
})
|
||||
}
|
||||
},
|
||||
}),
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 30 * 1000,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CategoryManager } from '@/modules/bookmarks/components/CategoryManager'
|
||||
|
||||
export const Route = createFileRoute('/_protected/admin/bookmarks' as never)({
|
||||
loader: async ({ context }: { context: { queryClient: QueryClient } }) => {
|
||||
await context.queryClient.ensureQueryData(orpc.bookmarks.category.list.queryOptions())
|
||||
await context.queryClient.fetchQuery(orpc.bookmarks.category.list.queryOptions())
|
||||
},
|
||||
component: BookmarksAdmin,
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ import { SearchBar } from '@/modules/bookmarks/components/SearchBar'
|
||||
|
||||
export const Route = createFileRoute('/_protected/' as never)({
|
||||
loader: async ({ context }: { context: { queryClient: QueryClient } }) => {
|
||||
await context.queryClient.ensureQueryData(orpc.bookmarks.category.list.queryOptions())
|
||||
await context.queryClient.fetchQuery(orpc.bookmarks.category.list.queryOptions())
|
||||
},
|
||||
component: DashboardPage,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user