diff --git a/src/pages/Application/ModelManagement/ModelForms/BehaviorRecommendForm.tsx b/src/pages/Application/ModelManagement/ModelForms/BehaviorRecommendForm.tsx deleted file mode 100644 index 75dde59..0000000 --- a/src/pages/Application/ModelManagement/ModelForms/BehaviorRecommendForm.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { Form, Row, Col, Card, Slider } from 'antd'; - -const BehaviorRecommendForm: React.FC = () => { - return ( -
- - - - - - - - - - - - - - - - - - -
- ); -}; - -export default BehaviorRecommendForm; diff --git a/src/pages/Application/ModelManagement/ModelForms/CompetitivenessForm.tsx b/src/pages/Application/ModelManagement/ModelForms/CompetitivenessForm.tsx deleted file mode 100644 index 951e6ac..0000000 --- a/src/pages/Application/ModelManagement/ModelForms/CompetitivenessForm.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Form, Row, Col, Card, Slider } from 'antd'; - -const CompetitivenessForm: React.FC = () => { - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -}; - -export default CompetitivenessForm; diff --git a/src/pages/Application/ModelManagement/ModelForms/LocationMatchForm.tsx b/src/pages/Application/ModelManagement/ModelForms/LocationMatchForm.tsx deleted file mode 100644 index 6a5d687..0000000 --- a/src/pages/Application/ModelManagement/ModelForms/LocationMatchForm.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { Form, Row, Col, Card, InputNumber, Select } from 'antd'; - -const { Option } = Select; - -const LocationMatchForm: React.FC = () => { - return ( -
- - - - - - - - - - - - - - - - - - - - - - -
- ); -}; - -export default LocationMatchForm; diff --git a/src/pages/Application/ModelManagement/ModelForms/MatchDegreeForm.tsx b/src/pages/Application/ModelManagement/ModelForms/MatchDegreeForm.tsx deleted file mode 100644 index 415df69..0000000 --- a/src/pages/Application/ModelManagement/ModelForms/MatchDegreeForm.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Form, Row, Col, Card, Slider } from 'antd'; - -const MatchDegreeForm: React.FC = () => { - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -}; - -export default MatchDegreeForm; diff --git a/src/pages/Application/ModelManagement/ModelForms/PreciseMatchForm.tsx b/src/pages/Application/ModelManagement/ModelForms/PreciseMatchForm.tsx deleted file mode 100644 index ce55c9d..0000000 --- a/src/pages/Application/ModelManagement/ModelForms/PreciseMatchForm.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { Form, Row, Col, Card, Divider, InputNumber, Slider } from 'antd'; - -const PreciseMatchForm: React.FC = () => { - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); -}; - -export default PreciseMatchForm; diff --git a/src/pages/Application/ModelManagement/index.tsx b/src/pages/Application/ModelManagement/index.tsx index 1109a05..aa33693 100644 --- a/src/pages/Application/ModelManagement/index.tsx +++ b/src/pages/Application/ModelManagement/index.tsx @@ -1,7 +1,19 @@ import React, { Fragment, useState, useEffect } from 'react'; import { useAccess } from '@umijs/max'; import { getModelConfig, saveModelConfig } from '@/services/application/modelManagement'; -import { Card, Row, Col, Form, Button, message, Menu, Space, Modal } from 'antd'; +import { + Card, + Row, + Col, + Form, + Button, + message, + Menu, + Space, + Modal, + Slider, + InputNumber, +} from 'antd'; import { SaveOutlined, ReloadOutlined, @@ -13,87 +25,57 @@ import { EnvironmentOutlined, } from '@ant-design/icons'; -// 导入表单组件 -import PreciseMatchForm from './ModelForms/PreciseMatchForm'; //人岗精准匹配模型 -import MatchDegreeForm from './ModelForms/MatchDegreeForm'; //人岗匹配度计算模型 -import BehaviorRecommendForm from './ModelForms/BehaviorRecommendForm'; //基于求职者行为的推荐模型 -import CompetitivenessForm from './ModelForms/CompetitivenessForm'; //竞争力计算模型 -import LocationMatchForm from './ModelForms/LocationMatchForm'; //位置匹配模型 +interface ConfigItem { + createTime: string | null; + id: number; + configName: string; + configValue: string; + modelType: string; + isWeight?: boolean; +} + +interface ModelConfig { + [key: string]: ConfigItem[]; +} function ModelManagement() { const access = useAccess(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); - const [selectedModel, setSelectedModel] = useState('preciseMatch'); - const [configData, setConfigData] = useState({}); + const [selectedModel, setSelectedModel] = useState(''); + const [configData, setConfigData] = useState({}); const [totalWeight, setTotalWeight] = useState(0); + const [modelTypes, setModelTypes] = useState([]); - // 模型菜单配置 - const modelMenu = [ - { - key: 'preciseMatch', - label: '人岗精准匹配模型', - icon: , - }, - { - key: 'matchDegree', - label: '人岗匹配度计算模型', - icon: , - }, - { - key: 'behaviorRecommend', - label: '基于求职者行为的推荐模型', - icon: , - }, - { - key: 'competitiveness', - label: '竞争力计算模型', - icon: , - }, - { - key: 'locationMatch', - label: '位置匹配模型', - icon: , - }, - ]; + // 图标映射 + const iconMap: { [key: string]: React.ReactNode } = { + 位置匹配模型: , + 人岗精准匹配模型: , + 基于求职者行为推荐模型: , + 竞争力计算模型: , + 人岗匹配度计算模型: , + }; + + const defaultIcon = ; + + // 生成模型菜单 + const generateModelMenu = () => { + return modelTypes.map((modelType) => ({ + key: modelType, + label: modelType, + icon: iconMap[modelType] || defaultIcon, + })); + }; // 计算总权重 - const calculateTotalWeight = (modelKey: string, formValues: any) => { - const weightConfigs: any = { - preciseMatch: [ - 'firstWorkExpWeight', - 'firstWorkExpTimeWeight', - 'secondWorkExpWeight', - 'secondWorkExpTimeWeight', - 'thirdWorkExpWeight', - 'thirdWorkExpTimeWeight', - ], - matchDegree: [ - 'educationWeight', - 'genderWeight', - 'ageWeight', - 'salaryWeight', - 'experienceWeight', - 'jobTitleWeight', - 'areaWeight', - ], - behaviorRecommend: ['browseWeight', 'applyWeight', 'applyHistoryWeight', 'collectWeight'], - competitiveness: [ - 'educationWeight', - 'genderWeight', - 'ageWeight', - 'salaryWeight', - 'experienceWeight', - 'jobTitleWeight', - 'areaWeight', - ], - locationMatch: [], // 位置匹配模型没有权重 - }; - - const weights = weightConfigs[modelKey] || []; - const total = weights.reduce((sum: number, key: string) => { - const value = formValues[key] || 0; - return sum + Math.round(value * 100); + const calculateTotalWeight = (modelType: string, formValues: any) => { + const modelConfigs = configData[modelType] || []; + const total = modelConfigs.reduce((sum: number, config: ConfigItem) => { + if (!!config.isWeight) { + const value = parseFloat(formValues[config.configName] || 0); + return sum + Math.round(value * 100); + } + return sum; }, 0); setTotalWeight(total / 100); @@ -103,87 +85,121 @@ function ModelManagement() { calculateTotalWeight(selectedModel, allValues); }; + // 设置表单值 + const setFormValues = (modelType: string, configs: ConfigItem[]) => { + const initialValues: any = {}; + configs.forEach((config) => { + // 确保值为数字类型,如果解析失败则使用0 + const value = parseFloat(config.configValue); + initialValues[config.configName] = isNaN(value) ? 0 : value; + }); + + // 确保设置表单值 + setTimeout(() => { + form.setFieldsValue(initialValues); + calculateTotalWeight(modelType, initialValues); + }, 30); + }; + // 加载配置数据 - const loadConfigData = async (modelKey: string) => { + const loadConfigData = async () => { setLoading(true); try { - const res = await getModelConfig(modelKey); + const res = await getModelConfig(''); if (res.code === 200) { - setConfigData(res.data); - form.setFieldsValue(res.data); - calculateTotalWeight(modelKey, res.data); + const data = res.data as ConfigItem[]; + + // 按模型类型分组 + const groupedData: ModelConfig = {}; + const types: string[] = []; + + data.forEach((item: ConfigItem) => { + if (!groupedData[item.modelType]) { + groupedData[item.modelType] = []; + types.push(item.modelType); + } + groupedData[item.modelType].push(item); + }); + + setConfigData(groupedData); + setModelTypes(types); + + // 设置默认选中的模型 - 修复:使用最新的 groupedData + if (types.length > 0) { + const currentModel = selectedModel || types[0]; + setSelectedModel(currentModel); + setFormValues(currentModel, groupedData[currentModel]); + } } else { message.error(res.msg); - setDefaultValues(modelKey); } } catch (error) { message.error('加载配置失败'); - setDefaultValues(modelKey); } finally { setLoading(false); } }; - // 默认值 - const setDefaultValues = (modelKey: string) => { - const defaultValues: any = { - preciseMatch: { - ageMatchRange: 5, - experienceMatchRange: 2, - firstWorkExpWeight: 0.4, - firstWorkExpTimeWeight: 0.3, - secondWorkExpWeight: 0.3, - secondWorkExpTimeWeight: 0.2, - thirdWorkExpWeight: 0.2, - thirdWorkExpTimeWeight: 0.1, - }, - matchDegree: { - educationWeight: 0.15, - genderWeight: 0.1, - ageWeight: 0.1, - salaryWeight: 0.2, - experienceWeight: 0.15, - jobTitleWeight: 0.2, - areaWeight: 0.1, - }, - behaviorRecommend: { - browseWeight: 0.3, - applyWeight: 0.4, - applyHistoryWeight: 0.2, - collectWeight: 0.1, - }, - competitiveness: { - educationWeight: 0.2, - genderWeight: 0.05, - ageWeight: 0.1, - salaryWeight: 0.15, - experienceWeight: 0.25, - jobTitleWeight: 0.15, - areaWeight: 0.1, - }, - locationMatch: { - distanceAlgorithm: 'euclidean', - subwayDistance: 1000, - businessDistrictDistance: 2000, - personalDistance: 5000, - }, - }; - const values = defaultValues[modelKey] || {}; - setConfigData(values); - form.setFieldsValue(values); - calculateTotalWeight(modelKey, values); + // 获取单位 + const getUnit = (configName: string): string => { + if (configName.includes('薪资') || configName.includes('工资')) { + return '元'; + } + if (configName.includes('距离') || configName.includes('半径') || configName.includes('通勤')) { + return 'Km'; + } + return ''; + }; + + // 校验权重 + const validateWeight = (modelType: string, values: any) => { + const modelConfigs = configData[modelType] || []; + const hasWeight = modelConfigs.some((config) => !!config.isWeight); + + if (!hasWeight) { + return true; + } + + const total = + modelConfigs.reduce((sum: number, config: ConfigItem) => { + if (!!config.isWeight) { + const value = values[config.configName] || 0; + return sum + Math.round(value * 100); + } + return sum; + }, 0) / 100; + + return Math.abs(total - 1) < 0.01; }; const handleSaveConfig = async (values: any) => { + if (!validateWeight(selectedModel, values)) { + message.error(`当前模型权重总和为 ${totalWeight.toFixed(2)},应等于 1.00 。`); + return; + } + await performSave(values); + }; + + // 保存 + const performSave = async (values: any) => { setLoading(true); try { - const resData = await saveModelConfig({ - modelKey: selectedModel, - ...values, - }); + const modelConfigs = configData[selectedModel] || []; + + const saveData = modelConfigs.map((config) => ({ + createTime: config.createTime, + id: config.id, + configName: config.configName, + configValue: values[config.configName].toString(), + modelType: config.modelType, + isWeight: config.isWeight, + })); + + const resData = await saveModelConfig({ modelConfigList: saveData }); if (resData.code === 200) { message.success('配置保存成功'); - setConfigData(values); + // 修复:保存成功后重新加载数据,确保使用最新的接口数据 + await loadConfigData(); } else { message.error(resData.msg || '保存失败'); } @@ -194,7 +210,7 @@ function ModelManagement() { } }; - // 重置 + // 重置 - 修复:确保重置后表单数据正确更新 const handleResetConfig = async () => { Modal.confirm({ title: '重置确认', @@ -202,51 +218,114 @@ function ModelManagement() { okText: '确认', cancelText: '取消', onOk: async () => { - loadConfigData(selectedModel); + // 重新加载数据,确保使用服务器的最新数据 + await loadConfigData(); }, }); }; - // 模型切换 + // 模型切换 - 修复:使用最新的 configData const handleModelChange = (key: string) => { setSelectedModel(key); - loadConfigData(key); + const modelConfigs = configData[key] || []; + setFormValues(key, modelConfigs); }; - //颜色 + // 颜色 const getWeightColor = () => { - if (totalWeight == 1) return '#52c41a'; + if (Math.abs(totalWeight - 1) < 0.01) return '#52c41a'; if (totalWeight > 1) return '#ff4d4f'; return '#faad14'; }; // 状态文本 const getWeightStatusText = () => { - if (totalWeight == 1) return '权重分配合理'; + if (Math.abs(totalWeight - 1) < 0.01) return '权重分配合理'; if (totalWeight > 1) return '权重超出范围'; return '权重分配不足'; }; - // 渲染表单组件 + // 检查当前模型是否有权重配置 + const hasWeightConfig = () => { + const modelConfigs = configData[selectedModel] || []; + return modelConfigs.some((config) => !!config.isWeight); + }; + + // 渲染表单组件 - 修复:添加默认值处理 const renderFormComponent = () => { - switch (selectedModel) { - case 'preciseMatch': - return ; - case 'matchDegree': - return ; - case 'behaviorRecommend': - return ; - case 'competitiveness': - return ; - case 'locationMatch': - return ; - default: - return null; - } + const modelConfigs = configData[selectedModel] || []; + + return modelConfigs.map((config: ConfigItem) => { + const unit = getUnit(config.configName); + const isWeight = !!config.isWeight; + + return ( + +
{config.configName}
+ {isWeight && ( +
+ 权重: {(form.getFieldValue(config.configName) || 0).toFixed(2)} +
+ )} + + } + name={config.configName} + initialValue={parseFloat(config.configValue) || 0} + rules={[ + { required: true, message: `请输入${config.configName}` }, + { + validator: (_, value) => { + if (value === null || value === undefined) return Promise.resolve(); + if (value < 0) return Promise.reject(new Error('不能小于0')); + if (isWeight && value > 1) return Promise.reject(new Error('权重不能大于1')); + return Promise.resolve(); + }, + }, + ]} + > + {isWeight ? ( + `${(value || 0).toFixed(2)}${unit ? ` ${unit}` : ''}`, + }} + /> + ) : ( + + )} +
+ ); + }); }; useEffect(() => { - loadConfigData(selectedModel); + loadConfigData(); }, []); return ( @@ -260,7 +339,7 @@ function ModelManagement() { mode="inline" selectedKeys={[selectedModel]} style={{ border: 'none', height: '100%' }} - items={modelMenu} + items={generateModelMenu()} onClick={({ key }) => handleModelChange(key)} /> @@ -269,15 +348,16 @@ function ModelManagement() { {/* 右侧参数配置区 */} - {modelMenu.find((item) => item.key === selectedModel)?.label} + {selectedModel} } extra={ - {selectedModel !== 'locationMatch' && ( + {hasWeightConfig() && (
} onClick={handleResetConfig} + loading={loading} disabled={!access.hasPerms('model:config:update')} > 重置 @@ -361,7 +442,7 @@ function ModelManagement() { } style={{ height: '100%' }} - styles={{ body:{height:'calc(80vh)'}}} + styles={{ body: { height: 'calc(80vh)', overflowY: 'auto', padding: '25px 60px' } }} >