feat : 模型管理页面重构,接口联调
This commit is contained in:
@@ -1,41 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form, Row, Col, Card, Slider } from 'antd';
|
|
||||||
|
|
||||||
const BehaviorRecommendForm: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card size="small" title="行为权重分配">
|
|
||||||
<Form.Item
|
|
||||||
label="浏览记录权重"
|
|
||||||
name="browseWeight"
|
|
||||||
tooltip="浏览记录在推荐算法中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="申请记录权重" name="applyWeight" tooltip="申请记录在推荐算法中的权重">
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="申请历史权重"
|
|
||||||
name="applyHistoryWeight"
|
|
||||||
tooltip="申请历史记录在推荐算法中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="收藏记录权重"
|
|
||||||
name="collectWeight"
|
|
||||||
tooltip="收藏记录在推荐算法中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BehaviorRecommendForm;
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form, Row, Col, Card, Slider } from 'antd';
|
|
||||||
|
|
||||||
const CompetitivenessForm: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="基本信息竞争力">
|
|
||||||
<Form.Item
|
|
||||||
label="学历字段权重"
|
|
||||||
name="educationWeight"
|
|
||||||
tooltip="学历在竞争力计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="性别字段权重" name="genderWeight" tooltip="性别在竞争力计算中的权重">
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="年龄字段权重" name="ageWeight" tooltip="年龄在竞争力计算中的权重">
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="期望信息竞争力">
|
|
||||||
<Form.Item
|
|
||||||
label="期望薪资权重"
|
|
||||||
name="salaryWeight"
|
|
||||||
tooltip="期望薪资在竞争力计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="工作经验权重"
|
|
||||||
name="experienceWeight"
|
|
||||||
tooltip="工作经验在竞争力计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="期望工作岗位权重"
|
|
||||||
name="jobTitleWeight"
|
|
||||||
tooltip="期望工作岗位在竞争力计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="地区竞争力">
|
|
||||||
<Form.Item
|
|
||||||
label="期望工作地区权重"
|
|
||||||
name="areaWeight"
|
|
||||||
tooltip="期望工作地区在竞争力计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CompetitivenessForm;
|
|
||||||
@@ -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 (
|
|
||||||
<div>
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card size="small" title="距离算法设置">
|
|
||||||
<Form.Item label="距离计算算法" name="distanceAlgorithm" tooltip="选择计算距离的算法">
|
|
||||||
<Select placeholder="请选择距离算法">
|
|
||||||
<Option value="euclidean">欧氏距离</Option>
|
|
||||||
<Option value="manhattan">曼哈顿距离</Option>
|
|
||||||
</Select>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="地铁口附近距离(Km)"
|
|
||||||
name="subwayDistance"
|
|
||||||
tooltip="地铁口附近的距离阈值"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={100}
|
|
||||||
max={5000}
|
|
||||||
step={100}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="请输入地铁口附近距离"
|
|
||||||
addonAfter="Km"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="商圈附近距离(Km)"
|
|
||||||
name="businessDistrictDistance"
|
|
||||||
tooltip="商圈附近的距离阈值"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={500}
|
|
||||||
max={10000}
|
|
||||||
step={100}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="请输入商圈附近距离"
|
|
||||||
addonAfter="Km"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card size="small" title="个人位置设置">
|
|
||||||
<Form.Item
|
|
||||||
label="个人附近距离(Km)"
|
|
||||||
name="personalDistance"
|
|
||||||
tooltip="个人位置附近的距离阈值"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={1000}
|
|
||||||
max={20000}
|
|
||||||
step={500}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="请输入个人附近距离"
|
|
||||||
addonAfter="Km"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default LocationMatchForm;
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form, Row, Col, Card, Slider } from 'antd';
|
|
||||||
|
|
||||||
const MatchDegreeForm: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="基本信息权重">
|
|
||||||
<Form.Item
|
|
||||||
label="学历字段权重"
|
|
||||||
name="educationWeight"
|
|
||||||
tooltip="学历在匹配度计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="性别字段权重" name="genderWeight" tooltip="性别在匹配度计算中的权重">
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="年龄字段权重" name="ageWeight" tooltip="年龄在匹配度计算中的权重">
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="期望信息权重">
|
|
||||||
<Form.Item
|
|
||||||
label="期望薪资权重"
|
|
||||||
name="salaryWeight"
|
|
||||||
tooltip="期望薪资在匹配度计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="工作经验权重"
|
|
||||||
name="experienceWeight"
|
|
||||||
tooltip="工作经验在匹配度计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="期望工作岗位权重"
|
|
||||||
name="jobTitleWeight"
|
|
||||||
tooltip="期望工作岗位在匹配度计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="地区权重">
|
|
||||||
<Form.Item
|
|
||||||
label="期望工作地区权重"
|
|
||||||
name="areaWeight"
|
|
||||||
tooltip="期望工作地区在匹配度计算中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MatchDegreeForm;
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Form, Row, Col, Card, Divider, InputNumber, Slider } from 'antd';
|
|
||||||
|
|
||||||
const PreciseMatchForm: React.FC = () => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={12}>
|
|
||||||
<Card size="small" title="基本匹配范围">
|
|
||||||
<Form.Item
|
|
||||||
label="年龄匹配范围(年)"
|
|
||||||
name="ageMatchRange"
|
|
||||||
tooltip="设置年龄匹配的允许偏差范围"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={0}
|
|
||||||
max={20}
|
|
||||||
step={1}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="请输入年龄匹配范围"
|
|
||||||
addonAfter="年"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="工作经验匹配范围(年)"
|
|
||||||
name="experienceMatchRange"
|
|
||||||
tooltip="设置工作经验匹配的允许偏差范围"
|
|
||||||
>
|
|
||||||
<InputNumber
|
|
||||||
min={0}
|
|
||||||
max={10}
|
|
||||||
step={0.5}
|
|
||||||
precision={1}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
placeholder="请输入工作经验匹配范围"
|
|
||||||
addonAfter="年"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
<Row gutter={24}>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="第一段工作经历">
|
|
||||||
<Form.Item
|
|
||||||
label="经历权重"
|
|
||||||
name="firstWorkExpWeight"
|
|
||||||
tooltip="第一段工作经历在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="距今时间权重"
|
|
||||||
name="firstWorkExpTimeWeight"
|
|
||||||
tooltip="第一段工作经历距今时间在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="第二段工作经历">
|
|
||||||
<Form.Item
|
|
||||||
label="经历权重"
|
|
||||||
name="secondWorkExpWeight"
|
|
||||||
tooltip="第二段工作经历在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="距今时间权重"
|
|
||||||
name="secondWorkExpTimeWeight"
|
|
||||||
tooltip="第二段工作经历距今时间在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={8}>
|
|
||||||
<Card size="small" title="第三段工作经历">
|
|
||||||
<Form.Item
|
|
||||||
label="经历权重"
|
|
||||||
name="thirdWorkExpWeight"
|
|
||||||
tooltip="第三段工作经历在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label="距今时间权重"
|
|
||||||
name="thirdWorkExpTimeWeight"
|
|
||||||
tooltip="第三段工作经历距今时间在匹配中的权重"
|
|
||||||
>
|
|
||||||
<Slider min={0} max={1} step={0.1} marks={{ 0: '0', 0.5: '0.5', 1: '1' }} />
|
|
||||||
</Form.Item>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PreciseMatchForm;
|
|
||||||
@@ -1,7 +1,19 @@
|
|||||||
import React, { Fragment, useState, useEffect } from 'react';
|
import React, { Fragment, useState, useEffect } from 'react';
|
||||||
import { useAccess } from '@umijs/max';
|
import { useAccess } from '@umijs/max';
|
||||||
import { getModelConfig, saveModelConfig } from '@/services/application/modelManagement';
|
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 {
|
import {
|
||||||
SaveOutlined,
|
SaveOutlined,
|
||||||
ReloadOutlined,
|
ReloadOutlined,
|
||||||
@@ -13,87 +25,57 @@ import {
|
|||||||
EnvironmentOutlined,
|
EnvironmentOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
|
|
||||||
// 导入表单组件
|
interface ConfigItem {
|
||||||
import PreciseMatchForm from './ModelForms/PreciseMatchForm'; //人岗精准匹配模型
|
createTime: string | null;
|
||||||
import MatchDegreeForm from './ModelForms/MatchDegreeForm'; //人岗匹配度计算模型
|
id: number;
|
||||||
import BehaviorRecommendForm from './ModelForms/BehaviorRecommendForm'; //基于求职者行为的推荐模型
|
configName: string;
|
||||||
import CompetitivenessForm from './ModelForms/CompetitivenessForm'; //竞争力计算模型
|
configValue: string;
|
||||||
import LocationMatchForm from './ModelForms/LocationMatchForm'; //位置匹配模型
|
modelType: string;
|
||||||
|
isWeight?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ModelConfig {
|
||||||
|
[key: string]: ConfigItem[];
|
||||||
|
}
|
||||||
|
|
||||||
function ModelManagement() {
|
function ModelManagement() {
|
||||||
const access = useAccess();
|
const access = useAccess();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
const [selectedModel, setSelectedModel] = useState<string>('preciseMatch');
|
const [selectedModel, setSelectedModel] = useState<string>('');
|
||||||
const [configData, setConfigData] = useState<any>({});
|
const [configData, setConfigData] = useState<ModelConfig>({});
|
||||||
const [totalWeight, setTotalWeight] = useState<number>(0);
|
const [totalWeight, setTotalWeight] = useState<number>(0);
|
||||||
|
const [modelTypes, setModelTypes] = useState<string[]>([]);
|
||||||
|
|
||||||
// 模型菜单配置
|
// 图标映射
|
||||||
const modelMenu = [
|
const iconMap: { [key: string]: React.ReactNode } = {
|
||||||
{
|
位置匹配模型: <EnvironmentOutlined />,
|
||||||
key: 'preciseMatch',
|
人岗精准匹配模型: <UserOutlined />,
|
||||||
label: '人岗精准匹配模型',
|
基于求职者行为推荐模型: <EyeOutlined />,
|
||||||
icon: <UserOutlined />,
|
竞争力计算模型: <TrophyOutlined />,
|
||||||
},
|
人岗匹配度计算模型: <CalculatorOutlined />,
|
||||||
{
|
|
||||||
key: 'matchDegree',
|
|
||||||
label: '人岗匹配度计算模型',
|
|
||||||
icon: <CalculatorOutlined />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'behaviorRecommend',
|
|
||||||
label: '基于求职者行为的推荐模型',
|
|
||||||
icon: <EyeOutlined />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'competitiveness',
|
|
||||||
label: '竞争力计算模型',
|
|
||||||
icon: <TrophyOutlined />,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'locationMatch',
|
|
||||||
label: '位置匹配模型',
|
|
||||||
icon: <EnvironmentOutlined />,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// 计算总权重
|
|
||||||
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 defaultIcon = <SettingOutlined />;
|
||||||
const total = weights.reduce((sum: number, key: string) => {
|
|
||||||
const value = formValues[key] || 0;
|
// 生成模型菜单
|
||||||
|
const generateModelMenu = () => {
|
||||||
|
return modelTypes.map((modelType) => ({
|
||||||
|
key: modelType,
|
||||||
|
label: modelType,
|
||||||
|
icon: iconMap[modelType] || defaultIcon,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 计算总权重
|
||||||
|
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 + Math.round(value * 100);
|
||||||
|
}
|
||||||
|
return sum;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
setTotalWeight(total / 100);
|
setTotalWeight(total / 100);
|
||||||
@@ -103,87 +85,121 @@ function ModelManagement() {
|
|||||||
calculateTotalWeight(selectedModel, allValues);
|
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);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await getModelConfig(modelKey);
|
const res = await getModelConfig('');
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
setConfigData(res.data);
|
const data = res.data as ConfigItem[];
|
||||||
form.setFieldsValue(res.data);
|
|
||||||
calculateTotalWeight(modelKey, res.data);
|
// 按模型类型分组
|
||||||
|
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 {
|
} else {
|
||||||
message.error(res.msg);
|
message.error(res.msg);
|
||||||
setDefaultValues(modelKey);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('加载配置失败');
|
message.error('加载配置失败');
|
||||||
setDefaultValues(modelKey);
|
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 默认值
|
// 获取单位
|
||||||
const setDefaultValues = (modelKey: string) => {
|
const getUnit = (configName: string): string => {
|
||||||
const defaultValues: any = {
|
if (configName.includes('薪资') || configName.includes('工资')) {
|
||||||
preciseMatch: {
|
return '元';
|
||||||
ageMatchRange: 5,
|
}
|
||||||
experienceMatchRange: 2,
|
if (configName.includes('距离') || configName.includes('半径') || configName.includes('通勤')) {
|
||||||
firstWorkExpWeight: 0.4,
|
return 'Km';
|
||||||
firstWorkExpTimeWeight: 0.3,
|
}
|
||||||
secondWorkExpWeight: 0.3,
|
return '';
|
||||||
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);
|
const validateWeight = (modelType: string, values: any) => {
|
||||||
calculateTotalWeight(modelKey, values);
|
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) => {
|
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);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const resData = await saveModelConfig({
|
const modelConfigs = configData[selectedModel] || [];
|
||||||
modelKey: selectedModel,
|
|
||||||
...values,
|
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) {
|
if (resData.code === 200) {
|
||||||
message.success('配置保存成功');
|
message.success('配置保存成功');
|
||||||
setConfigData(values);
|
// 修复:保存成功后重新加载数据,确保使用最新的接口数据
|
||||||
|
await loadConfigData();
|
||||||
} else {
|
} else {
|
||||||
message.error(resData.msg || '保存失败');
|
message.error(resData.msg || '保存失败');
|
||||||
}
|
}
|
||||||
@@ -194,7 +210,7 @@ function ModelManagement() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 重置
|
// 重置 - 修复:确保重置后表单数据正确更新
|
||||||
const handleResetConfig = async () => {
|
const handleResetConfig = async () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: '重置确认',
|
title: '重置确认',
|
||||||
@@ -202,51 +218,114 @@ function ModelManagement() {
|
|||||||
okText: '确认',
|
okText: '确认',
|
||||||
cancelText: '取消',
|
cancelText: '取消',
|
||||||
onOk: async () => {
|
onOk: async () => {
|
||||||
loadConfigData(selectedModel);
|
// 重新加载数据,确保使用服务器的最新数据
|
||||||
|
await loadConfigData();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 模型切换
|
// 模型切换 - 修复:使用最新的 configData
|
||||||
const handleModelChange = (key: string) => {
|
const handleModelChange = (key: string) => {
|
||||||
setSelectedModel(key);
|
setSelectedModel(key);
|
||||||
loadConfigData(key);
|
const modelConfigs = configData[key] || [];
|
||||||
|
setFormValues(key, modelConfigs);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 颜色
|
// 颜色
|
||||||
const getWeightColor = () => {
|
const getWeightColor = () => {
|
||||||
if (totalWeight == 1) return '#52c41a';
|
if (Math.abs(totalWeight - 1) < 0.01) return '#52c41a';
|
||||||
if (totalWeight > 1) return '#ff4d4f';
|
if (totalWeight > 1) return '#ff4d4f';
|
||||||
return '#faad14';
|
return '#faad14';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 状态文本
|
// 状态文本
|
||||||
const getWeightStatusText = () => {
|
const getWeightStatusText = () => {
|
||||||
if (totalWeight == 1) return '权重分配合理';
|
if (Math.abs(totalWeight - 1) < 0.01) return '权重分配合理';
|
||||||
if (totalWeight > 1) return '权重超出范围';
|
if (totalWeight > 1) return '权重超出范围';
|
||||||
return '权重分配不足';
|
return '权重分配不足';
|
||||||
};
|
};
|
||||||
|
|
||||||
// 渲染表单组件
|
// 检查当前模型是否有权重配置
|
||||||
|
const hasWeightConfig = () => {
|
||||||
|
const modelConfigs = configData[selectedModel] || [];
|
||||||
|
return modelConfigs.some((config) => !!config.isWeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染表单组件 - 修复:添加默认值处理
|
||||||
const renderFormComponent = () => {
|
const renderFormComponent = () => {
|
||||||
switch (selectedModel) {
|
const modelConfigs = configData[selectedModel] || [];
|
||||||
case 'preciseMatch':
|
|
||||||
return <PreciseMatchForm />;
|
return modelConfigs.map((config: ConfigItem) => {
|
||||||
case 'matchDegree':
|
const unit = getUnit(config.configName);
|
||||||
return <MatchDegreeForm />;
|
const isWeight = !!config.isWeight;
|
||||||
case 'behaviorRecommend':
|
|
||||||
return <BehaviorRecommendForm />;
|
return (
|
||||||
case 'competitiveness':
|
<Form.Item
|
||||||
return <CompetitivenessForm />;
|
key={config.id}
|
||||||
case 'locationMatch':
|
label={
|
||||||
return <LocationMatchForm />;
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
default:
|
<div>{config.configName}</div>
|
||||||
return null;
|
{isWeight && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: '#1890ff',
|
||||||
|
fontSize: '13px',
|
||||||
|
marginTop: '1px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
权重: {(form.getFieldValue(config.configName) || 0).toFixed(2)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
|
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 ? (
|
||||||
|
<Slider
|
||||||
|
min={0}
|
||||||
|
max={1}
|
||||||
|
step={0.05}
|
||||||
|
marks={{
|
||||||
|
0: '0',
|
||||||
|
0.2: '0.2',
|
||||||
|
0.4: '0.4',
|
||||||
|
0.6: '0.6',
|
||||||
|
0.8: '0.8',
|
||||||
|
1: '1',
|
||||||
|
}}
|
||||||
|
tooltip={{
|
||||||
|
formatter: (value) => `${(value || 0).toFixed(2)}${unit ? ` ${unit}` : ''}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<InputNumber
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
min={0}
|
||||||
|
step={unit === '元' ? 100 : unit === 'Km' ? 1 : 0.1}
|
||||||
|
placeholder={`请输入${config.configName}`}
|
||||||
|
addonAfter={unit}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadConfigData(selectedModel);
|
loadConfigData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -260,7 +339,7 @@ function ModelManagement() {
|
|||||||
mode="inline"
|
mode="inline"
|
||||||
selectedKeys={[selectedModel]}
|
selectedKeys={[selectedModel]}
|
||||||
style={{ border: 'none', height: '100%' }}
|
style={{ border: 'none', height: '100%' }}
|
||||||
items={modelMenu}
|
items={generateModelMenu()}
|
||||||
onClick={({ key }) => handleModelChange(key)}
|
onClick={({ key }) => handleModelChange(key)}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -269,15 +348,16 @@ function ModelManagement() {
|
|||||||
{/* 右侧参数配置区 */}
|
{/* 右侧参数配置区 */}
|
||||||
<Col span={18}>
|
<Col span={18}>
|
||||||
<Card
|
<Card
|
||||||
|
loading={loading}
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
<SettingOutlined />
|
<SettingOutlined />
|
||||||
{modelMenu.find((item) => item.key === selectedModel)?.label}
|
{selectedModel}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
{selectedModel !== 'locationMatch' && (
|
{hasWeightConfig() && (
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -345,6 +425,7 @@ function ModelManagement() {
|
|||||||
<Button
|
<Button
|
||||||
icon={<ReloadOutlined />}
|
icon={<ReloadOutlined />}
|
||||||
onClick={handleResetConfig}
|
onClick={handleResetConfig}
|
||||||
|
loading={loading}
|
||||||
disabled={!access.hasPerms('model:config:update')}
|
disabled={!access.hasPerms('model:config:update')}
|
||||||
>
|
>
|
||||||
重置
|
重置
|
||||||
@@ -361,7 +442,7 @@ function ModelManagement() {
|
|||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
style={{ height: '100%' }}
|
style={{ height: '100%' }}
|
||||||
styles={{ body:{height:'calc(80vh)'}}}
|
styles={{ body: { height: 'calc(80vh)', overflowY: 'auto', padding: '25px 60px' } }}
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { request } from '@umijs/max';
|
import { request } from '@umijs/max';
|
||||||
|
|
||||||
export async function getModelConfig(params?: any) {
|
export async function getModelConfig(params?: any) {
|
||||||
return request(`/api/cms/appUser/aaa`, {
|
return request(`/api/cms/modelConfig/list`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
params: params,
|
params: params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveModelConfig(data?: any) {
|
export async function saveModelConfig(data?: any) {
|
||||||
return request(`/api/cms/appUser`, {
|
return request(`/api/cms/modelConfig`, {
|
||||||
method: 'POST',
|
method: 'PUT',
|
||||||
data: data,
|
data: data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user