delete copyFile
This commit is contained in:
@@ -1,375 +0,0 @@
|
|||||||
import { Modal, Row, Col, Card, Statistic } from 'antd';
|
|
||||||
import React, { useEffect, useRef, useState } from 'react';
|
|
||||||
|
|
||||||
export type ListFormProps = {
|
|
||||||
onClose: (flag?: boolean, formVals?: unknown) => void;
|
|
||||||
open: boolean;
|
|
||||||
values?: Partial<API.MobileUser.ListRow>;
|
|
||||||
competitivenessData?: API.ManagementList.CompetitivenessData;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Detail: React.FC<ListFormProps> = (props) => {
|
|
||||||
const { open, values, competitivenessData } = props;
|
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
const [currentStep, setCurrentStep] = useState<number>(0);
|
|
||||||
const [isStyleInjected, setIsStyleInjected] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const matchingDegree = ['一般', '良好', '优秀', '极好'];
|
|
||||||
|
|
||||||
// 注入进度条样式
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isStyleInjected) {
|
|
||||||
const styleId = 'progress-bar-styles';
|
|
||||||
if (!document.getElementById(styleId)) {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
style.id = styleId;
|
|
||||||
|
|
||||||
// 生成样式
|
|
||||||
let styleContent = `
|
|
||||||
.progress-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-text {
|
|
||||||
margin-top: 4px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
justify-content: space-around;
|
|
||||||
width: 100%;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 10px;
|
|
||||||
color: #999999;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-item {
|
|
||||||
width: 25%;
|
|
||||||
height: 12px;
|
|
||||||
background-color: #eee;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-item.active {
|
|
||||||
background: linear-gradient(to right, #256bfa, #8c68ff);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 生成半透明进度样式
|
|
||||||
for (let i = 0; i <= 100; i++) {
|
|
||||||
styleContent += `
|
|
||||||
.progress-item.half${i}::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
background: linear-gradient(to right, #256bfa ${i}%, #eaeaea ${i}%);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
style.textContent = styleContent;
|
|
||||||
document.head.appendChild(style);
|
|
||||||
}
|
|
||||||
setIsStyleInjected(true);
|
|
||||||
}
|
|
||||||
}, [isStyleInjected]);
|
|
||||||
|
|
||||||
// 绘制雷达图
|
|
||||||
const drawRadarChart = () => {
|
|
||||||
if (!canvasRef.current || !competitivenessData?.radarChart) return;
|
|
||||||
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
if (!ctx) return;
|
|
||||||
|
|
||||||
// 清空画布
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
const { skill, experience, education, salary, age, location } = competitivenessData.radarChart;
|
|
||||||
|
|
||||||
// 使用与移动端相同的5个维度(去掉技能)
|
|
||||||
const labels = ['学历', '年龄', '工作地', '工作经验', '期望薪资'];
|
|
||||||
const data = [education, age, location, experience, salary].map(item => item * 0.05);
|
|
||||||
|
|
||||||
// 绘制参数
|
|
||||||
const width = 120;
|
|
||||||
const height = 120;
|
|
||||||
const centerX = canvas.width / 2;
|
|
||||||
const centerY = 125; // 与移动端保持一致的中心Y坐标
|
|
||||||
const colors = ['#F5F5F5', '#F5F5F5', '#F5F5F5', '#F5F5F5', '#F5F5F5'];
|
|
||||||
const maxScore = 5;
|
|
||||||
const angleStep = (2 * Math.PI) / labels.length;
|
|
||||||
|
|
||||||
// 1. 绘制背景圆形
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(centerX, centerY, width, 0, 2 * Math.PI);
|
|
||||||
ctx.fillStyle = '#FFFFFF';
|
|
||||||
ctx.fill();
|
|
||||||
ctx.closePath();
|
|
||||||
|
|
||||||
// 2. 绘制5层多边形
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
for (let i = 5; i > 0; i--) {
|
|
||||||
ctx.strokeStyle = colors[i - 1];
|
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
labels.forEach((_, index) => {
|
|
||||||
const x = centerX + (width / 5) * i * Math.cos(angleStep * index - Math.PI / 2);
|
|
||||||
const y = centerY + (height / 5) * i * Math.sin(angleStep * index - Math.PI / 2);
|
|
||||||
if (index === 0) {
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 绘制坐标轴
|
|
||||||
ctx.strokeStyle = '#F5F5F5';
|
|
||||||
labels.forEach((_, index) => {
|
|
||||||
ctx.beginPath();
|
|
||||||
const x1 = centerX + width * 0.6 * Math.cos(angleStep * index - Math.PI / 2);
|
|
||||||
const y1 = centerY + height * 0.6 * Math.sin(angleStep * index - Math.PI / 2);
|
|
||||||
const x = centerX + width * Math.cos(angleStep * index - Math.PI / 2);
|
|
||||||
const y = centerY + height * Math.sin(angleStep * index - Math.PI / 2);
|
|
||||||
ctx.moveTo(x1, y1);
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 4. 绘制数据区域
|
|
||||||
const pointList: Array<{x: number, y: number}> = [];
|
|
||||||
ctx.strokeStyle = '#256BFA';
|
|
||||||
ctx.fillStyle = 'rgba(37,107,250,0.24)';
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.beginPath();
|
|
||||||
|
|
||||||
data.forEach((score, index) => {
|
|
||||||
const x = centerX + width * (score / maxScore) * Math.cos(angleStep * index - Math.PI / 2);
|
|
||||||
const y = centerY + height * (score / maxScore) * Math.sin(angleStep * index - Math.PI / 2);
|
|
||||||
pointList.push({ x, y });
|
|
||||||
|
|
||||||
if (index === 0) {
|
|
||||||
ctx.moveTo(x, y);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(x, y);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.fill();
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// 5. 绘制数据点
|
|
||||||
ctx.fillStyle = '#256BFA';
|
|
||||||
pointList.forEach(point => {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(point.x, point.y, 4, 0, 2 * Math.PI);
|
|
||||||
ctx.fill();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 6. 绘制标签
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
ctx.fillStyle = '#000';
|
|
||||||
ctx.font = 'bold 12px sans-serif';
|
|
||||||
|
|
||||||
labels.forEach((label, index) => {
|
|
||||||
const x = centerX + (width + 30) * Math.cos(angleStep * index - Math.PI / 2);
|
|
||||||
const y = centerY + (height + 26) * Math.sin(angleStep * index - Math.PI / 2);
|
|
||||||
ctx.fillText(label, x, y);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 获取进度条类名(和移动端逻辑一致)
|
|
||||||
const getProgressClass = (index: number) => {
|
|
||||||
if (currentStep === 0) return '';
|
|
||||||
|
|
||||||
const floorIndex = Math.floor(currentStep);
|
|
||||||
|
|
||||||
if (index < floorIndex) {
|
|
||||||
return 'active';
|
|
||||||
} else if (index === floorIndex) {
|
|
||||||
const decimal = currentStep % 1;
|
|
||||||
const percent = Math.round(decimal * 100);
|
|
||||||
return `half${percent}`;
|
|
||||||
} else {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (competitivenessData?.matchScore !== undefined) {
|
|
||||||
// 计算当前步数(匹配分数转换为0-4的进度)
|
|
||||||
const score = competitivenessData.matchScore;
|
|
||||||
const step = score * 0.04; // 和移动端一样的计算方式
|
|
||||||
setCurrentStep(step);
|
|
||||||
}
|
|
||||||
}, [competitivenessData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (open && competitivenessData && canvasRef.current) {
|
|
||||||
// 初始化Canvas
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
canvas.width = 400; // 与移动端保持一致
|
|
||||||
canvas.height = 350; // 与移动端保持一致
|
|
||||||
|
|
||||||
drawRadarChart();
|
|
||||||
}
|
|
||||||
}, [open, competitivenessData]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
title="竞争力分析"
|
|
||||||
width={600}
|
|
||||||
open={open}
|
|
||||||
footer={null}
|
|
||||||
onCancel={() => props.onClose()}
|
|
||||||
maskClosable={true}
|
|
||||||
>
|
|
||||||
<Row gutter={[16, 16]}>
|
|
||||||
{/* 统计数据 */}
|
|
||||||
<Col span={24}>
|
|
||||||
<Row gutter={16}>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
|
||||||
<Statistic
|
|
||||||
title="总申请人数"
|
|
||||||
value={competitivenessData?.totalApplicants || 0}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
|
||||||
<Statistic
|
|
||||||
title="匹配分数"
|
|
||||||
value={competitivenessData?.matchScore || 0}
|
|
||||||
suffix="分"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
|
||||||
<Statistic
|
|
||||||
title="排名"
|
|
||||||
value={competitivenessData?.rank || 0}
|
|
||||||
suffix="位"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<Card>
|
|
||||||
<Statistic
|
|
||||||
title="超过百分比"
|
|
||||||
value={competitivenessData?.percentile || 0}
|
|
||||||
suffix="%"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
|
|
||||||
{/* 用户基本信息 */}
|
|
||||||
{values && (
|
|
||||||
<Col span={24}>
|
|
||||||
<Card title="用户基本信息" size="small">
|
|
||||||
<Row gutter={16}>
|
|
||||||
<Col span={6}>
|
|
||||||
<div>用户名:{values.name}</div>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<div>
|
|
||||||
期望薪资:{values.salaryMin}-{values.salaryMax}
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<div>学历:{values.education}</div>
|
|
||||||
</Col>
|
|
||||||
<Col span={6}>
|
|
||||||
<div>区域:{values.area}</div>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* 竞争力分析 */}
|
|
||||||
<Col span={24}>
|
|
||||||
<Card title="竞争力分析" size="small">
|
|
||||||
<div style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: '14px',
|
|
||||||
color: '#333',
|
|
||||||
lineHeight: '1.6',
|
|
||||||
padding: '10px 0'
|
|
||||||
}}>
|
|
||||||
三个月内共{competitivenessData?.totalApplicants || 0}位求职者申请,
|
|
||||||
你的简历匹配度为{competitivenessData?.matchScore || 0}分,
|
|
||||||
排名位于第{competitivenessData?.rank || 0}位,
|
|
||||||
超过{competitivenessData?.percentile || 0}%的竞争者,处在优秀位置。
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 雷达图 */}
|
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', padding: '20px 0' }}>
|
|
||||||
<canvas
|
|
||||||
ref={canvasRef}
|
|
||||||
style={{
|
|
||||||
border: 'none', // 移除边框,与移动端保持一致
|
|
||||||
width: '300px',
|
|
||||||
height: '250px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 匹配度进度条(使用DOM渲染,和移动端一样) */}
|
|
||||||
<div style={{ padding: '0 20px', marginTop: '10px' }}>
|
|
||||||
<div style={{
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: '14px',
|
|
||||||
color: '#000000',
|
|
||||||
marginBottom: '10px'
|
|
||||||
}}>
|
|
||||||
你与职位的匹配度
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 进度条容器 */}
|
|
||||||
<div className="progress-container">
|
|
||||||
{matchingDegree.map((_, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`progress-item ${getProgressClass(index)}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 进度条文字 */}
|
|
||||||
<div className="progress-text">
|
|
||||||
{matchingDegree.map((text, index) => (
|
|
||||||
<div key={index} style={{ width: '25%' }}>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Detail;
|
|
||||||
Reference in New Issue
Block a user