From ee41ac4922e1333757e0add84d3dab562a7b8dcd Mon Sep 17 00:00:00 2001 From: lzt404 <2596933790@qq.com> Date: Fri, 14 Aug 2026 18:03:01 +0800 Subject: [PATCH] feat(api-access): improve provider drag-and-drop sorting animations --- src/i18n/ja.ts | 1 - src/i18n/locales/en.ts | 1 - src/i18n/locales/zh-CN.ts | 1 - src/pages/ApiAccessPage.tsx | 52 +++++++++++++++++++++++++++++++------ src/styles.css | 16 +++++++++--- 5 files changed, 56 insertions(+), 15 deletions(-) diff --git a/src/i18n/ja.ts b/src/i18n/ja.ts index 3d3b1ff..8d5c821 100644 --- a/src/i18n/ja.ts +++ b/src/i18n/ja.ts @@ -652,7 +652,6 @@ export const jaOverrides = { 'apiAccess.notice.deleted': '接続を削除しました', 'apiAccess.notice.enabled': '接続を有効にしました', 'apiAccess.notice.disabled': '接続を無効にしました', - 'apiAccess.notice.reordered': 'プロバイダーの順序を更新しました', 'apiAccess.title': 'API 接続', 'apiAccess.count': '接続 {count} 件', 'apiAccess.add': '追加', diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index a25180f..e441ef7 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -652,7 +652,6 @@ export const en: Record = { 'apiAccess.notice.deleted': 'Connection deleted', 'apiAccess.notice.enabled': 'Connection enabled', 'apiAccess.notice.disabled': 'Connection disabled', - 'apiAccess.notice.reordered': 'Provider order updated', 'apiAccess.title': 'API Access', 'apiAccess.count': '{count} connections', 'apiAccess.add': 'Add', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 0b98b36..08171bf 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -650,7 +650,6 @@ export const zhCN = { 'apiAccess.notice.deleted': '接入已删除', 'apiAccess.notice.enabled': '接入已启用', 'apiAccess.notice.disabled': '接入已停用', - 'apiAccess.notice.reordered': '供应商排序已更新', 'apiAccess.title': 'API 接入', 'apiAccess.count': '{count} 个接入', 'apiAccess.add': '新增', diff --git a/src/pages/ApiAccessPage.tsx b/src/pages/ApiAccessPage.tsx index 35b2b47..277df70 100644 --- a/src/pages/ApiAccessPage.tsx +++ b/src/pages/ApiAccessPage.tsx @@ -105,8 +105,17 @@ type ProviderRow = { remark: string; }; -const providerDragId = (row: Pick) => - `${row.section}:${row.index}`; +const providerDragId = ( + row: Pick, +) => { + const identity = `${row.section}\u0000${row.name}\u0000${row.apiKey}\u0000${row.baseUrl}`; + let hash = 2166136261; + for (let index = 0; index < identity.length; index += 1) { + hash ^= identity.charCodeAt(index); + hash = Math.imul(hash, 16777619); + } + return `${row.section}:${(hash >>> 0).toString(36)}`; +}; function SortableProviderRow({ row, @@ -128,10 +137,26 @@ function SortableProviderRow({ transform, transition, isDragging, - } = useSortable({ id: providerDragId(row), disabled }); + } = useSortable({ + id: providerDragId(row), + disabled, + transition: { + duration: 220, + easing: 'cubic-bezier(0.2, 0, 0, 1)', + }, + }); + const renderedTransform = transform + ? { + ...transform, + scaleX: isDragging ? 1.012 : transform.scaleX, + scaleY: isDragging ? 1.012 : transform.scaleY, + } + : transform; const style: CSSProperties = { - transform: DndCss.Transform.toString(transform), - transition, + position: 'relative', + zIndex: isDragging ? 2 : undefined, + transform: DndCss.Transform.toString(renderedTransform), + transition: isDragging ? undefined : transition, }; return ( @@ -1100,9 +1125,9 @@ export function ApiAccessPage() { const nextRows = reorderProviderRecords(latestRows, rows, source, target); if (!nextRows) throw new Error(t('apiAccess.error.stale')); await managementApi.put(`/${source.section}`, nextRows); - setNotice(t('apiAccess.notice.reordered')); await loadProviders(); } catch (requestError) { + await loadProviders(); setError(requestErrorMessage(requestError)); } finally { setDragOverId(null); @@ -1114,7 +1139,18 @@ export function ApiAccessPage() { setDragOverId(null); const source = rows.find((row) => providerDragId(row) === String(event.active.id)); const target = rows.find((row) => providerDragId(row) === String(event.over?.id ?? '')); - if (source && target) void reorderProviders(source, target); + if (!source || !target || source.index === target.index) return; + + const optimisticRows = reorderProviderRecords( + records[source.section], + rows, + source, + target, + ); + if (optimisticRows) { + setRecords((current) => ({ ...current, [source.section]: optimisticRows })); + } + void reorderProviders(source, target); }; const totalCount = Object.values(records).reduce((sum, items) => sum + items.length, 0); @@ -1200,7 +1236,7 @@ export function ApiAccessPage() {
{rows.map((row) => (