当前位置:首页>python>python简历工具箱 v1.0 - PyQt5 桌面版

python简历工具箱 v1.0 - PyQt5 桌面版

  • 2026-10-11 22:12:42
python简历工具箱 v1.0 - PyQt5 桌面版

简历工具箱 v1.0 - PyQt5 桌面版

简历生成、优化建议、评分打分、批量排序分析的桌面工具。

作者

项目
内容
作者
杨少平
公众号
Python学在坚持
微信
ysp2338084

运行

pip install PyQt5 python-docxpython ysp/code/resume_tool.py

功能

Tab1: 简历生成与优化

  • 左侧表单填写基本信息/教育/工作/技能/项目/自我评价
  • 一键生成 Markdown 格式简历
  • 导入已有 MD 简历文件编辑
  • 加载示例简历快速体验
  • 6 维度自动评分(满分100):基本信息15 + 教育15 + 工作25 + 技能20 + 项目15 + 格式10
  • 智能优化建议(根据各维度得分生成)
  • 导出 MD / Word 格式

Tab2: 批量分析与排名

  • 批量上传多个 MD 简历文件
  • 自动评分并按分数降序排名
  • 统计卡片:总数/平均分/最高分/最低分
  • 表格展示各维度得分,颜色区分优良差
  • 点击行查看详情和优化建议
  • 导出完整分析报告(MD格式,含排名表格和每份简历的建议)

评分维度

维度
满分
评分依据
基本信息完整度
15
姓名/电话/邮箱等关键词
教育背景
15
学历/学校等关键词
工作经验
25
公司/职位/年限等
技能匹配度
20
技术关键词命中数
项目经验
15
项目/成果等关键词
格式规范
10
Markdown标题数+内容长度

代码架构

resume_tool.py+-- ResumeScorer         # 评分引擎|   +-- score()          # 6维度打分|   +-- suggest()        # 生成优化建议+-- ResumeEditorTab      # Tab1: 生成与优化|   +-- 表单输入 -> 生成MD -> 预览编辑 -> 评分 -> 导出+-- BatchAnalysisTab     # Tab2: 批量分析|   +-- 批量上传 -> 自动评分排序 -> 统计卡片 -> 导出报告+-- ResumeToolApp        # 主窗口

依赖

PyQt5python-docx (Word导出)
-- coding: utf-8 --"""简历工具箱 v1.0 - PyQt5功能:简历生成/优化/打分/批量排序/导出作者:杨少平 | 公众号:Python学在坚持运行:python resume_tool.py依赖:pip install PyQt5 python-docx markdown"""import sys, os, re, json, uuidfrom datetime import datetimefrom PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,QLabel, QLineEdit, QTextEdit, QComboBox, QPushButton, QTabWidget,QGroupBox, QScrollArea, QSplitter, QFileDialog, QMessageBox,QTableWidget, QTableWidgetItem, QHeaderView, QProgressBar, QFrame,QFormLayout, QSpinBox, QCheckBox)from PyQt5.QtCore import Qtfrom PyQt5.QtGui import QFont, QColor==================== 简历评分引擎 ====================class ResumeScorer:"""简历评分:基于关键维度打分"""WEIGHTS = {"基本信息完整度": 15, "教育背景": 15, "工作经验": 25,"技能匹配度": 20, "项目经验": 15, "格式规范": 10,}KEYWORDS = {"技术": ["Python", "Java", "JavaScript", "Go", "C++", "SQL", "Docker", "K8s","React", "Vue", "Spring", "Django", "Flask", "TensorFlow", "PyTorch","AWS", "Azure", "Linux", "Git", "CI/CD", "微服务", "分布式"],"软技能": ["团队", "沟通", "领导", "管理", "协作", "解决问题", "创新"],}@staticmethoddef score(text):    scores = {}    total = 0    # 基本信息    info_keys = ["姓名", "电话", "邮箱", "手机", "email", "phone", "name"]    info_count = sum(1 for k in info_keys if k.lower() in text.lower())    scores["基本信息完整度"] = min(15, int(info_count / 3 * 15))    # 教育背景    edu_keys = ["学历", "大学", "本科", "硕士", "博士", "学士", "education", "university"]    edu_count = sum(1 for k in edu_keys if k.lower() in text.lower())    scores["教育背景"] = min(15, int(edu_count / 2 * 15))    # 工作经验    exp_keys = ["工作经验", "公司", "职位", "负责", "experience", "company", "负责人"]    exp_count = sum(1 for k in exp_keys if k.lower() in text.lower())    year_match = re.findall(r'(\d+)\s*年', text)    exp_years = max([int(y) for y in year_match] + [0])    scores["工作经验"] = min(25, int((exp_count / 3 * 15) + min(exp_years * 2, 10)))    # 技能    tech_count = sum(1 for k in ResumeScorer.KEYWORDS["技术"] if k.lower() in text.lower())    scores["技能匹配度"] = min(20, int(tech_count / 5 * 20))    # 项目    proj_keys = ["项目", "project", "成果", "业绩", "贡献"]    proj_count = sum(1 for k in proj_keys if k.lower() in text.lower())    scores["项目经验"] = min(15, int(proj_count / 2 * 15))    # 格式    lines = text.strip().split("\n")    has_sections = sum(1 for l in lines if l.startswith("#") or l.startswith("##"))    fmt_score = min(10, has_sections * 2 + (5 if len(text) > 500 else 0))    scores["格式规范"] = fmt_score    total = sum(scores.values())    return total, scores@staticmethoddef suggest(text, scores):    tips = []    if scores.get("基本信息完整度", 0) < 10:        tips.append("建议补充完整的联系方式(姓名、电话、邮箱)")    if scores.get("教育背景", 0) < 10:        tips.append("建议详细描述教育背景(学校、专业、学历、时间)")    if scores.get("工作经验", 0) < 15:        tips.append("建议丰富工作经验描述,使用STAR法则(情境-任务-行动-结果)")    if scores.get("技能匹配度", 0) < 12:        tips.append("建议列出更多技术技能关键词,与目标岗位匹配")    if scores.get("项目经验", 0) < 10:        tips.append("建议增加项目经验,突出个人贡献和量化成果")    if scores.get("格式规范", 0) < 6:        tips.append("建议使用Markdown标题分节(# 基本信息、## 工作经验等)")    if len(text) < 300:        tips.append("简历内容偏少,建议扩充到500字以上")    if not tips:        tips.append("简历整体不错,可以进一步量化工作成果")    return tips==================== 简历模板 ====================RESUME_TEMPLATE = """# 个人简历基本信息姓名:{name}电话:{phone}邮箱:{email}求职意向:{position}教育背景edu_school}  {edu_major | edu_degree}  {edu_time工作经验company1}  {title1 | {work_time1}{work_desc1}专业技能{skills}项目经验{project1}项目描述:{proj_desc1}个人职责:{proj_role1}项目成果:{proj_result1}自我评价{self_eval}"""==================== Tab1: 简历生成与优化 ====================class ResumeEditorTab(QWidget):def init(self):super().init()self._build()def _build(self):    splitter = QSplitter(Qt.Horizontal)    lay = QVBoxLayout(self)    lay.setContentsMargins(0, 0, 0, 0)    lay.addWidget(splitter)    # ---- 左侧:输入/生成 ----    left_scroll = QScrollArea()    left_scroll.setWidgetResizable(True)    left_scroll.setMinimumWidth(360)    left_scroll.setMaximumWidth(450)    left_w = QWidget()    ll = QVBoxLayout(left_w)    ll.setContentsMargins(12, 12, 12, 12)    ll.setSpacing(8)    # 导入    imp_row = QHBoxLayout()    btn_import = QPushButton("📂 导入MD简历")    btn_import.setStyleSheet("padding:8px;background:#1976d2;color:#fff;border:none;border-radius:4px;")    btn_import.clicked.connect(self._import_md)    imp_row.addWidget(btn_import)    btn_sample = QPushButton("📝 加载示例")    btn_sample.setStyleSheet("padding:8px;background:#ff9800;color:#fff;border:none;border-radius:4px;")    btn_sample.clicked.connect(self._load_sample)    imp_row.addWidget(btn_sample)    ll.addLayout(imp_row)    # 表单    g1 = QGroupBox("基本信息")    g1l = QFormLayout()    self.ed_name = QLineEdit(); self.ed_name.setPlaceholderText("张三")    g1l.addRow("姓名:", self.ed_name)    self.ed_phone = QLineEdit(); self.ed_phone.setPlaceholderText("13800138000")    g1l.addRow("电话:", self.ed_phone)    self.ed_email = QLineEdit(); self.ed_email.setPlaceholderText("zhangsan@email.com")    g1l.addRow("邮箱:", self.ed_email)    self.ed_position = QLineEdit(); self.ed_position.setPlaceholderText("Python开发工程师")    g1l.addRow("求职意向:", self.ed_position)    g1.setLayout(g1l)    ll.addWidget(g1)    g2 = QGroupBox("教育背景")    g2l = QFormLayout()    self.ed_school = QLineEdit(); self.ed_school.setPlaceholderText("XX大学")    g2l.addRow("学校:", self.ed_school)    self.ed_major = QLineEdit(); self.ed_major.setPlaceholderText("计算机科学")    g2l.addRow("专业:", self.ed_major)    self.cb_degree = QComboBox(); self.cb_degree.addItems(["本科", "硕士", "博士", "大专"])    g2l.addRow("学历:", self.cb_degree)    self.ed_edu_time = QLineEdit(); self.ed_edu_time.setPlaceholderText("2018-2022")    g2l.addRow("时间:", self.ed_edu_time)    g2.setLayout(g2l)    ll.addWidget(g2)    g3 = QGroupBox("工作经验")    g3l = QFormLayout()    self.ed_company = QLineEdit(); self.ed_company.setPlaceholderText("XX科技有限公司")    g3l.addRow("公司:", self.ed_company)    self.ed_title = QLineEdit(); self.ed_title.setPlaceholderText("高级开发工程师")    g3l.addRow("职位:", self.ed_title)    self.ed_work_time = QLineEdit(); self.ed_work_time.setPlaceholderText("2022-至今")    g3l.addRow("时间:", self.ed_work_time)    self.ed_work_desc = QTextEdit(); self.ed_work_desc.setMaximumHeight(60)    self.ed_work_desc.setPlaceholderText("负责核心业务系统开发...")    g3l.addRow("描述:", self.ed_work_desc)    g3.setLayout(g3l)    ll.addWidget(g3)    g4 = QGroupBox("技能与项目")    g4l = QFormLayout()    self.ed_skills = QTextEdit(); self.ed_skills.setMaximumHeight(50)    self.ed_skills.setPlaceholderText("Python, Java, Docker, MySQL...")    g4l.addRow("技能:", self.ed_skills)    self.ed_proj = QLineEdit(); self.ed_proj.setPlaceholderText("智能数据分析平台")    g4l.addRow("项目名:", self.ed_proj)    self.ed_proj_desc = QTextEdit(); self.ed_proj_desc.setMaximumHeight(50)    self.ed_proj_desc.setPlaceholderText("项目描述...")    g4l.addRow("项目描述:", self.ed_proj_desc)    self.ed_proj_role = QLineEdit(); self.ed_proj_role.setPlaceholderText("核心开发,负责后端架构设计")    g4l.addRow("个人职责:", self.ed_proj_role)    self.ed_proj_result = QLineEdit(); self.ed_proj_result.setPlaceholderText("系统性能提升50%")    g4l.addRow("项目成果:", self.ed_proj_result)    g4.setLayout(g4l)    ll.addWidget(g4)    self.ed_eval = QTextEdit(); self.ed_eval.setMaximumHeight(50)    self.ed_eval.setPlaceholderText("热爱技术,善于学习...")    ll.addWidget(QLabel("自我评价:"))    ll.addWidget(self.ed_eval)    btn_gen = QPushButton("🚀 生成简历")    btn_gen.setStyleSheet("padding:10px;background:#d32f2f;color:#fff;border:none;border-radius:4px;font-size:14px;font-weight:bold;")    btn_gen.clicked.connect(self._generate)    ll.addWidget(btn_gen)    ll.addStretch()    left_scroll.setWidget(left_w)    splitter.addWidget(left_scroll)    # ---- 右侧:预览/评分/导出 ----    right = QWidget()    rl = QVBoxLayout(right)    rl.setContentsMargins(8, 8, 8, 8)    rl.addWidget(QLabel("📄 简历预览(可直接编辑)"))    self.editor = QTextEdit()    self.editor.setFont(QFont("Microsoft YaHei", 11))    self.editor.setPlaceholderText("生成的简历将显示在这里...\n\n也可以直接导入MD文件编辑")    self.editor.textChanged.connect(self._on_text_change)    rl.addWidget(self.editor, 1)    # 评分区    score_row = QHBoxLayout()    self.lbl_score = QLabel("评分: --")    self.lbl_score.setStyleSheet("font-size:18px;font-weight:bold;color:#1976d2;")    score_row.addWidget(self.lbl_score)    self.progress_score = QProgressBar()    self.progress_score.setRange(0, 100)    self.progress_score.setValue(0)    self.progress_score.setMaximumHeight(12)    score_row.addWidget(self.progress_score, 1)    btn_score = QPushButton("📊 评分")    btn_score.setStyleSheet("padding:6px 14px;background:#388e3c;color:#fff;border:none;border-radius:4px;")    btn_score.clicked.connect(self._do_score)    score_row.addWidget(btn_score)    rl.addLayout(score_row)    # 优化建议    self.tips_box = QTextEdit()    self.tips_box.setMaximumHeight(100)    self.tips_box.setReadOnly(True)    self.tips_box.setStyleSheet("background:#fff8e1;border:1px solid #ffe082;border-radius:4px;font-size:12px;")    self.tips_box.setPlaceholderText("点击「评分」查看优化建议...")    rl.addWidget(self.tips_box)    # 导出    exp_row = QHBoxLayout()    btn_md = QPushButton("💾 导出MD")    btn_md.setStyleSheet("padding:6px 14px;background:#1976d2;color:#fff;border:none;border-radius:4px;")    btn_md.clicked.connect(lambda: self._export("md"))    btn_doc = QPushButton("💾 导出Word")    btn_doc.setStyleSheet("padding:6px 14px;background:#388e3c;color:#fff;border:none;border-radius:4px;")    btn_doc.clicked.connect(lambda: self._export("docx"))    self.lbl_count = QLabel("字数: 0")    self.lbl_count.setStyleSheet("color:#888;")    exp_row.addWidget(btn_md)    exp_row.addWidget(btn_doc)    exp_row.addStretch()    exp_row.addWidget(self.lbl_count)    rl.addLayout(exp_row)    splitter.addWidget(right)    splitter.setStretchFactor(0, 0)    splitter.setStretchFactor(1, 1)def _generate(self):    text = RESUME_TEMPLATE.format(        name=self.ed_name.text() or "姓名", phone=self.ed_phone.text() or "电话",        email=self.ed_email.text() or "邮箱", position=self.ed_position.text() or "求职意向",        edu_school=self.ed_school.text() or "学校", edu_major=self.ed_major.text() or "专业",        edu_degree=self.cb_degree.currentText(), edu_time=self.ed_edu_time.text() or "时间",        company1=self.ed_company.text() or "公司", title1=self.ed_title.text() or "职位",        work_time1=self.ed_work_time.text() or "时间",        work_desc1=self.ed_work_desc.toPlainText() or "工作描述",        skills=self.ed_skills.toPlainText() or "技能列表",        project1=self.ed_proj.text() or "项目名称",        proj_desc1=self.ed_proj_desc.toPlainText() or "项目描述",        proj_role1=self.ed_proj_role.text() or "个人职责",        proj_result1=self.ed_proj_result.text() or "项目成果",        self_eval=self.ed_eval.toPlainText() or "自我评价",    )    self.editor.setPlainText(text)def _import_md(self):    path, _ = QFileDialog.getOpenFileName(self, "导入MD简历", "", "Markdown (*.md);;文本 (*.txt);;所有 (*)")    if path:        with open(path, "r", encoding="utf-8") as f:            self.editor.setPlainText(f.read())def _load_sample(self):    sample = RESUME_TEMPLATE.format(        name="张三", phone="13800138000", email="zhangsan@email.com",        position="Python高级开发工程师", edu_school="北京大学", edu_major="计算机科学与技术",        edu_degree="硕士", edu_time="2016-2019", company1="XX科技有限公司",        title1="高级Python开发工程师", work_time1="2019-至今",        work_desc1="负责公司核心数据平台后端开发,使用Python/Django/Flask构建微服务架构,日处理数据量1000万+",        skills="Python, Java, Docker, Kubernetes, MySQL, Redis, Elasticsearch, Git, Linux, CI/CD",        project1="智能数据分析平台",        proj_desc1="基于Python+Elasticsearch+Vue的企业级数据分析平台,支持多维度数据查询和可视化",        proj_role1="核心开发,负责后端架构设计、ES检索优化、API开发",        proj_result1="系统查询性能提升60%,支撑日均100万次查询",        self_eval="5年Python开发经验,熟悉分布式系统架构,具备良好的团队协作和沟通能力",    )    self.editor.setPlainText(sample)def _on_text_change(self):    self.lbl_count.setText(f"字数: {len(self.editor.toPlainText())}")def _do_score(self):    text = self.editor.toPlainText()    if not text.strip():        QMessageBox.warning(self, "提示", "请先输入或生成简历内容"); return    total, scores = ResumeScorer.score(text)    self.lbl_score.setText(f"评分: {total}/100")    self.progress_score.setValue(total)    if total >= 80: color = "#388e3c"    elif total >= 60: color = "#ff9800"    else: color = "#d32f2f"    self.lbl_score.setStyleSheet(f"font-size:18px;font-weight:bold;color:{color};")    # 详细分数    detail = "\n".join(f"  {k}: {v}/{ResumeScorer.WEIGHTS[k]}" for k, v in scores.items())    tips = ResumeScorer.suggest(text, scores)    self.tips_box.setPlainText(f"📊 各维度得分:\n{detail}\n\n💡 优化建议:\n" + "\n".join(f"  • {t}" for t in tips))def _export(self, fmt):    text = self.editor.toPlainText()    if not text.strip():        QMessageBox.warning(self, "提示", "没有内容可导出"); return    if fmt == "md":        path, _ = QFileDialog.getSaveFileName(self, "导出MD", "resume.md", "Markdown (*.md)")        if path:            with open(path, "w", encoding="utf-8") as f: f.write(text)            QMessageBox.information(self, "成功", f"已导出到 {path}")    elif fmt == "docx":        try:            from docx import Document            path, _ = QFileDialog.getSaveFileName(self, "导出Word", "resume.docx", "Word (*.docx)")            if path:                doc = Document()                for line in text.split("\n"):                    line = line.strip()                    if line.startswith("# "): doc.add_heading(line[2:], level=1)                    elif line.startswith("## "): doc.add_heading(line[3:], level=2)                    elif line.startswith("### "): doc.add_heading(line[4:], level=3)                    elif line.startswith("- "): doc.add_paragraph(line[2:], style="List Bullet")                    elif line: doc.add_paragraph(line)                doc.save(path)                QMessageBox.information(self, "成功", f"已导出到 {path}")        except ImportError:            QMessageBox.warning(self, "缺少依赖", "pip install python-docx")==================== Tab2: 批量简历分析 ====================class BatchAnalysisTab(QWidget):def init(self):super().init()self.resumes = []  # [{"name":, "text":, "score":, "scores":}]self._build()def _build(self):    lay = QVBoxLayout(self)    lay.setContentsMargins(12, 12, 12, 12)    # 顶部操作    top = QHBoxLayout()    btn_upload = QPushButton("📂 批量上传MD简历")    btn_upload.setStyleSheet("padding:8px 16px;background:#1976d2;color:#fff;border:none;border-radius:4px;font-size:13px;")    btn_upload.clicked.connect(self._upload)    top.addWidget(btn_upload)    btn_clear = QPushButton("🗑 清空")    btn_clear.setStyleSheet("padding:8px 16px;background:#757575;color:#fff;border:none;border-radius:4px;")    btn_clear.clicked.connect(self._clear)    top.addWidget(btn_clear)    btn_export = QPushButton("📥 导出分析报告")    btn_export.setStyleSheet("padding:8px 16px;background:#388e3c;color:#fff;border:none;border-radius:4px;")    btn_export.clicked.connect(self._export_report)    top.addWidget(btn_export)    top.addStretch()    self.lbl_stats = QLabel("已加载: 0 份简历")    self.lbl_stats.setStyleSheet("color:#888;font-size:13px;")    top.addWidget(self.lbl_stats)    lay.addLayout(top)    # 统计卡片    cards = QHBoxLayout()    self.card_total = self._card("📄 总数", "0", "#1976d2")    self.card_avg = self._card("📊 平均分", "0", "#ff9800")    self.card_max = self._card("🏆 最高分", "0", "#388e3c")    self.card_min = self._card("📉 最低分", "0", "#d32f2f")    cards.addWidget(self.card_total)    cards.addWidget(self.card_avg)    cards.addWidget(self.card_max)    cards.addWidget(self.card_min)    lay.addLayout(cards)    # 排名表格    self.table = QTableWidget()    self.table.setColumnCount(9)    self.table.setHorizontalHeaderLabels(["排名", "文件名", "总分", "基本信息", "教育背景", "工作经验", "技能匹配", "项目经验", "格式规范"])    self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)    self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeToContents)    self.table.setSelectionBehavior(QTableWidget.SelectRows)    self.table.setEditTriggers(QTableWidget.NoEditTriggers)    self.table.setAlternatingRowColors(True)    self.table.setStyleSheet("QTableWidget{font-size:12px;} QTableWidget::item:selected{background:#e3f2fd;}")    self.table.cellClicked.connect(self._on_row_click)    lay.addWidget(self.table, 1)    # 底部预览    self.preview = QTextEdit()    self.preview.setMaximumHeight(180)    self.preview.setReadOnly(True)    self.preview.setFont(QFont("Microsoft YaHei", 10))    self.preview.setPlaceholderText("点击表格行查看简历内容和优化建议...")    lay.addWidget(self.preview)def _card(self, title, value, color):    w = QFrame()    w.setStyleSheet(f"background:#fff;border:1px solid #e0e0e0;border-radius:8px;border-left:4px solid {color};")    l = QVBoxLayout(w)    l.setContentsMargins(12, 8, 12, 8)    lbl_t = QLabel(title); lbl_t.setStyleSheet("color:#888;font-size:11px;")    lbl_v = QLabel(value); lbl_v.setStyleSheet(f"color:{color};font-size:22px;font-weight:bold;")    lbl_v.setObjectName("card_value")    l.addWidget(lbl_t); l.addWidget(lbl_v)    return wdef _update_card(self, card, value):    card.findChild(QLabel, "card_value").setText(str(value))def _upload(self):    paths, _ = QFileDialog.getOpenFileNames(self, "选择MD简历文件", "", "Markdown (*.md);;文本 (*.txt);;所有 (*)")    if not paths: return    for p in paths:        try:            with open(p, "r", encoding="utf-8") as f:                text = f.read()            total, scores = ResumeScorer.score(text)            self.resumes.append({"name": os.path.basename(p), "text": text, "score": total, "scores": scores})        except Exception as e:            print(f"读取失败: {p}{e}")    self._refresh()def _clear(self):    self.resumes.clear()    self._refresh()    self.preview.clear()def _refresh(self):    # 按分数降序排序    self.resumes.sort(key=lambda x: x["score"], reverse=True)    self.table.setRowCount(len(self.resumes))    for i, r in enumerate(self.resumes):        self.table.setItem(i, 0, QTableWidgetItem(str(i + 1)))        self.table.setItem(i, 1, QTableWidgetItem(r["name"]))        score_item = QTableWidgetItem(str(r["score"]))        if r["score"] >= 80: score_item.setForeground(QColor("#388e3c"))        elif r["score"] >= 60: score_item.setForeground(QColor("#ff9800"))        else: score_item.setForeground(QColor("#d32f2f"))        score_item.setFont(QFont("", -1, QFont.Bold))        self.table.setItem(i, 2, score_item)        keys = ["基本信息完整度", "教育背景", "工作经验", "技能匹配度", "项目经验", "格式规范"]        for j, k in enumerate(keys):            self.table.setItem(i, 3 + j, QTableWidgetItem(str(r["scores"].get(k, 0))))    # 统计    n = len(self.resumes)    self.lbl_stats.setText(f"已加载: {n} 份简历")    self._update_card(self.card_total, n)    if n > 0:        scores = [r["score"] for r in self.resumes]        self._update_card(self.card_avg, f"{sum(scores)/n:.1f}")        self._update_card(self.card_max, max(scores))        self._update_card(self.card_min, min(scores))    else:        for c in [self.card_avg, self.card_max, self.card_min]:            self._update_card(c, "0")def _on_row_click(self, row, col):    if 0 <= row < len(self.resumes):        r = self.resumes[row]        tips = ResumeScorer.suggest(r["text"], r["scores"])        self.preview.setPlainText(            f"📄 {r['name']} | 评分: {r['score']}/100\n\n"            f"💡 优化建议:\n" + "\n".join(f"  • {t}" for t in tips) +            f"\n\n{'='*40}\n简历内容:\n{'='*40}\n{r['text'][:500]}..."        )def _export_report(self):    if not self.resumes:        QMessageBox.warning(self, "提示", "请先上传简历"); return    path, _ = QFileDialog.getSaveFileName(self, "导出分析报告", "resume_report.md", "Markdown (*.md)")    if not path: return    n = len(self.resumes)    scores = [r["score"] for r in self.resumes]    md = f"# 简历批量分析报告\n\n"    md += f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M')}\n\n"    md += f"## 统计概览\n\n"    md += f"| 指标 | 数值 |\n|------|------|\n"    md += f"| 简历总数 | {n} |\n"    md += f"| 平均分 | {sum(scores)/n:.1f} |\n"    md += f"| 最高分 | {max(scores)} |\n"    md += f"| 最低分 | {min(scores)} |\n\n"    md += f"## 排名详情\n\n"    md += "| 排名 | 文件名 | 总分 | 基本信息 | 教育 | 工作 | 技能 | 项目 | 格式 |\n"    md += "|------|--------|------|----------|------|------|------|------|------|\n"    for i, r in enumerate(self.resumes):        s = r["scores"]        md += f"| {i+1} | {r['name']} | {r['score']} | {s.get('基本信息完整度',0)} | {s.get('教育背景',0)} | {s.get('工作经验',0)} | {s.get('技能匹配度',0)} | {s.get('项目经验',0)} | {s.get('格式规范',0)} |\n"    md += f"\n## 各简历优化建议\n\n"    for i, r in enumerate(self.resumes):        tips = ResumeScorer.suggest(r["text"], r["scores"])        md += f"### {i+1}. {r['name']} ({r['score']}分)\n\n"        for t in tips:            md += f"- {t}\n"        md += "\n"    with open(path, "w", encoding="utf-8") as f:        f.write(md)    QMessageBox.information(self, "成功", f"报告已导出到:\n{path}")==================== 主窗口 ====================class ResumeToolApp(QMainWindow):def init(self):super().init()self.setWindowTitle("📄 简历工具箱 v1.0 | Python学在坚持")self.setMinimumSize(1000, 650)self.resize(1200, 750)tabs = QTabWidget()tabs.setStyleSheet("""QTabBar::tab{padding:10px 24px;font-size:13px;}QTabBar::tab:selected{background:#1976d2;color:#fff;border-radius:4px 4px 0 0;}""")tabs.addTab(ResumeEditorTab(), "📝 简历生成与优化")tabs.addTab(BatchAnalysisTab(), "📊 批量分析与排名")self.setCentralWidget(tabs)self.statusBar().showMessage("公众号: Python学在坚持 | 微信: ysp2338084 | 作者: 杨少平")if name == "main":app = QApplication(sys.argv)app.setStyle("Fusion")app.setFont(QFont("Microsoft YaHei", 10))win = ResumeToolApp()win.show()sys.exit(app.exec_())

最新文章

随机文章