index.html 112 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>HR招聘管理系统</title>
  7. <script src="https://cdn.tailwindcss.com"></script>
  8. <script>
  9. tailwind.config = {
  10. theme: {
  11. extend: {
  12. colors: {
  13. primary: '#3B82F6',
  14. success: '#10B981',
  15. warning: '#F59E0B',
  16. danger: '#EF4444',
  17. secondary: '#6B7280'
  18. }
  19. }
  20. }
  21. }
  22. </script>
  23. <!-- 引入API适配器 -->
  24. <script src="api-adapter.js"></script>
  25. <style>
  26. .page {
  27. display: none;
  28. animation: slideInRight 0.3s ease-out;
  29. }
  30. .page.active {
  31. display: block;
  32. }
  33. @keyframes slideInRight {
  34. from {
  35. transform: translateX(100%);
  36. opacity: 0;
  37. }
  38. to {
  39. transform: translateX(0);
  40. opacity: 1;
  41. }
  42. }
  43. @keyframes slideInLeft {
  44. from {
  45. transform: translateX(-100%);
  46. opacity: 0;
  47. }
  48. to {
  49. transform: translateX(0);
  50. opacity: 1;
  51. }
  52. }
  53. .slide-back {
  54. animation: slideInLeft 0.3s ease-out;
  55. }
  56. .card-hover:active {
  57. transform: scale(0.98);
  58. transition: transform 0.1s;
  59. }
  60. .btn-active:active {
  61. transform: scale(0.95);
  62. transition: transform 0.1s;
  63. }
  64. .progress-ring {
  65. transform: rotate(-90deg);
  66. }
  67. .progress-ring-bar {
  68. transition: stroke-dashoffset 0.5s;
  69. }
  70. /* 自定义滚动条 */
  71. .custom-scrollbar::-webkit-scrollbar {
  72. width: 4px;
  73. }
  74. .custom-scrollbar::-webkit-scrollbar-track {
  75. background: #f1f5f9;
  76. }
  77. .custom-scrollbar::-webkit-scrollbar-thumb {
  78. background: #cbd5e1;
  79. border-radius: 2px;
  80. }
  81. /* 底部导航栏样式 */
  82. .bottom-nav {
  83. position: fixed;
  84. bottom: 0;
  85. left: 0;
  86. right: 0;
  87. background: white;
  88. border-top: 1px solid #e5e7eb;
  89. z-index: 50;
  90. }
  91. .nav-item {
  92. display: flex;
  93. flex-direction: column;
  94. align-items: center;
  95. padding: 8px 4px;
  96. transition: all 0.2s;
  97. }
  98. .nav-item.active {
  99. color: #3B82F6;
  100. }
  101. .nav-item:not(.active) {
  102. color: #6B7280;
  103. }
  104. /* 加载动画 */
  105. .loading-spinner {
  106. border: 2px solid #f3f4f6;
  107. border-top: 2px solid #3B82F6;
  108. border-radius: 50%;
  109. width: 16px;
  110. height: 16px;
  111. animation: spin 1s linear infinite;
  112. }
  113. @keyframes spin {
  114. 0% { transform: rotate(0deg); }
  115. 100% { transform: rotate(360deg); }
  116. }
  117. /* Toast 样式 */
  118. .toast {
  119. position: fixed;
  120. top: 20px;
  121. left: 50%;
  122. transform: translateX(-50%);
  123. background: #1f2937;
  124. color: white;
  125. padding: 12px 24px;
  126. border-radius: 8px;
  127. z-index: 100;
  128. animation: slideDown 0.3s ease-out;
  129. }
  130. @keyframes slideDown {
  131. from {
  132. transform: translateX(-50%) translateY(-100%);
  133. opacity: 0;
  134. }
  135. to {
  136. transform: translateX(-50%) translateY(0);
  137. opacity: 1;
  138. }
  139. }
  140. </style>
  141. </head>
  142. <body class="bg-gray-50 min-h-screen">
  143. <main class="max-w-md mx-auto bg-white min-h-screen relative pb-16">
  144. <!-- 页面一:岗位管理页 -->
  145. <div id="page-job-management" class="page active">
  146. <!-- 顶部导航 -->
  147. <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
  148. <div class="flex items-center justify-between">
  149. <h1 class="text-lg font-semibold text-gray-900">岗位管理</h1>
  150. <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">
  151. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  152. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"></path>
  153. </svg>
  154. </button>
  155. </div>
  156. </header>
  157. <!-- 岗位列表 -->
  158. <div id="job-management-container" class="p-4 space-y-4">
  159. <!-- 动态生成岗位卡片 -->
  160. </div>
  161. </div>
  162. <!-- 页面二:创建/编辑岗位页 -->
  163. <div id="page-create-job" class="page">
  164. <!-- 顶部导航 -->
  165. <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
  166. <div class="flex items-center justify-between">
  167. <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700">
  168. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  169. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
  170. </svg>
  171. </button>
  172. <h1 id="create-job-title" class="text-lg font-semibold text-gray-900">创建新岗位</h1>
  173. <div class="flex space-x-2">
  174. <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">
  175. 保存草稿
  176. </button>
  177. <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">
  178. 发布
  179. </button>
  180. </div>
  181. </div>
  182. </header>
  183. <!-- 表单内容 -->
  184. <div class="p-4 space-y-6 custom-scrollbar overflow-y-auto" style="height: calc(100vh - 140px);">
  185. <!-- 基础信息卡片 -->
  186. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  187. <h3 class="font-semibold text-gray-900 mb-4">基础信息</h3>
  188. <div class="space-y-4">
  189. <div>
  190. <label class="block text-sm font-medium text-gray-700 mb-2">岗位名称</label>
  191. <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="请输入岗位名称">
  192. </div>
  193. <div>
  194. <label class="block text-sm font-medium text-gray-700 mb-2">所属部门</label>
  195. <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">
  196. <option value="">请选择部门</option>
  197. <option value="技术部">技术部</option>
  198. <option value="产品部">产品部</option>
  199. <option value="设计部">设计部</option>
  200. <option value="市场部">市场部</option>
  201. <option value="销售部">销售部</option>
  202. <option value="人事部">人事部</option>
  203. </select>
  204. </div>
  205. <div>
  206. <label class="block text-sm font-medium text-gray-700 mb-2">工作地点</label>
  207. <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">
  208. <option value="">请选择工作地点</option>
  209. <option value="北京">北京</option>
  210. <option value="上海">上海</option>
  211. <option value="深圳">深圳</option>
  212. <option value="杭州">杭州</option>
  213. <option value="广州">广州</option>
  214. <option value="远程办公">远程办公</option>
  215. </select>
  216. </div>
  217. </div>
  218. </div>
  219. <!-- 岗位描述卡片 -->
  220. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  221. <h3 class="font-semibold text-gray-900 mb-4">岗位描述</h3>
  222. <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>
  223. </div>
  224. <!-- 人才基本面卡片 -->
  225. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  226. <h3 class="font-semibold text-gray-900 mb-4">人才基本面</h3>
  227. <div class="space-y-4">
  228. <div>
  229. <label class="block text-sm font-medium text-gray-700 mb-2">职位描述</label>
  230. <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>
  231. </div>
  232. <div>
  233. <label class="block text-sm font-medium text-gray-700 mb-2">技能和经验要求</label>
  234. <div id="skills-container" class="space-y-2">
  235. <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="请输入技能要求...">
  236. </div>
  237. <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">
  238. + 添加技能要求
  239. </button>
  240. </div>
  241. <div>
  242. <label class="block text-sm font-medium text-gray-700 mb-2">公司文化和价值观</label>
  243. <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>
  244. </div>
  245. <div>
  246. <label class="block text-sm font-medium text-gray-700 mb-2">薪酬范围和福利</label>
  247. <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>
  248. </div>
  249. <div>
  250. <label class="block text-sm font-medium text-gray-700 mb-2">工作地点和安排</label>
  251. <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>
  252. </div>
  253. <div>
  254. <label class="block text-sm font-medium text-gray-700 mb-2">职业发展机会</label>
  255. <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>
  256. </div>
  257. <div>
  258. <label class="block text-sm font-medium text-gray-700 mb-2">特殊要求或条件</label>
  259. <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>
  260. </div>
  261. <div>
  262. <label class="block text-sm font-medium text-gray-700 mb-2">招聘流程和时间线</label>
  263. <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>
  264. </div>
  265. </div>
  266. </div>
  267. <!-- 简历评分准则卡片 -->
  268. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  269. <h3 class="font-semibold text-gray-900 mb-4">简历评分准则</h3>
  270. <div class="space-y-4">
  271. <!-- 维度权重设置 -->
  272. <div class="p-3 bg-gray-50 rounded-lg">
  273. <h4 class="font-medium text-gray-800 mb-3">维度权重设置</h4>
  274. <div class="space-y-3">
  275. <div class="flex items-center">
  276. <span class="w-24 text-sm text-gray-700">工作经验:</span>
  277. <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">
  278. <span class="text-sm text-gray-700">%</span>
  279. </div>
  280. <div class="flex items-center">
  281. <span class="w-24 text-sm text-gray-700">技术能力:</span>
  282. <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">
  283. <span class="text-sm text-gray-700">%</span>
  284. </div>
  285. <div class="flex items-center">
  286. <span class="w-24 text-sm text-gray-700">软性技能:</span>
  287. <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">
  288. <span class="text-sm text-gray-700">%</span>
  289. </div>
  290. <div class="flex items-center">
  291. <span class="w-24 text-sm text-gray-700">其他:</span>
  292. <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">
  293. <span class="text-sm text-gray-700">%</span>
  294. </div>
  295. <div id="weight-error" class="text-xs text-red-500 hidden">总权重必须等于100%</div>
  296. </div>
  297. </div>
  298. <!-- 工作经验评分 -->
  299. <div class="p-3 bg-gray-50 rounded-lg">
  300. <div class="flex items-center justify-between mb-2">
  301. <h4 class="font-medium text-gray-800">工作经验</h4>
  302. <button onclick="addExpItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
  303. + 添加条件
  304. </button>
  305. </div>
  306. <div id="exp-criteria-container" class="space-y-3">
  307. <div class="exp-item">
  308. <div class="flex items-center mb-1">
  309. <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="2">
  310. <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="年以上相关工作经验" placeholder="年以上相关工作经验">
  311. </div>
  312. <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
  313. </div>
  314. </div>
  315. </div>
  316. <!-- 技术能力评分 -->
  317. <div class="p-3 bg-gray-50 rounded-lg">
  318. <div class="flex items-center justify-between mb-2">
  319. <h4 class="font-medium text-gray-800">技术能力</h4>
  320. <button onclick="addTechItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
  321. + 添加技能
  322. </button>
  323. </div>
  324. <div id="tech-criteria-container" class="space-y-3">
  325. <!-- 示例技能项 -->
  326. <div class="tech-item">
  327. <div class="mb-1">
  328. <input type="text" class="tech-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="编程语言">
  329. <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="熟练掌握Java基础,熟悉常用数据结构与算法">
  330. </div>
  331. <div class="flex space-x-2 mt-1">
  332. <label class="flex items-center text-xs text-gray-700">
  333. <input type="radio" name="tech-score-1" value="15" checked class="mr-1 tech-score-high"> 优秀(
  334. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="15">分)
  335. </label>
  336. <label class="flex items-center text-xs text-gray-700">
  337. <input type="radio" name="tech-score-1" value="10" class="mr-1 tech-score-med"> 良好(
  338. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  339. </label>
  340. <label class="flex items-center text-xs text-gray-700">
  341. <input type="radio" name="tech-score-1" value="5" class="mr-1 tech-score-low"> 一般(
  342. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="5">分)
  343. </label>
  344. <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
  345. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  346. <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>
  347. </svg>
  348. </button>
  349. </div>
  350. </div>
  351. </div>
  352. </div>
  353. <!-- 软性技能评分 -->
  354. <div class="p-3 bg-gray-50 rounded-lg">
  355. <div class="flex items-center justify-between mb-2">
  356. <h4 class="font-medium text-gray-800">软性技能</h4>
  357. <button onclick="addSoftItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
  358. + 添加技能
  359. </button>
  360. </div>
  361. <div id="soft-criteria-container" class="space-y-3">
  362. <div class="soft-item flex items-center justify-between">
  363. <div>
  364. <input type="text" class="soft-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称" value="学习能力">
  365. <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="简历或自我评价中体现出较强的学习能力">
  366. </div>
  367. <div class="flex items-center ml-4">
  368. <label class="flex items-center text-sm text-gray-700 mr-4">
  369. <input type="radio" name="soft-score-1" value="10" checked class="mr-1 soft-score-yes"> 有(
  370. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  371. </label>
  372. <label class="flex items-center text-sm text-gray-700">
  373. <input type="radio" name="soft-score-1" value="0" class="mr-1 soft-score-no"> 无(0分)
  374. </label>
  375. <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  376. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  377. <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>
  378. </svg>
  379. </button>
  380. </div>
  381. </div>
  382. </div>
  383. </div>
  384. <!-- 其他评分 -->
  385. <div class="p-3 bg-gray-50 rounded-lg">
  386. <div class="flex items-center justify-between mb-2">
  387. <h4 class="font-medium text-gray-800">其他</h4>
  388. <button onclick="addOtherItem()" class="px-2 py-1 bg-gray-100 text-xs text-gray-600 rounded hover:bg-gray-200 transition-colors">
  389. + 添加条件
  390. </button>
  391. </div>
  392. <div id="other-criteria-container" class="space-y-3">
  393. <div class="other-item flex items-center justify-between">
  394. <div>
  395. <input type="text" class="other-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="条件名称" value="教育背景">
  396. <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述" value="本科及以上学历">
  397. </div>
  398. <div class="flex items-center ml-4">
  399. <label class="flex items-center text-sm text-gray-700 mr-4">
  400. <input type="radio" name="other-score-1" value="10" checked class="mr-1 other-score-yes"> 符合(
  401. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  402. </label>
  403. <label class="flex items-center text-sm text-gray-700">
  404. <input type="radio" name="other-score-1" value="0" class="mr-1 other-score-no"> 不符合(0分)
  405. </label>
  406. <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  407. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  408. <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>
  409. </svg>
  410. </button>
  411. </div>
  412. </div>
  413. </div>
  414. </div>
  415. <!-- 通过标准 -->
  416. <div class="mt-4">
  417. <div class="flex items-center justify-between">
  418. <label class="block font-medium text-gray-800">通过标准:</label>
  419. <div class="flex items-center">
  420. <input type="number" id="passing-score" class="w-16 px-2 py-1 border border-gray-300 rounded text-sm" value="60">
  421. <span class="ml-1 text-sm text-gray-700">分以上</span>
  422. </div>
  423. </div>
  424. </div>
  425. </div>
  426. </div>
  427. <!-- AI筛选硬性指标卡片 -->
  428. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  429. <div class="flex items-center justify-between mb-4">
  430. <h3 class="font-semibold text-gray-900">AI筛选硬性指标</h3>
  431. <button onclick="addCriteria()" class="px-3 py-1 bg-primary text-white rounded text-sm btn-active hover:bg-blue-600 transition-colors">
  432. + 添加指标
  433. </button>
  434. </div>
  435. <div id="criteria-container" class="space-y-3">
  436. <!-- 动态生成筛选指标 -->
  437. </div>
  438. </div>
  439. </div>
  440. </div>
  441. <!-- 页面三:候选人列表页 -->
  442. <div id="page-candidate-list" class="page">
  443. <!-- 顶部导航 -->
  444. <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
  445. <div class="flex items-center">
  446. <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700 mr-2">
  447. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  448. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
  449. </svg>
  450. </button>
  451. <h1 id="candidate-list-title" class="text-lg font-semibold text-gray-900">候选人列表</h1>
  452. </div>
  453. </header>
  454. <!-- 候选人列表 -->
  455. <div id="candidate-list-container" class="p-4 space-y-4">
  456. <!-- 动态生成候选人卡片 -->
  457. </div>
  458. </div>
  459. <!-- 页面四:简历详情页 -->
  460. <div id="page-resume-detail" class="page">
  461. <!-- 顶部导航 -->
  462. <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
  463. <div class="flex items-center">
  464. <button onclick="goBack()" class="p-2 -ml-2 text-gray-500 hover:text-gray-700 mr-2">
  465. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  466. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"></path>
  467. </svg>
  468. </button>
  469. <h1 id="resume-detail-title" class="text-lg font-semibold text-gray-900">候选人详情</h1>
  470. </div>
  471. </header>
  472. <!-- Tab 切换 -->
  473. <nav class="bg-white border-b border-gray-200">
  474. <div class="flex">
  475. <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">
  476. AI分析
  477. </button>
  478. <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">
  479. 简历原文
  480. </button>
  481. </div>
  482. </nav>
  483. <!-- AI分析内容 -->
  484. <div id="content-ai-analysis" class="tab-content p-4 pb-24">
  485. <!-- 匹配度仪表盘 -->
  486. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-6 mb-4 text-center">
  487. <div class="relative inline-block">
  488. <svg class="progress-ring w-24 h-24" viewBox="0 0 100 100">
  489. <circle cx="50" cy="50" r="45" fill="none" stroke="#f1f5f9" stroke-width="8"/>
  490. <circle id="progress-circle" cx="50" cy="50" r="45" fill="none" stroke="#3B82F6" stroke-width="8"
  491. stroke-linecap="round" class="progress-ring-bar"
  492. stroke-dasharray="283" stroke-dashoffset="113"/>
  493. </svg>
  494. <div class="absolute inset-0 flex items-center justify-center">
  495. <span id="score-display" class="text-2xl font-bold text-gray-900">88</span>
  496. </div>
  497. </div>
  498. <p class="text-gray-600 mt-2">匹配度评分</p>
  499. </div>
  500. <!-- AI分析详情 -->
  501. <div id="ai-analysis-details" class="space-y-4">
  502. <!-- 动态生成分析内容 -->
  503. </div>
  504. </div>
  505. <!-- 简历原文内容 -->
  506. <div id="content-resume-content" class="tab-content hidden p-4 pb-24">
  507. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  508. <div id="resume-text" class="text-sm text-gray-700 leading-relaxed custom-scrollbar max-h-96 overflow-y-auto">
  509. <!-- 动态生成简历内容 -->
  510. </div>
  511. </div>
  512. </div>
  513. <!-- 底部操作栏 -->
  514. <footer class="fixed bottom-16 left-0 right-0 bg-white border-t border-gray-200 p-4 max-w-md mx-auto">
  515. <div class="flex space-x-3">
  516. <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">
  517. 不合适
  518. </button>
  519. <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">
  520. 通过初筛
  521. </button>
  522. </div>
  523. </footer>
  524. </div>
  525. <!-- 页面五:数据统计页 -->
  526. <div id="page-statistics" class="page">
  527. <!-- 顶部导航 -->
  528. <header class="bg-white border-b border-gray-200 px-4 py-3 sticky top-0 z-10">
  529. <div class="flex items-center justify-between">
  530. <h1 class="text-lg font-semibold text-gray-900">数据统计</h1>
  531. </div>
  532. </header>
  533. <!-- 统计内容 -->
  534. <div class="p-4 space-y-4">
  535. <!-- 总览卡片 -->
  536. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  537. <h3 class="font-semibold text-gray-900 mb-4">招聘总览</h3>
  538. <div class="grid grid-cols-2 gap-4">
  539. <div class="text-center">
  540. <div class="text-2xl font-bold text-primary" id="total-jobs">0</div>
  541. <div class="text-sm text-gray-600">活跃岗位</div>
  542. </div>
  543. <div class="text-center">
  544. <div class="text-2xl font-bold text-success" id="total-candidates">0</div>
  545. <div class="text-sm text-gray-600">候选人总数</div>
  546. </div>
  547. <div class="text-center">
  548. <div class="text-2xl font-bold text-warning" id="pending-resumes">0</div>
  549. <div class="text-sm text-gray-600">待处理简历</div>
  550. </div>
  551. <div class="text-center">
  552. <div class="text-2xl font-bold text-success" id="passed-resumes">0</div>
  553. <div class="text-sm text-gray-600">已通过初筛</div>
  554. </div>
  555. </div>
  556. </div>
  557. <!-- 部门统计 -->
  558. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  559. <h3 class="font-semibold text-gray-900 mb-4">部门招聘情况</h3>
  560. <div id="department-stats" class="space-y-3">
  561. <!-- 动态生成部门统计 -->
  562. </div>
  563. </div>
  564. </div>
  565. </div>
  566. <!-- 底部导航栏 -->
  567. <nav class="bottom-nav max-w-md mx-auto">
  568. <div class="flex">
  569. <button onclick="switchPage('page-job-management')" class="flex-1 nav-item active" id="nav-jobs">
  570. <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  571. <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>
  572. </svg>
  573. <span class="text-xs">岗位管理</span>
  574. </button>
  575. <button onclick="switchPage('page-candidate-list')" class="flex-1 nav-item" id="nav-candidates">
  576. <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  577. <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>
  578. </svg>
  579. <span class="text-xs">候选人</span>
  580. </button>
  581. <button onclick="switchPage('page-statistics')" class="flex-1 nav-item" id="nav-stats">
  582. <svg class="w-6 h-6 mb-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  583. <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>
  584. </svg>
  585. <span class="text-xs">数据统计</span>
  586. </button>
  587. </div>
  588. </nav>
  589. </main>
  590. <script>
  591. // 模拟数据
  592. const mockData = {
  593. jobs: [
  594. {
  595. id: 1,
  596. title: "资深产品经理",
  597. department: "产品部",
  598. location: "北京",
  599. status: "招聘中",
  600. pendingResumes: 12,
  601. passedResumes: 3,
  602. description: "负责产品规划和设计,具备良好的用户体验意识",
  603. criteria: [
  604. { field: "学历", condition: "大于等于", value: "本科" },
  605. { field: "工作年限", condition: "大于等于", value: "5年" },
  606. { field: "必要技能", condition: "包含", value: "产品设计" }
  607. ],
  608. talentProfile: {
  609. positionDesc: "资深产品经理,负责公司核心产品的规划、设计和迭代优化。",
  610. skills: [
  611. "5年以上产品管理经验",
  612. "熟悉产品设计流程和方法",
  613. "具备数据分析能力",
  614. "良好的沟通协调能力",
  615. "对用户体验有深刻理解"
  616. ],
  617. companyCulture: "积极向上,鼓励创新,注重团队合作,重视个人发展。",
  618. salaryBenefits: "25K-35K,五险一金、带薪年假、定期体检等。",
  619. workArrangement: "北京,全职。",
  620. careerDevelopment: "提供内部培训、晋升通道,鼓励员工不断提升技能。",
  621. specialRequirements: "有B端产品经验优先。",
  622. recruitmentProcess: "简历筛选 -> 笔试 -> 面试 -> 最终录用,预计2周内完成"
  623. },
  624. scoringCriteria: {
  625. expYears: "5",
  626. internshipYears: "1",
  627. javaScore: "10",
  628. frontendScore: "7",
  629. databaseScore: "7",
  630. frameworkScore: "3",
  631. learningAbility: "10",
  632. teamwork: "10",
  633. education: "10",
  634. projectScore: "8",
  635. passingScore: "70"
  636. },
  637. candidates: [
  638. {
  639. id: 101,
  640. name: "张三",
  641. score: 88,
  642. tags: ["SaaS经验", "5年经验", "B端产品"],
  643. status: "pending"
  644. },
  645. {
  646. id: 102,
  647. name: "李四",
  648. score: 92,
  649. tags: ["电商背景", "团队管理", "数据驱动"],
  650. status: "pending"
  651. }
  652. ]
  653. },
  654. {
  655. id: 2,
  656. title: "前端工程师",
  657. department: "技术部",
  658. location: "上海",
  659. status: "招聘中",
  660. pendingResumes: 8,
  661. passedResumes: 5,
  662. description: "负责前端开发工作,熟练掌握React、Vue等框架",
  663. criteria: [
  664. { field: "学历", condition: "大于等于", value: "大专" },
  665. { field: "工作年限", condition: "大于等于", value: "3年" },
  666. { field: "必要技能", condition: "包含", value: "React" }
  667. ],
  668. talentProfile: {
  669. positionDesc: "前端工程师,负责公司Web应用的开发、维护和优化。",
  670. skills: [
  671. "3年以上前端开发经验",
  672. "熟练掌握HTML5、CSS3、JavaScript",
  673. "熟悉React、Vue等前端框架",
  674. "了解Node.js和常用构建工具",
  675. "有良好的代码风格和团队合作精神"
  676. ],
  677. companyCulture: "开放包容,技术驱动,鼓励创新,重视个人成长。",
  678. salaryBenefits: "15K-25K,五险一金、带薪年假、免费健身等。",
  679. workArrangement: "上海,全职。",
  680. careerDevelopment: "提供技术分享会、内部培训,有晋升为技术专家或管理岗的机会。",
  681. specialRequirements: "有大型Web应用开发经验优先。",
  682. recruitmentProcess: "简历筛选 -> 技术面试 -> HR面试 -> Offer,预计3周内完成"
  683. },
  684. scoringCriteria: {
  685. expYears: "3",
  686. internshipYears: "1",
  687. javaScore: "5",
  688. frontendScore: "15",
  689. databaseScore: "3",
  690. frameworkScore: "5",
  691. learningAbility: "10",
  692. teamwork: "10",
  693. education: "10",
  694. projectScore: "7",
  695. passingScore: "65"
  696. },
  697. candidates: [
  698. {
  699. id: 201,
  700. name: "赵六",
  701. score: 85,
  702. tags: ["React", "Vue", "TypeScript"],
  703. status: "pending"
  704. }
  705. ]
  706. },
  707. {
  708. id: 3,
  709. title: "Java开发工程师",
  710. department: "技术部",
  711. location: "上海",
  712. status: "招聘中",
  713. pendingResumes: 15,
  714. passedResumes: 4,
  715. description: "负责公司项目的后端开发、维护和优化。",
  716. criteria: [
  717. { field: "学历", condition: "大于等于", value: "本科" },
  718. { field: "工作年限", condition: "大于等于", value: "2年" },
  719. { field: "必要技能", condition: "包含", value: "Java" }
  720. ],
  721. talentProfile: {
  722. positionDesc: "初级Java开发工程师,负责公司项目的开发、维护和优化。",
  723. skills: [
  724. "2年以上Java或Java Web开发经验",
  725. "熟练使用Java后台技术",
  726. "熟练使用HTML5、CSS等前端技术",
  727. "熟练使用SQL语言",
  728. "熟悉SpringMVC、SSM、SpringBoot等Web框架",
  729. "有较强的学习能力",
  730. "有良好的创新精神和团队协作经验"
  731. ],
  732. companyCulture: "积极向上,鼓励创新,注重团队合作,重视个人发展。",
  733. salaryBenefits: "根据经验和能力面议,五险一金、带薪年假、定期体检等。",
  734. workArrangement: "上海,全职。",
  735. careerDevelopment: "提供内部培训、晋升通道,鼓励员工不断提升技能。",
  736. specialRequirements: "无。",
  737. recruitmentProcess: "简历筛选 -> 笔试 -> 面试 -> 最终录用,预计2周内完成"
  738. },
  739. scoringCriteria: {
  740. expYears: "2",
  741. internshipYears: "1",
  742. javaScore: "15",
  743. frontendScore: "10",
  744. databaseScore: "10",
  745. frameworkScore: "5",
  746. learningAbility: "10",
  747. teamwork: "10",
  748. education: "10",
  749. projectScore: "5",
  750. passingScore: "60"
  751. },
  752. candidates: [
  753. {
  754. id: 301,
  755. name: "王五",
  756. score: 78,
  757. tags: ["Java", "Spring", "MySQL"],
  758. status: "pending"
  759. },
  760. {
  761. id: 302,
  762. name: "刘七",
  763. score: 82,
  764. tags: ["Java", "微服务", "Docker"],
  765. status: "pending"
  766. }
  767. ]
  768. }
  769. ],
  770. resumeDetails: {
  771. 101: {
  772. name: "张三",
  773. score: 88,
  774. summary: "候选人具有丰富的B端产品管理经验,在SaaS领域有深入理解,具备良好的数据分析能力和用户洞察力。",
  775. pros: [
  776. "5年产品管理经验,其中3年专注于B端SaaS产品",
  777. "成功主导过用户量从0到10万的产品项目",
  778. "具备良好的数据敏感度,熟练使用各种分析工具",
  779. "有跨部门协作经验,沟通能力强"
  780. ],
  781. cons: [
  782. "缺乏C端产品经验,对用户增长策略了解有限",
  783. "技术背景相对薄弱,对开发流程理解不够深入"
  784. ],
  785. resumeText: `
  786. 姓名:张三
  787. 联系电话:138****1234
  788. 邮箱:zhangsan@email.com
  789. 教育背景:
  790. 2015-2019 北京理工大学 工商管理学士
  791. 工作经历:
  792. 2019.7-至今 ABC科技有限公司 产品经理
  793. - 负责B端SaaS产品的规划和设计,用户量从0增长到10万+
  794. - 主导产品需求分析,完成用户调研和竞品分析
  795. - 协调开发、设计、测试团队,确保产品按时交付
  796. 技能特长:
  797. - 熟练使用Axure、Figma等产品设计工具
  798. - 具备SQL数据查询能力,熟悉Google Analytics等分析工具
  799. - 了解前端开发基础,能与技术团队有效沟通
  800. `
  801. },
  802. 102: {
  803. name: "李四",
  804. score: 92,
  805. summary: "优秀的产品管理人才,具有丰富的电商和数据产品经验,团队管理能力突出。",
  806. pros: [
  807. "6年产品管理经验,涵盖电商、数据分析等多个领域",
  808. "有成功的团队管理经验,曾带领15人产品团队",
  809. "具备强大的商业敏感度,主导过多个商业化项目"
  810. ],
  811. cons: [
  812. "主要经验集中在C端产品,B端产品经验相对较少"
  813. ],
  814. resumeText: `
  815. 姓名:李四
  816. 联系电话:139****5678
  817. 邮箱:lisi@email.com
  818. 教育背景:
  819. 2014-2018 清华大学 计算机科学与技术学士
  820. 工作经历:
  821. 2020.3-至今 DEF电商集团 高级产品经理
  822. - 负责电商平台核心交易流程优化,GMV提升30%
  823. - 带领15人产品团队,负责用户增长和留存策略
  824. `
  825. }
  826. }
  827. };
  828. // 当前状态
  829. let currentJobId = null;
  830. let currentCandidateId = null;
  831. let pageHistory = [];
  832. let editingJobId = null;
  833. // 页面初始化
  834. document.addEventListener('DOMContentLoaded', function() {
  835. renderJobManagement();
  836. updateStatistics();
  837. // 确保新建岗位按钮可点击
  838. const addJobBtn = document.querySelector('button[onclick="createNewJob()"]');
  839. if (addJobBtn) {
  840. addJobBtn.addEventListener('click', createNewJob);
  841. }
  842. });
  843. // 页面切换函数
  844. function showPage(pageId, isBack = false) {
  845. // 隐藏所有页面
  846. document.querySelectorAll('.page').forEach(page => {
  847. page.classList.remove('active');
  848. if (isBack) {
  849. page.classList.add('slide-back');
  850. }
  851. });
  852. // 显示目标页面
  853. const targetPage = document.getElementById(pageId);
  854. targetPage.classList.add('active');
  855. // 更新历史记录
  856. if (!isBack) {
  857. pageHistory.push(pageId);
  858. }
  859. }
  860. // 底部导航切换
  861. function switchPage(pageId) {
  862. // 更新导航状态
  863. document.querySelectorAll('.nav-item').forEach(item => {
  864. item.classList.remove('active');
  865. });
  866. // 根据页面ID设置对应的导航项为活跃状态
  867. if (pageId === 'page-job-management') {
  868. document.getElementById('nav-jobs').classList.add('active');
  869. } else if (pageId === 'page-candidate-list') {
  870. document.getElementById('nav-candidates').classList.add('active');
  871. renderAllCandidates();
  872. } else if (pageId === 'page-statistics') {
  873. document.getElementById('nav-stats').classList.add('active');
  874. updateStatistics();
  875. }
  876. // 重置页面历史
  877. pageHistory = [pageId];
  878. showPage(pageId);
  879. }
  880. // 返回上一页
  881. function goBack() {
  882. if (pageHistory.length > 1) {
  883. pageHistory.pop(); // 移除当前页
  884. const previousPage = pageHistory[pageHistory.length - 1];
  885. showPage(previousPage, true);
  886. }
  887. }
  888. // 渲染岗位管理页面
  889. async function renderJobManagement() {
  890. const container = document.getElementById('job-management-container');
  891. container.innerHTML = '<div class="flex justify-center py-10"><div class="loading-spinner"></div></div>';
  892. try {
  893. const jobs = await API.Job.getAllJobs();
  894. if (jobs.length === 0) {
  895. container.innerHTML = `
  896. <div class="text-center py-10">
  897. <p class="text-gray-500">暂无岗位数据</p>
  898. <button onclick="createNewJob()" class="mt-4 px-4 py-2 bg-primary text-white rounded-lg">
  899. 创建新岗位
  900. </button>
  901. </div>
  902. `;
  903. return;
  904. }
  905. container.innerHTML = jobs.map(job => `
  906. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  907. <div class="flex items-start justify-between mb-3">
  908. <div class="flex-1">
  909. <h3 class="font-semibold text-gray-900">${job.title}</h3>
  910. <p class="text-sm text-gray-600">${job.department} · ${job.location}</p>
  911. </div>
  912. <span class="px-2 py-1 text-xs rounded-full ${getStatusColor(job.status)}">${job.status}</span>
  913. </div>
  914. <div class="mb-3">
  915. <p class="text-sm text-gray-700 line-clamp-2">${job.talentProfile?.positionDesc || job.description}</p>
  916. </div>
  917. ${job.talentProfile?.skills ? `
  918. <div class="mb-3">
  919. <div class="flex flex-wrap gap-1 mb-2">
  920. ${job.talentProfile.skills.slice(0, 3).map(skill => `
  921. <span class="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">${skill}</span>
  922. `).join('')}
  923. ${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>` : ''}
  924. </div>
  925. </div>
  926. ` : ''}
  927. <div class="mb-4 flex items-center justify-between">
  928. <div>
  929. <div class="text-2xl font-bold text-primary">${job.pendingResumes}</div>
  930. <div class="text-sm text-gray-600">待处理简历</div>
  931. </div>
  932. <div>
  933. <div class="text-2xl font-bold text-success">${job.passedResumes}</div>
  934. <div class="text-sm text-gray-600">已通过初筛</div>
  935. </div>
  936. </div>
  937. <div class="space-y-2">
  938. ${job.status === '招聘中' ? `
  939. <button onclick="startScreening(${job.id})"
  940. 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'}"
  941. ${job.pendingResumes === 0 ? 'disabled' : ''}>
  942. ${job.pendingResumes > 0 ? '一键智能筛选' : '暂无待处理简历'}
  943. </button>
  944. ` : `
  945. <button onclick="publishDraft(${job.id})"
  946. class="w-full py-3 px-4 rounded-lg font-medium btn-active transition-colors bg-primary text-white hover:bg-blue-600">
  947. 发布招聘
  948. </button>
  949. `}
  950. <div class="flex space-x-2">
  951. <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">
  952. 编辑岗位
  953. </button>
  954. ${job.status === '招聘中' ? `
  955. <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">
  956. 查看候选人
  957. </button>
  958. ` : `
  959. <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">
  960. 删除草稿
  961. </button>
  962. `}
  963. </div>
  964. </div>
  965. </div>
  966. `).join('');
  967. } catch (error) {
  968. console.error('获取岗位列表失败:', error);
  969. container.innerHTML = `
  970. <div class="text-center py-10">
  971. <p class="text-red-500">获取岗位数据失败</p>
  972. <button onclick="renderJobManagement()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
  973. 重试
  974. </button>
  975. </div>
  976. `;
  977. }
  978. }
  979. // 创建新岗位
  980. function createNewJob() {
  981. editingJobId = null;
  982. document.getElementById('create-job-title').textContent = '创建新岗位';
  983. clearJobForm();
  984. showPage('page-create-job');
  985. }
  986. // 编辑岗位
  987. async function editJob(jobId) {
  988. editingJobId = jobId;
  989. document.getElementById('create-job-title').textContent = '编辑岗位';
  990. try {
  991. const job = await API.Job.getJobById(jobId);
  992. if (job) {
  993. fillJobForm(job);
  994. showPage('page-create-job');
  995. } else {
  996. showToast('获取岗位详情失败');
  997. }
  998. } catch (error) {
  999. console.error('获取岗位详情失败:', error);
  1000. showToast('获取岗位详情失败');
  1001. }
  1002. }
  1003. // 清空表单
  1004. function clearJobForm() {
  1005. document.getElementById('job-title').value = '';
  1006. document.getElementById('job-department').value = '';
  1007. document.getElementById('job-location').value = '';
  1008. document.getElementById('job-description').value = '';
  1009. document.getElementById('criteria-container').innerHTML = '';
  1010. // 清空人才基本面
  1011. document.getElementById('job-position-desc').value = '';
  1012. 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="请输入技能要求...">';
  1013. document.getElementById('company-culture').value = '';
  1014. document.getElementById('salary-benefits').value = '';
  1015. document.getElementById('work-arrangement').value = '';
  1016. document.getElementById('career-development').value = '';
  1017. document.getElementById('special-requirements').value = '';
  1018. document.getElementById('recruitment-process').value = '';
  1019. // 重置评分准则为默认值
  1020. // 设置权重
  1021. document.getElementById('exp-weight').value = '30';
  1022. document.getElementById('tech-weight').value = '40';
  1023. document.getElementById('soft-weight').value = '20';
  1024. document.getElementById('other-weight').value = '10';
  1025. // 重置通过标准
  1026. document.getElementById('passing-score').value = '60';
  1027. // 重置评分标准容器
  1028. document.getElementById('exp-criteria-container').innerHTML = '';
  1029. document.getElementById('tech-criteria-container').innerHTML = '';
  1030. document.getElementById('soft-criteria-container').innerHTML = '';
  1031. document.getElementById('other-criteria-container').innerHTML = '';
  1032. // 添加默认项
  1033. addExpItem();
  1034. addTechItem();
  1035. addSoftItem();
  1036. addOtherItem();
  1037. }
  1038. // 填充表单
  1039. function fillJobForm(job) {
  1040. document.getElementById('job-title').value = job.title;
  1041. document.getElementById('job-department').value = job.department;
  1042. document.getElementById('job-location').value = job.location;
  1043. document.getElementById('job-description').value = job.description;
  1044. // 填充筛选条件
  1045. const container = document.getElementById('criteria-container');
  1046. container.innerHTML = '';
  1047. job.criteria.forEach(criteria => {
  1048. addCriteriaRow(criteria);
  1049. });
  1050. // 填充人才基本面(如果存在)
  1051. if (job.talentProfile) {
  1052. document.getElementById('job-position-desc').value = job.talentProfile.positionDesc || '';
  1053. // 清空并填充技能要求
  1054. const skillsContainer = document.getElementById('skills-container');
  1055. skillsContainer.innerHTML = '';
  1056. if (job.talentProfile.skills && job.talentProfile.skills.length > 0) {
  1057. job.talentProfile.skills.forEach(skill => {
  1058. const input = document.createElement('div');
  1059. input.className = 'flex items-center';
  1060. input.innerHTML = `
  1061. <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}">
  1062. <button onclick="removeSkillItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
  1063. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1064. <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>
  1065. </svg>
  1066. </button>
  1067. `;
  1068. skillsContainer.appendChild(input);
  1069. });
  1070. } else {
  1071. 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="请输入技能要求...">';
  1072. }
  1073. document.getElementById('company-culture').value = job.talentProfile.companyCulture || '';
  1074. document.getElementById('salary-benefits').value = job.talentProfile.salaryBenefits || '';
  1075. document.getElementById('work-arrangement').value = job.talentProfile.workArrangement || '';
  1076. document.getElementById('career-development').value = job.talentProfile.careerDevelopment || '';
  1077. document.getElementById('special-requirements').value = job.talentProfile.specialRequirements || '';
  1078. document.getElementById('recruitment-process').value = job.talentProfile.recruitmentProcess || '';
  1079. }
  1080. // 填充评分准则(如果存在)
  1081. if (job.scoringCriteria) {
  1082. // 填充权重设置
  1083. if (job.scoringCriteria.weights) {
  1084. document.getElementById('exp-weight').value = job.scoringCriteria.weights.expWeight || 30;
  1085. document.getElementById('tech-weight').value = job.scoringCriteria.weights.techWeight || 40;
  1086. document.getElementById('soft-weight').value = job.scoringCriteria.weights.softWeight || 20;
  1087. document.getElementById('other-weight').value = job.scoringCriteria.weights.otherWeight || 10;
  1088. }
  1089. // 填充工作经验评分项
  1090. const expContainer = document.getElementById('exp-criteria-container');
  1091. expContainer.innerHTML = '';
  1092. if (job.scoringCriteria.expCriteria && job.scoringCriteria.expCriteria.length > 0) {
  1093. job.scoringCriteria.expCriteria.forEach(exp => {
  1094. const div = document.createElement('div');
  1095. div.className = 'exp-item';
  1096. div.innerHTML = `
  1097. <div class="flex items-center mb-1">
  1098. <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="${exp.years}">
  1099. <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="${exp.desc}" placeholder="年以上相关工作经验">
  1100. <button onclick="removeExpItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
  1101. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1102. <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>
  1103. </svg>
  1104. </button>
  1105. </div>
  1106. <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
  1107. `;
  1108. expContainer.appendChild(div);
  1109. });
  1110. } else {
  1111. // 添加默认项
  1112. addExpItem();
  1113. }
  1114. // 填充技术能力评分项
  1115. const techContainer = document.getElementById('tech-criteria-container');
  1116. techContainer.innerHTML = '';
  1117. if (job.scoringCriteria.techCriteria && job.scoringCriteria.techCriteria.length > 0) {
  1118. job.scoringCriteria.techCriteria.forEach((tech, index) => {
  1119. const div = document.createElement('div');
  1120. div.className = 'tech-item';
  1121. div.innerHTML = `
  1122. <div class="mb-1">
  1123. <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 || ''}">
  1124. <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="${tech.desc || ''}">
  1125. </div>
  1126. <div class="flex space-x-2 mt-1">
  1127. <label class="flex items-center text-xs text-gray-700">
  1128. <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.high || 15}" checked class="mr-1 tech-score-high"> 优秀(
  1129. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.high || 15}">分)
  1130. </label>
  1131. <label class="flex items-center text-xs text-gray-700">
  1132. <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.medium || 10}" class="mr-1 tech-score-med"> 良好(
  1133. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.medium || 10}">分)
  1134. </label>
  1135. <label class="flex items-center text-xs text-gray-700">
  1136. <input type="radio" name="tech-score-${index+1}" value="${tech.scores?.low || 5}" class="mr-1 tech-score-low"> 一般(
  1137. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${tech.scores?.low || 5}">分)
  1138. </label>
  1139. <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
  1140. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1141. <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>
  1142. </svg>
  1143. </button>
  1144. </div>
  1145. `;
  1146. techContainer.appendChild(div);
  1147. });
  1148. } else {
  1149. // 保留默认项
  1150. }
  1151. // 填充软性技能评分项
  1152. const softContainer = document.getElementById('soft-criteria-container');
  1153. softContainer.innerHTML = '';
  1154. if (job.scoringCriteria.softCriteria && job.scoringCriteria.softCriteria.length > 0) {
  1155. job.scoringCriteria.softCriteria.forEach((soft, index) => {
  1156. const div = document.createElement('div');
  1157. div.className = 'soft-item flex items-center justify-between';
  1158. div.innerHTML = `
  1159. <div>
  1160. <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 || ''}">
  1161. <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述" value="${soft.desc || ''}">
  1162. </div>
  1163. <div class="flex items-center ml-4">
  1164. <label class="flex items-center text-sm text-gray-700 mr-4">
  1165. <input type="radio" name="soft-score-${index+1}" value="${soft.score || 10}" checked class="mr-1 soft-score-yes"> 有(
  1166. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${soft.score || 10}">分)
  1167. </label>
  1168. <label class="flex items-center text-sm text-gray-700">
  1169. <input type="radio" name="soft-score-${index+1}" value="0" class="mr-1 soft-score-no"> 无(0分)
  1170. </label>
  1171. <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  1172. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1173. <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>
  1174. </svg>
  1175. </button>
  1176. </div>
  1177. `;
  1178. softContainer.appendChild(div);
  1179. });
  1180. } else {
  1181. // 保留默认项
  1182. }
  1183. // 填充其他评分项
  1184. const otherContainer = document.getElementById('other-criteria-container');
  1185. otherContainer.innerHTML = '';
  1186. if (job.scoringCriteria.otherCriteria && job.scoringCriteria.otherCriteria.length > 0) {
  1187. job.scoringCriteria.otherCriteria.forEach((other, index) => {
  1188. const div = document.createElement('div');
  1189. div.className = 'other-item flex items-center justify-between';
  1190. div.innerHTML = `
  1191. <div>
  1192. <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 || ''}">
  1193. <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述" value="${other.desc || ''}">
  1194. </div>
  1195. <div class="flex items-center ml-4">
  1196. <label class="flex items-center text-sm text-gray-700 mr-4">
  1197. <input type="radio" name="other-score-${index+1}" value="${other.score || 10}" checked class="mr-1 other-score-yes"> 符合(
  1198. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="${other.score || 10}">分)
  1199. </label>
  1200. <label class="flex items-center text-sm text-gray-700">
  1201. <input type="radio" name="other-score-${index+1}" value="0" class="mr-1 other-score-no"> 不符合(0分)
  1202. </label>
  1203. <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  1204. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1205. <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>
  1206. </svg>
  1207. </button>
  1208. </div>
  1209. `;
  1210. otherContainer.appendChild(div);
  1211. });
  1212. } else {
  1213. // 保留默认项
  1214. }
  1215. // 设置通过标准
  1216. document.getElementById('passing-score').value = job.scoringCriteria.passingScore || '60';
  1217. }
  1218. }
  1219. // 保存岗位为草稿
  1220. async function saveJob(shouldPublish = false) {
  1221. // 验证权重总和是否为100%
  1222. if (!checkWeightTotals()) {
  1223. showToast('评分维度权重总和必须为100%');
  1224. return;
  1225. }
  1226. const title = document.getElementById('job-title').value.trim();
  1227. const department = document.getElementById('job-department').value;
  1228. const location = document.getElementById('job-location').value;
  1229. const description = document.getElementById('job-description').value.trim();
  1230. if (!title || !department || !location) {
  1231. showToast('请填写完整的基础信息');
  1232. return;
  1233. }
  1234. // 收集筛选条件
  1235. const criteria = [];
  1236. const criteriaRows = document.querySelectorAll('.criteria-row');
  1237. criteriaRows.forEach(row => {
  1238. const field = row.querySelector('.criteria-field').value;
  1239. const condition = row.querySelector('.criteria-condition').value;
  1240. const value = row.querySelector('.criteria-value').value.trim();
  1241. if (field && condition && value) {
  1242. criteria.push({ field, condition, value });
  1243. }
  1244. });
  1245. // 收集权重设置
  1246. const scoringWeights = {
  1247. expWeight: document.getElementById('exp-weight').value,
  1248. techWeight: document.getElementById('tech-weight').value,
  1249. softWeight: document.getElementById('soft-weight').value,
  1250. otherWeight: document.getElementById('other-weight').value
  1251. };
  1252. // 收集工作经验评分项
  1253. const expCriteria = [];
  1254. document.querySelectorAll('.exp-item').forEach(item => {
  1255. const years = item.querySelector('.exp-years').value;
  1256. const desc = item.querySelector('.exp-desc').value;
  1257. if (years && desc) {
  1258. expCriteria.push({ years, desc });
  1259. }
  1260. });
  1261. // 收集技术能力评分项
  1262. const techCriteria = [];
  1263. document.querySelectorAll('.tech-item').forEach((item, index) => {
  1264. const name = item.querySelector('.tech-name').value;
  1265. const desc = item.querySelector('.tech-desc').value;
  1266. const highScore = item.querySelector('.tech-score-high').parentNode.querySelector('input[type="number"]').value;
  1267. const medScore = item.querySelector('.tech-score-med').parentNode.querySelector('input[type="number"]').value;
  1268. const lowScore = item.querySelector('.tech-score-low').parentNode.querySelector('input[type="number"]').value;
  1269. if (name) {
  1270. techCriteria.push({
  1271. name,
  1272. desc,
  1273. scores: {
  1274. high: highScore,
  1275. medium: medScore,
  1276. low: lowScore
  1277. }
  1278. });
  1279. }
  1280. });
  1281. // 收集软性技能评分项
  1282. const softCriteria = [];
  1283. document.querySelectorAll('.soft-item').forEach((item, index) => {
  1284. const name = item.querySelector('.soft-name').value;
  1285. const desc = item.querySelector('.soft-desc').value;
  1286. const score = item.querySelector('.soft-score-yes').parentNode.querySelector('input[type="number"]').value;
  1287. if (name) {
  1288. softCriteria.push({ name, desc, score });
  1289. }
  1290. });
  1291. // 收集其他评分项
  1292. const otherCriteria = [];
  1293. document.querySelectorAll('.other-item').forEach((item, index) => {
  1294. const name = item.querySelector('.other-name').value;
  1295. const desc = item.querySelector('.other-desc').value;
  1296. const score = item.querySelector('.other-score-yes').parentNode.querySelector('input[type="number"]').value;
  1297. if (name) {
  1298. otherCriteria.push({ name, desc, score });
  1299. }
  1300. });
  1301. // 收集人才基本面信息
  1302. const skillItems = document.querySelectorAll('.skill-item');
  1303. const skills = [];
  1304. skillItems.forEach(item => {
  1305. if (item.value.trim()) {
  1306. skills.push(item.value.trim());
  1307. }
  1308. });
  1309. const talentProfile = {
  1310. positionDesc: document.getElementById('job-position-desc').value.trim(),
  1311. skills: skills,
  1312. companyCulture: document.getElementById('company-culture').value.trim(),
  1313. salaryBenefits: document.getElementById('salary-benefits').value.trim(),
  1314. workArrangement: document.getElementById('work-arrangement').value.trim(),
  1315. careerDevelopment: document.getElementById('career-development').value.trim(),
  1316. specialRequirements: document.getElementById('special-requirements').value.trim(),
  1317. recruitmentProcess: document.getElementById('recruitment-process').value.trim()
  1318. };
  1319. // 收集评分准则
  1320. const scoringCriteria = {
  1321. weights: scoringWeights,
  1322. expCriteria,
  1323. techCriteria,
  1324. softCriteria,
  1325. otherCriteria,
  1326. passingScore: document.getElementById('passing-score').value
  1327. };
  1328. const jobData = {
  1329. title,
  1330. department,
  1331. location,
  1332. description,
  1333. criteria,
  1334. talentProfile,
  1335. scoringCriteria,
  1336. status: shouldPublish ? '招聘中' : '草稿'
  1337. };
  1338. try {
  1339. let response;
  1340. if (editingJobId) {
  1341. // 编辑现有岗位
  1342. response = await API.Job.updateJob(editingJobId, jobData);
  1343. } else {
  1344. // 创建新岗位
  1345. response = await API.Job.createJob(jobData);
  1346. }
  1347. showToast(response.message || (shouldPublish ? '岗位已成功发布' : '岗位已保存为草稿'));
  1348. renderJobManagement();
  1349. updateStatistics();
  1350. goBack();
  1351. } catch (error) {
  1352. console.error('保存岗位失败:', error);
  1353. showToast(shouldPublish ? '发布岗位失败,请重试' : '保存岗位失败,请重试');
  1354. }
  1355. }
  1356. // 发布岗位
  1357. function publishJob() {
  1358. saveJob(true);
  1359. }
  1360. // 添加筛选条件
  1361. function addCriteria() {
  1362. addCriteriaRow();
  1363. }
  1364. // 添加筛选条件行
  1365. function addCriteriaRow(criteria = null) {
  1366. const container = document.getElementById('criteria-container');
  1367. const row = document.createElement('div');
  1368. row.className = 'criteria-row flex items-center space-x-2 p-3 bg-gray-50 rounded-lg';
  1369. row.innerHTML = `
  1370. <select class="criteria-field flex-1 px-2 py-1 border border-gray-300 rounded text-sm">
  1371. <option value="">选择指标</option>
  1372. <option value="学历" ${criteria?.field === '学历' ? 'selected' : ''}>学历</option>
  1373. <option value="工作年限" ${criteria?.field === '工作年限' ? 'selected' : ''}>工作年限</option>
  1374. <option value="必要技能" ${criteria?.field === '必要技能' ? 'selected' : ''}>必要技能</option>
  1375. <option value="语言要求" ${criteria?.field === '语言要求' ? 'selected' : ''}>语言要求</option>
  1376. </select>
  1377. <select class="criteria-condition flex-1 px-2 py-1 border border-gray-300 rounded text-sm">
  1378. <option value="">选择条件</option>
  1379. <option value="等于" ${criteria?.condition === '等于' ? 'selected' : ''}>等于</option>
  1380. <option value="大于等于" ${criteria?.condition === '大于等于' ? 'selected' : ''}>大于等于</option>
  1381. <option value="包含" ${criteria?.condition === '包含' ? 'selected' : ''}>包含</option>
  1382. <option value="不包含" ${criteria?.condition === '不包含' ? 'selected' : ''}>不包含</option>
  1383. </select>
  1384. <input type="text" class="criteria-value flex-1 px-2 py-1 border border-gray-300 rounded text-sm"
  1385. placeholder="输入值" value="${criteria?.value || ''}">
  1386. <button onclick="removeCriteria(this)" class="p-1 text-red-500 hover:text-red-700">
  1387. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1388. <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>
  1389. </svg>
  1390. </button>
  1391. `;
  1392. container.appendChild(row);
  1393. }
  1394. // 删除筛选条件
  1395. function removeCriteria(button) {
  1396. button.closest('.criteria-row').remove();
  1397. }
  1398. // 添加技能要求项
  1399. function addSkillItem() {
  1400. const container = document.getElementById('skills-container');
  1401. const input = document.createElement('div');
  1402. input.className = 'flex items-center';
  1403. input.innerHTML = `
  1404. <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="请输入技能要求...">
  1405. <button onclick="removeSkillItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
  1406. <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1407. <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>
  1408. </svg>
  1409. </button>
  1410. `;
  1411. container.appendChild(input);
  1412. }
  1413. // 删除技能要求项
  1414. function removeSkillItem(button) {
  1415. button.closest('.flex').remove();
  1416. }
  1417. // 开始筛选
  1418. async function startScreening(jobId) {
  1419. const button = event.target;
  1420. const originalText = button.textContent;
  1421. // 显示加载状态
  1422. button.innerHTML = '<div class="loading-spinner inline-block mr-2"></div>筛选中...';
  1423. button.disabled = true;
  1424. try {
  1425. const response = await API.Job.startScreening(jobId);
  1426. showToast(response.message || '筛选完成!');
  1427. renderJobManagement();
  1428. updateStatistics();
  1429. } catch (error) {
  1430. console.error('启动筛选失败:', error);
  1431. showToast('启动筛选失败,请重试');
  1432. // 恢复按钮状态
  1433. button.textContent = originalText;
  1434. button.disabled = false;
  1435. }
  1436. }
  1437. // 查看岗位候选人
  1438. async function viewJobCandidates(jobId) {
  1439. currentJobId = jobId;
  1440. document.getElementById('candidate-list-title').textContent = '加载中...';
  1441. showPage('page-candidate-list');
  1442. try {
  1443. const response = await API.Job.getJobCandidates(jobId);
  1444. document.getElementById('candidate-list-title').textContent = response.jobTitle;
  1445. renderCandidateList(response.candidates);
  1446. } catch (error) {
  1447. console.error('获取候选人列表失败:', error);
  1448. document.getElementById('candidate-list-container').innerHTML = `
  1449. <div class="text-center py-10">
  1450. <p class="text-red-500">获取候选人数据失败</p>
  1451. <button onclick="viewJobCandidates(${jobId})" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
  1452. 重试
  1453. </button>
  1454. </div>
  1455. `;
  1456. }
  1457. }
  1458. // 渲染所有候选人
  1459. async function renderAllCandidates() {
  1460. document.getElementById('candidate-list-title').textContent = '加载中...';
  1461. showPage('page-candidate-list');
  1462. try {
  1463. const candidates = await API.Candidate.getAllCandidates();
  1464. document.getElementById('candidate-list-title').textContent = '全部候选人';
  1465. renderCandidateList(candidates, true);
  1466. } catch (error) {
  1467. console.error('获取候选人列表失败:', error);
  1468. document.getElementById('candidate-list-container').innerHTML = `
  1469. <div class="text-center py-10">
  1470. <p class="text-red-500">获取候选人数据失败</p>
  1471. <button onclick="renderAllCandidates()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
  1472. 重试
  1473. </button>
  1474. </div>
  1475. `;
  1476. }
  1477. }
  1478. // 渲染候选人列表
  1479. function renderCandidateList(candidates, showJobTitle = false) {
  1480. const container = document.getElementById('candidate-list-container');
  1481. if (candidates.length === 0) {
  1482. container.innerHTML = `
  1483. <div class="text-center py-12">
  1484. <svg class="w-16 h-16 text-gray-300 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1485. <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>
  1486. </svg>
  1487. <p class="text-gray-500">暂无候选人</p>
  1488. </div>
  1489. `;
  1490. return;
  1491. }
  1492. container.innerHTML = candidates.map(candidate => `
  1493. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4 card-hover cursor-pointer" onclick="viewResumeDetail(${candidate.id})">
  1494. <div class="flex items-start justify-between mb-3">
  1495. <div>
  1496. <h3 class="font-semibold text-gray-900">${candidate.name}</h3>
  1497. ${showJobTitle ? `<p class="text-sm text-gray-600">${candidate.jobTitle}</p>` : ''}
  1498. <div class="flex items-center mt-1">
  1499. <span class="text-2xl font-bold text-primary mr-2">${candidate.score}</span>
  1500. <span class="text-sm text-gray-600">匹配度</span>
  1501. </div>
  1502. </div>
  1503. <div class="w-3 h-3 rounded-full ${getStatusDot(candidate.status)}"></div>
  1504. </div>
  1505. <div class="flex flex-wrap gap-2 mb-3">
  1506. ${candidate.tags.map(tag => `
  1507. <span class="px-2 py-1 text-xs bg-blue-50 text-blue-700 rounded">${tag}</span>
  1508. `).join('')}
  1509. </div>
  1510. <div class="flex items-center justify-end">
  1511. <svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1512. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
  1513. </svg>
  1514. </div>
  1515. </div>
  1516. `).join('');
  1517. }
  1518. // 查看简历详情
  1519. async function viewResumeDetail(candidateId) {
  1520. currentCandidateId = candidateId;
  1521. document.getElementById('resume-detail-title').textContent = '加载中...';
  1522. showPage('page-resume-detail');
  1523. try {
  1524. const detail = await API.Candidate.getCandidateDetails(candidateId);
  1525. if (detail) {
  1526. document.getElementById('resume-detail-title').textContent = detail.name;
  1527. renderResumeDetail(detail);
  1528. } else {
  1529. document.getElementById('ai-analysis-details').innerHTML = `
  1530. <div class="text-center py-10">
  1531. <p class="text-red-500">未找到候选人数据</p>
  1532. <button onclick="goBack()" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
  1533. 返回
  1534. </button>
  1535. </div>
  1536. `;
  1537. }
  1538. } catch (error) {
  1539. console.error('获取候选人详情失败:', error);
  1540. document.getElementById('ai-analysis-details').innerHTML = `
  1541. <div class="text-center py-10">
  1542. <p class="text-red-500">获取候选人详情失败</p>
  1543. <button onclick="viewResumeDetail(${candidateId})" class="mt-4 px-4 py-2 bg-gray-200 text-gray-700 rounded-lg">
  1544. 重试
  1545. </button>
  1546. </div>
  1547. `;
  1548. }
  1549. }
  1550. // 渲染简历详情
  1551. function renderResumeDetail(detail) {
  1552. // 更新匹配度圆环
  1553. updateProgressRing(detail.score);
  1554. // 渲染AI分析详情
  1555. const analysisContainer = document.getElementById('ai-analysis-details');
  1556. analysisContainer.innerHTML = `
  1557. <!-- 综合评语 -->
  1558. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  1559. <h3 class="font-semibold text-gray-900 mb-2 flex items-center">
  1560. <svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1561. <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>
  1562. </svg>
  1563. 综合评语
  1564. </h3>
  1565. <p class="text-gray-700 text-sm leading-relaxed">${detail.summary}</p>
  1566. </div>
  1567. <!-- 亮点 -->
  1568. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  1569. <h3 class="font-semibold text-gray-900 mb-3 flex items-center">
  1570. <svg class="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1571. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
  1572. </svg>
  1573. 优势亮点
  1574. </h3>
  1575. <ul class="space-y-2">
  1576. ${detail.pros.map(pro => `
  1577. <li class="flex items-start text-sm text-gray-700">
  1578. <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">
  1579. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
  1580. </svg>
  1581. ${pro}
  1582. </li>
  1583. `).join('')}
  1584. </ul>
  1585. </div>
  1586. <!-- 待考察点 -->
  1587. <div class="bg-white rounded-lg shadow-sm border border-gray-200 p-4">
  1588. <h3 class="font-semibold text-gray-900 mb-3 flex items-center">
  1589. <svg class="w-5 h-5 text-orange-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1590. <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>
  1591. </svg>
  1592. 待考察点
  1593. </h3>
  1594. <ul class="space-y-2">
  1595. ${detail.cons.map(con => `
  1596. <li class="flex items-start text-sm text-gray-700">
  1597. <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">
  1598. <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>
  1599. </svg>
  1600. ${con}
  1601. </li>
  1602. `).join('')}
  1603. </ul>
  1604. </div>
  1605. `;
  1606. // 更新简历原文
  1607. document.getElementById('resume-text').innerHTML = detail.resumeText.replace(/\n/g, '<br>');
  1608. }
  1609. // 更新进度环
  1610. function updateProgressRing(score) {
  1611. const circle = document.getElementById('progress-circle');
  1612. const scoreDisplay = document.getElementById('score-display');
  1613. const circumference = 2 * Math.PI * 45;
  1614. const offset = circumference - (score / 100) * circumference;
  1615. circle.style.strokeDashoffset = offset;
  1616. scoreDisplay.textContent = score;
  1617. }
  1618. // Tab切换
  1619. function switchTab(tabName) {
  1620. // 更新tab按钮状态
  1621. document.querySelectorAll('[id^="tab-"]').forEach(tab => {
  1622. tab.classList.remove('text-primary', 'border-primary');
  1623. tab.classList.add('text-gray-500', 'border-transparent');
  1624. });
  1625. document.getElementById(`tab-${tabName}`).classList.remove('text-gray-500', 'border-transparent');
  1626. document.getElementById(`tab-${tabName}`).classList.add('text-primary', 'border-primary');
  1627. // 切换内容
  1628. document.querySelectorAll('.tab-content').forEach(content => {
  1629. content.classList.add('hidden');
  1630. });
  1631. document.getElementById(`content-${tabName}`).classList.remove('hidden');
  1632. }
  1633. // 处理拒绝
  1634. function handleReject() {
  1635. if (currentCandidateId) {
  1636. updateCandidateStatus(currentCandidateId, 'rejected');
  1637. showToast('已标记为不合适');
  1638. setTimeout(() => goBack(), 1000);
  1639. }
  1640. }
  1641. // 处理通过
  1642. function handleApprove() {
  1643. if (currentCandidateId) {
  1644. updateCandidateStatus(currentCandidateId, 'approved');
  1645. showToast('已通过初筛');
  1646. setTimeout(() => goBack(), 1000);
  1647. }
  1648. }
  1649. // 更新候选人状态
  1650. function updateCandidateStatus(candidateId, status) {
  1651. mockData.jobs.forEach(job => {
  1652. const candidate = job.candidates.find(c => c.id === candidateId);
  1653. if (candidate) {
  1654. candidate.status = status;
  1655. if (status === 'approved') {
  1656. job.passedResumes++;
  1657. }
  1658. }
  1659. });
  1660. updateStatistics();
  1661. }
  1662. // 更新统计数据
  1663. async function updateStatistics() {
  1664. try {
  1665. // 获取总览数据
  1666. const overviewStats = await API.Statistics.getOverviewStats();
  1667. document.getElementById('total-jobs').textContent = overviewStats.totalJobs;
  1668. document.getElementById('total-candidates').textContent = overviewStats.totalCandidates;
  1669. document.getElementById('pending-resumes').textContent = overviewStats.pendingResumes;
  1670. document.getElementById('passed-resumes').textContent = overviewStats.passedResumes;
  1671. // 获取部门统计
  1672. const departmentStats = await API.Statistics.getDepartmentStats();
  1673. const departmentContainer = document.getElementById('department-stats');
  1674. if (departmentStats.length === 0) {
  1675. departmentContainer.innerHTML = `
  1676. <div class="text-center py-4">
  1677. <p class="text-gray-500">暂无部门数据</p>
  1678. </div>
  1679. `;
  1680. return;
  1681. }
  1682. departmentContainer.innerHTML = departmentStats.map(dept => `
  1683. <div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
  1684. <div>
  1685. <div class="font-medium text-gray-900">${dept.department || '未分类'}</div>
  1686. <div class="text-sm text-gray-600">${dept.jobs}个岗位 · ${dept.candidates}名候选人</div>
  1687. </div>
  1688. <div class="text-right">
  1689. <div class="text-sm text-warning">${dept.pending}待处理</div>
  1690. <div class="text-sm text-success">${dept.passed}已通过</div>
  1691. </div>
  1692. </div>
  1693. `).join('');
  1694. } catch (error) {
  1695. console.error('获取统计数据失败:', error);
  1696. }
  1697. }
  1698. // 获取状态颜色
  1699. function getStatusColor(status) {
  1700. switch(status) {
  1701. case '招聘中': return 'bg-green-100 text-green-800';
  1702. case '已暂停': return 'bg-gray-100 text-gray-800';
  1703. case '草稿': return 'bg-blue-100 text-blue-800';
  1704. default: return 'bg-gray-100 text-gray-800';
  1705. }
  1706. }
  1707. // 获取状态指示点颜色
  1708. function getStatusDot(status) {
  1709. switch(status) {
  1710. case 'pending': return 'bg-yellow-400';
  1711. case 'approved': return 'bg-green-400';
  1712. case 'rejected': return 'bg-red-400';
  1713. default: return 'bg-gray-400';
  1714. }
  1715. }
  1716. // 显示Toast消息
  1717. function showToast(message) {
  1718. const toast = document.createElement('div');
  1719. toast.className = 'toast';
  1720. toast.textContent = message;
  1721. document.body.appendChild(toast);
  1722. setTimeout(() => {
  1723. toast.remove();
  1724. }, 3000);
  1725. }
  1726. // 检查维度权重总和是否为100%
  1727. function checkWeightTotals() {
  1728. const expWeight = parseInt(document.getElementById('exp-weight').value) || 0;
  1729. const techWeight = parseInt(document.getElementById('tech-weight').value) || 0;
  1730. const softWeight = parseInt(document.getElementById('soft-weight').value) || 0;
  1731. const otherWeight = parseInt(document.getElementById('other-weight').value) || 0;
  1732. const total = expWeight + techWeight + softWeight + otherWeight;
  1733. const errorElement = document.getElementById('weight-error');
  1734. if (total !== 100) {
  1735. errorElement.classList.remove('hidden');
  1736. return false;
  1737. } else {
  1738. errorElement.classList.add('hidden');
  1739. return true;
  1740. }
  1741. }
  1742. // 监听权重变化
  1743. document.addEventListener('DOMContentLoaded', function() {
  1744. const weightInputs = ['exp-weight', 'tech-weight', 'soft-weight', 'other-weight'];
  1745. weightInputs.forEach(id => {
  1746. const input = document.getElementById(id);
  1747. if (input) {
  1748. input.addEventListener('change', checkWeightTotals);
  1749. }
  1750. });
  1751. });
  1752. // 添加工作经验条件
  1753. function addExpItem() {
  1754. const container = document.getElementById('exp-criteria-container');
  1755. const index = container.children.length + 1;
  1756. const div = document.createElement('div');
  1757. div.className = 'exp-item';
  1758. div.innerHTML = `
  1759. <div class="flex items-center mb-1">
  1760. <input type="number" class="exp-years w-12 px-2 py-1 border border-gray-300 rounded text-sm mr-2" value="1">
  1761. <input type="text" class="exp-desc flex-1 px-2 py-1 border border-gray-300 rounded text-sm" value="" placeholder="年以上相关工作经验">
  1762. <button onclick="removeExpItem(this)" class="ml-2 p-1 text-red-500 hover:text-red-700">
  1763. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1764. <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>
  1765. </svg>
  1766. </button>
  1767. </div>
  1768. <div class="text-xs text-gray-500 ml-2">(符合:满分30分;不符合:0分)</div>
  1769. `;
  1770. container.appendChild(div);
  1771. }
  1772. // 删除工作经验条件
  1773. function removeExpItem(button) {
  1774. button.closest('.exp-item').remove();
  1775. }
  1776. // 添加技术能力项
  1777. function addTechItem() {
  1778. const container = document.getElementById('tech-criteria-container');
  1779. const index = container.children.length + 1;
  1780. const div = document.createElement('div');
  1781. div.className = 'tech-item';
  1782. div.innerHTML = `
  1783. <div class="mb-1">
  1784. <input type="text" class="tech-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称">
  1785. <input type="text" class="tech-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述">
  1786. </div>
  1787. <div class="flex space-x-2 mt-1">
  1788. <label class="flex items-center text-xs text-gray-700">
  1789. <input type="radio" name="tech-score-${index}" value="15" checked class="mr-1 tech-score-high"> 优秀(
  1790. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="15">分)
  1791. </label>
  1792. <label class="flex items-center text-xs text-gray-700">
  1793. <input type="radio" name="tech-score-${index}" value="10" class="mr-1 tech-score-med"> 良好(
  1794. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  1795. </label>
  1796. <label class="flex items-center text-xs text-gray-700">
  1797. <input type="radio" name="tech-score-${index}" value="5" class="mr-1 tech-score-low"> 一般(
  1798. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="5">分)
  1799. </label>
  1800. <button onclick="removeTechItem(this)" class="text-red-500 hover:text-red-700">
  1801. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1802. <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>
  1803. </svg>
  1804. </button>
  1805. </div>
  1806. `;
  1807. container.appendChild(div);
  1808. }
  1809. // 删除技术能力项
  1810. function removeTechItem(button) {
  1811. button.closest('.tech-item').remove();
  1812. }
  1813. // 添加软性技能项
  1814. function addSoftItem() {
  1815. const container = document.getElementById('soft-criteria-container');
  1816. const index = container.children.length + 1;
  1817. const div = document.createElement('div');
  1818. div.className = 'soft-item flex items-center justify-between';
  1819. div.innerHTML = `
  1820. <div>
  1821. <input type="text" class="soft-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="技能名称">
  1822. <input type="text" class="soft-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="技能描述">
  1823. </div>
  1824. <div class="flex items-center ml-4">
  1825. <label class="flex items-center text-sm text-gray-700 mr-4">
  1826. <input type="radio" name="soft-score-${index}" value="10" checked class="mr-1 soft-score-yes"> 有(
  1827. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  1828. </label>
  1829. <label class="flex items-center text-sm text-gray-700">
  1830. <input type="radio" name="soft-score-${index}" value="0" class="mr-1 soft-score-no"> 无(0分)
  1831. </label>
  1832. <button onclick="removeSoftItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  1833. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1834. <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>
  1835. </svg>
  1836. </button>
  1837. </div>
  1838. `;
  1839. container.appendChild(div);
  1840. }
  1841. // 删除软性技能项
  1842. function removeSoftItem(button) {
  1843. button.closest('.soft-item').remove();
  1844. }
  1845. // 添加其他评分条件
  1846. function addOtherItem() {
  1847. const container = document.getElementById('other-criteria-container');
  1848. const index = container.children.length + 1;
  1849. const div = document.createElement('div');
  1850. div.className = 'other-item flex items-center justify-between';
  1851. div.innerHTML = `
  1852. <div>
  1853. <input type="text" class="other-name w-full px-2 py-1 border border-gray-300 rounded text-sm mb-1" placeholder="条件名称">
  1854. <input type="text" class="other-desc w-full px-2 py-1 border border-gray-300 rounded text-sm" placeholder="条件描述">
  1855. </div>
  1856. <div class="flex items-center ml-4">
  1857. <label class="flex items-center text-sm text-gray-700 mr-4">
  1858. <input type="radio" name="other-score-${index}" value="10" checked class="mr-1 other-score-yes"> 符合(
  1859. <input type="number" class="w-8 px-1 border border-gray-300 rounded text-xs mx-1" value="10">分)
  1860. </label>
  1861. <label class="flex items-center text-sm text-gray-700">
  1862. <input type="radio" name="other-score-${index}" value="0" class="mr-1 other-score-no"> 不符合(0分)
  1863. </label>
  1864. <button onclick="removeOtherItem(this)" class="ml-2 text-red-500 hover:text-red-700">
  1865. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  1866. <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>
  1867. </svg>
  1868. </button>
  1869. </div>
  1870. `;
  1871. container.appendChild(div);
  1872. }
  1873. // 删除其他评分条件
  1874. function removeOtherItem(button) {
  1875. button.closest('.other-item').remove();
  1876. }
  1877. // 发布草稿
  1878. async function publishDraft(jobId) {
  1879. try {
  1880. const response = await API.Job.publishDraft(jobId);
  1881. showToast(response.message || '岗位已成功发布');
  1882. renderJobManagement();
  1883. updateStatistics();
  1884. } catch (error) {
  1885. console.error('发布岗位失败:', error);
  1886. showToast('发布岗位失败,请重试');
  1887. }
  1888. }
  1889. // 删除岗位草稿
  1890. async function deleteJob(jobId) {
  1891. try {
  1892. const response = await API.Job.deleteJob(jobId);
  1893. showToast(response.message || '岗位草稿已删除');
  1894. renderJobManagement();
  1895. updateStatistics();
  1896. } catch (error) {
  1897. console.error('删除岗位失败:', error);
  1898. showToast('删除岗位失败,请重试');
  1899. }
  1900. }
  1901. </script>
  1902. </body>
  1903. </html>