feat: 实现实时权重编辑和精确保存功能

- 选中分类后右侧权重立即变为可编辑输入框
- 保存时只保存选中分类的权重,删除未选中分类的记录
- 简化UI,移除重复的权重配置区域
- 优化用户体验,权重编辑更直观便捷
This commit is contained in:
imeepos 2025-07-25 17:35:17 +08:00
parent 076878287d
commit 7fdd95bbb4
1 changed files with 62 additions and 95 deletions

View File

@ -31,9 +31,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
const [error, setError] = useState<string | null>(null);
// 权重配置相关状态
const [currentWeights, setCurrentWeights] = useState<Record<string, number>>({});
const [editingWeights, setEditingWeights] = useState<Record<string, number>>({});
const [weightLoading, setWeightLoading] = useState(false);
const { updateSegmentMatchingRule } = useTemplateStore();
@ -59,17 +57,13 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
if (!templateId) return;
try {
setWeightLoading(true);
const weights = await TemplateSegmentWeightService.getSegmentWeightsWithDefaults(
templateId,
segmentId
);
setCurrentWeights(weights);
setEditingWeights({ ...weights });
} catch (error) {
console.error('Failed to load weight data:', error);
} finally {
setWeightLoading(false);
}
};
@ -107,12 +101,23 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
// 如果是按顺序匹配规则且有模板ID同时保存权重配置
if (SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && templateId) {
try {
// 获取当前选中的分类ID
const selectedCategoryIds = typeof editingRule === 'object' && 'PriorityOrder' in editingRule
? editingRule.PriorityOrder.category_ids
: [];
// 只保存选中分类的权重,过滤掉未选中的分类
const selectedWeights = Object.fromEntries(
Object.entries(editingWeights).filter(([classificationId]) =>
selectedCategoryIds.includes(classificationId)
)
);
await TemplateSegmentWeightService.setSegmentWeights(
templateId,
segmentId,
editingWeights
selectedWeights
);
setCurrentWeights({ ...editingWeights });
} catch (weightError) {
console.error('保存权重配置失败:', weightError);
// 权重保存失败不阻止规则保存
@ -168,9 +173,23 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
if (isSelected) {
// 添加分类ID
newCategoryIds = [...currentCategoryIds, categoryId];
// 为新选中的分类设置默认权重(如果还没有的话)
if (!editingWeights[categoryId]) {
const classification = aiClassifications.find(c => c.id === categoryId);
setEditingWeights(prev => ({
...prev,
[categoryId]: classification?.weight || 50
}));
}
} else {
// 移除分类ID
newCategoryIds = currentCategoryIds.filter(id => id !== categoryId);
// 从权重配置中移除未选中的分类
setEditingWeights(prev => {
const newWeights = { ...prev };
delete newWeights[categoryId];
return newWeights;
});
}
setEditingRule(SegmentMatchingRuleHelper.createPriorityOrder(newCategoryIds));
@ -189,12 +208,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
return weight >= 0 && weight <= 100;
};
const getWeightDisplayInfo = (weight: number) => {
if (weight >= 80) return { text: '高', colorClass: 'text-green-600' };
if (weight >= 60) return { text: '中', colorClass: 'text-yellow-600' };
if (weight >= 40) return { text: '低', colorClass: 'text-orange-600' };
return { text: '极低', colorClass: 'text-red-600' };
};
const getCurrentRuleType = (rule: SegmentMatchingRule): string => {
if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) {
@ -325,7 +339,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
const isSelected = currentCategoryIds.includes(classification.id);
return (
<label key={classification.id} className="flex items-center space-x-2 cursor-pointer hover:bg-blue-50 p-1 rounded">
<div key={classification.id} className="flex items-center space-x-2 hover:bg-blue-50 p-1 rounded">
<input
type="checkbox"
checked={isSelected}
@ -335,10 +349,36 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
<span className="text-xs text-gray-700 flex-1">
{classification.name}
</span>
<span className="text-xs text-gray-500 bg-gray-100 px-1 rounded">
: {classification.weight || 0}
</span>
</label>
{/* 权重编辑区域 */}
<div className="flex items-center space-x-1">
{isSelected ? (
// 选中时显示可编辑的输入框
<div className="flex items-center space-x-1">
<span className="text-xs text-gray-500">:</span>
<input
type="number"
min="0"
max="100"
value={editingWeights[classification.id] || classification.weight || 0}
onChange={(e) => {
const value = parseInt(e.target.value) || 0;
if (validateWeight(value)) {
handleWeightChange(classification.id, value);
}
}}
className="w-12 text-xs text-center border border-gray-300 rounded px-1 py-0.5 focus:border-blue-500 focus:ring-1 focus:ring-blue-500"
onClick={(e) => e.stopPropagation()}
/>
</div>
) : (
// 未选中时显示只读的权重值
<span className="text-xs text-gray-500 bg-gray-100 px-1 rounded">
: {classification.weight || 0}
</span>
)}
</div>
</div>
);
})}
</div>
@ -348,83 +388,10 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
</div>
)}
{/* 权重配置区域 - 只在有模板ID且选择了分类时显示 */}
{/* 权重配置说明 */}
{templateId && typeof editingRule === 'object' && 'PriorityOrder' in editingRule && editingRule.PriorityOrder.category_ids.length > 0 && (
<div className="mt-4 pt-3 border-t border-blue-200">
<div className="flex items-center justify-between mb-2">
<label className="block text-xs font-medium text-blue-800">
</label>
{weightLoading && (
<div className="text-xs text-blue-600 flex items-center">
<div className="animate-spin rounded-full h-3 w-3 border-b-2 border-blue-600 mr-1"></div>
...
</div>
)}
</div>
<div className="text-xs text-gray-600 mb-3">
</div>
<div className="space-y-2 max-h-40 overflow-y-auto">
{aiClassifications
.filter(classification =>
typeof editingRule === 'object' && 'PriorityOrder' in editingRule &&
editingRule.PriorityOrder.category_ids.includes(classification.id)
)
.sort((a, b) => (editingWeights[b.id] || b.weight || 0) - (editingWeights[a.id] || a.weight || 0))
.map((classification) => {
const currentWeight = editingWeights[classification.id] || classification.weight || 0;
const displayInfo = getWeightDisplayInfo(currentWeight);
return (
<div key={classification.id} className="bg-gray-50 border border-gray-200 rounded-md p-2">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-medium text-gray-700">
{classification.name}
</span>
<div className="flex items-center space-x-2">
<span className={`text-xs font-medium ${displayInfo.colorClass}`}>
{displayInfo.text}
</span>
<span className="text-xs text-gray-500 bg-white px-2 py-1 rounded border">
{currentWeight}
</span>
</div>
</div>
<div className="flex items-center space-x-2">
<input
type="range"
min="0"
max="100"
step="5"
value={currentWeight}
onChange={(e) => handleWeightChange(classification.id, parseInt(e.target.value))}
className="flex-1 h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider"
/>
<input
type="number"
min="0"
max="100"
value={currentWeight}
onChange={(e) => {
const value = parseInt(e.target.value) || 0;
if (validateWeight(value)) {
handleWeightChange(classification.id, value);
}
}}
className="w-12 text-xs text-center border border-gray-300 rounded px-1 py-1"
/>
</div>
</div>
);
})}
</div>
<div className="mt-3 text-xs text-gray-500 bg-blue-50 border border-blue-200 p-2 rounded-md">
💡 0-100
</div>
<div className="mt-3 text-xs text-gray-500 bg-blue-50 border border-blue-200 p-2 rounded-md">
💡 0-100
</div>
)}
</div>