|
@@ -0,0 +1,2068 @@
|
|
|
|
+<!DOCTYPE html>
|
|
|
|
+<html lang="zh-CN">
|
|
|
|
+<head>
|
|
|
|
+ <meta charset="UTF-8">
|
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
+ <title>HR招聘管理系统</title>
|
|
|
|
+ <script src="https://cdn.tailwindcss.com"></script>
|
|
|
|
+ <script>
|
|
|
|
+ tailwind.config = {
|
|
|
|
+ theme: {
|
|
|
|
+ extend: {
|
|
|
|
+ colors: {
|
|
|
|
+ primary: '#3B82F6',
|
|
|
|
+ success: '#10B981',
|
|
|
|
+ warning: '#F59E0B',
|
|
|
|
+ danger: '#EF4444',
|
|
|
|
+ secondary: '#6B7280'
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ </script>
|
|
|
|
+ <!-- 引入API适配器 -->
|
|
|
|
+ <script src="api-adapter.js"></script>
|
|
|
|
+ <style>
|
|
|
|
+ .page {
|
|
|
|
+ display: none;
|
|
|
|
+ animation: slideInRight 0.3s ease-out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .page.active {
|
|
|
|
+ display: block;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes slideInRight {
|
|
|
|
+ from {
|
|
|
|
+ transform: translateX(100%);
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
+ to {
|
|
|
|
+ transform: translateX(0);
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes slideInLeft {
|
|
|
|
+ from {
|
|
|
|
+ transform: translateX(-100%);
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
+ to {
|
|
|
|
+ transform: translateX(0);
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .slide-back {
|
|
|
|
+ animation: slideInLeft 0.3s ease-out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .card-hover:active {
|
|
|
|
+ transform: scale(0.98);
|
|
|
|
+ transition: transform 0.1s;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .btn-active:active {
|
|
|
|
+ transform: scale(0.95);
|
|
|
|
+ transition: transform 0.1s;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .progress-ring {
|
|
|
|
+ transform: rotate(-90deg);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .progress-ring-bar {
|
|
|
|
+ transition: stroke-dashoffset 0.5s;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 自定义滚动条 */
|
|
|
|
+ .custom-scrollbar::-webkit-scrollbar {
|
|
|
|
+ width: 4px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .custom-scrollbar::-webkit-scrollbar-track {
|
|
|
|
+ background: #f1f5f9;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .custom-scrollbar::-webkit-scrollbar-thumb {
|
|
|
|
+ background: #cbd5e1;
|
|
|
|
+ border-radius: 2px;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 底部导航栏样式 */
|
|
|
|
+ .bottom-nav {
|
|
|
|
+ position: fixed;
|
|
|
|
+ bottom: 0;
|
|
|
|
+ left: 0;
|
|
|
|
+ right: 0;
|
|
|
|
+ background: white;
|
|
|
|
+ border-top: 1px solid #e5e7eb;
|
|
|
|
+ z-index: 50;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .nav-item {
|
|
|
|
+ display: flex;
|
|
|
|
+ flex-direction: column;
|
|
|
|
+ align-items: center;
|
|
|
|
+ padding: 8px 4px;
|
|
|
|
+ transition: all 0.2s;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .nav-item.active {
|
|
|
|
+ color: #3B82F6;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ .nav-item:not(.active) {
|
|
|
|
+ color: #6B7280;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* 加载动画 */
|
|
|
|
+ .loading-spinner {
|
|
|
|
+ border: 2px solid #f3f4f6;
|
|
|
|
+ border-top: 2px solid #3B82F6;
|
|
|
|
+ border-radius: 50%;
|
|
|
|
+ width: 16px;
|
|
|
|
+ height: 16px;
|
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes spin {
|
|
|
|
+ 0% { transform: rotate(0deg); }
|
|
|
|
+ 100% { transform: rotate(360deg); }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* Toast 样式 */
|
|
|
|
+ .toast {
|
|
|
|
+ position: fixed;
|
|
|
|
+ top: 20px;
|
|
|
|
+ left: 50%;
|
|
|
|
+ transform: translateX(-50%);
|
|
|
|
+ background: #1f2937;
|
|
|
|
+ color: white;
|
|
|
|
+ padding: 12px 24px;
|
|
|
|
+ border-radius: 8px;
|
|
|
|
+ z-index: 100;
|
|
|
|
+ animation: slideDown 0.3s ease-out;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @keyframes slideDown {
|
|
|
|
+ from {
|
|
|
|
+ transform: translateX(-50%) translateY(-100%);
|
|
|
|
+ opacity: 0;
|
|
|
|
+ }
|
|
|
|
+ to {
|
|
|
|
+ transform: translateX(-50%) translateY(0);
|
|
|
|
+ opacity: 1;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ </style>
|
|
|
|
+</head>
|
|
|
|
+<body class="bg-gray-50 min-h-screen">
|
|
|
|
+ <main class="max-w-md mx-auto bg-white min-h-screen relative pb-16">
|
|
|
|
+ <!-- 页面一:岗位管理页 -->
|
|
|
|
+ <div id="page-job-management" class="page active">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
+ <h1 class="text-lg font-semibold text-gray-900">岗位管理</h1>
|
|
|
|
+ <button onclick="createNewJob()" class="w-10 h-10 bg-primary text-white rounded-full flex items-center justify-center btn-active hover:bg-blue-600 transition-colors">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- 岗位列表 -->
|
|
|
|
+ <div id="job-management-container" class="p-4 space-y-4">
|
|
|
|
+ <!-- 动态生成岗位卡片 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 页面二:创建/编辑岗位页 -->
|
|
|
|
+ <div id="page-create-job" class="page">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
+ <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ <h1 id="create-job-title" class="text-lg font-semibold text-gray-900">创建新岗位</h1>
|
|
|
|
+ <div class="flex space-x-2">
|
|
|
|
+ <button onclick="saveJob()" class="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg text-sm font-medium btn-active hover:bg-gray-200 transition-colors">
|
|
|
|
+ 保存草稿
|
|
|
|
+ </button>
|
|
|
|
+ <button onclick="publishJob()" class="px-4 py-2 bg-primary text-white rounded-lg text-sm font-medium btn-active hover:bg-blue-600 transition-colors">
|
|
|
|
+ 发布
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- 表单内容 -->
|
|
|
|
+ <div class="p-4 space-y-6 custom-scrollbar overflow-y-auto" style="height: calc(100vh - 140px);">
|
|
|
|
+ <!-- 基础信息卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">基础信息</h3>
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">岗位名称</label>
|
|
|
|
+ <input id="job-title" type="text" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入岗位名称">
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">所属部门</label>
|
|
|
|
+ <select id="job-department" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
|
|
|
+ <option value="">请选择部门</option>
|
|
|
|
+ <option value="技术部">技术部</option>
|
|
|
|
+ <option value="产品部">产品部</option>
|
|
|
|
+ <option value="设计部">设计部</option>
|
|
|
|
+ <option value="市场部">市场部</option>
|
|
|
|
+ <option value="销售部">销售部</option>
|
|
|
|
+ <option value="人事部">人事部</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">工作地点</label>
|
|
|
|
+ <select id="job-location" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent">
|
|
|
|
+ <option value="">请选择工作地点</option>
|
|
|
|
+ <option value="北京">北京</option>
|
|
|
|
+ <option value="上海">上海</option>
|
|
|
|
+ <option value="深圳">深圳</option>
|
|
|
|
+ <option value="杭州">杭州</option>
|
|
|
|
+ <option value="广州">广州</option>
|
|
|
|
+ <option value="远程办公">远程办公</option>
|
|
|
|
+ </select>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 岗位描述卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">岗位描述</h3>
|
|
|
|
+ <textarea id="job-description" rows="6" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请详细描述岗位职责、任职要求等信息..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 人才基本面卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">人才基本面</h3>
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">职位描述</label>
|
|
|
|
+ <textarea id="job-position-desc" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请输入职位描述..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">技能和经验要求</label>
|
|
|
|
+ <div id="skills-container" class="space-y-2">
|
|
|
|
+ <input type="text" class="skill-item w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入技能要求...">
|
|
|
|
+ </div>
|
|
|
|
+ <button onclick="addSkillItem()" class="mt-2 px-3 py-1 bg-gray-100 text-gray-600 rounded text-sm hover:bg-gray-200 transition-colors">
|
|
|
|
+ + 添加技能要求
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">公司文化和价值观</label>
|
|
|
|
+ <textarea id="company-culture" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述公司文化和价值观..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">薪酬范围和福利</label>
|
|
|
|
+ <textarea id="salary-benefits" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述薪酬范围和福利..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">工作地点和安排</label>
|
|
|
|
+ <textarea id="work-arrangement" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述工作地点和安排..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">职业发展机会</label>
|
|
|
|
+ <textarea id="career-development" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述职业发展机会..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">特殊要求或条件</label>
|
|
|
|
+ <textarea id="special-requirements" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述特殊要求或条件..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-2">招聘流程和时间线</label>
|
|
|
|
+ <textarea id="recruitment-process" rows="2" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent resize-none" placeholder="请描述招聘流程和时间线..."></textarea>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 简历评分准则卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">简历评分准则</h3>
|
|
|
|
+ <div class="space-y-4">
|
|
|
|
+ <!-- 维度权重设置 -->
|
|
|
|
+ <div class="p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <h4 class="font-medium text-gray-800 mb-3">维度权重设置</h4>
|
|
|
|
+ <div class="space-y-3">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="w-24 text-sm text-gray-700">工作经验:</span>
|
|
|
|
+ <input type="number" id="exp-weight" min="0" max="100" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm mr-1" value="30">
|
|
|
|
+ <span class="text-sm text-gray-700">%</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="w-24 text-sm text-gray-700">技术能力:</span>
|
|
|
|
+ <input type="number" id="tech-weight" min="0" max="100" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm mr-1" value="40">
|
|
|
|
+ <span class="text-sm text-gray-700">%</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="w-24 text-sm text-gray-700">软性技能:</span>
|
|
|
|
+ <input type="number" id="soft-weight" min="0" max="100" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm mr-1" value="20">
|
|
|
|
+ <span class="text-sm text-gray-700">%</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <span class="w-24 text-sm text-gray-700">其他:</span>
|
|
|
|
+ <input type="number" id="other-weight" min="0" max="100" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm mr-1" value="10">
|
|
|
|
+ <span class="text-sm text-gray-700">%</span>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="weight-error" class="text-xs text-red-500 hidden">总权重必须等于100%</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 工作经验评分 -->
|
|
|
|
+ <div class="p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
|
+ <h4 class="font-medium text-gray-800">工作经验</h4>
|
|
|
|
+ <button onclick="addExpItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
|
|
|
|
+ + 添加条件
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="exp-criteria-container" class="space-y-3">
|
|
|
|
+ <div class="exp-item">
|
|
|
|
+ <div class="flex items-center mb-1">
|
|
|
|
+ <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="2">
|
|
|
|
+ <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="年以上相关工作经验" placeholder="年以上相关工作经验">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 技术能力评分 -->
|
|
|
|
+ <div class="p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
|
+ <h4 class="font-medium text-gray-800">技术能力</h4>
|
|
|
|
+ <button onclick="addTechItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
|
|
|
|
+ + 添加技能
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="tech-criteria-container" class="space-y-3">
|
|
|
|
+ <!-- 示例技能项 -->
|
|
|
|
+ <div class="tech-item">
|
|
|
|
+ <div class="mb-1">
|
|
|
|
+ <input type="text" class="tech-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="编程语言">
|
|
|
|
+ <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="熟练掌握Java基础,熟悉常用数据结构与算法">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex space-x-2 mt-1">
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-1" value="15" checked class="mr-1 tech-score-high"> 优秀(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="15">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-1" value="10" class="mr-1 tech-score-med"> 良好(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-1" value="5" class="mr-1 tech-score-low"> 一般(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="5">分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 软性技能评分 -->
|
|
|
|
+ <div class="p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
|
+ <h4 class="font-medium text-gray-800">软性技能</h4>
|
|
|
|
+ <button onclick="addSoftItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
|
|
|
|
+ + 添加技能
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="soft-criteria-container" class="space-y-3">
|
|
|
|
+ <div class="soft-item flex items-center justify-between">
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="soft-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="学习能力">
|
|
|
|
+ <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="简历或自我评价中体现出较强的学习能力">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="soft-score-1" value="10" checked class="mr-1 soft-score-yes"> 有(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="soft-score-1" value="0" class="mr-1 soft-score-no"> 无(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 其他评分 -->
|
|
|
|
+ <div class="p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <div class="flex items-center justify-between mb-2">
|
|
|
|
+ <h4 class="font-medium text-gray-800">其他</h4>
|
|
|
|
+ <button onclick="addOtherItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
|
|
|
|
+ + 添加条件
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="other-criteria-container" class="space-y-3">
|
|
|
|
+ <div class="other-item flex items-center justify-between">
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="other-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="条件名称" value="教育背景">
|
|
|
|
+ <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述" value="本科及以上学历">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="other-score-1" value="10" checked class="mr-1 other-score-yes"> 符合(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="other-score-1" value="0" class="mr-1 other-score-no"> 不符合(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 通过标准 -->
|
|
|
|
+ <div class="mt-4">
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
+ <label class="block font-medium text-gray-800">通过标准:</label>
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <input type="number" id="passing-score" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm" value="60">
|
|
|
|
+ <span class="ml-1 text-sm text-gray-700">分以上</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- AI筛选硬性指标卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <div class="flex items-center justify-between mb-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900">AI筛选硬性指标</h3>
|
|
|
|
+ <button onclick="addCriteria()" class="px-3 py-1 bg-primary text-white rounded text-sm btn-active hover:bg-blue-600 transition-colors">
|
|
|
|
+ + 添加指标
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div id="criteria-container" class="space-y-3">
|
|
|
|
+ <!-- 动态生成筛选指标 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 页面三:候选人列表页 -->
|
|
|
|
+ <div id="page-candidate-list" class="page">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700 mr-2">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ <h1 id="candidate-list-title" class="text-lg font-semibold text-gray-900">候选人列表</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- 候选人列表 -->
|
|
|
|
+ <div id="candidate-list-container" class="p-4 space-y-4">
|
|
|
|
+ <!-- 动态生成候选人卡片 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 页面四:简历详情页 -->
|
|
|
|
+ <div id="page-resume-detail" class="page">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
|
|
|
|
+ <div class="flex items-center">
|
|
|
|
+ <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700 mr-2">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ <h1 id="resume-detail-title" class="text-lg font-semibold text-gray-900">候选人详情</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- Tab 切换 -->
|
|
|
|
+ <nav class="bg-white border-b border-gray-200">
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <button id="tab-ai-analysis" onclick="switchTab('ai-analysis')" class="flex-1 py-3 px-4 text-center font-medium text-primary border-b-2 border-primary">
|
|
|
|
+ AI分析
|
|
|
|
+ </button>
|
|
|
|
+ <button id="tab-resume-content" onclick="switchTab('resume-content')" class="flex-1 py-3 px-4 text-center font-medium text-gray-500 border-b-2 border-transparent">
|
|
|
|
+ 简历原文
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </nav>
|
|
|
|
+
|
|
|
|
+ <!-- AI分析内容 -->
|
|
|
|
+ <div id="content-ai-analysis" class="tab-content p-4 pb-24">
|
|
|
|
+ <!-- 匹配度仪表盘 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-4 text-center">
|
|
|
|
+ <div class="relative inline-block">
|
|
|
|
+ <svg class="progress-ring w-24 h-24" viewBox="0 0 100 100">
|
|
|
|
+ <circle cx="50" cy="50" r="45" fill="none" stroke="#f1f5f9" stroke-width="8"/>
|
|
|
|
+ <circle id="progress-circle" cx="50" cy="50" r="45" fill="none" stroke="#3B82F6" stroke-width="8"
|
|
|
|
+ stroke-linecap="round" class="progress-ring-bar"
|
|
|
|
+ stroke-dasharray="283" stroke-dashoffset="113"/>
|
|
|
|
+ </svg>
|
|
|
|
+ <div class="absolute inset-0 flex items-center justify-center">
|
|
|
|
+ <span id="score-display" class="text-2xl font-bold text-gray-900">88</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <p class="text-gray-600 mt-2">匹配度评分</p>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- AI分析详情 -->
|
|
|
|
+ <div id="ai-analysis-details" class="space-y-4">
|
|
|
|
+ <!-- 动态生成分析内容 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 简历原文内容 -->
|
|
|
|
+ <div id="content-resume-content" class="tab-content hidden p-4 pb-24">
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <div id="resume-text" class="text-sm text-gray-700 leading-relaxed custom-scrollbar max-h-96 overflow-y-auto">
|
|
|
|
+ <!-- 动态生成简历内容 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 底部操作栏 -->
|
|
|
|
+ <footer class="fixed bottom-16 left-0 right-0 bg-white border-t border-gray-200 p-4 max-w-md mx-auto">
|
|
|
|
+ <div class="flex space-x-3">
|
|
|
|
+ <button onclick="handleReject()" class="flex-1 bg-gray-500 text-white py-3 px-4 rounded-lg font-medium btn-active hover:bg-gray-600 transition-colors">
|
|
|
|
+ 不合适
|
|
|
|
+ </button>
|
|
|
|
+ <button onclick="handleApprove()" class="flex-1 bg-success text-white py-3 px-4 rounded-lg font-medium btn-active hover:bg-green-600 transition-colors">
|
|
|
|
+ 通过初筛
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </footer>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 页面五:数据统计页 -->
|
|
|
|
+ <div id="page-statistics" class="page">
|
|
|
|
+ <!-- 顶部导航 -->
|
|
|
|
+ <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
|
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
|
+ <h1 class="text-lg font-semibold text-gray-900">数据统计</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </header>
|
|
|
|
+
|
|
|
|
+ <!-- 统计内容 -->
|
|
|
|
+ <div class="p-4 space-y-4">
|
|
|
|
+ <!-- 总览卡片 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">招聘总览</h3>
|
|
|
|
+ <div class="grid grid-cols-2 gap-4">
|
|
|
|
+ <div class="text-center">
|
|
|
|
+ <div class="text-2xl font-bold text-primary" id="total-jobs">0</div>
|
|
|
|
+ <div class="text-sm text-gray-600">活跃岗位</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-center">
|
|
|
|
+ <div class="text-2xl font-bold text-success" id="total-candidates">0</div>
|
|
|
|
+ <div class="text-sm text-gray-600">候选人总数</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-center">
|
|
|
|
+ <div class="text-2xl font-bold text-warning" id="pending-resumes">0</div>
|
|
|
|
+ <div class="text-sm text-gray-600">待处理简历</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-center">
|
|
|
|
+ <div class="text-2xl font-bold text-success" id="passed-resumes">0</div>
|
|
|
|
+ <div class="text-sm text-gray-600">已通过初筛</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 部门统计 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-4">部门招聘情况</h3>
|
|
|
|
+ <div id="department-stats" class="space-y-3">
|
|
|
|
+ <!-- 动态生成部门统计 -->
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 底部导航栏 -->
|
|
|
|
+ <nav class="bottom-nav max-w-md mx-auto">
|
|
|
|
+ <div class="flex">
|
|
|
|
+ <button onclick="switchPage('page-job-management')" class="flex-1 nav-item active" id="nav-jobs">
|
|
|
|
+ <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2-2v2m8 0V6a2 2 0 012 2v6a2 2 0 01-2 2H8a2 2 0 01-2-2V8a2 2 0 012-2V6"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ <span class="text-xs">岗位管理</span>
|
|
|
|
+ </button>
|
|
|
|
+ <button onclick="switchPage('page-candidate-list')" class="flex-1 nav-item" id="nav-candidates">
|
|
|
|
+ <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ <span class="text-xs">候选人</span>
|
|
|
|
+ </button>
|
|
|
|
+ <button onclick="switchPage('page-statistics')" class="flex-1 nav-item" id="nav-stats">
|
|
|
|
+ <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ <span class="text-xs">数据统计</span>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ </nav>
|
|
|
|
+ </main>
|
|
|
|
+
|
|
|
|
+ <script>
|
|
|
|
+ // 模拟数据
|
|
|
|
+ const mockData = {
|
|
|
|
+ jobs: [
|
|
|
|
+ {
|
|
|
|
+ id: 1,
|
|
|
|
+ title: "资深产品经理",
|
|
|
|
+ department: "产品部",
|
|
|
|
+ location: "北京",
|
|
|
|
+ status: "招聘中",
|
|
|
|
+ pendingResumes: 12,
|
|
|
|
+ passedResumes: 3,
|
|
|
|
+ description: "负责产品规划和设计,具备良好的用户体验意识",
|
|
|
|
+ criteria: [
|
|
|
|
+ { field: "学历", condition: "大于等于", value: "本科" },
|
|
|
|
+ { field: "工作年限", condition: "大于等于", value: "5年" },
|
|
|
|
+ { field: "必要技能", condition: "包含", value: "产品设计" }
|
|
|
|
+ ],
|
|
|
|
+ talentProfile: {
|
|
|
|
+ positionDesc: "资深产品经理,负责公司核心产品的规划、设计和迭代优化。",
|
|
|
|
+ skills: [
|
|
|
|
+ "5年以上产品管理经验",
|
|
|
|
+ "熟悉产品设计流程和方法",
|
|
|
|
+ "具备数据分析能力",
|
|
|
|
+ "良好的沟通协调能力",
|
|
|
|
+ "对用户体验有深刻理解"
|
|
|
|
+ ],
|
|
|
|
+ companyCulture: "积极向上,鼓励创新,注重团队合作,重视个人发展。",
|
|
|
|
+ salaryBenefits: "25K-35K,五险一金、带薪年假、定期体检等。",
|
|
|
|
+ workArrangement: "北京,全职。",
|
|
|
|
+ careerDevelopment: "提供内部培训、晋升通道,鼓励员工不断提升技能。",
|
|
|
|
+ specialRequirements: "有B端产品经验优先。",
|
|
|
|
+ recruitmentProcess: "简历筛选 -> 笔试 -> 面试 -> 最终录用,预计2周内完成"
|
|
|
|
+ },
|
|
|
|
+ scoringCriteria: {
|
|
|
|
+ expYears: "5",
|
|
|
|
+ internshipYears: "1",
|
|
|
|
+ javaScore: "10",
|
|
|
|
+ frontendScore: "7",
|
|
|
|
+ databaseScore: "7",
|
|
|
|
+ frameworkScore: "3",
|
|
|
|
+ learningAbility: "10",
|
|
|
|
+ teamwork: "10",
|
|
|
|
+ education: "10",
|
|
|
|
+ projectScore: "8",
|
|
|
|
+ passingScore: "70"
|
|
|
|
+ },
|
|
|
|
+ candidates: [
|
|
|
|
+ {
|
|
|
|
+ id: 101,
|
|
|
|
+ name: "张三",
|
|
|
|
+ score: 88,
|
|
|
|
+ tags: ["SaaS经验", "5年经验", "B端产品"],
|
|
|
|
+ status: "pending"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 102,
|
|
|
|
+ name: "李四",
|
|
|
|
+ score: 92,
|
|
|
|
+ tags: ["电商背景", "团队管理", "数据驱动"],
|
|
|
|
+ status: "pending"
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 2,
|
|
|
|
+ title: "前端工程师",
|
|
|
|
+ department: "技术部",
|
|
|
|
+ location: "上海",
|
|
|
|
+ status: "招聘中",
|
|
|
|
+ pendingResumes: 8,
|
|
|
|
+ passedResumes: 5,
|
|
|
|
+ description: "负责前端开发工作,熟练掌握React、Vue等框架",
|
|
|
|
+ criteria: [
|
|
|
|
+ { field: "学历", condition: "大于等于", value: "大专" },
|
|
|
|
+ { field: "工作年限", condition: "大于等于", value: "3年" },
|
|
|
|
+ { field: "必要技能", condition: "包含", value: "React" }
|
|
|
|
+ ],
|
|
|
|
+ talentProfile: {
|
|
|
|
+ positionDesc: "前端工程师,负责公司Web应用的开发、维护和优化。",
|
|
|
|
+ skills: [
|
|
|
|
+ "3年以上前端开发经验",
|
|
|
|
+ "熟练掌握HTML5、CSS3、JavaScript",
|
|
|
|
+ "熟悉React、Vue等前端框架",
|
|
|
|
+ "了解Node.js和常用构建工具",
|
|
|
|
+ "有良好的代码风格和团队合作精神"
|
|
|
|
+ ],
|
|
|
|
+ companyCulture: "开放包容,技术驱动,鼓励创新,重视个人成长。",
|
|
|
|
+ salaryBenefits: "15K-25K,五险一金、带薪年假、免费健身等。",
|
|
|
|
+ workArrangement: "上海,全职。",
|
|
|
|
+ careerDevelopment: "提供技术分享会、内部培训,有晋升为技术专家或管理岗的机会。",
|
|
|
|
+ specialRequirements: "有大型Web应用开发经验优先。",
|
|
|
|
+ recruitmentProcess: "简历筛选 -> 技术面试 -> HR面试 -> Offer,预计3周内完成"
|
|
|
|
+ },
|
|
|
|
+ scoringCriteria: {
|
|
|
|
+ expYears: "3",
|
|
|
|
+ internshipYears: "1",
|
|
|
|
+ javaScore: "5",
|
|
|
|
+ frontendScore: "15",
|
|
|
|
+ databaseScore: "3",
|
|
|
|
+ frameworkScore: "5",
|
|
|
|
+ learningAbility: "10",
|
|
|
|
+ teamwork: "10",
|
|
|
|
+ education: "10",
|
|
|
|
+ projectScore: "7",
|
|
|
|
+ passingScore: "65"
|
|
|
|
+ },
|
|
|
|
+ candidates: [
|
|
|
|
+ {
|
|
|
|
+ id: 201,
|
|
|
|
+ name: "赵六",
|
|
|
|
+ score: 85,
|
|
|
|
+ tags: ["React", "Vue", "TypeScript"],
|
|
|
|
+ status: "pending"
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 3,
|
|
|
|
+ title: "Java开发工程师",
|
|
|
|
+ department: "技术部",
|
|
|
|
+ location: "上海",
|
|
|
|
+ status: "招聘中",
|
|
|
|
+ pendingResumes: 15,
|
|
|
|
+ passedResumes: 4,
|
|
|
|
+ description: "负责公司项目的后端开发、维护和优化。",
|
|
|
|
+ criteria: [
|
|
|
|
+ { field: "学历", condition: "大于等于", value: "本科" },
|
|
|
|
+ { field: "工作年限", condition: "大于等于", value: "2年" },
|
|
|
|
+ { field: "必要技能", condition: "包含", value: "Java" }
|
|
|
|
+ ],
|
|
|
|
+ talentProfile: {
|
|
|
|
+ positionDesc: "初级Java开发工程师,负责公司项目的开发、维护和优化。",
|
|
|
|
+ skills: [
|
|
|
|
+ "2年以上Java或Java Web开发经验",
|
|
|
|
+ "熟练使用Java后台技术",
|
|
|
|
+ "熟练使用HTML5、CSS等前端技术",
|
|
|
|
+ "熟练使用SQL语言",
|
|
|
|
+ "熟悉SpringMVC、SSM、SpringBoot等Web框架",
|
|
|
|
+ "有较强的学习能力",
|
|
|
|
+ "有良好的创新精神和团队协作经验"
|
|
|
|
+ ],
|
|
|
|
+ companyCulture: "积极向上,鼓励创新,注重团队合作,重视个人发展。",
|
|
|
|
+ salaryBenefits: "根据经验和能力面议,五险一金、带薪年假、定期体检等。",
|
|
|
|
+ workArrangement: "上海,全职。",
|
|
|
|
+ careerDevelopment: "提供内部培训、晋升通道,鼓励员工不断提升技能。",
|
|
|
|
+ specialRequirements: "无。",
|
|
|
|
+ recruitmentProcess: "简历筛选 -> 笔试 -> 面试 -> 最终录用,预计2周内完成"
|
|
|
|
+ },
|
|
|
|
+ scoringCriteria: {
|
|
|
|
+ expYears: "2",
|
|
|
|
+ internshipYears: "1",
|
|
|
|
+ javaScore: "15",
|
|
|
|
+ frontendScore: "10",
|
|
|
|
+ databaseScore: "10",
|
|
|
|
+ frameworkScore: "5",
|
|
|
|
+ learningAbility: "10",
|
|
|
|
+ teamwork: "10",
|
|
|
|
+ education: "10",
|
|
|
|
+ projectScore: "5",
|
|
|
|
+ passingScore: "60"
|
|
|
|
+ },
|
|
|
|
+ candidates: [
|
|
|
|
+ {
|
|
|
|
+ id: 301,
|
|
|
|
+ name: "王五",
|
|
|
|
+ score: 78,
|
|
|
|
+ tags: ["Java", "Spring", "MySQL"],
|
|
|
|
+ status: "pending"
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ id: 302,
|
|
|
|
+ name: "刘七",
|
|
|
|
+ score: 82,
|
|
|
|
+ tags: ["Java", "微服务", "Docker"],
|
|
|
|
+ status: "pending"
|
|
|
|
+ }
|
|
|
|
+ ]
|
|
|
|
+ }
|
|
|
|
+ ],
|
|
|
|
+ resumeDetails: {
|
|
|
|
+ 101: {
|
|
|
|
+ name: "张三",
|
|
|
|
+ score: 88,
|
|
|
|
+ summary: "候选人具有丰富的B端产品管理经验,在SaaS领域有深入理解,具备良好的数据分析能力和用户洞察力。",
|
|
|
|
+ pros: [
|
|
|
|
+ "5年产品管理经验,其中3年专注于B端SaaS产品",
|
|
|
|
+ "成功主导过用户量从0到10万的产品项目",
|
|
|
|
+ "具备良好的数据敏感度,熟练使用各种分析工具",
|
|
|
|
+ "有跨部门协作经验,沟通能力强"
|
|
|
|
+ ],
|
|
|
|
+ cons: [
|
|
|
|
+ "缺乏C端产品经验,对用户增长策略了解有限",
|
|
|
|
+ "技术背景相对薄弱,对开发流程理解不够深入"
|
|
|
|
+ ],
|
|
|
|
+ resumeText: `
|
|
|
|
+ 姓名:张三
|
|
|
|
+ 联系电话:138****1234
|
|
|
|
+ 邮箱:zhangsan@email.com
|
|
|
|
+
|
|
|
|
+ 教育背景:
|
|
|
|
+ 2015-2019 北京理工大学 工商管理学士
|
|
|
|
+
|
|
|
|
+ 工作经历:
|
|
|
|
+ 2019.7-至今 ABC科技有限公司 产品经理
|
|
|
|
+ - 负责B端SaaS产品的规划和设计,用户量从0增长到10万+
|
|
|
|
+ - 主导产品需求分析,完成用户调研和竞品分析
|
|
|
|
+ - 协调开发、设计、测试团队,确保产品按时交付
|
|
|
|
+
|
|
|
|
+ 技能特长:
|
|
|
|
+ - 熟练使用Axure、Figma等产品设计工具
|
|
|
|
+ - 具备SQL数据查询能力,熟悉Google Analytics等分析工具
|
|
|
|
+ - 了解前端开发基础,能与技术团队有效沟通
|
|
|
|
+ `
|
|
|
|
+ },
|
|
|
|
+ 102: {
|
|
|
|
+ name: "李四",
|
|
|
|
+ score: 92,
|
|
|
|
+ summary: "优秀的产品管理人才,具有丰富的电商和数据产品经验,团队管理能力突出。",
|
|
|
|
+ pros: [
|
|
|
|
+ "6年产品管理经验,涵盖电商、数据分析等多个领域",
|
|
|
|
+ "有成功的团队管理经验,曾带领15人产品团队",
|
|
|
|
+ "具备强大的商业敏感度,主导过多个商业化项目"
|
|
|
|
+ ],
|
|
|
|
+ cons: [
|
|
|
|
+ "主要经验集中在C端产品,B端产品经验相对较少"
|
|
|
|
+ ],
|
|
|
|
+ resumeText: `
|
|
|
|
+ 姓名:李四
|
|
|
|
+ 联系电话:139****5678
|
|
|
|
+ 邮箱:lisi@email.com
|
|
|
|
+
|
|
|
|
+ 教育背景:
|
|
|
|
+ 2014-2018 清华大学 计算机科学与技术学士
|
|
|
|
+
|
|
|
|
+ 工作经历:
|
|
|
|
+ 2020.3-至今 DEF电商集团 高级产品经理
|
|
|
|
+ - 负责电商平台核心交易流程优化,GMV提升30%
|
|
|
|
+ - 带领15人产品团队,负责用户增长和留存策略
|
|
|
|
+ `
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 当前状态
|
|
|
|
+ let currentJobId = null;
|
|
|
|
+ let currentCandidateId = null;
|
|
|
|
+ let pageHistory = [];
|
|
|
|
+ let editingJobId = null;
|
|
|
|
+
|
|
|
|
+ // 页面初始化
|
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
+ renderJobManagement();
|
|
|
|
+ updateStatistics();
|
|
|
|
+
|
|
|
|
+ // 确保新建岗位按钮可点击
|
|
|
|
+ const addJobBtn = document.querySelector('button[onclick="createNewJob()"]');
|
|
|
|
+ if (addJobBtn) {
|
|
|
|
+ addJobBtn.addEventListener('click', createNewJob);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 页面切换函数
|
|
|
|
+ function showPage(pageId, isBack = false) {
|
|
|
|
+ // 隐藏所有页面
|
|
|
|
+ document.querySelectorAll('.page').forEach(page => {
|
|
|
|
+ page.classList.remove('active');
|
|
|
|
+ if (isBack) {
|
|
|
|
+ page.classList.add('slide-back');
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 显示目标页面
|
|
|
|
+ const targetPage = document.getElementById(pageId);
|
|
|
|
+ targetPage.classList.add('active');
|
|
|
|
+
|
|
|
|
+ // 更新历史记录
|
|
|
|
+ if (!isBack) {
|
|
|
|
+ pageHistory.push(pageId);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 底部导航切换
|
|
|
|
+ function switchPage(pageId) {
|
|
|
|
+ // 更新导航状态
|
|
|
|
+ document.querySelectorAll('.nav-item').forEach(item => {
|
|
|
|
+ item.classList.remove('active');
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 根据页面ID设置对应的导航项为活跃状态
|
|
|
|
+ if (pageId === 'page-job-management') {
|
|
|
|
+ document.getElementById('nav-jobs').classList.add('active');
|
|
|
|
+ } else if (pageId === 'page-candidate-list') {
|
|
|
|
+ document.getElementById('nav-candidates').classList.add('active');
|
|
|
|
+ renderAllCandidates();
|
|
|
|
+ } else if (pageId === 'page-statistics') {
|
|
|
|
+ document.getElementById('nav-stats').classList.add('active');
|
|
|
|
+ updateStatistics();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重置页面历史
|
|
|
|
+ pageHistory = [pageId];
|
|
|
|
+ showPage(pageId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 返回上一页
|
|
|
|
+ function goBack() {
|
|
|
|
+ if (pageHistory.length > 1) {
|
|
|
|
+ pageHistory.pop(); // 移除当前页
|
|
|
|
+ const previousPage = pageHistory[pageHistory.length - 1];
|
|
|
|
+ showPage(previousPage, true);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 渲染岗位管理页面
|
|
|
|
+ async function renderJobManagement() {
|
|
|
|
+ const container = document.getElementById('job-management-container');
|
|
|
|
+ container.innerHTML = '<div class="flex justify-center py-10"><div class="loading-spinner"></div></div>';
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const jobs = await API.Job.getAllJobs();
|
|
|
|
+
|
|
|
|
+ if (jobs.length === 0) {
|
|
|
|
+ container.innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-gray-500">暂无岗位数据</p>
|
|
|
|
+ <button onclick="createNewJob()" class="mt-4 px-4 py-2 bg-primary text-white rounded-lg">
|
|
|
|
+ 创建新岗位
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ container.innerHTML = jobs.map(job => `
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <div class="flex items-start justify-between mb-3">
|
|
|
|
+ <div class="flex-1">
|
|
|
|
+ <h3 class="font-semibold text-gray-900">${job.title}</h3>
|
|
|
|
+ <p class="text-sm text-gray-600">${job.department} · ${job.location}</p>
|
|
|
|
+ </div>
|
|
|
|
+ <span class="px-2 py-1 text-xs rounded-full ${getStatusColor(job.status)}">${job.status}</span>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="mb-3">
|
|
|
|
+ <p class="text-sm text-gray-700 line-clamp-2">${job.talentProfile?.positionDesc || job.description}</p>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ ${job.talentProfile?.skills ? `
|
|
|
|
+ <div class="mb-3">
|
|
|
|
+ <div class="flex flex-wrap gap-1 mb-2">
|
|
|
|
+ ${job.talentProfile.skills.slice(0, 3).map(skill => `
|
|
|
|
+ <span class="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">${skill}</span>
|
|
|
|
+ `).join('')}
|
|
|
|
+ ${job.talentProfile.skills.length > 3 ? `<span class="px-2 py-1 text-xs bg-gray-50 text-gray-500 rounded">+${job.talentProfile.skills.length - 3}</span>` : ''}
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ ` : ''}
|
|
|
|
+
|
|
|
|
+ <div class="mb-4 flex items-center justify-between">
|
|
|
|
+ <div>
|
|
|
|
+ <div class="text-2xl font-bold text-primary">${job.pendingResumes}</div>
|
|
|
|
+ <div class="text-sm text-gray-600">待处理简历</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div>
|
|
|
|
+ <div class="text-2xl font-bold text-success">${job.passedResumes}</div>
|
|
|
|
+ <div class="text-sm text-gray-600">已通过初筛</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="space-y-2">
|
|
|
|
+ ${job.status === '招聘中' ? `
|
|
|
|
+ <button onclick="startScreening(${job.id})"
|
|
|
|
+ class="w-full py-3 px-4 rounded-lg font-medium btn-active transition-colors ${job.pendingResumes > 0 ? 'bg-primary text-white hover:bg-blue-600' : 'bg-gray-100 text-gray-400 cursor-not-allowed'}"
|
|
|
|
+ ${job.pendingResumes === 0 ? 'disabled' : ''}>
|
|
|
|
+ ${job.pendingResumes > 0 ? '一键智能筛选' : '暂无待处理简历'}
|
|
|
|
+ </button>
|
|
|
|
+ ` : `
|
|
|
|
+ <button onclick="publishDraft(${job.id})"
|
|
|
|
+ class="w-full py-3 px-4 rounded-lg font-medium btn-active transition-colors bg-primary text-white hover:bg-blue-600">
|
|
|
|
+ 发布招聘
|
|
|
|
+ </button>
|
|
|
|
+ `}
|
|
|
|
+ <div class="flex space-x-2">
|
|
|
|
+ <button onclick="editJob(${job.id})" class="flex-1 py-2 px-3 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 btn-active">
|
|
|
|
+ 编辑岗位
|
|
|
|
+ </button>
|
|
|
|
+ ${job.status === '招聘中' ? `
|
|
|
|
+ <button onclick="viewJobCandidates(${job.id})" class="flex-1 py-2 px-3 text-sm text-gray-600 border border-gray-300 rounded-lg hover:bg-gray-50 btn-active">
|
|
|
|
+ 查看候选人
|
|
|
|
+ </button>
|
|
|
|
+ ` : `
|
|
|
|
+ <button onclick="deleteJob(${job.id})" class="flex-1 py-2 px-3 text-sm text-red-600 border border-red-200 rounded-lg hover:bg-red-50 btn-active">
|
|
|
|
+ 删除草稿
|
|
|
|
+ </button>
|
|
|
|
+ `}
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ `).join('');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取岗位列表失败:', error);
|
|
|
|
+ container.innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-red-500">获取岗位数据失败</p>
|
|
|
|
+ <button onclick="renderJobManagement()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
|
|
|
|
+ 重试
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 创建新岗位
|
|
|
|
+ function createNewJob() {
|
|
|
|
+ editingJobId = null;
|
|
|
|
+ document.getElementById('create-job-title').textContent = '创建新岗位';
|
|
|
|
+ clearJobForm();
|
|
|
|
+ showPage('page-create-job');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 编辑岗位
|
|
|
|
+ async function editJob(jobId) {
|
|
|
|
+ editingJobId = jobId;
|
|
|
|
+ document.getElementById('create-job-title').textContent = '编辑岗位';
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const job = await API.Job.getJobById(jobId);
|
|
|
|
+ if (job) {
|
|
|
|
+ fillJobForm(job);
|
|
|
|
+ showPage('page-create-job');
|
|
|
|
+ } else {
|
|
|
|
+ showToast('获取岗位详情失败');
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取岗位详情失败:', error);
|
|
|
|
+ showToast('获取岗位详情失败');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 清空表单
|
|
|
|
+ function clearJobForm() {
|
|
|
|
+ document.getElementById('job-title').value = '';
|
|
|
|
+ document.getElementById('job-department').value = '';
|
|
|
|
+ document.getElementById('job-location').value = '';
|
|
|
|
+ document.getElementById('job-description').value = '';
|
|
|
|
+ document.getElementById('criteria-container').innerHTML = '';
|
|
|
|
+
|
|
|
|
+ // 清空人才基本面
|
|
|
|
+ document.getElementById('job-position-desc').value = '';
|
|
|
|
+ document.getElementById('skills-container').innerHTML = '<input type="text" class="skill-item w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入技能要求...">';
|
|
|
|
+ document.getElementById('company-culture').value = '';
|
|
|
|
+ document.getElementById('salary-benefits').value = '';
|
|
|
|
+ document.getElementById('work-arrangement').value = '';
|
|
|
|
+ document.getElementById('career-development').value = '';
|
|
|
|
+ document.getElementById('special-requirements').value = '';
|
|
|
|
+ document.getElementById('recruitment-process').value = '';
|
|
|
|
+
|
|
|
|
+ // 重置评分准则为默认值
|
|
|
|
+ // 设置权重
|
|
|
|
+ document.getElementById('exp-weight').value = '30';
|
|
|
|
+ document.getElementById('tech-weight').value = '40';
|
|
|
|
+ document.getElementById('soft-weight').value = '20';
|
|
|
|
+ document.getElementById('other-weight').value = '10';
|
|
|
|
+
|
|
|
|
+ // 重置通过标准
|
|
|
|
+ document.getElementById('passing-score').value = '60';
|
|
|
|
+
|
|
|
|
+ // 重置评分标准容器
|
|
|
|
+ document.getElementById('exp-criteria-container').innerHTML = '';
|
|
|
|
+ document.getElementById('tech-criteria-container').innerHTML = '';
|
|
|
|
+ document.getElementById('soft-criteria-container').innerHTML = '';
|
|
|
|
+ document.getElementById('other-criteria-container').innerHTML = '';
|
|
|
|
+
|
|
|
|
+ // 添加默认项
|
|
|
|
+ addExpItem();
|
|
|
|
+ addTechItem();
|
|
|
|
+ addSoftItem();
|
|
|
|
+ addOtherItem();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充表单
|
|
|
|
+ function fillJobForm(job) {
|
|
|
|
+ document.getElementById('job-title').value = job.title;
|
|
|
|
+ document.getElementById('job-department').value = job.department;
|
|
|
|
+ document.getElementById('job-location').value = job.location;
|
|
|
|
+ document.getElementById('job-description').value = job.description;
|
|
|
|
+
|
|
|
|
+ // 填充筛选条件
|
|
|
|
+ const container = document.getElementById('criteria-container');
|
|
|
|
+ container.innerHTML = '';
|
|
|
|
+ job.criteria.forEach(criteria => {
|
|
|
|
+ addCriteriaRow(criteria);
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 填充人才基本面(如果存在)
|
|
|
|
+ if (job.talentProfile) {
|
|
|
|
+ document.getElementById('job-position-desc').value = job.talentProfile.positionDesc || '';
|
|
|
|
+
|
|
|
|
+ // 清空并填充技能要求
|
|
|
|
+ const skillsContainer = document.getElementById('skills-container');
|
|
|
|
+ skillsContainer.innerHTML = '';
|
|
|
|
+ if (job.talentProfile.skills && job.talentProfile.skills.length > 0) {
|
|
|
|
+ job.talentProfile.skills.forEach(skill => {
|
|
|
|
+ const input = document.createElement('div');
|
|
|
|
+ input.className = 'flex items-center';
|
|
|
|
+ input.innerHTML = `
|
|
|
|
+ <input type="text" class="skill-item flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入技能要求..." value="${skill}">
|
|
|
|
+ <button onclick="removeSkillItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ `;
|
|
|
|
+ skillsContainer.appendChild(input);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ skillsContainer.innerHTML = '<input type="text" class="skill-item w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入技能要求...">';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ document.getElementById('company-culture').value = job.talentProfile.companyCulture || '';
|
|
|
|
+ document.getElementById('salary-benefits').value = job.talentProfile.salaryBenefits || '';
|
|
|
|
+ document.getElementById('work-arrangement').value = job.talentProfile.workArrangement || '';
|
|
|
|
+ document.getElementById('career-development').value = job.talentProfile.careerDevelopment || '';
|
|
|
|
+ document.getElementById('special-requirements').value = job.talentProfile.specialRequirements || '';
|
|
|
|
+ document.getElementById('recruitment-process').value = job.talentProfile.recruitmentProcess || '';
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充评分准则(如果存在)
|
|
|
|
+ if (job.scoringCriteria) {
|
|
|
|
+ // 填充权重设置
|
|
|
|
+ if (job.scoringCriteria.weights) {
|
|
|
|
+ document.getElementById('exp-weight').value = job.scoringCriteria.weights.expWeight || 30;
|
|
|
|
+ document.getElementById('tech-weight').value = job.scoringCriteria.weights.techWeight || 40;
|
|
|
|
+ document.getElementById('soft-weight').value = job.scoringCriteria.weights.softWeight || 20;
|
|
|
|
+ document.getElementById('other-weight').value = job.scoringCriteria.weights.otherWeight || 10;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充工作经验评分项
|
|
|
|
+ const expContainer = document.getElementById('exp-criteria-container');
|
|
|
|
+ expContainer.innerHTML = '';
|
|
|
|
+ if (job.scoringCriteria.expCriteria && job.scoringCriteria.expCriteria.length > 0) {
|
|
|
|
+ job.scoringCriteria.expCriteria.forEach(exp => {
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'exp-item';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div class="flex items-center mb-1">
|
|
|
|
+ <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="${exp.years}">
|
|
|
|
+ <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="${exp.desc}" placeholder="年以上相关工作经验">
|
|
|
|
+ <button onclick="removeExpItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
|
|
|
|
+ `;
|
|
|
|
+ expContainer.appendChild(div);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ // 添加默认项
|
|
|
|
+ addExpItem();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充技术能力评分项
|
|
|
|
+ const techContainer = document.getElementById('tech-criteria-container');
|
|
|
|
+ techContainer.innerHTML = '';
|
|
|
|
+ if (job.scoringCriteria.techCriteria && job.scoringCriteria.techCriteria.length > 0) {
|
|
|
|
+ job.scoringCriteria.techCriteria.forEach((tech, index) => {
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'tech-item';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div class="mb-1">
|
|
|
|
+ <input type="text" class="tech-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="${tech.name || ''}">
|
|
|
|
+ <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="${tech.desc || ''}">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex space-x-2 mt-1">
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.high || 15}" checked class="mr-1 tech-score-high"> 优秀(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.high || 15}">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.medium || 10}" class="mr-1 tech-score-med"> 良好(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.medium || 10}">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.low || 5}" class="mr-1 tech-score-low"> 一般(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.low || 5}">分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ techContainer.appendChild(div);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ // 保留默认项
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充软性技能评分项
|
|
|
|
+ const softContainer = document.getElementById('soft-criteria-container');
|
|
|
|
+ softContainer.innerHTML = '';
|
|
|
|
+ if (job.scoringCriteria.softCriteria && job.scoringCriteria.softCriteria.length > 0) {
|
|
|
|
+ job.scoringCriteria.softCriteria.forEach((soft, index) => {
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'soft-item flex items-center justify-between';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="soft-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="${soft.name || ''}">
|
|
|
|
+ <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="${soft.desc || ''}">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="soft-score-${index+1}" value="${soft.score || 10}" checked class="mr-1 soft-score-yes"> 有(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${soft.score || 10}">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="soft-score-${index+1}" value="0" class="mr-1 soft-score-no"> 无(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ softContainer.appendChild(div);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ // 保留默认项
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 填充其他评分项
|
|
|
|
+ const otherContainer = document.getElementById('other-criteria-container');
|
|
|
|
+ otherContainer.innerHTML = '';
|
|
|
|
+ if (job.scoringCriteria.otherCriteria && job.scoringCriteria.otherCriteria.length > 0) {
|
|
|
|
+ job.scoringCriteria.otherCriteria.forEach((other, index) => {
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'other-item flex items-center justify-between';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="other-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="条件名称" value="${other.name || ''}">
|
|
|
|
+ <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述" value="${other.desc || ''}">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="other-score-${index+1}" value="${other.score || 10}" checked class="mr-1 other-score-yes"> 符合(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${other.score || 10}">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="other-score-${index+1}" value="0" class="mr-1 other-score-no"> 不符合(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ otherContainer.appendChild(div);
|
|
|
|
+ });
|
|
|
|
+ } else {
|
|
|
|
+ // 保留默认项
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 设置通过标准
|
|
|
|
+ document.getElementById('passing-score').value = job.scoringCriteria.passingScore || '60';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 保存岗位为草稿
|
|
|
|
+ async function saveJob(shouldPublish = false) {
|
|
|
|
+ // 验证权重总和是否为100%
|
|
|
|
+ if (!checkWeightTotals()) {
|
|
|
|
+ showToast('评分维度权重总和必须为100%');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const title = document.getElementById('job-title').value.trim();
|
|
|
|
+ const department = document.getElementById('job-department').value;
|
|
|
|
+ const location = document.getElementById('job-location').value;
|
|
|
|
+ const description = document.getElementById('job-description').value.trim();
|
|
|
|
+
|
|
|
|
+ if (!title || !department || !location) {
|
|
|
|
+ showToast('请填写完整的基础信息');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 收集筛选条件
|
|
|
|
+ const criteria = [];
|
|
|
|
+ const criteriaRows = document.querySelectorAll('.criteria-row');
|
|
|
|
+ criteriaRows.forEach(row => {
|
|
|
|
+ const field = row.querySelector('.criteria-field').value;
|
|
|
|
+ const condition = row.querySelector('.criteria-condition').value;
|
|
|
|
+ const value = row.querySelector('.criteria-value').value.trim();
|
|
|
|
+
|
|
|
|
+ if (field && condition && value) {
|
|
|
|
+ criteria.push({ field, condition, value });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 收集权重设置
|
|
|
|
+ const scoringWeights = {
|
|
|
|
+ expWeight: document.getElementById('exp-weight').value,
|
|
|
|
+ techWeight: document.getElementById('tech-weight').value,
|
|
|
|
+ softWeight: document.getElementById('soft-weight').value,
|
|
|
|
+ otherWeight: document.getElementById('other-weight').value
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 收集工作经验评分项
|
|
|
|
+ const expCriteria = [];
|
|
|
|
+ document.querySelectorAll('.exp-item').forEach(item => {
|
|
|
|
+ const years = item.querySelector('.exp-years').value;
|
|
|
|
+ const desc = item.querySelector('.exp-desc').value;
|
|
|
|
+ if (years && desc) {
|
|
|
|
+ expCriteria.push({ years, desc });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 收集技术能力评分项
|
|
|
|
+ const techCriteria = [];
|
|
|
|
+ document.querySelectorAll('.tech-item').forEach((item, index) => {
|
|
|
|
+ const name = item.querySelector('.tech-name').value;
|
|
|
|
+ const desc = item.querySelector('.tech-desc').value;
|
|
|
|
+ const highScore = item.querySelector('.tech-score-high').parentNode.querySelector('input[type="number"]').value;
|
|
|
|
+ const medScore = item.querySelector('.tech-score-med').parentNode.querySelector('input[type="number"]').value;
|
|
|
|
+ const lowScore = item.querySelector('.tech-score-low').parentNode.querySelector('input[type="number"]').value;
|
|
|
|
+
|
|
|
|
+ if (name) {
|
|
|
|
+ techCriteria.push({
|
|
|
|
+ name,
|
|
|
|
+ desc,
|
|
|
|
+ scores: {
|
|
|
|
+ high: highScore,
|
|
|
|
+ medium: medScore,
|
|
|
|
+ low: lowScore
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 收集软性技能评分项
|
|
|
|
+ const softCriteria = [];
|
|
|
|
+ document.querySelectorAll('.soft-item').forEach((item, index) => {
|
|
|
|
+ const name = item.querySelector('.soft-name').value;
|
|
|
|
+ const desc = item.querySelector('.soft-desc').value;
|
|
|
|
+ const score = item.querySelector('.soft-score-yes').parentNode.querySelector('input[type="number"]').value;
|
|
|
|
+
|
|
|
|
+ if (name) {
|
|
|
|
+ softCriteria.push({ name, desc, score });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 收集其他评分项
|
|
|
|
+ const otherCriteria = [];
|
|
|
|
+ document.querySelectorAll('.other-item').forEach((item, index) => {
|
|
|
|
+ const name = item.querySelector('.other-name').value;
|
|
|
|
+ const desc = item.querySelector('.other-desc').value;
|
|
|
|
+ const score = item.querySelector('.other-score-yes').parentNode.querySelector('input[type="number"]').value;
|
|
|
|
+
|
|
|
|
+ if (name) {
|
|
|
|
+ otherCriteria.push({ name, desc, score });
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 收集人才基本面信息
|
|
|
|
+ const skillItems = document.querySelectorAll('.skill-item');
|
|
|
|
+ const skills = [];
|
|
|
|
+ skillItems.forEach(item => {
|
|
|
|
+ if (item.value.trim()) {
|
|
|
|
+ skills.push(item.value.trim());
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ const talentProfile = {
|
|
|
|
+ positionDesc: document.getElementById('job-position-desc').value.trim(),
|
|
|
|
+ skills: skills,
|
|
|
|
+ companyCulture: document.getElementById('company-culture').value.trim(),
|
|
|
|
+ salaryBenefits: document.getElementById('salary-benefits').value.trim(),
|
|
|
|
+ workArrangement: document.getElementById('work-arrangement').value.trim(),
|
|
|
|
+ careerDevelopment: document.getElementById('career-development').value.trim(),
|
|
|
|
+ specialRequirements: document.getElementById('special-requirements').value.trim(),
|
|
|
|
+ recruitmentProcess: document.getElementById('recruitment-process').value.trim()
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ // 收集评分准则
|
|
|
|
+ const scoringCriteria = {
|
|
|
|
+ weights: scoringWeights,
|
|
|
|
+ expCriteria,
|
|
|
|
+ techCriteria,
|
|
|
|
+ softCriteria,
|
|
|
|
+ otherCriteria,
|
|
|
|
+ passingScore: document.getElementById('passing-score').value
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ const jobData = {
|
|
|
|
+ title,
|
|
|
|
+ department,
|
|
|
|
+ location,
|
|
|
|
+ description,
|
|
|
|
+ criteria,
|
|
|
|
+ talentProfile,
|
|
|
|
+ scoringCriteria,
|
|
|
|
+ status: shouldPublish ? '招聘中' : '草稿'
|
|
|
|
+ };
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ let response;
|
|
|
|
+
|
|
|
|
+ if (editingJobId) {
|
|
|
|
+ // 编辑现有岗位
|
|
|
|
+ response = await API.Job.updateJob(editingJobId, jobData);
|
|
|
|
+ } else {
|
|
|
|
+ // 创建新岗位
|
|
|
|
+ response = await API.Job.createJob(jobData);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ showToast(response.message || (shouldPublish ? '岗位已成功发布' : '岗位已保存为草稿'));
|
|
|
|
+ renderJobManagement();
|
|
|
|
+ updateStatistics();
|
|
|
|
+ goBack();
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('保存岗位失败:', error);
|
|
|
|
+ showToast(shouldPublish ? '发布岗位失败,请重试' : '保存岗位失败,请重试');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 发布岗位
|
|
|
|
+ function publishJob() {
|
|
|
|
+ saveJob(true);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加筛选条件
|
|
|
|
+ function addCriteria() {
|
|
|
|
+ addCriteriaRow();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加筛选条件行
|
|
|
|
+ function addCriteriaRow(criteria = null) {
|
|
|
|
+ const container = document.getElementById('criteria-container');
|
|
|
|
+ const row = document.createElement('div');
|
|
|
|
+ row.className = 'criteria-row flex items-center space-x-2 p-3 bg-gray-50 rounded-lg';
|
|
|
|
+
|
|
|
|
+ row.innerHTML = `
|
|
|
|
+ <select class="criteria-field flex-1 px-2 py-1 border border-gray-300 rounded text-sm">
|
|
|
|
+ <option value="">选择指标</option>
|
|
|
|
+ <option value="学历" ${criteria?.field === '学历' ? 'selected' : ''}>学历</option>
|
|
|
|
+ <option value="工作年限" ${criteria?.field === '工作年限' ? 'selected' : ''}>工作年限</option>
|
|
|
|
+ <option value="必要技能" ${criteria?.field === '必要技能' ? 'selected' : ''}>必要技能</option>
|
|
|
|
+ <option value="语言要求" ${criteria?.field === '语言要求' ? 'selected' : ''}>语言要求</option>
|
|
|
|
+ </select>
|
|
|
|
+ <select class="criteria-condition flex-1 px-2 py-1 border border-gray-300 rounded text-sm">
|
|
|
|
+ <option value="">选择条件</option>
|
|
|
|
+ <option value="等于" ${criteria?.condition === '等于' ? 'selected' : ''}>等于</option>
|
|
|
|
+ <option value="大于等于" ${criteria?.condition === '大于等于' ? 'selected' : ''}>大于等于</option>
|
|
|
|
+ <option value="包含" ${criteria?.condition === '包含' ? 'selected' : ''}>包含</option>
|
|
|
|
+ <option value="不包含" ${criteria?.condition === '不包含' ? 'selected' : ''}>不包含</option>
|
|
|
|
+ </select>
|
|
|
|
+ <input type="text" class="criteria-value flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
|
|
|
|
+ placeholder="输入值" value="${criteria?.value || ''}">
|
|
|
|
+ <button onclick="removeCriteria(this)" class="p-1 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ `;
|
|
|
|
+
|
|
|
|
+ container.appendChild(row);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除筛选条件
|
|
|
|
+ function removeCriteria(button) {
|
|
|
|
+ button.closest('.criteria-row').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加技能要求项
|
|
|
|
+ function addSkillItem() {
|
|
|
|
+ const container = document.getElementById('skills-container');
|
|
|
|
+ const input = document.createElement('div');
|
|
|
|
+ input.className = 'flex items-center';
|
|
|
|
+ input.innerHTML = `
|
|
|
|
+ <input type="text" class="skill-item flex-1 px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent" placeholder="请输入技能要求...">
|
|
|
|
+ <button onclick="removeSkillItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ `;
|
|
|
|
+ container.appendChild(input);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除技能要求项
|
|
|
|
+ function removeSkillItem(button) {
|
|
|
|
+ button.closest('.flex').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 开始筛选
|
|
|
|
+ async function startScreening(jobId) {
|
|
|
|
+ const button = event.target;
|
|
|
|
+ const originalText = button.textContent;
|
|
|
|
+
|
|
|
|
+ // 显示加载状态
|
|
|
|
+ button.innerHTML = '<div class="loading-spinner inline-block mr-2"></div>筛选中...';
|
|
|
|
+ button.disabled = true;
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const response = await API.Job.startScreening(jobId);
|
|
|
|
+
|
|
|
|
+ showToast(response.message || '筛选完成!');
|
|
|
|
+ renderJobManagement();
|
|
|
|
+ updateStatistics();
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('启动筛选失败:', error);
|
|
|
|
+ showToast('启动筛选失败,请重试');
|
|
|
|
+ // 恢复按钮状态
|
|
|
|
+ button.textContent = originalText;
|
|
|
|
+ button.disabled = false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查看岗位候选人
|
|
|
|
+ async function viewJobCandidates(jobId) {
|
|
|
|
+ currentJobId = jobId;
|
|
|
|
+ document.getElementById('candidate-list-title').textContent = '加载中...';
|
|
|
|
+ showPage('page-candidate-list');
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const response = await API.Job.getJobCandidates(jobId);
|
|
|
|
+ document.getElementById('candidate-list-title').textContent = response.jobTitle;
|
|
|
|
+ renderCandidateList(response.candidates);
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取候选人列表失败:', error);
|
|
|
|
+ document.getElementById('candidate-list-container').innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-red-500">获取候选人数据失败</p>
|
|
|
|
+ <button onclick="viewJobCandidates(${jobId})" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
|
|
|
|
+ 重试
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 渲染所有候选人
|
|
|
|
+ async function renderAllCandidates() {
|
|
|
|
+ document.getElementById('candidate-list-title').textContent = '加载中...';
|
|
|
|
+ showPage('page-candidate-list');
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const candidates = await API.Candidate.getAllCandidates();
|
|
|
|
+ document.getElementById('candidate-list-title').textContent = '全部候选人';
|
|
|
|
+ renderCandidateList(candidates, true);
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取候选人列表失败:', error);
|
|
|
|
+ document.getElementById('candidate-list-container').innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-red-500">获取候选人数据失败</p>
|
|
|
|
+ <button onclick="renderAllCandidates()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
|
|
|
|
+ 重试
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 渲染候选人列表
|
|
|
|
+ function renderCandidateList(candidates, showJobTitle = false) {
|
|
|
|
+ const container = document.getElementById('candidate-list-container');
|
|
|
|
+ if (candidates.length === 0) {
|
|
|
|
+ container.innerHTML = `
|
|
|
|
+ <div class="text-center py-12">
|
|
|
|
+ <svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ <p class="text-gray-500">暂无候选人</p>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ container.innerHTML = candidates.map(candidate => `
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 card-hover cursor-pointer" onclick="viewResumeDetail(${candidate.id})">
|
|
|
|
+ <div class="flex items-start justify-between mb-3">
|
|
|
|
+ <div>
|
|
|
|
+ <h3 class="font-semibold text-gray-900">${candidate.name}</h3>
|
|
|
|
+ ${showJobTitle ? `<p class="text-sm text-gray-600">${candidate.jobTitle}</p>` : ''}
|
|
|
|
+ <div class="flex items-center mt-1">
|
|
|
|
+ <span class="text-2xl font-bold text-primary mr-2">${candidate.score}</span>
|
|
|
|
+ <span class="text-sm text-gray-600">匹配度</span>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="w-3 h-3 rounded-full ${getStatusDot(candidate.status)}"></div>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex flex-wrap gap-2 mb-3">
|
|
|
|
+ ${candidate.tags.map(tag => `
|
|
|
|
+ <span class="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">${tag}</span>
|
|
|
|
+ `).join('')}
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <div class="flex items-center justify-end">
|
|
|
|
+ <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ `).join('');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 查看简历详情
|
|
|
|
+ async function viewResumeDetail(candidateId) {
|
|
|
|
+ currentCandidateId = candidateId;
|
|
|
|
+ document.getElementById('resume-detail-title').textContent = '加载中...';
|
|
|
|
+ showPage('page-resume-detail');
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ const detail = await API.Candidate.getCandidateDetails(candidateId);
|
|
|
|
+ if (detail) {
|
|
|
|
+ document.getElementById('resume-detail-title').textContent = detail.name;
|
|
|
|
+ renderResumeDetail(detail);
|
|
|
|
+ } else {
|
|
|
|
+ document.getElementById('ai-analysis-details').innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-red-500">未找到候选人数据</p>
|
|
|
|
+ <button onclick="goBack()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
|
|
|
|
+ 返回
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取候选人详情失败:', error);
|
|
|
|
+ document.getElementById('ai-analysis-details').innerHTML = `
|
|
|
|
+ <div class="text-center py-10">
|
|
|
|
+ <p class="text-red-500">获取候选人详情失败</p>
|
|
|
|
+ <button onclick="viewResumeDetail(${candidateId})" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
|
|
|
|
+ 重试
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 渲染简历详情
|
|
|
|
+ function renderResumeDetail(detail) {
|
|
|
|
+ // 更新匹配度圆环
|
|
|
|
+ updateProgressRing(detail.score);
|
|
|
|
+
|
|
|
|
+ // 渲染AI分析详情
|
|
|
|
+ const analysisContainer = document.getElementById('ai-analysis-details');
|
|
|
|
+ analysisContainer.innerHTML = `
|
|
|
|
+ <!-- 综合评语 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-2 flex items-center">
|
|
|
|
+ <svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ 综合评语
|
|
|
|
+ </h3>
|
|
|
|
+ <p class="text-gray-700 text-sm leading-relaxed">${detail.summary}</p>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 亮点 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-3 flex items-center">
|
|
|
|
+ <svg class="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ 优势亮点
|
|
|
|
+ </h3>
|
|
|
|
+ <ul class="space-y-2">
|
|
|
|
+ ${detail.pros.map(pro => `
|
|
|
|
+ <li class="flex items-start text-sm text-gray-700">
|
|
|
|
+ <svg class="w-4 h-4 text-green-500 mr-2 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ ${pro}
|
|
|
|
+ </li>
|
|
|
|
+ `).join('')}
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <!-- 待考察点 -->
|
|
|
|
+ <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
|
|
|
|
+ <h3 class="font-semibold text-gray-900 mb-3 flex items-center">
|
|
|
|
+ <svg class="w-5 h-5 text-orange-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ 待考察点
|
|
|
|
+ </h3>
|
|
|
|
+ <ul class="space-y-2">
|
|
|
|
+ ${detail.cons.map(con => `
|
|
|
|
+ <li class="flex items-start text-sm text-gray-700">
|
|
|
|
+ <svg class="w-4 h-4 text-orange-500 mr-2 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ ${con}
|
|
|
|
+ </li>
|
|
|
|
+ `).join('')}
|
|
|
|
+ </ul>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+
|
|
|
|
+ // 更新简历原文
|
|
|
|
+ document.getElementById('resume-text').innerHTML = detail.resumeText.replace(/\n/g, '<br>');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新进度环
|
|
|
|
+ function updateProgressRing(score) {
|
|
|
|
+ const circle = document.getElementById('progress-circle');
|
|
|
|
+ const scoreDisplay = document.getElementById('score-display');
|
|
|
|
+ const circumference = 2 * Math.PI * 45;
|
|
|
|
+ const offset = circumference - (score / 100) * circumference;
|
|
|
|
+
|
|
|
|
+ circle.style.strokeDashoffset = offset;
|
|
|
|
+ scoreDisplay.textContent = score;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Tab切换
|
|
|
|
+ function switchTab(tabName) {
|
|
|
|
+ // 更新tab按钮状态
|
|
|
|
+ document.querySelectorAll('[id^="tab-"]').forEach(tab => {
|
|
|
|
+ tab.classList.remove('text-primary', 'border-primary');
|
|
|
|
+ tab.classList.add('text-gray-500', 'border-transparent');
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document.getElementById(`tab-${tabName}`).classList.remove('text-gray-500', 'border-transparent');
|
|
|
|
+ document.getElementById(`tab-${tabName}`).classList.add('text-primary', 'border-primary');
|
|
|
|
+
|
|
|
|
+ // 切换内容
|
|
|
|
+ document.querySelectorAll('.tab-content').forEach(content => {
|
|
|
|
+ content.classList.add('hidden');
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ document.getElementById(`content-${tabName}`).classList.remove('hidden');
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理拒绝
|
|
|
|
+ function handleReject() {
|
|
|
|
+ if (currentCandidateId) {
|
|
|
|
+ updateCandidateStatus(currentCandidateId, 'rejected');
|
|
|
|
+ showToast('已标记为不合适');
|
|
|
|
+ setTimeout(() => goBack(), 1000);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 处理通过
|
|
|
|
+ function handleApprove() {
|
|
|
|
+ if (currentCandidateId) {
|
|
|
|
+ updateCandidateStatus(currentCandidateId, 'approved');
|
|
|
|
+ showToast('已通过初筛');
|
|
|
|
+ setTimeout(() => goBack(), 1000);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新候选人状态
|
|
|
|
+ function updateCandidateStatus(candidateId, status) {
|
|
|
|
+ mockData.jobs.forEach(job => {
|
|
|
|
+ const candidate = job.candidates.find(c => c.id === candidateId);
|
|
|
|
+ if (candidate) {
|
|
|
|
+ candidate.status = status;
|
|
|
|
+ if (status === 'approved') {
|
|
|
|
+ job.passedResumes++;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ updateStatistics();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 更新统计数据
|
|
|
|
+ async function updateStatistics() {
|
|
|
|
+ try {
|
|
|
|
+ // 获取总览数据
|
|
|
|
+ const overviewStats = await API.Statistics.getOverviewStats();
|
|
|
|
+ document.getElementById('total-jobs').textContent = overviewStats.totalJobs;
|
|
|
|
+ document.getElementById('total-candidates').textContent = overviewStats.totalCandidates;
|
|
|
|
+ document.getElementById('pending-resumes').textContent = overviewStats.pendingResumes;
|
|
|
|
+ document.getElementById('passed-resumes').textContent = overviewStats.passedResumes;
|
|
|
|
+
|
|
|
|
+ // 获取部门统计
|
|
|
|
+ const departmentStats = await API.Statistics.getDepartmentStats();
|
|
|
|
+ const departmentContainer = document.getElementById('department-stats');
|
|
|
|
+
|
|
|
|
+ if (departmentStats.length === 0) {
|
|
|
|
+ departmentContainer.innerHTML = `
|
|
|
|
+ <div class="text-center py-4">
|
|
|
|
+ <p class="text-gray-500">暂无部门数据</p>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ departmentContainer.innerHTML = departmentStats.map(dept => `
|
|
|
|
+ <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
|
|
+ <div>
|
|
|
|
+ <div class="font-medium text-gray-900">${dept.department || '未分类'}</div>
|
|
|
|
+ <div class="text-sm text-gray-600">${dept.jobs}个岗位 · ${dept.candidates}名候选人</div>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-right">
|
|
|
|
+ <div class="text-sm text-warning">${dept.pending}待处理</div>
|
|
|
|
+ <div class="text-sm text-success">${dept.passed}已通过</div>
|
|
|
|
+ </div>
|
|
|
|
+ </div>
|
|
|
|
+ `).join('');
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('获取统计数据失败:', error);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取状态颜色
|
|
|
|
+ function getStatusColor(status) {
|
|
|
|
+ switch(status) {
|
|
|
|
+ case '招聘中': return 'bg-green-100 text-green-800';
|
|
|
|
+ case '已暂停': return 'bg-gray-100 text-gray-800';
|
|
|
|
+ case '草稿': return 'bg-blue-100 text-blue-800';
|
|
|
|
+ default: return 'bg-gray-100 text-gray-800';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 获取状态指示点颜色
|
|
|
|
+ function getStatusDot(status) {
|
|
|
|
+ switch(status) {
|
|
|
|
+ case 'pending': return 'bg-yellow-400';
|
|
|
|
+ case 'approved': return 'bg-green-400';
|
|
|
|
+ case 'rejected': return 'bg-red-400';
|
|
|
|
+ default: return 'bg-gray-400';
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 显示Toast消息
|
|
|
|
+ function showToast(message) {
|
|
|
|
+ const toast = document.createElement('div');
|
|
|
|
+ toast.className = 'toast';
|
|
|
|
+ toast.textContent = message;
|
|
|
|
+ document.body.appendChild(toast);
|
|
|
|
+
|
|
|
|
+ setTimeout(() => {
|
|
|
|
+ toast.remove();
|
|
|
|
+ }, 3000);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 检查维度权重总和是否为100%
|
|
|
|
+ function checkWeightTotals() {
|
|
|
|
+ const expWeight = parseInt(document.getElementById('exp-weight').value) || 0;
|
|
|
|
+ const techWeight = parseInt(document.getElementById('tech-weight').value) || 0;
|
|
|
|
+ const softWeight = parseInt(document.getElementById('soft-weight').value) || 0;
|
|
|
|
+ const otherWeight = parseInt(document.getElementById('other-weight').value) || 0;
|
|
|
|
+
|
|
|
|
+ const total = expWeight + techWeight + softWeight + otherWeight;
|
|
|
|
+ const errorElement = document.getElementById('weight-error');
|
|
|
|
+
|
|
|
|
+ if (total !== 100) {
|
|
|
|
+ errorElement.classList.remove('hidden');
|
|
|
|
+ return false;
|
|
|
|
+ } else {
|
|
|
|
+ errorElement.classList.add('hidden');
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 监听权重变化
|
|
|
|
+ document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
+ const weightInputs = ['exp-weight', 'tech-weight', 'soft-weight', 'other-weight'];
|
|
|
|
+ weightInputs.forEach(id => {
|
|
|
|
+ const input = document.getElementById(id);
|
|
|
|
+ if (input) {
|
|
|
|
+ input.addEventListener('change', checkWeightTotals);
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ // 添加工作经验条件
|
|
|
|
+ function addExpItem() {
|
|
|
|
+ const container = document.getElementById('exp-criteria-container');
|
|
|
|
+ const index = container.children.length + 1;
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'exp-item';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div class="flex items-center mb-1">
|
|
|
|
+ <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="1">
|
|
|
|
+ <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="" placeholder="年以上相关工作经验">
|
|
|
|
+ <button onclick="removeExpItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
|
|
|
|
+ `;
|
|
|
|
+ container.appendChild(div);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除工作经验条件
|
|
|
|
+ function removeExpItem(button) {
|
|
|
|
+ button.closest('.exp-item').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加技术能力项
|
|
|
|
+ function addTechItem() {
|
|
|
|
+ const container = document.getElementById('tech-criteria-container');
|
|
|
|
+ const index = container.children.length + 1;
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'tech-item';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div class="mb-1">
|
|
|
|
+ <input type="text" class="tech-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称">
|
|
|
|
+ <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex space-x-2 mt-1">
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index}" value="15" checked class="mr-1 tech-score-high"> 优秀(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="15">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index}" value="10" class="mr-1 tech-score-med"> 良好(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-xs text-gray-700">
|
|
|
|
+ <input type="radio" name="tech-score-${index}" value="5" class="mr-1 tech-score-low"> 一般(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="5">分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ container.appendChild(div);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除技术能力项
|
|
|
|
+ function removeTechItem(button) {
|
|
|
|
+ button.closest('.tech-item').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加软性技能项
|
|
|
|
+ function addSoftItem() {
|
|
|
|
+ const container = document.getElementById('soft-criteria-container');
|
|
|
|
+ const index = container.children.length + 1;
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'soft-item flex items-center justify-between';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="soft-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称">
|
|
|
|
+ <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="soft-score-${index}" value="10" checked class="mr-1 soft-score-yes"> 有(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="soft-score-${index}" value="0" class="mr-1 soft-score-no"> 无(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ container.appendChild(div);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除软性技能项
|
|
|
|
+ function removeSoftItem(button) {
|
|
|
|
+ button.closest('.soft-item').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 添加其他评分条件
|
|
|
|
+ function addOtherItem() {
|
|
|
|
+ const container = document.getElementById('other-criteria-container');
|
|
|
|
+ const index = container.children.length + 1;
|
|
|
|
+ const div = document.createElement('div');
|
|
|
|
+ div.className = 'other-item flex items-center justify-between';
|
|
|
|
+ div.innerHTML = `
|
|
|
|
+ <div>
|
|
|
|
+ <input type="text" class="other-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="条件名称">
|
|
|
|
+ <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述">
|
|
|
|
+ </div>
|
|
|
|
+ <div class="flex items-center ml-4">
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700 mr-4">
|
|
|
|
+ <input type="radio" name="other-score-${index}" value="10" checked class="mr-1 other-score-yes"> 符合(
|
|
|
|
+ <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
|
|
|
|
+ </label>
|
|
|
|
+ <label class="flex items-center text-sm text-gray-700">
|
|
|
|
+ <input type="radio" name="other-score-${index}" value="0" class="mr-1 other-score-no"> 不符合(0分)
|
|
|
|
+ </label>
|
|
|
|
+ <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
|
|
|
|
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1-1H8a1 1 0 00-1 1v3M4 7h16"></path>
|
|
|
|
+ </svg>
|
|
|
|
+ </button>
|
|
|
|
+ </div>
|
|
|
|
+ `;
|
|
|
|
+ container.appendChild(div);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除其他评分条件
|
|
|
|
+ function removeOtherItem(button) {
|
|
|
|
+ button.closest('.other-item').remove();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 发布草稿
|
|
|
|
+ async function publishDraft(jobId) {
|
|
|
|
+ try {
|
|
|
|
+ const response = await API.Job.publishDraft(jobId);
|
|
|
|
+ showToast(response.message || '岗位已成功发布');
|
|
|
|
+ renderJobManagement();
|
|
|
|
+ updateStatistics();
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('发布岗位失败:', error);
|
|
|
|
+ showToast('发布岗位失败,请重试');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 删除岗位草稿
|
|
|
|
+ async function deleteJob(jobId) {
|
|
|
|
+ try {
|
|
|
|
+ const response = await API.Job.deleteJob(jobId);
|
|
|
|
+ showToast(response.message || '岗位草稿已删除');
|
|
|
|
+ renderJobManagement();
|
|
|
|
+ updateStatistics();
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('删除岗位失败:', error);
|
|
|
|
+ showToast('删除岗位失败,请重试');
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ </script>
|
|
|
|
+</body>
|
|
|
|
+</html>
|