You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
730 lines
41 KiB
730 lines
41 KiB
from pathlib import Path
|
|
|
|
from docx import Document
|
|
from docx.enum.section import WD_SECTION_START
|
|
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT, WD_ROW_HEIGHT_RULE, WD_TABLE_ALIGNMENT
|
|
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_BREAK, WD_LINE_SPACING
|
|
from docx.oxml import OxmlElement
|
|
from docx.oxml.ns import qn
|
|
from docx.shared import Cm, Inches, Pt, RGBColor
|
|
|
|
|
|
ROOT = Path(r"D:\Look at the mountain")
|
|
OUT = ROOT / "志愿通数据审核系统使用手册.docx"
|
|
SHOT = ROOT / "tmp" / "review-manual-shots"
|
|
|
|
BLUE = "1F4E78"
|
|
MID_BLUE = "5B9BD5"
|
|
LIGHT_BLUE = "D9EAF7"
|
|
LIGHTER_BLUE = "EDF4FA"
|
|
GRAY = "666666"
|
|
LIGHT_GRAY = "F2F2F2"
|
|
BORDER = "D9D9D9"
|
|
WHITE = "FFFFFF"
|
|
BLACK = "111111"
|
|
GREEN = "70AD47"
|
|
YELLOW = "FFC000"
|
|
RED = "C00000"
|
|
|
|
|
|
def set_cell_shading(cell, fill):
|
|
tc_pr = cell._tc.get_or_add_tcPr()
|
|
shd = tc_pr.find(qn("w:shd"))
|
|
if shd is None:
|
|
shd = OxmlElement("w:shd")
|
|
tc_pr.append(shd)
|
|
shd.set(qn("w:fill"), fill)
|
|
|
|
|
|
def set_cell_border(cell, color=BORDER, size="6"):
|
|
tc_pr = cell._tc.get_or_add_tcPr()
|
|
borders = tc_pr.first_child_found_in("w:tcBorders")
|
|
if borders is None:
|
|
borders = OxmlElement("w:tcBorders")
|
|
tc_pr.append(borders)
|
|
for edge in ("top", "left", "bottom", "right", "insideH", "insideV"):
|
|
tag = "w:" + edge
|
|
el = borders.find(qn(tag))
|
|
if el is None:
|
|
el = OxmlElement(tag)
|
|
borders.append(el)
|
|
el.set(qn("w:val"), "single")
|
|
el.set(qn("w:sz"), size)
|
|
el.set(qn("w:space"), "0")
|
|
el.set(qn("w:color"), color)
|
|
|
|
|
|
def set_cell_margins(cell, top=100, start=120, bottom=100, end=120):
|
|
tc = cell._tc
|
|
tc_pr = tc.get_or_add_tcPr()
|
|
tc_mar = tc_pr.first_child_found_in("w:tcMar")
|
|
if tc_mar is None:
|
|
tc_mar = OxmlElement("w:tcMar")
|
|
tc_pr.append(tc_mar)
|
|
for m, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
|
|
node = tc_mar.find(qn("w:" + m))
|
|
if node is None:
|
|
node = OxmlElement("w:" + m)
|
|
tc_mar.append(node)
|
|
node.set(qn("w:w"), str(value))
|
|
node.set(qn("w:type"), "dxa")
|
|
|
|
|
|
def prevent_row_split(row):
|
|
tr_pr = row._tr.get_or_add_trPr()
|
|
cant_split = OxmlElement("w:cantSplit")
|
|
tr_pr.append(cant_split)
|
|
|
|
|
|
def repeat_table_header(row):
|
|
tr_pr = row._tr.get_or_add_trPr()
|
|
tbl_header = OxmlElement("w:tblHeader")
|
|
tbl_header.set(qn("w:val"), "true")
|
|
tr_pr.append(tbl_header)
|
|
|
|
|
|
def set_repeat_header_text(run, color=WHITE, bold=True):
|
|
run.bold = bold
|
|
run.font.color.rgb = RGBColor.from_string(color)
|
|
run.font.size = Pt(9)
|
|
run.font.name = "微软雅黑"
|
|
run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
|
|
|
|
|
def add_page_number(paragraph):
|
|
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
run = paragraph.add_run("志愿通数据审核系统使用手册 · 第 ")
|
|
run.font.size = Pt(8.5)
|
|
run.font.color.rgb = RGBColor.from_string(GRAY)
|
|
begin = OxmlElement("w:fldChar")
|
|
begin.set(qn("w:fldCharType"), "begin")
|
|
instr = OxmlElement("w:instrText")
|
|
instr.set(qn("xml:space"), "preserve")
|
|
instr.text = " PAGE "
|
|
separate = OxmlElement("w:fldChar")
|
|
separate.set(qn("w:fldCharType"), "separate")
|
|
text = OxmlElement("w:t")
|
|
text.text = "1"
|
|
end = OxmlElement("w:fldChar")
|
|
end.set(qn("w:fldCharType"), "end")
|
|
r = OxmlElement("w:r")
|
|
r_pr = OxmlElement("w:rPr")
|
|
color = OxmlElement("w:color")
|
|
color.set(qn("w:val"), GRAY)
|
|
size = OxmlElement("w:sz")
|
|
size.set(qn("w:val"), "17")
|
|
r_pr.append(color)
|
|
r_pr.append(size)
|
|
r.append(r_pr)
|
|
r.extend([begin, instr, separate, text, end])
|
|
paragraph._p.append(r)
|
|
tail = paragraph.add_run(" 页")
|
|
tail.font.size = Pt(8.5)
|
|
tail.font.color.rgb = RGBColor.from_string(GRAY)
|
|
|
|
|
|
def set_run_font(run, name="微软雅黑", size=10.5, color=BLACK, bold=None):
|
|
run.font.name = name
|
|
run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), name)
|
|
run.font.size = Pt(size)
|
|
run.font.color.rgb = RGBColor.from_string(color)
|
|
if bold is not None:
|
|
run.bold = bold
|
|
|
|
|
|
def add_para(doc, text="", *, style=None, bold_lead=None, keep=False, space_after=4, align=None):
|
|
p = doc.add_paragraph(style=style)
|
|
p.paragraph_format.space_after = Pt(space_after)
|
|
p.paragraph_format.line_spacing = 1.25
|
|
if keep:
|
|
p.paragraph_format.keep_with_next = True
|
|
if align is not None:
|
|
p.alignment = align
|
|
if bold_lead and text.startswith(bold_lead):
|
|
r1 = p.add_run(bold_lead)
|
|
set_run_font(r1, bold=True)
|
|
r2 = p.add_run(text[len(bold_lead):])
|
|
set_run_font(r2)
|
|
else:
|
|
r = p.add_run(text)
|
|
set_run_font(r)
|
|
return p
|
|
|
|
|
|
def add_bullet(doc, text, level=0):
|
|
p = doc.add_paragraph(style="List Bullet" if level == 0 else "List Bullet 2")
|
|
p.paragraph_format.left_indent = Cm(0.63 + level * 0.5)
|
|
p.paragraph_format.first_line_indent = Cm(-0.25)
|
|
p.paragraph_format.space_after = Pt(2)
|
|
p.paragraph_format.line_spacing = 1.18
|
|
r = p.add_run(text)
|
|
set_run_font(r, size=10)
|
|
return p
|
|
|
|
|
|
_step_counter = 0
|
|
|
|
|
|
def add_number(doc, text):
|
|
global _step_counter
|
|
_step_counter += 1
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.left_indent = Cm(0.65)
|
|
p.paragraph_format.first_line_indent = Cm(-0.28)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
p.paragraph_format.line_spacing = 1.2
|
|
n = p.add_run(f"{_step_counter}. ")
|
|
set_run_font(n, size=10, color=BLUE, bold=True)
|
|
r = p.add_run(text)
|
|
set_run_font(r, size=10)
|
|
return p
|
|
|
|
|
|
def add_heading(doc, text, level=1):
|
|
global _step_counter
|
|
if level <= 2:
|
|
_step_counter = 0
|
|
p = doc.add_paragraph(style=f"Heading {level}")
|
|
p.paragraph_format.keep_with_next = True
|
|
p.paragraph_format.space_before = Pt(12 if level == 1 else 8)
|
|
p.paragraph_format.space_after = Pt(5)
|
|
r = p.add_run(text)
|
|
set_run_font(r, size=17 if level == 1 else 13 if level == 2 else 11.5,
|
|
color=BLUE if level <= 2 else BLACK, bold=True)
|
|
return p
|
|
|
|
|
|
def add_note(doc, lead, text):
|
|
p = doc.add_paragraph()
|
|
p.paragraph_format.space_before = Pt(3)
|
|
p.paragraph_format.space_after = Pt(5)
|
|
p.paragraph_format.line_spacing = 1.22
|
|
r1 = p.add_run(lead)
|
|
set_run_font(r1, size=10, color=BLUE, bold=True)
|
|
r2 = p.add_run(text)
|
|
set_run_font(r2, size=10)
|
|
return p
|
|
|
|
|
|
def add_table(doc, headers, rows, widths=None, font_size=8.8):
|
|
table = doc.add_table(rows=1, cols=len(headers))
|
|
table.alignment = WD_TABLE_ALIGNMENT.CENTER
|
|
table.autofit = False
|
|
hdr = table.rows[0]
|
|
repeat_table_header(hdr)
|
|
for i, header in enumerate(headers):
|
|
cell = hdr.cells[i]
|
|
if widths:
|
|
cell.width = Cm(widths[i])
|
|
set_cell_shading(cell, BLUE)
|
|
set_cell_border(cell)
|
|
set_cell_margins(cell)
|
|
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
|
p = cell.paragraphs[0]
|
|
p.paragraph_format.space_after = Pt(0)
|
|
run = p.add_run(str(header))
|
|
set_repeat_header_text(run)
|
|
for ridx, row_data in enumerate(rows):
|
|
row = table.add_row()
|
|
prevent_row_split(row)
|
|
if ridx % 2 == 1:
|
|
for cell in row.cells:
|
|
set_cell_shading(cell, LIGHT_GRAY)
|
|
for i, value in enumerate(row_data):
|
|
cell = row.cells[i]
|
|
if widths:
|
|
cell.width = Cm(widths[i])
|
|
set_cell_border(cell)
|
|
set_cell_margins(cell)
|
|
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
|
p = cell.paragraphs[0]
|
|
p.paragraph_format.space_after = Pt(0)
|
|
p.paragraph_format.line_spacing = 1.08
|
|
run = p.add_run(str(value))
|
|
set_run_font(run, size=font_size)
|
|
doc.add_paragraph().paragraph_format.space_after = Pt(0)
|
|
return table
|
|
|
|
|
|
def add_figure(doc, filename, caption, width=6.75):
|
|
path = SHOT / filename
|
|
if not path.exists():
|
|
raise FileNotFoundError(path)
|
|
p = doc.add_paragraph()
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.keep_with_next = True
|
|
p.paragraph_format.space_before = Pt(4)
|
|
p.paragraph_format.space_after = Pt(3)
|
|
p.add_run().add_picture(str(path), width=Inches(width))
|
|
cap = doc.add_paragraph()
|
|
cap.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
cap.paragraph_format.space_after = Pt(7)
|
|
r = cap.add_run(caption)
|
|
set_run_font(r, size=8.5, color=GRAY)
|
|
|
|
|
|
def add_page_break(doc):
|
|
doc.add_page_break()
|
|
|
|
|
|
doc = Document()
|
|
section = doc.sections[0]
|
|
section.page_width = Inches(8.5)
|
|
section.page_height = Inches(11)
|
|
section.top_margin = Inches(0.68)
|
|
section.bottom_margin = Inches(0.63)
|
|
section.left_margin = Inches(0.72)
|
|
section.right_margin = Inches(0.72)
|
|
section.header_distance = Inches(0.28)
|
|
section.footer_distance = Inches(0.28)
|
|
|
|
styles = doc.styles
|
|
normal = styles["Normal"]
|
|
normal.font.name = "微软雅黑"
|
|
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
|
normal.font.size = Pt(10.5)
|
|
normal.font.color.rgb = RGBColor.from_string(BLACK)
|
|
normal.paragraph_format.space_after = Pt(4)
|
|
normal.paragraph_format.line_spacing = 1.25
|
|
|
|
for sname in ("Title", "Subtitle"):
|
|
styles[sname].font.name = "微软雅黑"
|
|
styles[sname]._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
|
|
|
for i in range(1, 4):
|
|
style = styles[f"Heading {i}"]
|
|
style.font.name = "微软雅黑"
|
|
style._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
|
|
style.font.color.rgb = RGBColor.from_string(BLUE if i <= 2 else BLACK)
|
|
|
|
# Header/footer
|
|
hp = section.header.paragraphs[0]
|
|
hp.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
|
hr = hp.add_run("志愿通 · 数据审核系统")
|
|
set_run_font(hr, size=8.5, color=GRAY)
|
|
add_page_number(section.footer.paragraphs[0])
|
|
|
|
# Cover
|
|
for _ in range(4):
|
|
doc.add_paragraph()
|
|
p = doc.add_paragraph(style="Title")
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_after = Pt(18)
|
|
r = p.add_run("志愿通数据审核系统\n使用手册")
|
|
set_run_font(r, size=30, color=BLUE, bold=True)
|
|
|
|
p = doc.add_paragraph()
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_after = Pt(28)
|
|
r = p.add_run("数据查看 · 来源追溯 · 逐条审核 · 覆盖管理")
|
|
set_run_font(r, size=13, color=GRAY)
|
|
|
|
meta = add_table(doc, ["文档信息", "内容"], [
|
|
["文档版本", "V1.0"],
|
|
["适用系统", "志愿通数据审核系统(本地审核环境)"],
|
|
["适用角色", "数据采集、数据审核、产品运营、项目管理人员"],
|
|
["编制日期", "2026年9月6日"],
|
|
], widths=[3.0, 12.5], font_size=10)
|
|
|
|
doc.add_paragraph()
|
|
p = doc.add_paragraph()
|
|
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
|
p.paragraph_format.space_before = Pt(18)
|
|
r = p.add_run("内部使用文档")
|
|
set_run_font(r, size=10, color=GRAY, bold=True)
|
|
|
|
add_page_break(doc)
|
|
|
|
# Contents and introduction
|
|
add_heading(doc, "目录", 1)
|
|
add_para(doc, "本手册按实际使用顺序组织,既可从头阅读,也可直接定位到具体数据集。")
|
|
add_table(doc, ["章节", "主要内容"], [
|
|
["1. 文档说明", "使用范围、角色、基本原则"],
|
|
["2. 快速开始", "访问系统、完成一次审核的最短路径"],
|
|
["3. 首页与导航", "数据总览、左侧菜单、指标含义"],
|
|
["4. 省份与年份", "采集矩阵、灰色状态、范围页"],
|
|
["5. 通用查询与详情", "筛选、分页、详情抽屉、来源追溯"],
|
|
["6. 审核操作", "状态选择、审核人、备注、审计日志"],
|
|
["7. 数据集审核要点", "院校、计划、分数、覆盖、来源等分类规则"],
|
|
["8. 推荐审核流程", "按依赖关系组织审核批次"],
|
|
["9. 覆盖与来源管理", "覆盖口径、来源类型、网页与哈希"],
|
|
["10. 常见场景", "按省年审核、定位异常、核对分数"],
|
|
["11. 故障排查与常见问题", "连接、空列表、保存失败、性能"],
|
|
["附录", "状态、字段和审核记录用语"],
|
|
], widths=[4.3, 11.2], font_size=9.2)
|
|
|
|
add_heading(doc, "1. 文档说明", 1)
|
|
add_heading(doc, "1.1 使用范围", 2)
|
|
add_para(doc, "本系统用于查看高校基础库及各省高考招录数据,核对来源和字段,并保存审核结论。它覆盖全国院校、批次与段次、计划发布批次、院校专业组、专业招生计划、专业录取线、一分一段、省级控制线、院校投档线、覆盖与缺口、来源档案等数据。")
|
|
add_heading(doc, "1.2 角色与职责", 2)
|
|
add_table(doc, ["角色", "主要职责", "建议权限边界"], [
|
|
["采集人员", "登记来源、导入数据、补充覆盖说明", "不替代审核人员给出最终业务判断"],
|
|
["审核人员", "核对字段、来源、匹配关系并提交结论", "按可验证事实审核,疑点标记“需复核”"],
|
|
["产品/运营", "查看覆盖率、缺口和审核进度", "不直接改写原始采集事实"],
|
|
["项目管理员", "维护运行环境、账号和数据任务", "定期备份数据库与来源文件"],
|
|
], widths=[2.7, 7.1, 5.7], font_size=8.8)
|
|
add_heading(doc, "1.3 三条基本原则", 2)
|
|
add_bullet(doc, "来源优先:分数、位次、计划数等关键值必须能回溯到来源档案或官方页面。")
|
|
add_bullet(doc, "不猜测缺失值:页面中的“—”、空值和“未标注”表示源数据未提供或当前未解析,不应凭经验补写。")
|
|
add_bullet(doc, "审核与原始数据分离:提交审核只更新审核状态并新增审计记录,不会改写原始采集数据。")
|
|
|
|
add_heading(doc, "2. 快速开始", 1)
|
|
add_heading(doc, "2.1 访问条件", 2)
|
|
add_table(doc, ["项目", "默认值", "检查方法"], [
|
|
["前端地址", "http://127.0.0.1:5173", "浏览器打开后应显示数据总览"],
|
|
["后端服务", "http://127.0.0.1:8080", "前端能显示统计卡片和列表即表示通信正常"],
|
|
["数据库", "MySQL 3306", "列表查询和审核保存均依赖数据库"],
|
|
["推荐浏览器", "Chrome / Edge 最新稳定版", "建议窗口宽度不低于 1280 像素"],
|
|
], widths=[3.1, 5.1, 7.3], font_size=9.2)
|
|
add_note(doc, "提示:", "如果首页能打开但所有统计均为 0,通常是后端或数据库未连接,而不是系统真的没有数据。")
|
|
|
|
add_heading(doc, "2.2 完成一次审核的最短路径", 2)
|
|
for item in [
|
|
"进入“省份与年份”,选择目标年份和省份。绿色或蓝色通常表示已有数据;灰色表示尚未收集。",
|
|
"进入范围页后,选择需要审核的数据集,例如“省级控制线”或“院校投档线”。",
|
|
"使用关键词、科类、批次、分数范围和审核状态等条件缩小列表。",
|
|
"点击行或“详情”,核对完整字段、来源标题、来源地址、稳定 ID 和历史审核记录。",
|
|
"点击“审核”,选择结论,填写审核人;存在疑点时填写可复现的备注并保存。",
|
|
]:
|
|
add_number(doc, item)
|
|
add_heading(doc, "2.3 第一次使用建议", 2)
|
|
add_bullet(doc, "先浏览“数据总览”和“覆盖与缺口”,了解当前采集范围,再开始逐条审核。")
|
|
add_bullet(doc, "优先从一个省份、一个年份、一个数据集开始,避免审核上下文混淆。")
|
|
add_bullet(doc, "首次提交时认真填写审核人姓名;系统会在当前浏览器中记住,后续仍可修改。")
|
|
|
|
add_heading(doc, "3. 首页与导航", 1)
|
|
add_figure(doc, "01-dashboard.png", "图 1 数据总览:首页集中显示数据规模、匹配质量与覆盖概况")
|
|
add_heading(doc, "3.1 统计卡片怎么读", 2)
|
|
add_para(doc, "首页统计卡片是当前数据库的即时汇总。截图时可见全国院校 2,988 所、院校专业组 200,185 条、专业招生计划 868,304 条、专业录取线 432,005 条、院校投档线 74,978 条、2025 一分一段 28,010 条、2025 省控线 257 条。数量会随重新导入、补采和数据治理而变化。")
|
|
add_table(doc, ["指标", "用途", "重点关注"], [
|
|
["院校/专业组/计划/分数线数量", "判断数据规模与导入结果", "异常突增、突减或长期为 0"],
|
|
["未匹配投档线", "发现无法关联到院校基础库的记录", "应优先完成学校代码和名称映射"],
|
|
["待审核业务记录", "评估审核工作量", "只表示当前审核状态,不等同于数据缺失"],
|
|
["匹配质量", "查看稳定 ID 关联情况", "未匹配项需追查代码、别名、年份口径"],
|
|
["最近覆盖状态", "快速查看省年数据覆盖", "部分覆盖和暂无公开全量需阅读备注"],
|
|
], widths=[4.0, 5.7, 5.8], font_size=8.8)
|
|
|
|
add_heading(doc, "3.2 左侧导航", 2)
|
|
add_table(doc, ["分组", "菜单", "作用"], [
|
|
["工作台", "数据总览", "全局数据量、匹配质量、覆盖状态"],
|
|
["工作台", "省份与年份", "按 31 个省级地区和年份切换审核范围"],
|
|
["工作台", "审核工作台", "集中进入各类待审核业务数据"],
|
|
["院校基础库", "全国院校", "审核学校稳定 ID、教育层次、主管部门等"],
|
|
["各省招录", "批次/发布批次/专业组/计划/录取线/一分一段/省控线/投档线", "查看和审核各省招录业务数据"],
|
|
["数据治理", "覆盖与缺口", "记录公开数据可得性、采集范围和缺口"],
|
|
["数据治理", "来源档案", "核对来源发布方、标题、日期、网页和文件哈希"],
|
|
], widths=[2.5, 5.2, 7.8], font_size=8.6)
|
|
|
|
add_heading(doc, "4. 省份与年份分类", 1)
|
|
add_figure(doc, "02-scopes.png", "图 2 省份与年份矩阵:尚未收集的省份和年份以灰色显示")
|
|
add_heading(doc, "4.1 状态颜色", 2)
|
|
add_table(doc, ["显示状态", "常见颜色", "含义", "审核动作"], [
|
|
["未收集", "灰色", "当前省份与年份尚无业务数据", "不要误判为查询错误;记录采集任务"],
|
|
["部分采集", "黄色", "已有部分数据,但品类、批次或覆盖范围不完整", "进入范围页阅读覆盖说明并确认缺口"],
|
|
["已采集", "绿色", "目标范围已有可审核数据", "开始逐数据集核验"],
|
|
["已核验", "蓝色", "该范围已完成既定核验", "必要时抽检来源与最新变更"],
|
|
], widths=[2.5, 2.5, 5.8, 4.7], font_size=8.7)
|
|
add_note(doc, "特别说明:", "灰色只表示当前系统尚未收集,不代表该省份或年份没有公开数据;黄色也不代表数据错误,只说明覆盖仍不完整。")
|
|
|
|
add_heading(doc, "4.2 使用步骤", 2)
|
|
add_number(doc, "在页面顶部选择年份。系统当前可展示 2022—2026 等年度,具体可用数据以卡片颜色为准。")
|
|
add_number(doc, "点击省份卡片进入该省、该年的范围页。后续打开的数据页会继承省份和年份上下文。")
|
|
add_number(doc, "查看范围页中的各数据集数量卡片。数量为 0 的类别表示此省年当前没有该类记录。")
|
|
add_number(doc, "从批次规则、专业组、计划、分数、覆盖和来源等入口进入具体列表。")
|
|
|
|
add_heading(doc, "4.3 范围页的九类数据", 2)
|
|
add_table(doc, ["类别", "主要回答的问题"], [
|
|
["批次与段次", "本省当年如何划分批次、段次和志愿模式?"],
|
|
["计划发布批次", "哪一版招生计划、哪一轮次、何时发布?"],
|
|
["院校专业组", "院校代码、专业组代码和选科要求如何组织?"],
|
|
["专业招生计划", "每个专业计划招生多少人,有何选科与备注?"],
|
|
["专业录取线", "专业最低分、位次、最高分、平均分和录取人数是多少?"],
|
|
["一分一段", "某分数同分人数、累计人数和位次区间是多少?"],
|
|
["省级控制线", "不同科类和层次的文化/专业控制线是多少?"],
|
|
["院校投档线", "院校或专业组的投档分、位次和志愿序号是多少?"],
|
|
["覆盖与缺口", "公开数据是否完整、已采集多少、缺什么?"],
|
|
], widths=[4.0, 11.5], font_size=8.8)
|
|
|
|
add_heading(doc, "5. 通用查询、列表与详情", 1)
|
|
add_heading(doc, "5.1 筛选区", 2)
|
|
add_para(doc, "不同数据页会按业务配置不同条件,但操作方式一致。输入关键词后可按 Enter 或点击“查询”;更改多个条件后再查询,可组合缩小范围。")
|
|
add_table(doc, ["筛选类型", "示例", "使用建议"], [
|
|
["省份/年份", "甘肃、2025", "先固定省年,避免跨口径比较"],
|
|
["关键词", "院校名、院校代码、专业名、组代码、发布方、哈希", "代码优先精确,名称可做模糊查找"],
|
|
["业务枚举", "科类、教育层次、批次、段次、所有制", "用于批量定位同一口径记录"],
|
|
["分数范围", "最低分至最高分", "排查极值、控制线附近或特定分段"],
|
|
["匹配状态", "已匹配/未匹配", "优先处理缺少稳定 ID 的记录"],
|
|
["审核状态", "待审核/已通过/需复核/已驳回", "复查时切换到特定结论"],
|
|
], widths=[3.0, 5.1, 7.4], font_size=8.8)
|
|
add_bullet(doc, "重置:清空当前页的筛选条件并恢复默认范围。")
|
|
add_bullet(doc, "刷新:重新从后端读取当前条件下的数据,适合审核保存或数据重新导入后使用。")
|
|
add_bullet(doc, "分页:支持 20、50、100、200 条/页。大页适合扫查,小页更适合逐条审核。")
|
|
|
|
add_heading(doc, "5.2 列表操作", 2)
|
|
add_bullet(doc, "点击整行或“详情”打开详情抽屉;列表只展示高频字段,详情中可查看完整字段。")
|
|
add_bullet(doc, "列表中的“稳定 ID”用于跨来源关联;缺失时应检查外部代码、院校别名或年份差异。")
|
|
add_bullet(doc, "遇到长文本、备注或复合分值时,以详情中的完整值和来源页面为准。")
|
|
|
|
add_heading(doc, "5.3 详情抽屉", 2)
|
|
add_para(doc, "详情抽屉通常包含业务字段、内部稳定 ID、来源标题、来源地址、当前审核状态和审核历史。全国院校详情还可能展示外部代码映射、历史名称和校区。")
|
|
add_note(doc, "核对顺序:", "先确认省份、年份、科类和批次上下文,再看院校/专业标识,然后核对分数、位次和计划数,最后打开来源页面确认。")
|
|
|
|
add_heading(doc, "6. 审核操作", 1)
|
|
add_heading(doc, "6.1 四种审核状态", 2)
|
|
add_table(doc, ["状态", "何时使用", "备注建议"], [
|
|
["待审核", "尚未形成审核结论,或需要撤回到待处理", "说明撤回原因或下一步"],
|
|
["已通过", "字段、口径、来源和关联关系均可验证", "可简写核对依据,关键记录建议写来源日期"],
|
|
["需复核", "存在疑点,但证据不足以判断为错误", "明确疑点字段、当前值、来源和建议复核动作"],
|
|
["已驳回", "确认存在错误、错源、错年、错省、重复或不可用记录", "写清错误事实和正确依据,便于后续修复"],
|
|
], widths=[2.5, 6.3, 6.7], font_size=8.7)
|
|
|
|
add_heading(doc, "6.2 提交审核", 2)
|
|
for item in [
|
|
"在列表点击“审核”,或先打开详情后点击“审核这条数据”。",
|
|
"选择审核状态。没有充分证据时使用“需复核”,不要直接“已驳回”。",
|
|
"填写审核人。该字段必填;系统会在当前浏览器中记住最近一次填写值。",
|
|
"填写审核备注,最多 2,000 个字符。对关键数据和异常结论,建议记录可复现依据。",
|
|
"点击保存。成功后当前状态被更新,同时新增一条带时间的审核日志。",
|
|
"返回列表或刷新,确认状态和筛选结果已变化。",
|
|
]:
|
|
add_number(doc, item)
|
|
|
|
add_heading(doc, "6.3 审核记录机制", 2)
|
|
add_para(doc, "系统将当前审核结果保存在审核记录中,并把每次操作追加到审计日志。详情页按时间倒序展示历史,便于判断一条记录经历过哪些结论。审核操作不会直接修改院校、计划、分数线、来源等原始业务字段。")
|
|
add_heading(doc, "6.4 推荐备注格式", 2)
|
|
add_table(doc, ["场景", "示例写法"], [
|
|
["已通过", "已核对省教育考试院 2025-06-25 发布页面,科类、分数和位次一致。"],
|
|
["需复核", "最低分一致,但原网页未给出最低位次;请核对附件表第 3 页。"],
|
|
["已驳回", "院校代码属于 2024 年旧代码,本记录错误套用到 2025 年;应重新映射。"],
|
|
["覆盖说明", "当前仅采集普通类本科批,艺术类和征集志愿尚缺。"],
|
|
], widths=[3.2, 12.3], font_size=9)
|
|
|
|
add_heading(doc, "7. 数据集审核要点", 1)
|
|
add_heading(doc, "7.1 全国院校基础库", 2)
|
|
add_table(doc, ["重点字段", "核验要点"], [
|
|
["教育部院校代码", "应保持稳定并与学校主体一一对应;不能把招生代码当作教育部代码"],
|
|
["正式名称", "以主管部门或学校官网现行正式名称为准,历史名称进入别名"],
|
|
["省份/城市", "核对学校主体所在地;多校区可在校区信息中分别记录"],
|
|
["教育层次/所有制/主管部门", "以官方分类口径为准,避免按社会印象推断"],
|
|
["外部代码映射", "同一学校在不同省年可有不同招生代码,必须带来源和适用范围"],
|
|
], widths=[4.2, 11.3], font_size=8.8)
|
|
|
|
add_heading(doc, "7.2 批次与段次、计划发布批次", 2)
|
|
add_bullet(doc, "批次名称、段次、类别、教育层次、志愿模式、最大专业组志愿数、每组最大专业数和征集次数需按当年省级规则核对。")
|
|
add_bullet(doc, "计划发布批次应记录发布标题、轮次、覆盖范围、发布日期和计划数;不同版本不得无依据覆盖。")
|
|
add_bullet(doc, "同名批次在不同年份的规则可能变化,审核时不可沿用上一年度结论。")
|
|
|
|
add_heading(doc, "7.3 院校专业组", 2)
|
|
add_table(doc, ["字段", "审核重点"], [
|
|
["院校代码/名称", "确认已映射到正确学校稳定 ID,处理简称、改名和历史代码"],
|
|
["专业组代码/名称", "核对前导零、字母大小写和当年编排规则"],
|
|
["组类型/选科要求", "区分普通组、专项、民族班等;选科组合不得自行标准化丢失信息"],
|
|
["批次/科类/段次", "必须与该省当年招生规则一致"],
|
|
], widths=[4.0, 11.5], font_size=8.8)
|
|
|
|
add_heading(doc, "7.4 专业招生计划", 2)
|
|
add_bullet(doc, "核对院校、专业组、专业代码、专业名称、选科要求、计划数和备注。")
|
|
add_bullet(doc, "专业代码可能只在某省某年某批次内有效,不应直接作为全国稳定专业 ID。")
|
|
add_bullet(doc, "计划数应为非负整数;合计校验要以官方发布口径为准,注意专项和征集计划是否另列。")
|
|
|
|
add_heading(doc, "7.5 专业录取线", 2)
|
|
add_table(doc, ["字段", "审核重点"], [
|
|
["最低分/最低位次", "优先核对;位次必须使用相同科类和同分规则"],
|
|
["最高分/平均分", "源未公布时保留空值,不能从最低分推算"],
|
|
["录取人数", "与招生计划不必完全相等,需考虑扩招、退档、征集等"],
|
|
["院校/组/专业稳定 ID", "确保分数记录能关联到正确主体"],
|
|
["批次/段次/科类", "防止普通类、艺术类、专科、本科数据混入"],
|
|
], widths=[4.0, 11.5], font_size=8.8)
|
|
|
|
add_heading(doc, "7.6 一分一段", 2)
|
|
add_bullet(doc, "逐分核对分数、同分人数、累计人数、位次起点和位次终点。")
|
|
add_bullet(doc, "累计人数通常应随分数下降而不减少;发现反向跳变、重复分值或缺档时标记“需复核”。")
|
|
add_bullet(doc, "不同科类、赋分口径和综合分必须分开,不能用普通类位次解释艺术类综合分。")
|
|
|
|
add_heading(doc, "7.7 省级控制线", 2)
|
|
add_figure(doc, "04-control-lines.png", "图 3 省级控制线:可按科类、层次、分数范围和审核状态筛选")
|
|
add_bullet(doc, "普通类重点核对科类、层次、文化分;艺术体育类还要核对专业分或综合分口径。")
|
|
add_bullet(doc, "复合文本(如文化成绩与体检、专业要求并列)应完整保留到备注,不要强行拆成单一分数。")
|
|
add_bullet(doc, "同一省份同一年可能有特殊类型招生控制线、资格线、段线等,应通过层次和备注区分。")
|
|
|
|
add_heading(doc, "7.8 院校投档线", 2)
|
|
add_table(doc, ["重点字段", "常见风险"], [
|
|
["院校代码/名称", "招生代码变更、简称或合并导致错配"],
|
|
["专业组", "新高考省份把院校线误当专业组线"],
|
|
["投档分/最低位次", "分数口径不一致、位次来自错误科类"],
|
|
["志愿序号", "平行志愿与顺序志愿字段含义不同"],
|
|
["稳定 ID", "未匹配记录无法可靠关联院校基础库"],
|
|
["批次/段次/层次", "本科、专科、提前批、征集批混淆"],
|
|
], widths=[4.0, 11.5], font_size=8.8)
|
|
|
|
add_heading(doc, "7.9 覆盖与缺口", 2)
|
|
add_table(doc, ["覆盖状态", "使用口径"], [
|
|
["公开完整快照", "按当前公开口径已获得完整可用快照,并能说明其范围"],
|
|
["公开部分数据", "仅覆盖部分批次、科类、学校、轮次或页面"],
|
|
["暂无公开全量", "未发现可公开获得的全量数据,可能仅有查询页或零散公告"],
|
|
], widths=[4.3, 11.2], font_size=9)
|
|
add_bullet(doc, "记录数是当前导入量,不自动证明“完整”;完整性必须结合发布范围和缺口说明判断。")
|
|
add_bullet(doc, "缺口备注应写清缺的是哪些批次、科类、轮次、文件或字段,而不是只写“数据不全”。")
|
|
|
|
add_heading(doc, "7.10 来源档案", 2)
|
|
add_bullet(doc, "核对来源类型、发布方、标题、发布日期、落地页、文件路径和内容哈希。")
|
|
add_bullet(doc, "同一文件的哈希可用于发现重复导入或内容变更;网页标题相同不代表内容完全一致。")
|
|
add_bullet(doc, "优先官方来源;转载页应标注转载发布方和原始来源,并谨慎用于最终结论。")
|
|
|
|
add_heading(doc, "8. 推荐审核流程", 1)
|
|
add_figure(doc, "03-review.png", "图 4 审核工作台:按数据依赖关系组织审核入口")
|
|
add_heading(doc, "8.1 为什么要按顺序审核", 2)
|
|
add_para(doc, "招录数据之间存在关联:院校基础库和代码映射不可靠,会继续影响专业组、计划、投档线和专业录取线。建议先处理主体与匹配,再处理数量和分数,最后核对覆盖与来源。")
|
|
add_table(doc, ["阶段", "主要任务", "完成判断"], [
|
|
["1. 主体与代码", "全国院校、外部招生代码、别名和校区", "学校稳定 ID 清晰,未匹配主体已形成处理结论"],
|
|
["2. 结构与规则", "批次段次、计划发布批次、院校专业组", "省年口径、组代码和选科要求可验证"],
|
|
["3. 数量与分数", "专业计划、专业录取线、一分一段、省控线、投档线", "关键值与来源一致,异常有备注"],
|
|
["4. 覆盖与证据", "覆盖缺口、来源档案、内容哈希", "每类数据的完整性和证据链均有说明"],
|
|
], widths=[3.0, 6.5, 6.0], font_size=8.7)
|
|
|
|
add_heading(doc, "8.2 每个省年的建议批次", 2)
|
|
add_number(doc, "锁定省份和年份,记录本次审核范围、责任人和开始日期。")
|
|
add_number(doc, "查看覆盖与缺口,确定有哪些数据集、哪些为空、哪些仅部分覆盖。")
|
|
add_number(doc, "先筛选“未匹配”,处理院校和专业组关联问题。")
|
|
add_number(doc, "按批次和科类抽样核对计划、控制线、投档线和专业录取线。")
|
|
add_number(doc, "检查一分一段的连续性和累计人数逻辑。")
|
|
add_number(doc, "核对来源标题、发布方、日期、网页和哈希,补充缺口备注。")
|
|
add_number(doc, "回到审核状态筛选,确认“需复核”和“已驳回”都有明确的后续动作。")
|
|
|
|
add_heading(doc, "8.3 抽样与全量审核", 2)
|
|
add_para(doc, "关键控制线、来源档案、学校映射和异常记录建议逐条审核。数量庞大的计划和录取线可先按来源、批次、科类分层抽样,再对异常值、未匹配项和抽样失败的同批数据扩大检查。当前界面以逐条审核为主;不要把筛选结果误认为已批量改变状态。")
|
|
|
|
add_heading(doc, "9. 覆盖与来源管理", 1)
|
|
add_heading(doc, "9.1 覆盖状态的审核方法", 2)
|
|
add_number(doc, "确认省份、年份、数据集、科类和段次是否与备注描述一致。")
|
|
add_number(doc, "比较记录数与来源文件/页面的预计规模,注意分页、隐藏行和多个工作表。")
|
|
add_number(doc, "若只是部分批次或部分科类,选择“公开部分数据”,并精确写明缺口。")
|
|
add_number(doc, "只有在证据显示公开范围已完整纳入时,才使用“公开完整快照”。")
|
|
add_number(doc, "未找到全量公开数据时记录检索范围和日期,使用“暂无公开全量”,而不是制造空数据。")
|
|
|
|
add_heading(doc, "9.2 来源档案页面", 2)
|
|
add_figure(doc, "05-sources.png", "图 5 来源档案:集中保存发布方、标题、日期、落地页与内容哈希")
|
|
add_table(doc, ["来源类型", "含义", "审核重点"], [
|
|
["official_xls", "官方发布的 Excel/表格文件", "文件版本、工作表、发布时间、哈希"],
|
|
["official_html", "官方网页或查询页面", "页面标题、发布日期、参数和可访问性"],
|
|
["official_republication_html", "官方或权威渠道转载的页面", "是否保留原始出处,内容是否完整"],
|
|
["open_dataset_csv", "公开数据集 CSV", "许可、生成方式、字段映射、与官方口径差异"],
|
|
], widths=[4.1, 5.1, 6.3], font_size=8.6)
|
|
|
|
add_heading(doc, "9.3 来源证据链", 2)
|
|
add_para(doc, "一条可靠证据链至少应能回答:谁发布、何时发布、发布了什么、原始网页在哪里、下载文件是哪一版、内容是否发生变化、哪些业务记录由它生成。审核时优先打开落地页;本地文件存在时再核对文件名、大小、哈希和解析范围。")
|
|
add_note(doc, "网页失效时:", "不要删除已有来源档案。保留标题、发布日期、哈希和本地文件信息,并在审核备注中记录访问失败日期。")
|
|
|
|
add_heading(doc, "10. 常见审核场景", 1)
|
|
add_heading(doc, "10.1 审核某省某年的全部数据", 2)
|
|
add_number(doc, "从“省份与年份”选择目标省年,截图或记录初始状态。")
|
|
add_number(doc, "打开“覆盖与缺口”,列出已采集、部分采集和暂无公开全量的数据集。")
|
|
add_number(doc, "按第 8 章顺序进入各数据集,优先筛选待审核和未匹配。")
|
|
add_number(doc, "每完成一类,检查需复核/驳回记录的备注是否包含证据和后续动作。")
|
|
add_number(doc, "完成后回到矩阵,由负责人根据既定验收口径更新范围状态。")
|
|
|
|
add_heading(doc, "10.2 定位未匹配院校", 2)
|
|
add_number(doc, "进入院校专业组、专业计划、专业录取线或投档线页面。")
|
|
add_number(doc, "将“匹配状态”设为“未匹配”,再按省年和批次缩小范围。")
|
|
add_number(doc, "核对招生代码、学校名称、历史名称和同名院校,查看全国院校的外部代码映射。")
|
|
add_number(doc, "证据明确后再修复映射;审核页面本身只保存审核结论,不直接改写映射。")
|
|
|
|
add_heading(doc, "10.3 核对一条分数线", 2)
|
|
add_number(doc, "确认省份、年份、科类、层次、批次和段次。")
|
|
add_number(doc, "确认该记录是省控线、院校/专业组投档线还是专业录取线,避免类别混淆。")
|
|
add_number(doc, "比较分数、位次、志愿序号或专业分等字段,并确认空值是否来自源数据。")
|
|
add_number(doc, "打开来源页面定位原表位置;存在版本差异时记录版本和发布日期。")
|
|
add_number(doc, "提交已通过、需复核或已驳回,并写明依据。")
|
|
|
|
add_heading(doc, "10.4 核对数据导入结果", 2)
|
|
add_bullet(doc, "先看首页数量是否与导入预期接近,再按省年进入列表检查。")
|
|
add_bullet(doc, "检查首尾分数、随机中段、特殊字符、前导零代码、长备注和空值。")
|
|
add_bullet(doc, "检查来源档案和覆盖记录是否同步建立;只有业务表有数据并不代表导入完整。")
|
|
|
|
add_heading(doc, "11. 故障排查与常见问题", 1)
|
|
add_table(doc, ["现象", "可能原因", "处理方法"], [
|
|
["页面无法打开", "前端未启动或端口被占用", "确认 5173 服务运行;检查地址和端口"],
|
|
["页面能打开但列表报错/为 0", "后端未启动、数据库连接失败", "确认 8080 和 MySQL 3306;查看服务日志"],
|
|
["筛选后无结果", "省年、科类或审核状态组合过窄", "点击重置,再逐项增加条件"],
|
|
["保存审核失败", "审核人为空、状态无效、后端或数据库异常", "补全审核人;刷新后重试;仍失败则记录时间与记录 ID"],
|
|
["来源网页打不开", "页面下线、参数变化或网络限制", "保留档案与哈希;尝试官方站点检索同标题;记录访问失败"],
|
|
["列表滚动或加载较慢", "每页数量过大或筛选范围太宽", "改为 20/50 条并增加省年、批次或关键词"],
|
|
["代码前导零丢失", "源文件按数值列解析", "标记需复核,回查原文件;代码字段应按文本处理"],
|
|
["重复记录", "多版本或重复来源导入", "比较省年、批次、业务键、来源和哈希后再判断"],
|
|
], widths=[3.6, 5.0, 6.9], font_size=8.45)
|
|
|
|
add_heading(doc, "11.1 常见问题", 2)
|
|
add_para(doc, "问:灰色省份是否表示官方没有数据?", bold_lead="问:")
|
|
add_para(doc, "答:不是。灰色只表示当前系统尚未采集该省年数据。应创建采集任务并在公开渠道继续检索。", bold_lead="答:")
|
|
add_para(doc, "问:提交“已通过”会修改分数或计划数吗?", bold_lead="问:")
|
|
add_para(doc, "答:不会。审核结论与原始业务数据分开保存,并新增审计日志。", bold_lead="答:")
|
|
add_para(doc, "问:为什么记录数很多,但覆盖状态仍是“部分数据”?", bold_lead="问:")
|
|
add_para(doc, "答:记录量不等于覆盖完整。可能仍缺批次、科类、征集轮次、部分学校或关键字段。", bold_lead="答:")
|
|
add_para(doc, "问:找不到来源时能否凭其他网站补齐?", bold_lead="问:")
|
|
add_para(doc, "答:可以把权威转载或开放数据作为线索,但必须登记来源类型和差异;关键结论仍应尽量回溯官方证据。", bold_lead="答:")
|
|
|
|
add_heading(doc, "11.2 反馈问题时应提供", 2)
|
|
add_bullet(doc, "发生时间、操作页面、目标省份和年份。")
|
|
add_bullet(doc, "记录 ID 或稳定 ID、使用的筛选条件、审核状态。")
|
|
add_bullet(doc, "页面截图、错误提示、来源标题和来源地址。")
|
|
add_bullet(doc, "问题能否复现,以及复现所需的最短步骤。")
|
|
|
|
add_heading(doc, "附录 A:审核前后检查清单", 1)
|
|
add_heading(doc, "A.1 开始前", 2)
|
|
for text_ in [
|
|
"已确认本次省份、年份、数据集和批次范围。",
|
|
"已查看覆盖与缺口,知道哪些数据尚未收集或仅部分采集。",
|
|
"已确认来源档案可用,或已记录网页失效情况。",
|
|
"已明确抽样/全量策略和责任人。",
|
|
]:
|
|
add_bullet(doc, "□ " + text_)
|
|
add_heading(doc, "A.2 单条记录", 2)
|
|
for text_ in [
|
|
"省份、年份、科类、层次、批次和段次上下文正确。",
|
|
"院校、专业组、专业代码与稳定 ID 关联合理。",
|
|
"分数、位次、计划数和人数与来源一致。",
|
|
"空值、复合分值和备注没有被错误解释。",
|
|
"来源标题、发布日期、网页和哈希足以支持结论。",
|
|
"审核状态选择恰当,备注包含必要证据和后续动作。",
|
|
]:
|
|
add_bullet(doc, "□ " + text_)
|
|
add_heading(doc, "A.3 完成后", 2)
|
|
for text_ in [
|
|
"需复核和已驳回记录均有明确原因。",
|
|
"覆盖记录与实际导入范围一致。",
|
|
"来源档案没有遗漏,重复来源已通过哈希辨别。",
|
|
"已刷新页面并抽查审核状态与历史记录。",
|
|
"已向后续负责人移交未完成项。",
|
|
]:
|
|
add_bullet(doc, "□ " + text_)
|
|
|
|
add_heading(doc, "附录 B:字段速查", 1)
|
|
add_table(doc, ["字段/术语", "说明"], [
|
|
["稳定 ID", "系统内部用于跨来源、跨年份关联同一业务主体的标识"],
|
|
["外部代码", "教育部代码、各省招生代码或来源系统代码,需注明适用范围"],
|
|
["专业组", "新高考招生组织单元,通常带选科要求和组代码"],
|
|
["最低位次", "在指定省年和科类口径下,与最低分对应的排名位置"],
|
|
["同分人数", "该分值上的考生数量"],
|
|
["累计人数", "达到该分值及以上的累计人数,具体以省级公布口径为准"],
|
|
["内容哈希", "对来源文件内容计算的指纹,用于识别重复和变更"],
|
|
["覆盖状态", "描述公开数据可得性与当前采集完整性,不等同于审核状态"],
|
|
["审核状态", "描述单条记录的业务审核结论,不直接修改原始值"],
|
|
], widths=[4.0, 11.5], font_size=8.8)
|
|
|
|
add_heading(doc, "附录 C:使用边界", 1)
|
|
add_para(doc, "本系统是数据采集和审核工具,不替代各省教育考试院、教育部阳光高考平台或高校官方发布。面向考生展示或用于志愿决策前,应再次校验最新政策、招生章程、选科要求、计划变更和正式录取口径。")
|
|
add_para(doc, "本文档中的数量为编制时系统快照,仅用于说明界面和操作。数据重新导入、覆盖或审核后,首页指标会实时变化。")
|
|
|
|
# Global paragraph properties and document metadata
|
|
doc.core_properties.title = "志愿通数据审核系统使用手册"
|
|
doc.core_properties.subject = "数据查看、来源追溯、审核记录和覆盖管理"
|
|
doc.core_properties.author = "志愿通项目组"
|
|
doc.core_properties.keywords = "志愿通, 数据审核, 高考招录, 使用手册"
|
|
|
|
for p in doc.paragraphs:
|
|
if p.style and p.style.name.startswith("Heading"):
|
|
p.paragraph_format.widow_control = True
|
|
|
|
OUT.parent.mkdir(parents=True, exist_ok=True)
|
|
doc.save(OUT)
|
|
print(OUT)
|
|
|