feat: 实现实时权重编辑和精确保存功能
- 选中分类后右侧权重立即变为可编辑输入框 - 保存时只保存选中分类的权重,删除未选中分类的记录 - 简化UI,移除重复的权重配置区域 - 优化用户体验,权重编辑更直观便捷
This commit is contained in:
parent
076878287d
commit
7fdd95bbb4
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue