初始化12个产业教务系统项目
主要内容: - 包含12个产业的完整教务系统前端代码 - 智能启动脚本 (start-industry.sh) - 可视化产业导航页面 (index.html) - 项目文档 (README.md) 优化内容: - 删除所有node_modules和.yoyo文件夹,从7.5GB减少到2.7GB - 添加.gitignore文件避免上传不必要的文件 - 自动依赖管理和智能启动系统 产业列表: 1. 文旅产业 (5150) 2. 智能制造 (5151) 3. 智能开发 (5152) 4. 财经商贸 (5153) 5. 视觉设计 (5154) 6. 交通物流 (5155) 7. 大健康 (5156) 8. 土木水利 (5157) 9. 食品产业 (5158) 10. 化工产业 (5159) 11. 能源产业 (5160) 12. 环保产业 (5161) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
.curved-employment-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
> img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import CHARTIMG from "@/assets/images/JobStrategyDetailPage/bar_chart.png";
|
||||
import Locked from "@/components/Locked";
|
||||
import "./index.css";
|
||||
|
||||
export default ({ locked = true }) => {
|
||||
return (
|
||||
<div className="curved-employment-wrapper">
|
||||
<img src={CHARTIMG} />
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,264 @@
|
||||
.target-position-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
|
||||
* {
|
||||
user-select: none !important;
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
}
|
||||
|
||||
.target-position-content {
|
||||
width: 914px;
|
||||
height: 450px;
|
||||
margin-top: 50px;
|
||||
margin-left: 50px;
|
||||
position: relative;
|
||||
|
||||
.batch-icon {
|
||||
width: 340px;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: -40px;
|
||||
top: -30px;
|
||||
background-image: url("@/assets/images/JobStrategyDetailPage/batch.png");
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
> span {
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
color: #ffffff;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-top: 60px; /* 第一批次文字下移到圆柱体中心 */
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin-top: 25px; /* 第二批次保持原位 */
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
margin-top: 25px; /* 第三批次保持原位 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.batch-content {
|
||||
width: 702px;
|
||||
height: 120px;
|
||||
position: absolute;
|
||||
left: 299px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding-left: 25px;
|
||||
padding-right: 20px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
border-radius: 8px;
|
||||
scroll-behavior: smooth;
|
||||
/* 移除背景图片,使用CSS渐变 */
|
||||
|
||||
/* 自定义滚动条样式 */
|
||||
&::-webkit-scrollbar {
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: rgba(233, 226, 255, 0.1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(233, 226, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
transition: background 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: rgba(233, 226, 255, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
/* Firefox滚动条 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(233, 226, 255, 0.5) rgba(233, 226, 255, 0.1);
|
||||
|
||||
.avatar-wrapper {
|
||||
width: 85px;
|
||||
height: 100px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 15px;
|
||||
flex-shrink: 0;
|
||||
cursor: grab;
|
||||
transition: all 0.3s ease;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
/* 仅在悬停岗位名称时显示气泡 */
|
||||
.student-name:hover + .position-tooltip {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transform: translate(-50%, -8px);
|
||||
}
|
||||
|
||||
.student-avatar {
|
||||
position: relative;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
border: 2px solid #ffffff;
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
}
|
||||
|
||||
.student-name {
|
||||
margin-top: 8px;
|
||||
width: 85px;
|
||||
height: 24px;
|
||||
border-radius: 12px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.8);
|
||||
text-align: center;
|
||||
color: #1d2129;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 气泡提示框 */
|
||||
.position-tooltip {
|
||||
position: absolute;
|
||||
bottom: 35px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
background-color: rgba(0, 0, 0, 0.85);
|
||||
color: #ffffff;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
/* 气泡箭头 */
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: rgba(0, 0, 0, 0.85) transparent transparent transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.batch-content1 {
|
||||
top: 5px;
|
||||
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
|
||||
background: linear-gradient(to right,
|
||||
#E9E2FF 0%,
|
||||
rgba(233, 226, 255, 0.95) 10%,
|
||||
rgba(233, 226, 255, 0.9) 20%,
|
||||
rgba(233, 226, 255, 0.85) 30%,
|
||||
rgba(233, 226, 255, 0.75) 40%,
|
||||
rgba(233, 226, 255, 0.65) 50%,
|
||||
rgba(233, 226, 255, 0.5) 60%,
|
||||
rgba(233, 226, 255, 0.35) 70%,
|
||||
rgba(233, 226, 255, 0.2) 80%,
|
||||
rgba(233, 226, 255, 0.1) 90%,
|
||||
rgba(233, 226, 255, 0) 100%);
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.batch-content2 {
|
||||
top: 140px;
|
||||
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
|
||||
background: linear-gradient(to right,
|
||||
#E9E2FF 0%,
|
||||
rgba(233, 226, 255, 0.95) 10%,
|
||||
rgba(233, 226, 255, 0.9) 20%,
|
||||
rgba(233, 226, 255, 0.85) 30%,
|
||||
rgba(233, 226, 255, 0.75) 40%,
|
||||
rgba(233, 226, 255, 0.65) 50%,
|
||||
rgba(233, 226, 255, 0.5) 60%,
|
||||
rgba(233, 226, 255, 0.35) 70%,
|
||||
rgba(233, 226, 255, 0.2) 80%,
|
||||
rgba(233, 226, 255, 0.1) 90%,
|
||||
rgba(233, 226, 255, 0) 100%);
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
|
||||
.batch-content3 {
|
||||
top: 275px;
|
||||
/* 统一渐变样式 - E9E2FF从左到右缓慢淡化 */
|
||||
background: linear-gradient(to right,
|
||||
#E9E2FF 0%,
|
||||
rgba(233, 226, 255, 0.95) 10%,
|
||||
rgba(233, 226, 255, 0.9) 20%,
|
||||
rgba(233, 226, 255, 0.85) 30%,
|
||||
rgba(233, 226, 255, 0.75) 40%,
|
||||
rgba(233, 226, 255, 0.65) 50%,
|
||||
rgba(233, 226, 255, 0.5) 60%,
|
||||
rgba(233, 226, 255, 0.35) 70%,
|
||||
rgba(233, 226, 255, 0.2) 80%,
|
||||
rgba(233, 226, 255, 0.1) 90%,
|
||||
rgba(233, 226, 255, 0) 100%);
|
||||
border: 2px solid #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,651 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Modal, Message, Tooltip } from "@arco-design/web-react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragOverlay,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
horizontalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import Locked from "@/components/Locked";
|
||||
import jobLevelData from "@/data/joblevel.json";
|
||||
import "./index.css";
|
||||
|
||||
// 可排序的岗位组件
|
||||
const SortablePosition = ({ id, position, getPositionAvatar }) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: 'grab',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="avatar-wrapper"
|
||||
>
|
||||
<div className="student-avatar">
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(position)}
|
||||
draggable={false}
|
||||
style={{ userSelect: 'none', WebkitUserSelect: 'none', pointerEvents: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
<span className="student-name" style={{ userSelect: 'none', WebkitUserSelect: 'none' }}>{position}</span>
|
||||
<div className="position-tooltip">{position}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ locked = false }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const batch1Ref = useRef(null);
|
||||
const batch2Ref = useRef(null);
|
||||
const batch3Ref = useRef(null);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
const [pendingNavigation, setPendingNavigation] = useState(null);
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
|
||||
// 处理拖拽开始
|
||||
const handleDragStart = (event) => {
|
||||
const { active } = event;
|
||||
setActiveId(active.id);
|
||||
};
|
||||
|
||||
// 处理拖拽结束
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
setActiveId(null);
|
||||
|
||||
if (!over) return;
|
||||
|
||||
const activePosition = active.id.split('-').slice(1).join('-');
|
||||
const activeBatch = active.id.split('-')[0];
|
||||
|
||||
// 确定目标批次
|
||||
let targetBatch = over.id.split('-')[0];
|
||||
let targetPosition = over.id.split('-').slice(1).join('-');
|
||||
|
||||
// 如果目标和源相同,不做任何操作
|
||||
if (active.id === over.id) return;
|
||||
|
||||
setBatchPositions((prev) => {
|
||||
const newPositions = { ...prev };
|
||||
|
||||
// 如果是同一批次内的移动
|
||||
if (activeBatch === targetBatch) {
|
||||
const batch = [...newPositions[activeBatch]];
|
||||
const activeIndex = batch.indexOf(activePosition);
|
||||
const overIndex = batch.indexOf(targetPosition);
|
||||
|
||||
if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
|
||||
// 移除原位置
|
||||
batch.splice(activeIndex, 1);
|
||||
// 计算新位置索引
|
||||
const newOverIndex = activeIndex < overIndex ? overIndex - 1 : overIndex;
|
||||
// 插入到新位置
|
||||
batch.splice(newOverIndex, 0, activePosition);
|
||||
newPositions[activeBatch] = batch;
|
||||
}
|
||||
} else {
|
||||
// 跨批次移动
|
||||
// 从原批次删除
|
||||
const sourceBatch = [...newPositions[activeBatch]];
|
||||
const activeIndex = sourceBatch.indexOf(activePosition);
|
||||
if (activeIndex !== -1) {
|
||||
sourceBatch.splice(activeIndex, 1);
|
||||
newPositions[activeBatch] = sourceBatch;
|
||||
}
|
||||
|
||||
// 添加到目标批次
|
||||
const targetBatchArray = [...newPositions[targetBatch]];
|
||||
const overIndex = targetBatchArray.indexOf(targetPosition);
|
||||
if (overIndex !== -1) {
|
||||
// 插入到特定位置
|
||||
targetBatchArray.splice(overIndex, 0, activePosition);
|
||||
} else {
|
||||
// 如果目标批次为空或找不到目标位置,添加到末尾
|
||||
targetBatchArray.push(activePosition);
|
||||
}
|
||||
newPositions[targetBatch] = targetBatchArray;
|
||||
}
|
||||
|
||||
return newPositions;
|
||||
});
|
||||
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
// 处理拖拽到其他批次 - 仅用于预览,不实际移动
|
||||
const handleDragOver = (event) => {
|
||||
// 空函数 - 我们只在dragEnd时处理实际的移动
|
||||
};
|
||||
|
||||
// 监听路由变化
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [hasChanges]);
|
||||
|
||||
// 拦截导航 - 监听所有可能的页面切换
|
||||
useEffect(() => {
|
||||
if (!hasChanges) return;
|
||||
|
||||
const handleNavigation = (e) => {
|
||||
// 如果点击的是弹窗内的元素,不拦截
|
||||
if (e.target.closest('.arco-modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是链接点击
|
||||
const link = e.target.closest('a') || (e.target.tagName === 'A' ? e.target : null);
|
||||
const button = e.target.closest('button') || (e.target.tagName === 'BUTTON' ? e.target : null);
|
||||
|
||||
// 检查是否是导航相关的元素
|
||||
if (link || (button && (button.textContent?.includes('返回') || button.onclick))) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowSaveModal(true);
|
||||
|
||||
if (link) {
|
||||
setPendingNavigation(link.href);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听点击事件(捕获阶段)
|
||||
document.addEventListener('click', handleNavigation, true);
|
||||
|
||||
// 监听浏览器后退/前进
|
||||
const handlePopState = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
setShowSaveModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleNavigation, true);
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
};
|
||||
}, [hasChanges]);
|
||||
|
||||
useEffect(() => {
|
||||
// 添加鼠标滚轮事件监听,实现横向滚动
|
||||
const handleWheel = (e, ref) => {
|
||||
if (ref.current && ref.current.contains(e.target)) {
|
||||
e.preventDefault();
|
||||
ref.current.scrollLeft += e.deltaY;
|
||||
}
|
||||
};
|
||||
|
||||
const batch1El = batch1Ref.current;
|
||||
const batch2El = batch2Ref.current;
|
||||
const batch3El = batch3Ref.current;
|
||||
|
||||
const wheel1Handler = (e) => handleWheel(e, batch1Ref);
|
||||
const wheel2Handler = (e) => handleWheel(e, batch2Ref);
|
||||
const wheel3Handler = (e) => handleWheel(e, batch3Ref);
|
||||
|
||||
if (batch1El) batch1El.addEventListener('wheel', wheel1Handler, { passive: false });
|
||||
if (batch2El) batch2El.addEventListener('wheel', wheel2Handler, { passive: false });
|
||||
if (batch3El) batch3El.addEventListener('wheel', wheel3Handler, { passive: false });
|
||||
|
||||
return () => {
|
||||
if (batch1El) batch1El.removeEventListener('wheel', wheel1Handler);
|
||||
if (batch2El) batch2El.removeEventListener('wheel', wheel2Handler);
|
||||
if (batch3El) batch3El.removeEventListener('wheel', wheel3Handler);
|
||||
};
|
||||
}, []);
|
||||
// 根据岗位名称获取头像
|
||||
const getPositionAvatar = (positionName) => {
|
||||
const jobData = jobLevelData.data;
|
||||
for (const [key, levelData] of Object.entries(jobData)) {
|
||||
const found = levelData.list.find(item => item.position_name === positionName);
|
||||
if (found) {
|
||||
return found.img;
|
||||
}
|
||||
}
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png"; // 默认头像
|
||||
};
|
||||
|
||||
// 定义三个批次的岗位数据
|
||||
const initialBatchPositions = {
|
||||
batch1: [
|
||||
"电力销售总监助理",
|
||||
"电力巡检无人机飞手",
|
||||
"电力巡检员",
|
||||
"电厂运行技术员",
|
||||
"风电技术员",
|
||||
"风机叶片生产技术员",
|
||||
"节能咨询工程师助理",
|
||||
"光伏材料制备技术员",
|
||||
"光伏电池生产技术员",
|
||||
"光伏踏勘技术员",
|
||||
"封装技术员",
|
||||
"电池生产操作员",
|
||||
"电池质量检验员",
|
||||
"输配电线路技术员",
|
||||
"电力安全员",
|
||||
"充换电站运维工程师",
|
||||
"电气自动化技术员"
|
||||
],
|
||||
batch2: [
|
||||
"EHS工程师",
|
||||
"储能电站运维工程师",
|
||||
"储能系统工程师助理",
|
||||
"电力系统调度员",
|
||||
"电力交易员",
|
||||
"电网需求分析师助理",
|
||||
"继电保护工程师",
|
||||
"电力运维工程师",
|
||||
"电气运维工程师",
|
||||
"设备维护工程师",
|
||||
"风电场运维工程师",
|
||||
"光伏组件工程师",
|
||||
"光伏电站运维工程师",
|
||||
"电池pack测试工程师",
|
||||
"电力系统工程师助理",
|
||||
"虚拟电厂运营员",
|
||||
"电气自动化工程师"
|
||||
],
|
||||
batch3: [
|
||||
"配电运维工程师",
|
||||
"变电站运维工程师",
|
||||
"储能电站建设工程师",
|
||||
"智能电网运维工程师",
|
||||
"风机叶片研发工程师",
|
||||
"工业节能工程师",
|
||||
"光伏电池工艺工程师",
|
||||
"光伏组件工艺工程师",
|
||||
"电池PACK工程师",
|
||||
"电池pack结构工程师",
|
||||
"输电线路设计工程师",
|
||||
"自动化控制工程师"
|
||||
]
|
||||
};
|
||||
|
||||
const [batchPositions, setBatchPositions] = useState(initialBatchPositions);
|
||||
|
||||
// 拖拽传感器设置
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 8,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 获取所有岗位ID
|
||||
const getAllPositionIds = () => {
|
||||
const ids = [];
|
||||
Object.entries(batchPositions).forEach(([batch, positions]) => {
|
||||
positions.forEach(position => {
|
||||
ids.push(`${batch}-${position}`);
|
||||
});
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ userSelect: 'none', WebkitUserSelect: 'none', MozUserSelect: 'none', msUserSelect: 'none' }}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
<div className="target-position-wrapper">
|
||||
<div className="target-position-content">
|
||||
<div className="batch-icon">
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第一批次
|
||||
<Tooltip content="通过培训后能直接上岗的岗位,入职成功率最高。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第二批次
|
||||
<Tooltip content="需积累一定工作经验后可争取的晋升岗位方向。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第三批次
|
||||
<Tooltip content="需长期经验和能力沉淀,可作为学员的终极职业目标。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 第一批次 */}
|
||||
<div className="batch-content batch-content1" ref={batch1Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch1.map(p => `batch1-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch1.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch1-${position}`}
|
||||
id={`batch1-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第二批次 */}
|
||||
<div className="batch-content batch-content2" ref={batch2Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch2.map(p => `batch2-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch2.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch2-${position}`}
|
||||
id={`batch2-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第三批次 */}
|
||||
<div className="batch-content batch-content3" ref={batch3Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch3.map(p => `batch3-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch3.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch3-${position}`}
|
||||
id={`batch3-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 拖拽覆盖层 */}
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'grabbing',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
border: '2px solid #ffffff'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(activeId.split('-').slice(1).join('-'))}
|
||||
draggable={false}
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
|
||||
{/* 保存提示模态框 */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: '#1d2129',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15h-2v-2h2v2zm0-4h-2V5h2v6z" fill="#2c7fff"/>
|
||||
</svg>
|
||||
保存更改
|
||||
</div>
|
||||
}
|
||||
visible={showSaveModal}
|
||||
onCancel={() => {
|
||||
setShowSaveModal(false);
|
||||
setPendingNavigation(null);
|
||||
// 只关闭弹窗,保留hasChanges状态,下次点击返回还会弹出
|
||||
}}
|
||||
footer={[
|
||||
<button
|
||||
key="cancel"
|
||||
className="arco-btn arco-btn-secondary"
|
||||
onClick={() => {
|
||||
setShowSaveModal(false);
|
||||
setHasChanges(false);
|
||||
setPendingNavigation(null);
|
||||
navigate('/job-strategy'); // 返回定制求职策略页面
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 20px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e5e6eb',
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#4e5969',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f7f8fa';
|
||||
e.currentTarget.style.borderColor = '#c9cdd4';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#ffffff';
|
||||
e.currentTarget.style.borderColor = '#e5e6eb';
|
||||
}}
|
||||
>
|
||||
放弃更改
|
||||
</button>,
|
||||
<div
|
||||
key="save-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'inline-block'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'save-tooltip';
|
||||
tooltip.textContent = '非导师和学生本人无修改权限';
|
||||
tooltip.style.cssText = `
|
||||
position: absolute;
|
||||
bottom: 120%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, #1d2129 0%, #2e3440 100%);
|
||||
color: #ffffff;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(5px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 添加小箭头
|
||||
const arrow = document.createElement('div');
|
||||
arrow.style.cssText = `
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: #2e3440 transparent transparent transparent;
|
||||
`;
|
||||
tooltip.appendChild(arrow);
|
||||
e.currentTarget.appendChild(tooltip);
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const tooltip = e.currentTarget.querySelector('.save-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.remove();
|
||||
}
|
||||
const style = document.querySelector('style');
|
||||
if (style && style.textContent.includes('fadeIn')) {
|
||||
style.remove();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="arco-btn arco-btn-primary"
|
||||
disabled
|
||||
style={{
|
||||
padding: '8px 24px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: '#e5e6eb',
|
||||
color: '#86909c',
|
||||
cursor: 'not-allowed',
|
||||
border: 'none',
|
||||
opacity: '0.6',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
保存更改
|
||||
</button>
|
||||
</div>
|
||||
]}
|
||||
style={{
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: '12px'
|
||||
}}>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ marginTop: '2px', flexShrink: 0 }}>
|
||||
<path d="M9 13h2v2H9v-2zm0-8h2v6H9V5zm.99-5C4.47 0 0 4.48 0 10s4.47 10 9.99 10C15.52 20 20 15.52 20 10S15.52 0 9.99 0zM10 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" fill="#ff7d00"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style={{ margin: 0, fontWeight: '500', color: '#1d2129', marginBottom: '8px' }}>
|
||||
您对岗位顺序进行了修改
|
||||
</p>
|
||||
<p style={{ margin: 0, fontSize: '13px', color: '#86909c' }}>
|
||||
离开此页面前,是否要保存您的更改?未保存的更改将会丢失。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,650 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Modal, Message, Tooltip } from "@arco-design/web-react";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
DragOverlay,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
horizontalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import Locked from "@/components/Locked";
|
||||
import jobLevelData from "@/data/joblevel.json";
|
||||
import "./index.css";
|
||||
|
||||
// 可排序的岗位组件
|
||||
const SortablePosition = ({ id, position, getPositionAvatar }) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
isDragging,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
opacity: isDragging ? 0.5 : 1,
|
||||
cursor: 'grab',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
className="avatar-wrapper"
|
||||
>
|
||||
<div className="student-avatar">
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(position)}
|
||||
draggable={false}
|
||||
style={{ userSelect: 'none', WebkitUserSelect: 'none', pointerEvents: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
<span className="student-name" style={{ userSelect: 'none', WebkitUserSelect: 'none' }}>{position}</span>
|
||||
<div className="position-tooltip">{position}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ({ locked = false }) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const batch1Ref = useRef(null);
|
||||
const batch2Ref = useRef(null);
|
||||
const batch3Ref = useRef(null);
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
const [showSaveModal, setShowSaveModal] = useState(false);
|
||||
const [pendingNavigation, setPendingNavigation] = useState(null);
|
||||
const [activeId, setActiveId] = useState(null);
|
||||
|
||||
// 处理拖拽开始
|
||||
const handleDragStart = (event) => {
|
||||
const { active } = event;
|
||||
setActiveId(active.id);
|
||||
};
|
||||
|
||||
// 处理拖拽结束
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
setActiveId(null);
|
||||
|
||||
if (!over) return;
|
||||
|
||||
const activePosition = active.id.split('-').slice(1).join('-');
|
||||
const activeBatch = active.id.split('-')[0];
|
||||
|
||||
// 确定目标批次
|
||||
let targetBatch = over.id.split('-')[0];
|
||||
let targetPosition = over.id.split('-').slice(1).join('-');
|
||||
|
||||
// 如果目标和源相同,不做任何操作
|
||||
if (active.id === over.id) return;
|
||||
|
||||
setBatchPositions((prev) => {
|
||||
const newPositions = { ...prev };
|
||||
|
||||
// 如果是同一批次内的移动
|
||||
if (activeBatch === targetBatch) {
|
||||
const batch = [...newPositions[activeBatch]];
|
||||
const activeIndex = batch.indexOf(activePosition);
|
||||
const overIndex = batch.indexOf(targetPosition);
|
||||
|
||||
if (activeIndex !== -1 && overIndex !== -1 && activeIndex !== overIndex) {
|
||||
// 移除原位置
|
||||
batch.splice(activeIndex, 1);
|
||||
// 计算新位置索引
|
||||
const newOverIndex = activeIndex < overIndex ? overIndex - 1 : overIndex;
|
||||
// 插入到新位置
|
||||
batch.splice(newOverIndex, 0, activePosition);
|
||||
newPositions[activeBatch] = batch;
|
||||
}
|
||||
} else {
|
||||
// 跨批次移动
|
||||
// 从原批次删除
|
||||
const sourceBatch = [...newPositions[activeBatch]];
|
||||
const activeIndex = sourceBatch.indexOf(activePosition);
|
||||
if (activeIndex !== -1) {
|
||||
sourceBatch.splice(activeIndex, 1);
|
||||
newPositions[activeBatch] = sourceBatch;
|
||||
}
|
||||
|
||||
// 添加到目标批次
|
||||
const targetBatchArray = [...newPositions[targetBatch]];
|
||||
const overIndex = targetBatchArray.indexOf(targetPosition);
|
||||
if (overIndex !== -1) {
|
||||
// 插入到特定位置
|
||||
targetBatchArray.splice(overIndex, 0, activePosition);
|
||||
} else {
|
||||
// 如果目标批次为空或找不到目标位置,添加到末尾
|
||||
targetBatchArray.push(activePosition);
|
||||
}
|
||||
newPositions[targetBatch] = targetBatchArray;
|
||||
}
|
||||
|
||||
return newPositions;
|
||||
});
|
||||
|
||||
setHasChanges(true);
|
||||
};
|
||||
|
||||
// 处理拖拽到其他批次 - 仅用于预览,不实际移动
|
||||
const handleDragOver = (event) => {
|
||||
// 空函数 - 我们只在dragEnd时处理实际的移动
|
||||
};
|
||||
|
||||
// 监听路由变化
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||
}, [hasChanges]);
|
||||
|
||||
// 拦截导航 - 监听所有可能的页面切换
|
||||
useEffect(() => {
|
||||
if (!hasChanges) return;
|
||||
|
||||
const handleNavigation = (e) => {
|
||||
// 如果点击的是弹窗内的元素,不拦截
|
||||
if (e.target.closest('.arco-modal')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否是链接点击
|
||||
const link = e.target.closest('a') || (e.target.tagName === 'A' ? e.target : null);
|
||||
const button = e.target.closest('button') || (e.target.tagName === 'BUTTON' ? e.target : null);
|
||||
|
||||
// 检查是否是导航相关的元素
|
||||
if (link || (button && (button.textContent?.includes('返回') || button.onclick))) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setShowSaveModal(true);
|
||||
|
||||
if (link) {
|
||||
setPendingNavigation(link.href);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 监听点击事件(捕获阶段)
|
||||
document.addEventListener('click', handleNavigation, true);
|
||||
|
||||
// 监听浏览器后退/前进
|
||||
const handlePopState = (e) => {
|
||||
if (hasChanges) {
|
||||
e.preventDefault();
|
||||
setShowSaveModal(true);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('popstate', handlePopState);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleNavigation, true);
|
||||
window.removeEventListener('popstate', handlePopState);
|
||||
};
|
||||
}, [hasChanges]);
|
||||
|
||||
useEffect(() => {
|
||||
// 添加鼠标滚轮事件监听,实现横向滚动
|
||||
const handleWheel = (e, ref) => {
|
||||
if (ref.current && ref.current.contains(e.target)) {
|
||||
e.preventDefault();
|
||||
ref.current.scrollLeft += e.deltaY;
|
||||
}
|
||||
};
|
||||
|
||||
const batch1El = batch1Ref.current;
|
||||
const batch2El = batch2Ref.current;
|
||||
const batch3El = batch3Ref.current;
|
||||
|
||||
const wheel1Handler = (e) => handleWheel(e, batch1Ref);
|
||||
const wheel2Handler = (e) => handleWheel(e, batch2Ref);
|
||||
const wheel3Handler = (e) => handleWheel(e, batch3Ref);
|
||||
|
||||
if (batch1El) batch1El.addEventListener('wheel', wheel1Handler, { passive: false });
|
||||
if (batch2El) batch2El.addEventListener('wheel', wheel2Handler, { passive: false });
|
||||
if (batch3El) batch3El.addEventListener('wheel', wheel3Handler, { passive: false });
|
||||
|
||||
return () => {
|
||||
if (batch1El) batch1El.removeEventListener('wheel', wheel1Handler);
|
||||
if (batch2El) batch2El.removeEventListener('wheel', wheel2Handler);
|
||||
if (batch3El) batch3El.removeEventListener('wheel', wheel3Handler);
|
||||
};
|
||||
}, []);
|
||||
// 根据岗位名称获取头像
|
||||
const getPositionAvatar = (positionName) => {
|
||||
const jobData = jobLevelData.data;
|
||||
for (const [key, levelData] of Object.entries(jobData)) {
|
||||
const found = levelData.list.find(item => item.position_name === positionName);
|
||||
if (found) {
|
||||
return found.img;
|
||||
}
|
||||
}
|
||||
return "https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_teacher-avatar/recuUpSO4gUtJz.png"; // 默认头像
|
||||
};
|
||||
|
||||
// 定义三个批次的岗位数据
|
||||
const initialBatchPositions = {
|
||||
batch1: [
|
||||
"3D打印技术员",
|
||||
"产品测试技术员",
|
||||
"安规测试员",
|
||||
"工业产品设计师助理",
|
||||
"工业产品经理助理",
|
||||
"电气装配技术员",
|
||||
"BOM工程师",
|
||||
"工业机器人调试技术员",
|
||||
"工业机器人工程师助理",
|
||||
"机器视觉调试技术员",
|
||||
"CAD制图员",
|
||||
"自动化技术员",
|
||||
"设备调试技术员",
|
||||
"电气自动化管培生"
|
||||
],
|
||||
batch2: [
|
||||
"3D打印工程师",
|
||||
"3D打印检测员",
|
||||
"CNC五轴操作技术员",
|
||||
"CNC编程工程师",
|
||||
"PLC编程工程师",
|
||||
"PLC控制工程师",
|
||||
"钣金工程师",
|
||||
"产品测试工程师",
|
||||
"产品外观设计师",
|
||||
"Rhino建模师",
|
||||
"试制工程师",
|
||||
"生产经理储备干部",
|
||||
"模具工程师",
|
||||
"模具设计师",
|
||||
"注塑工艺工程师",
|
||||
"电火花加工工程师",
|
||||
"自动化仪表工程师",
|
||||
"MES制造经理助理"
|
||||
],
|
||||
batch3: [
|
||||
"机械工艺工程师",
|
||||
"工业产品设计师",
|
||||
"产品结构设计师",
|
||||
"电气工程师",
|
||||
"非标自动化工程师",
|
||||
"自动化产线设计师",
|
||||
"非标设备电气工程师",
|
||||
"工业机器人工程师",
|
||||
"焊接工艺工程师",
|
||||
"机加工工艺工程师",
|
||||
"机器视觉调试工程师",
|
||||
"自动化控制工程师",
|
||||
"自动化设备调试工程师"
|
||||
]
|
||||
};
|
||||
|
||||
const [batchPositions, setBatchPositions] = useState(initialBatchPositions);
|
||||
|
||||
// 拖拽传感器设置
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: {
|
||||
distance: 8,
|
||||
},
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 获取所有岗位ID
|
||||
const getAllPositionIds = () => {
|
||||
const ids = [];
|
||||
Object.entries(batchPositions).forEach(([batch, positions]) => {
|
||||
positions.forEach(position => {
|
||||
ids.push(`${batch}-${position}`);
|
||||
});
|
||||
});
|
||||
return ids;
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ userSelect: 'none', WebkitUserSelect: 'none', MozUserSelect: 'none', msUserSelect: 'none' }}>
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
<div className="target-position-wrapper">
|
||||
<div className="target-position-content">
|
||||
<div className="batch-icon">
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第一批次
|
||||
<Tooltip content="通过培训后能直接上岗的岗位,入职成功率最高。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第二批次
|
||||
<Tooltip content="需积累一定工作经验后可争取的晋升岗位方向。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
<span style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
|
||||
第三批次
|
||||
<Tooltip content="需长期经验和能力沉淀,可作为学员的终极职业目标。">
|
||||
<span style={{ fontSize: '12px',
|
||||
color: '#ffffff',
|
||||
backgroundColor: '#4080ff',
|
||||
borderRadius: '50%',
|
||||
width: '16px',
|
||||
height: '16px',
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontWeight: 'bold' }}>?</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 第一批次 */}
|
||||
<div className="batch-content batch-content1" ref={batch1Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch1.map(p => `batch1-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch1.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch1-${position}`}
|
||||
id={`batch1-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第二批次 */}
|
||||
<div className="batch-content batch-content2" ref={batch2Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch2.map(p => `batch2-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch2.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch2-${position}`}
|
||||
id={`batch2-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{/* 第三批次 */}
|
||||
<div className="batch-content batch-content3" ref={batch3Ref}>
|
||||
<SortableContext
|
||||
items={batchPositions.batch3.map(p => `batch3-${p}`)}
|
||||
strategy={horizontalListSortingStrategy}
|
||||
>
|
||||
{batchPositions.batch3.map((position) => (
|
||||
<SortablePosition
|
||||
key={`batch3-${position}`}
|
||||
id={`batch3-${position}`}
|
||||
position={position}
|
||||
getPositionAvatar={getPositionAvatar}
|
||||
/>
|
||||
))}
|
||||
</SortableContext>
|
||||
</div>
|
||||
|
||||
{locked && (
|
||||
<Locked text="该板块将在「垂直能力提升」阶段开放,完成线上1V1求职策略定制后解锁" />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 拖拽覆盖层 */}
|
||||
<DragOverlay>
|
||||
{activeId ? (
|
||||
<div
|
||||
style={{
|
||||
cursor: 'grabbing',
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
position: 'relative',
|
||||
width: '64px',
|
||||
height: '64px',
|
||||
borderRadius: '50%',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: '#ffffff',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
border: '2px solid #ffffff'
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt="avatar"
|
||||
src={getPositionAvatar(activeId.split('-').slice(1).join('-'))}
|
||||
draggable={false}
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
pointerEvents: 'none',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
objectFit: 'cover'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
|
||||
{/* 保存提示模态框 */}
|
||||
<Modal
|
||||
title={
|
||||
<div style={{
|
||||
fontSize: '18px',
|
||||
fontWeight: '600',
|
||||
color: '#1d2129',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '8px'
|
||||
}}>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15h-2v-2h2v2zm0-4h-2V5h2v6z" fill="#2c7fff"/>
|
||||
</svg>
|
||||
保存更改
|
||||
</div>
|
||||
}
|
||||
visible={showSaveModal}
|
||||
onCancel={() => {
|
||||
setShowSaveModal(false);
|
||||
setPendingNavigation(null);
|
||||
// 只关闭弹窗,保留hasChanges状态,下次点击返回还会弹出
|
||||
}}
|
||||
footer={[
|
||||
<button
|
||||
key="cancel"
|
||||
className="arco-btn arco-btn-secondary"
|
||||
onClick={() => {
|
||||
setShowSaveModal(false);
|
||||
setHasChanges(false);
|
||||
setPendingNavigation(null);
|
||||
navigate('/job-strategy'); // 返回定制求职策略页面
|
||||
}}
|
||||
style={{
|
||||
padding: '8px 20px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid #e5e6eb',
|
||||
backgroundColor: '#ffffff',
|
||||
color: '#4e5969',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#f7f8fa';
|
||||
e.currentTarget.style.borderColor = '#c9cdd4';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = '#ffffff';
|
||||
e.currentTarget.style.borderColor = '#e5e6eb';
|
||||
}}
|
||||
>
|
||||
放弃更改
|
||||
</button>,
|
||||
<div
|
||||
key="save-wrapper"
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'inline-block'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'save-tooltip';
|
||||
tooltip.textContent = '非导师和学生本人无修改权限';
|
||||
tooltip.style.cssText = `
|
||||
position: absolute;
|
||||
bottom: 120%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: linear-gradient(135deg, #1d2129 0%, #2e3440 100%);
|
||||
color: #ffffff;
|
||||
padding: 10px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateX(-50%) translateY(5px); }
|
||||
to { opacity: 1; transform: translateX(-50%) translateY(0); }
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 添加小箭头
|
||||
const arrow = document.createElement('div');
|
||||
arrow.style.cssText = `
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 6px 6px 0 6px;
|
||||
border-color: #2e3440 transparent transparent transparent;
|
||||
`;
|
||||
tooltip.appendChild(arrow);
|
||||
e.currentTarget.appendChild(tooltip);
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
const tooltip = e.currentTarget.querySelector('.save-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.remove();
|
||||
}
|
||||
const style = document.querySelector('style');
|
||||
if (style && style.textContent.includes('fadeIn')) {
|
||||
style.remove();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="arco-btn arco-btn-primary"
|
||||
disabled
|
||||
style={{
|
||||
padding: '8px 24px',
|
||||
fontSize: '14px',
|
||||
fontWeight: '500',
|
||||
borderRadius: '6px',
|
||||
backgroundColor: '#e5e6eb',
|
||||
color: '#86909c',
|
||||
cursor: 'not-allowed',
|
||||
border: 'none',
|
||||
opacity: '0.6',
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
保存更改
|
||||
</button>
|
||||
</div>
|
||||
]}
|
||||
style={{
|
||||
borderRadius: '12px'
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: '12px'
|
||||
}}>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" style={{ marginTop: '2px', flexShrink: 0 }}>
|
||||
<path d="M9 13h2v2H9v-2zm0-8h2v6H9V5zm.99-5C4.47 0 0 4.48 0 10s4.47 10 9.99 10C15.52 20 20 15.52 20 10S15.52 0 9.99 0zM10 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z" fill="#ff7d00"/>
|
||||
</svg>
|
||||
<div>
|
||||
<p style={{ margin: 0, fontWeight: '500', color: '#1d2129', marginBottom: '8px' }}>
|
||||
您对岗位顺序进行了修改
|
||||
</p>
|
||||
<p style={{ margin: 0, fontSize: '13px', color: '#86909c' }}>
|
||||
离开此页面前,是否要保存您的更改?未保存的更改将会丢失。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
232
frontend_能源/src/pages/JobStrategyDetailPage/index.css
Normal file
232
frontend_能源/src/pages/JobStrategyDetailPage/index.css
Normal file
@@ -0,0 +1,232 @@
|
||||
.job-strategy-detail-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
/* 禁止所有图片的拖拽和选中 */
|
||||
img {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-khtml-user-drag: none;
|
||||
-moz-user-drag: none;
|
||||
-o-user-drag: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 禁止所有文本选中 */
|
||||
* {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/* 返回按钮样式 */
|
||||
.back-button-wrapper {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 30px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.back-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e6eb;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #4e5969;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
background-color: #f7f8fa;
|
||||
border-color: #2c7aff;
|
||||
color: #2c7aff;
|
||||
transform: translateX(-2px);
|
||||
box-shadow: 0 4px 8px rgba(44, 122, 255, 0.15);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98) translateX(-2px);
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.back-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-strategy-detail-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
background-image: linear-gradient(180deg, #d7e5ff, #eff4fb);
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
border: 2px solid #fff;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
|
||||
.job-strategy-detail-header {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 310px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 25px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
z-index: 5;
|
||||
|
||||
.nav-item {
|
||||
color: #86909c;
|
||||
background-color: #f4f7f9;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
width: 140px;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
cursor: pointer;
|
||||
border-radius: 25px;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
background-color: #e8f3ff;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-text {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 优先目标岗位图标 */
|
||||
.target-icon {
|
||||
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5qlmzVhH.png");
|
||||
}
|
||||
|
||||
/* 曲线就业方案图标 */
|
||||
.curved-icon {
|
||||
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVUShwMZfFm.png");
|
||||
}
|
||||
}
|
||||
|
||||
.item-active {
|
||||
background-color: #0077ff !important;
|
||||
color: #fff !important;
|
||||
|
||||
/* 选中状态的优先目标岗位图标 */
|
||||
.target-icon {
|
||||
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuUY5s6knA9u.png");
|
||||
}
|
||||
|
||||
/* 选中状态的曲线就业方案图标 */
|
||||
.curved-icon {
|
||||
background-image: url("https://ddcz-1315997005.cos.ap-nanjing.myqcloud.com/static/img/teach_sys_icon/recuVUSJS109uJ.png");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-strategy-detail-content {
|
||||
width: 100%;
|
||||
height: calc(100% - 80px);
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
margin-top: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
.job-strategy-detail-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
background-image: linear-gradient(180deg, #d7e5ff, #eff4fb);
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
border: 2px solid #fff;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
|
||||
.job-strategy-detail-header {
|
||||
width: 310px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
background-color: #fff;
|
||||
border-radius: 25px;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
|
||||
> div {
|
||||
color: #86909c;
|
||||
background-color: #f4f7f9;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
width: 140px;
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
border-radius: 25px;
|
||||
}
|
||||
|
||||
.item-active {
|
||||
background-color: #f2f3f5;
|
||||
color: #fff;
|
||||
background-color: #0077ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-strategy-detail-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
78
frontend_能源/src/pages/JobStrategyDetailPage/index.css.bak
Normal file
78
frontend_能源/src/pages/JobStrategyDetailPage/index.css.bak
Normal file
@@ -0,0 +1,78 @@
|
||||
.job-strategy-detail-page {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
|
||||
.job-strategy-detail-body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 8px;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
padding: 20px;
|
||||
|
||||
.job-strategy-detail-header {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
color: #4e5969;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 32px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 12px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.item-active {
|
||||
background-color: #f2f3f5;
|
||||
border-radius: 100px;
|
||||
color: #2c7aff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.job-strategy-detail-content {
|
||||
width: 100%;
|
||||
height: 700px;
|
||||
margin-top: 50px;
|
||||
position: relative;
|
||||
.slide-wrapper {
|
||||
width: 123px;
|
||||
height: 125px;
|
||||
position: absolute;
|
||||
top: 20%;
|
||||
right: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 50%;
|
||||
transform: translateX(50%);
|
||||
top: 20px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background-image: url("@/assets/images/JobStrategyDetailPage/slide_icon.png");
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.slide-text {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
color: #0275f2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 22px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
frontend_能源/src/pages/JobStrategyDetailPage/index.jsx
Normal file
65
frontend_能源/src/pages/JobStrategyDetailPage/index.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import TargetPosition from "./components/TargetPosition";
|
||||
import CurvedEmployment from "./components/CurvedEmployment";
|
||||
import "./index.css";
|
||||
|
||||
// 滑动阈值(超过这个距离才触发事件)
|
||||
const SWIPE_THRESHOLD = 120;
|
||||
const JobStrategyDetailPage = () => {
|
||||
const [activeItem, setActiveItem] = useState("1");
|
||||
const navigate = useNavigate();
|
||||
|
||||
// 返回上一页
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="job-strategy-detail-page"
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
WebkitUserSelect: 'none',
|
||||
MozUserSelect: 'none',
|
||||
msUserSelect: 'none'
|
||||
}}
|
||||
>
|
||||
<div className="job-strategy-detail-wrapper">
|
||||
{/* 返回按钮 */}
|
||||
<div className="back-button-wrapper">
|
||||
<button className="back-button" onClick={handleBack}>
|
||||
<span className="back-icon">←</span>
|
||||
<span className="back-text">返回</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 顶部导航栏 */}
|
||||
<div className="job-strategy-detail-header">
|
||||
<div
|
||||
className={`nav-item ${activeItem === "1" ? "item-active" : ""}`}
|
||||
onClick={() => setActiveItem("1")}
|
||||
>
|
||||
<span className="nav-icon target-icon"></span>
|
||||
<span className="nav-text">目标岗位优先级</span>
|
||||
</div>
|
||||
<div
|
||||
className={`nav-item ${activeItem === "2" ? "item-active" : ""}`}
|
||||
onClick={() => setActiveItem("2")}
|
||||
>
|
||||
<span className="nav-icon curved-icon"></span>
|
||||
<span className="nav-text">就业与晋升路径</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 内容区域 */}
|
||||
<div className="job-strategy-detail-content">
|
||||
{activeItem === "1" && <TargetPosition locked={false} />}
|
||||
{activeItem === "2" && <CurvedEmployment locked={false} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default JobStrategyDetailPage;
|
||||
Reference in New Issue
Block a user