diff --git a/web/package.json b/web/package.json
index 706c5a6..145448d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -4,10 +4,6 @@
"private": true,
"type": "module",
"dependencies": {
- "@dnd-kit/core": "^6.3.1",
- "@dnd-kit/modifiers": "^9.0.0",
- "@dnd-kit/sortable": "^10.0.0",
- "@dnd-kit/utilities": "^3.2.2",
"@douyinfe/semi-icons": "^2.63.1",
"@douyinfe/semi-ui": "^2.69.1",
"@lobehub/icons": "^5.0.1",
diff --git a/web/src/components/table/model-pricing/filter/PricingVendors.jsx b/web/src/components/table/model-pricing/filter/PricingVendors.jsx
index 6d9906c..192d166 100644
--- a/web/src/components/table/model-pricing/filter/PricingVendors.jsx
+++ b/web/src/components/table/model-pricing/filter/PricingVendors.jsx
@@ -56,7 +56,7 @@ const PricingVendors = ({
});
return {
- vendors: Array.from(vendors),
+ vendors: Array.from(vendors).sort(),
vendorIcons,
hasUnknownVendor,
};
diff --git a/web/src/components/table/models/ModelsColumnDefs.jsx b/web/src/components/table/models/ModelsColumnDefs.jsx
index fc5afd4..4fa136c 100644
--- a/web/src/components/table/models/ModelsColumnDefs.jsx
+++ b/web/src/components/table/models/ModelsColumnDefs.jsx
@@ -26,7 +26,6 @@ import {
Modal,
Tooltip,
} from '@douyinfe/semi-ui';
-import { IconMenu } from '@douyinfe/semi-icons';
import {
timestamp2string,
getLobeHubIcon,
@@ -284,14 +283,9 @@ export const getModelsColumns = ({
}) => {
return [
{
- key: 'drag',
- width: 40,
- fixed: 'left',
- render: () => (
-
-
-
- ),
+ title: t('排序'),
+ dataIndex: 'sort_order',
+ width: 60,
},
{
title: t('图标'),
diff --git a/web/src/components/table/models/ModelsTable.jsx b/web/src/components/table/models/ModelsTable.jsx
index 9515ac0..643bff6 100644
--- a/web/src/components/table/models/ModelsTable.jsx
+++ b/web/src/components/table/models/ModelsTable.jsx
@@ -19,9 +19,6 @@ For commercial licensing, please contact support@quantumnous.com
import React, { useMemo } from 'react';
import { Empty } from '@douyinfe/semi-ui';
-import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
-import { SortableContext, verticalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
-import { restrictToVerticalAxis } from '@dnd-kit/modifiers';
import CardTable from '../../common/ui/CardTable';
import {
IllustrationNoResult,
@@ -29,21 +26,6 @@ import {
} from '@douyinfe/semi-illustrations';
import { getModelsColumns } from './ModelsColumnDefs';
-const SortableRow = (props) => {
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
- id: props['data-row-key'],
- });
- const style = {
- transform: transform ? `translate3d(0, ${transform.y}px, 0)` : undefined,
- transition,
- opacity: isDragging ? 0.5 : 1,
- ...(props.style || {}),
- };
- return (
-
- );
-};
-
const ModelsTable = (modelsData) => {
const {
models,
@@ -61,14 +43,10 @@ const ModelsTable = (modelsData) => {
setShowEdit,
refresh,
vendorMap,
- reorderModels,
t,
} = modelsData;
- const sensors = useSensors(
- useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
- );
-
+ // Get all columns
const columns = useMemo(() => {
return getModelsColumns({
t,
@@ -80,6 +58,7 @@ const ModelsTable = (modelsData) => {
});
}, [t, manageModel, setEditingModel, setShowEdit, refresh, vendorMap]);
+ // Handle compact mode by removing fixed positioning
const tableColumns = useMemo(() => {
return compactMode
? columns.map((col) => {
@@ -92,62 +71,37 @@ const ModelsTable = (modelsData) => {
: columns;
}, [compactMode, columns]);
- const handleDragEnd = (event) => {
- const { active, over } = event;
- if (!over || active.id === over.id) return;
- const oldIndex = models.findIndex((m) => m.id === active.id);
- const newIndex = models.findIndex((m) => m.id === over.id);
- if (oldIndex === -1 || newIndex === -1) return;
- const reordered = [...models];
- const [moved] = reordered.splice(oldIndex, 1);
- reordered.splice(newIndex, 0, moved);
- reorderModels(reordered);
- };
-
return (
-
- m.id)}
- strategy={verticalListSortingStrategy}
- >
- }
- darkModeImage={}
- description={t('搜索无结果')}
- style={{ padding: 30 }}
- />
+ }
+ darkModeImage={
+
}
- className='rounded-xl overflow-hidden'
- size='middle'
+ description={t('搜索无结果')}
+ style={{ padding: 30 }}
/>
-
-
+ }
+ className='rounded-xl overflow-hidden'
+ size='middle'
+ />
);
};
diff --git a/web/src/components/table/models/ModelsTabs.jsx b/web/src/components/table/models/ModelsTabs.jsx
index c0f5e14..820d707 100644
--- a/web/src/components/table/models/ModelsTabs.jsx
+++ b/web/src/components/table/models/ModelsTabs.jsx
@@ -18,76 +18,11 @@ For commercial licensing, please contact support@quantumnous.com
*/
import React from 'react';
-import { Tag, Button, Dropdown, Modal } from '@douyinfe/semi-ui';
+import { Tabs, TabPane, Tag, Button, Dropdown, Modal } from '@douyinfe/semi-ui';
import { IconEdit, IconDelete } from '@douyinfe/semi-icons';
-import { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
-import { SortableContext, horizontalListSortingStrategy, useSortable } from '@dnd-kit/sortable';
-import { CSS } from '@dnd-kit/utilities';
import { getLobeHubIcon, showError, showSuccess } from '../../../helpers';
import { API } from '../../../helpers';
-const SortableVendorItem = ({ vendor, activeVendorKey, vendorCounts, handleEditVendor, handleDeleteVendor, t }) => {
- const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id: String(vendor.id) });
- const style = {
- transform: CSS.Transform.toString(transform),
- transition,
- opacity: isDragging ? 0.5 : 1,
- display: 'inline-flex',
- alignItems: 'center',
- gap: 6,
- padding: '4px 12px',
- borderRadius: 6,
- cursor: 'grab',
- background: activeVendorKey === String(vendor.id) ? 'var(--semi-color-primary-light-default)' : 'var(--semi-color-bg-2)',
- border: activeVendorKey === String(vendor.id) ? '1px solid var(--semi-color-primary)' : '1px solid var(--semi-color-border)',
- userSelect: 'none',
- };
- const count = vendorCounts[vendor.id] || 0;
- return (
-
- {getLobeHubIcon(vendor.icon || 'Layers', 14)}
- {vendor.name}
-
- {count}
-
-
- } onClick={(e) => handleEditVendor(vendor, e)}>
- {t('编辑')}
-
- }
- onClick={(e) => {
- e.stopPropagation();
- Modal.confirm({
- title: t('确认删除'),
- content: t('确定要删除供应商 "{{name}}" 吗?此操作不可撤销。', { name: vendor.name }),
- onOk: () => handleDeleteVendor(vendor, e),
- okText: t('删除'),
- cancelText: t('取消'),
- type: 'warning',
- okType: 'danger',
- });
- }}
- >
- {t('删除')}
-
-
- }
- onClickOutSide={(e) => e.stopPropagation()}
- >
-
-
-
- );
-};
-
const ModelsTabs = ({
activeVendorKey,
setActiveVendorKey,
@@ -101,13 +36,8 @@ const ModelsTabs = ({
setShowEditVendor,
setEditingVendor,
loadVendors,
- reorderVendors,
t,
}) => {
- const sensors = useSensors(
- useSensor(PointerSensor, { activationConstraint: { distance: 8 } })
- );
-
const handleTabChange = (key) => {
setActiveVendorKey(key);
setActivePage(1);
@@ -115,24 +45,25 @@ const ModelsTabs = ({
};
const handleEditVendor = (vendor, e) => {
- e.stopPropagation();
+ e.stopPropagation(); // 阻止事件冒泡,避免触发tab切换
setEditingVendor(vendor);
setShowEditVendor(true);
};
const handleDeleteVendor = async (vendor, e) => {
- e.stopPropagation();
+ e.stopPropagation(); // 阻止事件冒泡,避免触发tab切换
try {
const res = await API.delete(`/api/vendors/${vendor.id}`);
if (res.data.success) {
showSuccess(t('供应商删除成功'));
+ // 如果删除的是当前选中的供应商,切换到"全部"
if (activeVendorKey === String(vendor.id)) {
setActiveVendorKey('all');
loadModels(1, pageSize, 'all');
} else {
loadModels(activePage, pageSize, activeVendorKey);
}
- loadVendors();
+ loadVendors(); // 重新加载供应商列表
} else {
showError(res.data.message || t('删除失败'));
}
@@ -141,62 +72,106 @@ const ModelsTabs = ({
}
};
- const handleDragEnd = (event) => {
- const { active, over } = event;
- if (!over || active.id === over.id) return;
- const oldIndex = vendors.findIndex((v) => String(v.id) === active.id);
- const newIndex = vendors.findIndex((v) => String(v.id) === over.id);
- if (oldIndex === -1 || newIndex === -1) return;
- const reordered = [...vendors];
- const [moved] = reordered.splice(oldIndex, 1);
- reordered.splice(newIndex, 0, moved);
- reorderVendors(reordered);
- };
-
return (
-
- {/* "All" tab - not draggable */}
-
handleTabChange('all')}
- style={{
- display: 'inline-flex',
- alignItems: 'center',
- gap: 6,
- padding: '4px 12px',
- borderRadius: 6,
- cursor: 'pointer',
- background: activeVendorKey === 'all' ? 'var(--semi-color-primary-light-default)' : 'var(--semi-color-bg-2)',
- border: activeVendorKey === 'all' ? '1px solid var(--semi-color-primary)' : '1px solid var(--semi-color-border)',
- userSelect: 'none',
- }}
- >
- {t('全部')}
-
- {vendorCounts['all'] || 0}
-
-
-
- {/* Draggable vendor tabs */}
-
- String(v.id))} strategy={horizontalListSortingStrategy}>
- {vendors.map((vendor) => (
-
- ))}
-
-
+
setShowAddVendor(true)}
+ >
+ {t('新增供应商')}
+
+ }
+ >
+
+ {t('全部')}
+
+ {vendorCounts['all'] || 0}
+
+
+ }
+ />
-
-
+ {vendors.map((vendor) => {
+ const key = String(vendor.id);
+ const count = vendorCounts[vendor.id] || 0;
+ return (
+
+ {getLobeHubIcon(vendor.icon || 'Layers', 14)}
+ {vendor.name}
+
+ {count}
+
+
+ }
+ onClick={(e) => handleEditVendor(vendor, e)}
+ >
+ {t('编辑')}
+
+ }
+ onClick={(e) => {
+ e.stopPropagation();
+ Modal.confirm({
+ title: t('确认删除'),
+ content: t(
+ '确定要删除供应商 "{{name}}" 吗?此操作不可撤销。',
+ { name: vendor.name },
+ ),
+ onOk: () => handleDeleteVendor(vendor, e),
+ okText: t('删除'),
+ cancelText: t('取消'),
+ type: 'warning',
+ okType: 'danger',
+ });
+ }}
+ >
+ {t('删除')}
+
+
+ }
+ onClickOutSide={(e) => e.stopPropagation()}
+ >
+
+
+
+ }
+ />
+ );
+ })}
+
);
};
diff --git a/web/src/components/table/models/modals/EditModelModal.jsx b/web/src/components/table/models/modals/EditModelModal.jsx
index 683d13d..e2c7f51 100644
--- a/web/src/components/table/models/modals/EditModelModal.jsx
+++ b/web/src/components/table/models/modals/EditModelModal.jsx
@@ -126,6 +126,7 @@ const EditModelModal = (props) => {
name_rule: props.editingModel?.model_name ? 0 : undefined, // 通过未配置模型过来的固定为精确匹配
status: true,
sync_official: true,
+ sort_order: 0,
});
const handleCancel = () => {
@@ -332,6 +333,15 @@ const EditModelModal = (props) => {
/>
+
+
+
+
{
description: '',
icon: '',
status: true,
+ sort_order: 0,
});
const handleCancel = () => {
@@ -140,6 +141,14 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
showClear
/>
+
+
+
{
}
};
- // Reorder vendors via drag-and-drop
- const reorderVendors = async (reorderedVendors) => {
- try {
- const items = reorderedVendors.map((v, index) => ({
- id: v.id,
- sort_order: index,
- }));
- const res = await API.put('/api/vendors/reorder', { items });
- if (res.data.success) {
- setVendors(reorderedVendors);
- showSuccess(t('供应商排序已更新'));
- } else {
- showError(res.data.message || t('排序更新失败'));
- await loadVendors();
- }
- } catch (error) {
- showError(t('排序更新失败'));
- await loadVendors();
- }
- };
-
- // Reorder models via drag-and-drop
- const reorderModels = async (reorderedModels) => {
- try {
- const items = reorderedModels.map((m, index) => ({
- id: m.id,
- sort_order: index,
- }));
- const res = await API.put('/api/models/reorder', { items });
- if (res.data.success) {
- showSuccess(t('模型排序已更新'));
- await refresh();
- } else {
- showError(res.data.message || t('排序更新失败'));
- await refresh();
- }
- } catch (error) {
- showError(t('排序更新失败'));
- await refresh();
- }
- };
-
// Load models data
const loadModels = async (
page = 1,
@@ -525,8 +483,6 @@ export const useModelsData = () => {
editingVendor,
setEditingVendor,
loadVendors,
- reorderVendors,
- reorderModels,
// Translation
t,
diff --git a/web/src/pages/Home/HomePricingFilters.jsx b/web/src/pages/Home/HomePricingFilters.jsx
index ed91d2f..4f4f76d 100644
--- a/web/src/pages/Home/HomePricingFilters.jsx
+++ b/web/src/pages/Home/HomePricingFilters.jsx
@@ -90,7 +90,7 @@ const HomePricingFilters = ({ t }) => {
hasUnknown = true;
}
});
- const vendors = Array.from(vendorSet);
+ const vendors = Array.from(vendorSet).sort();
const getVendorCount = () => {
// 计算供应商数量(包含未知供应商)
const count = vendorSet.size + (hasUnknown ? 1 : 0);