|
|
@@ -18,11 +18,76 @@ For commercial licensing, please contact support@quantumnous.com |
|
|
*/ |
|
|
*/ |
|
|
|
|
|
|
|
|
import React from 'react'; |
|
|
import React from 'react'; |
|
|
import { Tabs, TabPane, Tag, Button, Dropdown, Modal } from '@douyinfe/semi-ui'; |
|
|
|
|
|
|
|
|
import { Tag, Button, Dropdown, Modal } from '@douyinfe/semi-ui'; |
|
|
import { IconEdit, IconDelete } from '@douyinfe/semi-icons'; |
|
|
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 { getLobeHubIcon, showError, showSuccess } from '../../../helpers'; |
|
|
import { API } 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 ( |
|
|
|
|
|
<div ref={setNodeRef} style={style} {...attributes} {...listeners}> |
|
|
|
|
|
{getLobeHubIcon(vendor.icon || 'Layers', 14)} |
|
|
|
|
|
{vendor.name} |
|
|
|
|
|
<Tag color={activeVendorKey === String(vendor.id) ? 'red' : 'grey'} shape='circle' size='small'> |
|
|
|
|
|
{count} |
|
|
|
|
|
</Tag> |
|
|
|
|
|
<Dropdown |
|
|
|
|
|
trigger='click' |
|
|
|
|
|
position='bottomRight' |
|
|
|
|
|
render={ |
|
|
|
|
|
<Dropdown.Menu> |
|
|
|
|
|
<Dropdown.Item icon={<IconEdit />} onClick={(e) => handleEditVendor(vendor, e)}> |
|
|
|
|
|
{t('编辑')} |
|
|
|
|
|
</Dropdown.Item> |
|
|
|
|
|
<Dropdown.Item |
|
|
|
|
|
type='danger' |
|
|
|
|
|
icon={<IconDelete />} |
|
|
|
|
|
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('删除')} |
|
|
|
|
|
</Dropdown.Item> |
|
|
|
|
|
</Dropdown.Menu> |
|
|
|
|
|
} |
|
|
|
|
|
onClickOutSide={(e) => e.stopPropagation()} |
|
|
|
|
|
> |
|
|
|
|
|
<Button size='small' type='tertiary' theme='outline' onClick={(e) => e.stopPropagation()} style={{ marginLeft: 4 }}> |
|
|
|
|
|
{t('操作')} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</Dropdown> |
|
|
|
|
|
</div> |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const ModelsTabs = ({ |
|
|
const ModelsTabs = ({ |
|
|
activeVendorKey, |
|
|
activeVendorKey, |
|
|
setActiveVendorKey, |
|
|
setActiveVendorKey, |
|
|
@@ -36,8 +101,13 @@ const ModelsTabs = ({ |
|
|
setShowEditVendor, |
|
|
setShowEditVendor, |
|
|
setEditingVendor, |
|
|
setEditingVendor, |
|
|
loadVendors, |
|
|
loadVendors, |
|
|
|
|
|
reorderVendors, |
|
|
t, |
|
|
t, |
|
|
}) => { |
|
|
}) => { |
|
|
|
|
|
const sensors = useSensors( |
|
|
|
|
|
useSensor(PointerSensor, { activationConstraint: { distance: 8 } }) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
const handleTabChange = (key) => { |
|
|
const handleTabChange = (key) => { |
|
|
setActiveVendorKey(key); |
|
|
setActiveVendorKey(key); |
|
|
setActivePage(1); |
|
|
setActivePage(1); |
|
|
@@ -45,25 +115,24 @@ const ModelsTabs = ({ |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleEditVendor = (vendor, e) => { |
|
|
const handleEditVendor = (vendor, e) => { |
|
|
e.stopPropagation(); // 阻止事件冒泡,避免触发tab切换 |
|
|
|
|
|
|
|
|
e.stopPropagation(); |
|
|
setEditingVendor(vendor); |
|
|
setEditingVendor(vendor); |
|
|
setShowEditVendor(true); |
|
|
setShowEditVendor(true); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const handleDeleteVendor = async (vendor, e) => { |
|
|
const handleDeleteVendor = async (vendor, e) => { |
|
|
e.stopPropagation(); // 阻止事件冒泡,避免触发tab切换 |
|
|
|
|
|
|
|
|
e.stopPropagation(); |
|
|
try { |
|
|
try { |
|
|
const res = await API.delete(`/api/vendors/${vendor.id}`); |
|
|
const res = await API.delete(`/api/vendors/${vendor.id}`); |
|
|
if (res.data.success) { |
|
|
if (res.data.success) { |
|
|
showSuccess(t('供应商删除成功')); |
|
|
showSuccess(t('供应商删除成功')); |
|
|
// 如果删除的是当前选中的供应商,切换到"全部" |
|
|
|
|
|
if (activeVendorKey === String(vendor.id)) { |
|
|
if (activeVendorKey === String(vendor.id)) { |
|
|
setActiveVendorKey('all'); |
|
|
setActiveVendorKey('all'); |
|
|
loadModels(1, pageSize, 'all'); |
|
|
loadModels(1, pageSize, 'all'); |
|
|
} else { |
|
|
} else { |
|
|
loadModels(activePage, pageSize, activeVendorKey); |
|
|
loadModels(activePage, pageSize, activeVendorKey); |
|
|
} |
|
|
} |
|
|
loadVendors(); // 重新加载供应商列表 |
|
|
|
|
|
|
|
|
loadVendors(); |
|
|
} else { |
|
|
} else { |
|
|
showError(res.data.message || t('删除失败')); |
|
|
showError(res.data.message || t('删除失败')); |
|
|
} |
|
|
} |
|
|
@@ -72,106 +141,62 @@ 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 ( |
|
|
return ( |
|
|
<Tabs |
|
|
|
|
|
activeKey={activeVendorKey} |
|
|
|
|
|
type='card' |
|
|
|
|
|
collapsible |
|
|
|
|
|
onChange={handleTabChange} |
|
|
|
|
|
className='mb-2' |
|
|
|
|
|
tabBarExtraContent={ |
|
|
|
|
|
<Button |
|
|
|
|
|
type='primary' |
|
|
|
|
|
size='small' |
|
|
|
|
|
onClick={() => setShowAddVendor(true)} |
|
|
|
|
|
> |
|
|
|
|
|
{t('新增供应商')} |
|
|
|
|
|
</Button> |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<TabPane |
|
|
|
|
|
itemKey='all' |
|
|
|
|
|
tab={ |
|
|
|
|
|
<span className='flex items-center gap-2'> |
|
|
|
|
|
{t('全部')} |
|
|
|
|
|
<Tag |
|
|
|
|
|
color={activeVendorKey === 'all' ? 'red' : 'grey'} |
|
|
|
|
|
shape='circle' |
|
|
|
|
|
> |
|
|
|
|
|
{vendorCounts['all'] || 0} |
|
|
|
|
|
</Tag> |
|
|
|
|
|
</span> |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
<div className='flex items-center gap-2 flex-wrap mb-2'> |
|
|
|
|
|
{/* "All" tab - not draggable */} |
|
|
|
|
|
<div |
|
|
|
|
|
onClick={() => 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('全部')} |
|
|
|
|
|
<Tag color={activeVendorKey === 'all' ? 'red' : 'grey'} shape='circle' size='small'> |
|
|
|
|
|
{vendorCounts['all'] || 0} |
|
|
|
|
|
</Tag> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
{/* Draggable vendor tabs */} |
|
|
|
|
|
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}> |
|
|
|
|
|
<SortableContext items={vendors.map((v) => String(v.id))} strategy={horizontalListSortingStrategy}> |
|
|
|
|
|
{vendors.map((vendor) => ( |
|
|
|
|
|
<SortableVendorItem |
|
|
|
|
|
key={String(vendor.id)} |
|
|
|
|
|
vendor={vendor} |
|
|
|
|
|
activeVendorKey={activeVendorKey} |
|
|
|
|
|
vendorCounts={vendorCounts} |
|
|
|
|
|
handleEditVendor={handleEditVendor} |
|
|
|
|
|
handleDeleteVendor={handleDeleteVendor} |
|
|
|
|
|
t={t} |
|
|
|
|
|
/> |
|
|
|
|
|
))} |
|
|
|
|
|
</SortableContext> |
|
|
|
|
|
</DndContext> |
|
|
|
|
|
|
|
|
{vendors.map((vendor) => { |
|
|
|
|
|
const key = String(vendor.id); |
|
|
|
|
|
const count = vendorCounts[vendor.id] || 0; |
|
|
|
|
|
return ( |
|
|
|
|
|
<TabPane |
|
|
|
|
|
key={key} |
|
|
|
|
|
itemKey={key} |
|
|
|
|
|
tab={ |
|
|
|
|
|
<span className='flex items-center gap-2'> |
|
|
|
|
|
{getLobeHubIcon(vendor.icon || 'Layers', 14)} |
|
|
|
|
|
{vendor.name} |
|
|
|
|
|
<Tag |
|
|
|
|
|
color={activeVendorKey === key ? 'red' : 'grey'} |
|
|
|
|
|
shape='circle' |
|
|
|
|
|
> |
|
|
|
|
|
{count} |
|
|
|
|
|
</Tag> |
|
|
|
|
|
<Dropdown |
|
|
|
|
|
trigger='click' |
|
|
|
|
|
position='bottomRight' |
|
|
|
|
|
render={ |
|
|
|
|
|
<Dropdown.Menu> |
|
|
|
|
|
<Dropdown.Item |
|
|
|
|
|
icon={<IconEdit />} |
|
|
|
|
|
onClick={(e) => handleEditVendor(vendor, e)} |
|
|
|
|
|
> |
|
|
|
|
|
{t('编辑')} |
|
|
|
|
|
</Dropdown.Item> |
|
|
|
|
|
<Dropdown.Item |
|
|
|
|
|
type='danger' |
|
|
|
|
|
icon={<IconDelete />} |
|
|
|
|
|
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('删除')} |
|
|
|
|
|
</Dropdown.Item> |
|
|
|
|
|
</Dropdown.Menu> |
|
|
|
|
|
} |
|
|
|
|
|
onClickOutSide={(e) => e.stopPropagation()} |
|
|
|
|
|
> |
|
|
|
|
|
<Button |
|
|
|
|
|
size='small' |
|
|
|
|
|
type='tertiary' |
|
|
|
|
|
theme='outline' |
|
|
|
|
|
onClick={(e) => e.stopPropagation()} |
|
|
|
|
|
> |
|
|
|
|
|
{t('操作')} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</Dropdown> |
|
|
|
|
|
</span> |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
); |
|
|
|
|
|
})} |
|
|
|
|
|
</Tabs> |
|
|
|
|
|
|
|
|
<Button type='primary' size='small' onClick={() => setShowAddVendor(true)}> |
|
|
|
|
|
{t('新增供应商')} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</div> |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|