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 [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
// 权重配置相关状态
|
// 权重配置相关状态
|
||||||
const [currentWeights, setCurrentWeights] = useState<Record<string, number>>({});
|
|
||||||
const [editingWeights, setEditingWeights] = useState<Record<string, number>>({});
|
const [editingWeights, setEditingWeights] = useState<Record<string, number>>({});
|
||||||
const [weightLoading, setWeightLoading] = useState(false);
|
|
||||||
|
|
||||||
const { updateSegmentMatchingRule } = useTemplateStore();
|
const { updateSegmentMatchingRule } = useTemplateStore();
|
||||||
|
|
||||||
|
|
@ -59,17 +57,13 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
if (!templateId) return;
|
if (!templateId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setWeightLoading(true);
|
|
||||||
const weights = await TemplateSegmentWeightService.getSegmentWeightsWithDefaults(
|
const weights = await TemplateSegmentWeightService.getSegmentWeightsWithDefaults(
|
||||||
templateId,
|
templateId,
|
||||||
segmentId
|
segmentId
|
||||||
);
|
);
|
||||||
setCurrentWeights(weights);
|
|
||||||
setEditingWeights({ ...weights });
|
setEditingWeights({ ...weights });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load weight data:', error);
|
console.error('Failed to load weight data:', error);
|
||||||
} finally {
|
|
||||||
setWeightLoading(false);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -107,12 +101,23 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
// 如果是按顺序匹配规则且有模板ID,同时保存权重配置
|
// 如果是按顺序匹配规则且有模板ID,同时保存权重配置
|
||||||
if (SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && templateId) {
|
if (SegmentMatchingRuleHelper.isPriorityOrder(editingRule) && templateId) {
|
||||||
try {
|
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(
|
await TemplateSegmentWeightService.setSegmentWeights(
|
||||||
templateId,
|
templateId,
|
||||||
segmentId,
|
segmentId,
|
||||||
editingWeights
|
selectedWeights
|
||||||
);
|
);
|
||||||
setCurrentWeights({ ...editingWeights });
|
|
||||||
} catch (weightError) {
|
} catch (weightError) {
|
||||||
console.error('保存权重配置失败:', weightError);
|
console.error('保存权重配置失败:', weightError);
|
||||||
// 权重保存失败不阻止规则保存
|
// 权重保存失败不阻止规则保存
|
||||||
|
|
@ -168,9 +173,23 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
// 添加分类ID
|
// 添加分类ID
|
||||||
newCategoryIds = [...currentCategoryIds, categoryId];
|
newCategoryIds = [...currentCategoryIds, categoryId];
|
||||||
|
// 为新选中的分类设置默认权重(如果还没有的话)
|
||||||
|
if (!editingWeights[categoryId]) {
|
||||||
|
const classification = aiClassifications.find(c => c.id === categoryId);
|
||||||
|
setEditingWeights(prev => ({
|
||||||
|
...prev,
|
||||||
|
[categoryId]: classification?.weight || 50
|
||||||
|
}));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 移除分类ID
|
// 移除分类ID
|
||||||
newCategoryIds = currentCategoryIds.filter(id => id !== categoryId);
|
newCategoryIds = currentCategoryIds.filter(id => id !== categoryId);
|
||||||
|
// 从权重配置中移除未选中的分类
|
||||||
|
setEditingWeights(prev => {
|
||||||
|
const newWeights = { ...prev };
|
||||||
|
delete newWeights[categoryId];
|
||||||
|
return newWeights;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setEditingRule(SegmentMatchingRuleHelper.createPriorityOrder(newCategoryIds));
|
setEditingRule(SegmentMatchingRuleHelper.createPriorityOrder(newCategoryIds));
|
||||||
|
|
@ -189,12 +208,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
return weight >= 0 && weight <= 100;
|
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 => {
|
const getCurrentRuleType = (rule: SegmentMatchingRule): string => {
|
||||||
if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) {
|
if (SegmentMatchingRuleHelper.isFixedMaterial(rule)) {
|
||||||
|
|
@ -325,7 +339,7 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
const isSelected = currentCategoryIds.includes(classification.id);
|
const isSelected = currentCategoryIds.includes(classification.id);
|
||||||
|
|
||||||
return (
|
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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
|
|
@ -335,10 +349,36 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
<span className="text-xs text-gray-700 flex-1">
|
<span className="text-xs text-gray-700 flex-1">
|
||||||
{classification.name}
|
{classification.name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500 bg-gray-100 px-1 rounded">
|
|
||||||
权重: {classification.weight || 0}
|
{/* 权重编辑区域 */}
|
||||||
</span>
|
<div className="flex items-center space-x-1">
|
||||||
</label>
|
{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>
|
</div>
|
||||||
|
|
@ -348,83 +388,10 @@ export const SegmentMatchingRuleEditor: React.FC<SegmentMatchingRuleEditorProps>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 权重配置区域 - 只在有模板ID且选择了分类时显示 */}
|
{/* 权重配置说明 */}
|
||||||
{templateId && typeof editingRule === 'object' && 'PriorityOrder' in editingRule && editingRule.PriorityOrder.category_ids.length > 0 && (
|
{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="mt-3 text-xs text-gray-500 bg-blue-50 border border-blue-200 p-2 rounded-md">
|
||||||
<div className="flex items-center justify-between mb-2">
|
💡 选中分类后可直接在右侧编辑权重值(0-100),权重越高优先级越高。保存时会同时保存权重配置。
|
||||||
<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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue