1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068 |
- <!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>
|