Skip to content

Commit d76cdc9

Browse files
committed
refactor: Improve drag-and-drop functionality in ChatHistoryTree for better folder and chat ordering
1 parent cac428c commit d76cdc9

2 files changed

Lines changed: 176 additions & 103 deletions

File tree

src/renderer/src/components/layout/sidebar_items/chat/ChatHistoryTree.tsx

Lines changed: 136 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ export default function ChatHistoryTree({ onChatClick }: ChatHistoryTreeProps) {
140140
if (!dragNodeInfo || !dropNodeInfo) return
141141

142142
const { dropPosition, dropToGap } = info
143-
143+
144144
// 如果拖拽到文件夹内部(非gap位置)
145145
if (!dropToGap && dropNodeInfo.type === 'folder') {
146146
// 检查是否会形成循环引用(文件夹不能拖入自己的子文件夹中)
@@ -158,24 +158,39 @@ export default function ChatHistoryTree({ onChatClick }: ChatHistoryTreeProps) {
158158
console.warn('Cannot move folder: would create a cycle')
159159
return
160160
}
161+
}
161162

162-
// 获取目标文件夹下的最大order值(包括文件夹和聊天)
163-
const folderChildren = folders.filter((f) => f.parentId === dropNodeInfo.id)
164-
const folderChats = pages.filter((chat) => chat.folderId === dropNodeInfo.id)
165-
const allOrders = [
166-
...folderChildren.map((f) => f.order || 0),
167-
...folderChats.map((c) => c.order || 0)
168-
]
169-
const maxOrder = allOrders.length > 0 ? Math.max(...allOrders) : 0
163+
// 获取目标文件夹
164+
const targetFolder = folders.find(f => f.id === dropNodeInfo.id)
165+
166+
// 如果文件夹是展开的,且有子节点,根据dropPosition确定插入位置
167+
// dropPosition为0表示放在文件夹内部作为第一个子节点
168+
// 否则作为最后一个子节点
169+
const folderChildren = folders.filter((f) => f.parentId === dropNodeInfo.id)
170+
.map(f => ({ type: 'folder' as const, id: f.id, order: f.order || 0 }))
171+
const folderChats = pages.filter((chat) => chat.folderId === dropNodeInfo.id && chat.type !== 'settings')
172+
.map(c => ({ type: 'chat' as const, id: c.id, order: c.order || 0 }))
173+
const allChildren = [...folderChildren, ...folderChats].sort((a, b) => a.order - b.order)
174+
175+
let newOrder: number
176+
177+
// 如果文件夹是展开的,且dropPosition为0,放在最前面
178+
// 注意:Ant Design Tree的dropPosition在拖入文件夹时通常为0
179+
if (targetFolder?.expanded && allChildren.length > 0) {
180+
// 总是放在最前面,因为拖入文件夹通常意味着作为第一个子节点
181+
newOrder = allChildren[0].order - 1000
182+
} else if (allChildren.length > 0) {
183+
// 如果文件夹未展开或dropPosition不为0,放在最后
184+
newOrder = allChildren[allChildren.length - 1].order + 1000
185+
} else {
186+
// 文件夹为空,使用默认值
187+
newOrder = 1000
188+
}
170189

171-
moveFolder(dragNodeInfo.id, maxOrder + 1000, dropNodeInfo.id)
190+
if (dragNodeInfo.type === 'folder') {
191+
moveFolder(dragNodeInfo.id, newOrder, dropNodeInfo.id)
172192
} else if (dragNodeInfo.type === 'chat') {
173-
// 将聊天移动到文件夹内,获取该文件夹下聊天的最大order值
174-
const folderChats = pages.filter((chat) => chat.folderId === dropNodeInfo.id)
175-
const maxOrder =
176-
folderChats.length > 0 ? Math.max(...folderChats.map((chat) => chat.order || 0)) : 0
177-
178-
movePage(dragNodeInfo.id, dropNodeInfo.id, maxOrder + 1000)
193+
movePage(dragNodeInfo.id, dropNodeInfo.id, newOrder)
179194
}
180195
return
181196
}
@@ -221,55 +236,68 @@ export default function ChatHistoryTree({ onChatClick }: ChatHistoryTreeProps) {
221236
}
222237
}
223238

239+
// 获取目标位置的所有同级节点(包括文件夹和聊天)
240+
const allSiblings = [
241+
...folders
242+
.filter((folder) => folder.parentId === targetParentId)
243+
.map((folder) => ({ type: 'folder' as const, id: folder.id, order: folder.order || 0 })),
244+
...pages
245+
.filter((chat) => chat.folderId === targetParentId && chat.type !== 'settings')
246+
.map((chat) => ({ type: 'chat' as const, id: chat.id, order: chat.order || 0 }))
247+
]
248+
.filter((item) => !(item.type === dragNodeInfo.type && item.id === dragNodeInfo.id)) // 排除拖拽节点自身
249+
.sort((a, b) => a.order - b.order)
250+
251+
// 找到目标节点在同级节点中的位置
252+
const dropIndex = allSiblings.findIndex(
253+
(item) => item.type === dropNodeInfo.type && item.id === dropNodeInfo.id
254+
)
255+
224256
// 计算新的order值
225257
let newOrder: number = Date.now()
226258

227-
// 根据节点类型计算order
228-
if (dragNodeInfo.type === 'chat') {
229-
const siblings = pages
230-
.filter((chat) => chat.folderId === targetParentId && chat.id !== dragNodeInfo.id)
231-
.sort((a, b) => (a.order || 0) - (b.order || 0))
232-
233-
const dropIndex = siblings.findIndex((item) => item.id === dropNodeInfo.id)
234-
if (dropIndex >= 0) {
235-
if (dropPosition <= dropIndex) {
236-
// 拖拽到前面
237-
newOrder =
238-
dropIndex === 0
239-
? (siblings[0]?.order || 0) - 1000
240-
: ((siblings[dropIndex - 1]?.order || 0) + (siblings[dropIndex]?.order || 0)) / 2
259+
if (dropIndex >= 0) {
260+
// Ant Design Tree的dropPosition含义:
261+
// - 相对于目标节点的位置索引
262+
// - dropPosition === -1: 拖到目标节点上方
263+
// - dropPosition === 1: 拖到目标节点下方
264+
// - dropPosition > 1: 拖到更后面的位置
265+
266+
// 判断是插入到目标节点的前面还是后面
267+
const insertBefore = dropPosition === -1 || (dropPosition > 0 && dropPosition <= dropIndex)
268+
269+
if (insertBefore) {
270+
// 插入到目标节点前面
271+
if (dropIndex === 0) {
272+
// 插入到第一个位置
273+
newOrder = allSiblings[0].order - 1000
241274
} else {
242-
// 拖拽到后面
243-
newOrder =
244-
dropIndex === siblings.length - 1
245-
? (siblings[dropIndex]?.order || 0) + 1000
246-
: ((siblings[dropIndex]?.order || 0) + (siblings[dropIndex + 1]?.order || 0)) / 2
275+
// 插入到中间,取前一个节点和目标节点order的平均值
276+
const prevOrder = allSiblings[dropIndex - 1].order
277+
const currentOrder = allSiblings[dropIndex].order
278+
newOrder = (prevOrder + currentOrder) / 2
247279
}
248-
}
249-
250-
movePage(dragNodeInfo.id, targetParentId, newOrder)
251-
} else if (dragNodeInfo.type === 'folder') {
252-
const siblings = folders
253-
.filter((folder) => folder.parentId === targetParentId && folder.id !== dragNodeInfo.id)
254-
.sort((a, b) => (a.order || 0) - (b.order || 0))
255-
256-
const dropIndex = siblings.findIndex((item) => item.id === dropNodeInfo.id)
257-
if (dropIndex >= 0) {
258-
if (dropPosition <= dropIndex) {
259-
// 拖拽到前面
260-
newOrder =
261-
dropIndex === 0
262-
? (siblings[0]?.order || 0) - 1000
263-
: ((siblings[dropIndex - 1]?.order || 0) + (siblings[dropIndex]?.order || 0)) / 2
280+
} else {
281+
// 插入到目标节点后面
282+
if (dropIndex === allSiblings.length - 1) {
283+
// 插入到最后一个位置
284+
newOrder = allSiblings[dropIndex].order + 1000
264285
} else {
265-
// 拖拽到后面
266-
newOrder =
267-
dropIndex === siblings.length - 1
268-
? (siblings[dropIndex]?.order || 0) + 1000
269-
: ((siblings[dropIndex]?.order || 0) + (siblings[dropIndex + 1]?.order || 0)) / 2
286+
// 插入到中间,取目标节点和下一个节点order的平均值
287+
const currentOrder = allSiblings[dropIndex].order
288+
const nextOrder = allSiblings[dropIndex + 1].order
289+
newOrder = (currentOrder + nextOrder) / 2
270290
}
271291
}
292+
} else {
293+
// 如果找不到目标节点,添加到末尾
294+
newOrder = allSiblings.length > 0 ? allSiblings[allSiblings.length - 1].order + 1000 : 1000
295+
}
272296

297+
// 根据拖拽节点类型移动节点
298+
if (dragNodeInfo.type === 'chat') {
299+
movePage(dragNodeInfo.id, targetParentId, newOrder)
300+
} else if (dragNodeInfo.type === 'folder') {
273301
moveFolder(dragNodeInfo.id, newOrder, targetParentId)
274302
}
275303
}
@@ -280,55 +308,65 @@ export default function ChatHistoryTree({ onChatClick }: ChatHistoryTreeProps) {
280308
// 递归构建文件夹树
281309
const buildFolderTree = useCallback(
282310
(parentId?: string): DataNode[] => {
283-
const result: DataNode[] = []
284-
285311
// 获取指定父级下的文件夹
286312
const childFolders = folders
287313
.filter((folder) => folder.parentId === parentId)
288-
.sort((a, b) => (a.order || 0) - (b.order || 0))
314+
.map(folder => ({
315+
type: 'folder' as const,
316+
data: folder,
317+
order: folder.order || 0
318+
}))
289319

290320
// 获取指定父级下的聊天(过滤掉设置页面)
291321
const chats = pages
292322
.filter((chat) => chat.folderId === parentId && chat.type !== 'settings')
293-
.sort((a, b) => (a.order || 0) - (b.order || 0))
294-
295-
// 添加文件夹
296-
childFolders.forEach((folder) => {
297-
const folderChildren = buildFolderTree(folder.id)
298-
299-
result.push({
300-
key: `folder-${folder.id}`,
301-
title: (
302-
<ChatHistoryTreeNode
303-
type="folder"
304-
data={folder}
305-
onEdit={() => handleNodeEdit(folder.id, 'folder')}
306-
onDelete={() => handleDeleteFolder(folder.id)}
307-
onCreate={(type) => handleNodeCreate(folder.id, type)}
308-
onSaveEdit={handleSaveEdit}
309-
/>
310-
),
311-
checkable: false,
312-
children: folderChildren
313-
})
314-
})
315-
316-
// 添加聊天
317-
chats.forEach((chat) => {
318-
result.push({
319-
key: `chat-${chat.id}`,
320-
title: (
321-
<ChatHistoryTreeNode
322-
type="chat"
323-
data={chat}
324-
onEdit={() => handleNodeEdit(chat.id, 'chat')}
325-
onDelete={() => handleDeleteChat(chat.id)}
326-
onChatClick={onChatClick}
327-
onSaveEdit={handleSaveEdit}
328-
/>
329-
),
330-
isLeaf: true
331-
})
323+
.map(chat => ({
324+
type: 'chat' as const,
325+
data: chat,
326+
order: chat.order || 0
327+
}))
328+
329+
// 合并文件夹和聊天,按order排序
330+
const allItems = [...childFolders, ...chats].sort((a, b) => a.order - b.order)
331+
332+
// 构建树节点
333+
const result: DataNode[] = allItems.map(item => {
334+
if (item.type === 'folder') {
335+
const folder = item.data as typeof folders[0]
336+
const folderChildren = buildFolderTree(folder.id)
337+
338+
return {
339+
key: `folder-${folder.id}`,
340+
title: (
341+
<ChatHistoryTreeNode
342+
type="folder"
343+
data={folder}
344+
onEdit={() => handleNodeEdit(folder.id, 'folder')}
345+
onDelete={() => handleDeleteFolder(folder.id)}
346+
onCreate={(type) => handleNodeCreate(folder.id, type)}
347+
onSaveEdit={handleSaveEdit}
348+
/>
349+
),
350+
checkable: false,
351+
children: folderChildren
352+
}
353+
} else {
354+
const chat = item.data as typeof pages[0]
355+
return {
356+
key: `chat-${chat.id}`,
357+
title: (
358+
<ChatHistoryTreeNode
359+
type="chat"
360+
data={chat}
361+
onEdit={() => handleNodeEdit(chat.id, 'chat')}
362+
onDelete={() => handleDeleteChat(chat.id)}
363+
onChatClick={onChatClick}
364+
onSaveEdit={handleSaveEdit}
365+
/>
366+
),
367+
isLeaf: true
368+
}
369+
}
332370
})
333371

334372
return result

src/renderer/src/stores/pagesStore.ts

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,17 @@ export const usePagesStore = create<PagesState & PagesActions>()(
247247
// 文件夹管理
248248
createFolder: (name, parentId) => {
249249
try {
250-
const newFolder = createNewFolder(name, parentId)
250+
// 获取同级文件夹的最大order值
251+
const siblingFolders = get().folders.filter(f => f.parentId === parentId)
252+
const maxOrder = siblingFolders.length > 0
253+
? Math.max(...siblingFolders.map(f => f.order || 0))
254+
: 0
255+
256+
const newFolder = {
257+
...createNewFolder(name, parentId),
258+
order: maxOrder + 1000 // 新文件夹添加到最后
259+
}
260+
251261
set((state) => {
252262
state.folders.push(newFolder)
253263
})
@@ -349,12 +359,21 @@ export const usePagesStore = create<PagesState & PagesActions>()(
349359
// 页面创建和打开
350360
createAndOpenChat: (title, folderId, lineage) => {
351361
try {
362+
const timestamp = Date.now()
363+
364+
// 获取同文件夹下的最大order值
365+
const siblingPages = get().pages.filter(p => p.folderId === folderId)
366+
const maxOrder = siblingPages.length > 0
367+
? Math.max(...siblingPages.map(p => p.order || 0))
368+
: 0
369+
352370
const newPage: Page = {
353371
id: uuidv4(),
354372
title,
355373
type: 'regular',
356-
createdAt: Date.now(),
357-
updatedAt: Date.now(),
374+
createdAt: timestamp,
375+
updatedAt: timestamp,
376+
order: maxOrder + 1000, // 新节点添加到最后
358377
folderId,
359378
messages: [],
360379
messageMap: {},
@@ -383,10 +402,17 @@ export const usePagesStore = create<PagesState & PagesActions>()(
383402

384403
createAndOpenCrosstabChat: (title, folderId, lineage) => {
385404
try {
405+
// 获取同文件夹下的最大order值
406+
const siblingPages = get().pages.filter(p => p.folderId === folderId)
407+
const maxOrder = siblingPages.length > 0
408+
? Math.max(...siblingPages.map(p => p.order || 0))
409+
: 0
410+
386411
const newPage: Page = {
387412
title,
388413
folderId,
389414
...createNewCrosstabChat(title, folderId, lineage),
415+
order: maxOrder + 1000, // 新节点添加到最后
390416
...(lineage && { lineage })
391417
}
392418

@@ -409,12 +435,21 @@ export const usePagesStore = create<PagesState & PagesActions>()(
409435

410436
createAndOpenObjectChat: (title, folderId, lineage) => {
411437
try {
438+
const timestamp = Date.now()
439+
440+
// 获取同文件夹下的最大order值
441+
const siblingPages = get().pages.filter(p => p.folderId === folderId)
442+
const maxOrder = siblingPages.length > 0
443+
? Math.max(...siblingPages.map(p => p.order || 0))
444+
: 0
445+
412446
const newPage: Page = {
413447
id: uuidv4(),
414448
title,
415449
type: 'object',
416-
createdAt: Date.now(),
417-
updatedAt: Date.now(),
450+
createdAt: timestamp,
451+
updatedAt: timestamp,
452+
order: maxOrder + 1000, // 新节点添加到最后
418453
folderId,
419454
objectData: {
420455
rootNodeId: '',

0 commit comments

Comments
 (0)