修复权重设置闪烁
This commit is contained in:
+52
-32
@@ -43,7 +43,7 @@ import {
|
|||||||
Video,
|
Video,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { GripVertical } from 'lucide-react';
|
import { GripVertical } from 'lucide-react';
|
||||||
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
import { memo, Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
|
|
||||||
import { AdminConfig, AdminConfigResult } from '@/lib/admin.types';
|
import { AdminConfig, AdminConfigResult } from '@/lib/admin.types';
|
||||||
@@ -4502,6 +4502,13 @@ const VideoSourceConfig = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateWeight = (key: string, weight: number) => {
|
const handleUpdateWeight = (key: string, weight: number) => {
|
||||||
|
// 先乐观更新本地状态
|
||||||
|
setSources((prev) =>
|
||||||
|
prev.map((s) =>
|
||||||
|
s.key === key ? { ...s, weight } : s
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// 调用API更新
|
// 调用API更新
|
||||||
withLoading(`updateWeight_${key}`, async () => {
|
withLoading(`updateWeight_${key}`, async () => {
|
||||||
try {
|
try {
|
||||||
@@ -4766,8 +4773,48 @@ const VideoSourceConfig = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 权重输入组件 - 使用本地状态避免输入时失焦
|
||||||
|
const WeightInput = memo(({ sourceKey, initialWeight }: { sourceKey: string; initialWeight: number }) => {
|
||||||
|
const [localWeight, setLocalWeight] = useState(initialWeight);
|
||||||
|
|
||||||
|
// 当外部权重变化时同步
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalWeight(initialWeight);
|
||||||
|
}, [initialWeight]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type='number'
|
||||||
|
inputMode='numeric'
|
||||||
|
min='0'
|
||||||
|
max='100'
|
||||||
|
value={localWeight}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = parseInt(e.target.value) || 0;
|
||||||
|
const clampedValue = Math.min(100, Math.max(0, value));
|
||||||
|
setLocalWeight(clampedValue);
|
||||||
|
}}
|
||||||
|
onBlur={(e) => {
|
||||||
|
const newValue = parseInt(e.target.value) || 0;
|
||||||
|
const clampedValue = Math.min(100, Math.max(0, newValue));
|
||||||
|
const originalWeight = config?.SourceConfig?.find(s => s.key === sourceKey)?.weight ?? 0;
|
||||||
|
|
||||||
|
// 只有在值发生变化时才调用API
|
||||||
|
if (clampedValue !== originalWeight) {
|
||||||
|
handleUpdateWeight(sourceKey, clampedValue);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onPointerDown={(e) => e.stopPropagation()}
|
||||||
|
onTouchStart={(e) => e.stopPropagation()}
|
||||||
|
onMouseDown={(e) => e.stopPropagation()}
|
||||||
|
className='w-16 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent'
|
||||||
|
title='权重范围:0-100,用于排序和优选评分'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// 可拖拽行封装 (dnd-kit)
|
// 可拖拽行封装 (dnd-kit)
|
||||||
const DraggableRow = ({ source }: { source: DataSource }) => {
|
const DraggableRow = memo(({ source }: { source: DataSource }) => {
|
||||||
const { attributes, listeners, setNodeRef, transform, transition } =
|
const { attributes, listeners, setNodeRef, transform, transition } =
|
||||||
useSortable({ id: source.key });
|
useSortable({ id: source.key });
|
||||||
|
|
||||||
@@ -4852,35 +4899,8 @@ const VideoSourceConfig = ({
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
<td className='px-6 py-4 whitespace-nowrap'>
|
<td className='px-6 py-4 whitespace-nowrap' style={{ touchAction: 'auto' }}>
|
||||||
<input
|
<WeightInput sourceKey={source.key} initialWeight={source.weight ?? 0} />
|
||||||
type='number'
|
|
||||||
min='0'
|
|
||||||
max='100'
|
|
||||||
value={source.weight ?? 0}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = parseInt(e.target.value) || 0;
|
|
||||||
const clampedValue = Math.min(100, Math.max(0, value));
|
|
||||||
// 只更新本地状态,不调用API
|
|
||||||
setSources((prev) =>
|
|
||||||
prev.map((s) =>
|
|
||||||
s.key === source.key ? { ...s, weight: clampedValue } : s
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onBlur={(e) => {
|
|
||||||
const newValue = parseInt(e.target.value) || 0;
|
|
||||||
const clampedValue = Math.min(100, Math.max(0, newValue));
|
|
||||||
const originalWeight = config?.SourceConfig?.find(s => s.key === source.key)?.weight ?? 0;
|
|
||||||
|
|
||||||
// 只有在值发生变化时才调用API
|
|
||||||
if (clampedValue !== originalWeight) {
|
|
||||||
handleUpdateWeight(source.key, clampedValue);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className='w-16 px-2 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-blue-500 focus:border-transparent'
|
|
||||||
title='权重范围:0-100,用于排序和优选评分'
|
|
||||||
/>
|
|
||||||
</td>
|
</td>
|
||||||
<td className='px-6 py-4 whitespace-nowrap max-w-[1rem]'>
|
<td className='px-6 py-4 whitespace-nowrap max-w-[1rem]'>
|
||||||
{(() => {
|
{(() => {
|
||||||
@@ -4934,7 +4954,7 @@ const VideoSourceConfig = ({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
// 全选/取消全选
|
// 全选/取消全选
|
||||||
const handleSelectAll = useCallback(
|
const handleSelectAll = useCallback(
|
||||||
|
|||||||
Reference in New Issue
Block a user