424 lines
20 KiB
Python
424 lines
20 KiB
Python
from pathlib import Path
|
||
|
||
from docx import Document
|
||
from docx.enum.table import WD_CELL_VERTICAL_ALIGNMENT
|
||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||
from docx.oxml import OxmlElement
|
||
from docx.oxml.ns import qn
|
||
from docx.shared import Inches, Pt, RGBColor
|
||
|
||
|
||
OUT = Path(__file__).resolve().parents[1] / "docs" / "WanderQ官网客户验收与资料确认清单.docx"
|
||
|
||
GREEN = "315A46"
|
||
DEEP_GREEN = "18382A"
|
||
LIGHT_GREEN = "EAF2ED"
|
||
PALE_GREEN = "F5F8F6"
|
||
GOLD = "A77A3F"
|
||
INK = "26332C"
|
||
MUTED = "68736C"
|
||
LINE = "CCD8D0"
|
||
WHITE = "FFFFFF"
|
||
|
||
|
||
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_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 margin, value in (("top", top), ("start", start), ("bottom", bottom), ("end", end)):
|
||
node = tc_mar.find(qn(f"w:{margin}"))
|
||
if node is None:
|
||
node = OxmlElement(f"w:{margin}")
|
||
tc_mar.append(node)
|
||
node.set(qn("w:w"), str(value))
|
||
node.set(qn("w:type"), "dxa")
|
||
|
||
|
||
def set_cell_width(cell, width_dxa):
|
||
tc_pr = cell._tc.get_or_add_tcPr()
|
||
tc_w = tc_pr.find(qn("w:tcW"))
|
||
if tc_w is None:
|
||
tc_w = OxmlElement("w:tcW")
|
||
tc_pr.append(tc_w)
|
||
tc_w.set(qn("w:w"), str(width_dxa))
|
||
tc_w.set(qn("w:type"), "dxa")
|
||
|
||
|
||
def set_table_geometry(table, widths, indent=120):
|
||
table.autofit = False
|
||
tbl_pr = table._tbl.tblPr
|
||
tbl_w = tbl_pr.find(qn("w:tblW"))
|
||
if tbl_w is None:
|
||
tbl_w = OxmlElement("w:tblW")
|
||
tbl_pr.append(tbl_w)
|
||
tbl_w.set(qn("w:w"), str(sum(widths)))
|
||
tbl_w.set(qn("w:type"), "dxa")
|
||
tbl_ind = tbl_pr.find(qn("w:tblInd"))
|
||
if tbl_ind is None:
|
||
tbl_ind = OxmlElement("w:tblInd")
|
||
tbl_pr.append(tbl_ind)
|
||
tbl_ind.set(qn("w:w"), str(indent))
|
||
tbl_ind.set(qn("w:type"), "dxa")
|
||
grid = table._tbl.tblGrid
|
||
for child in list(grid):
|
||
grid.remove(child)
|
||
for width in widths:
|
||
col = OxmlElement("w:gridCol")
|
||
col.set(qn("w:w"), str(width))
|
||
grid.append(col)
|
||
for row in table.rows:
|
||
for index, cell in enumerate(row.cells):
|
||
set_cell_width(cell, widths[index])
|
||
set_cell_margins(cell)
|
||
cell.vertical_alignment = WD_CELL_VERTICAL_ALIGNMENT.CENTER
|
||
|
||
|
||
def set_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_cant_split(row):
|
||
tr_pr = row._tr.get_or_add_trPr()
|
||
cant_split = OxmlElement("w:cantSplit")
|
||
cant_split.set(qn("w:val"), "true")
|
||
tr_pr.append(cant_split)
|
||
|
||
|
||
def set_run(run, size=11, bold=False, color=INK, font="Noto Sans CJK SC"):
|
||
run.font.name = font
|
||
run._element.get_or_add_rPr().rFonts.set(qn("w:ascii"), font)
|
||
run._element.get_or_add_rPr().rFonts.set(qn("w:hAnsi"), font)
|
||
run._element.get_or_add_rPr().rFonts.set(qn("w:eastAsia"), font)
|
||
run.font.size = Pt(size)
|
||
run.bold = bold
|
||
run.font.color.rgb = RGBColor.from_string(color)
|
||
|
||
|
||
def add_text(paragraph, text, size=11, bold=False, color=INK):
|
||
run = paragraph.add_run(text)
|
||
set_run(run, size=size, bold=bold, color=color)
|
||
return run
|
||
|
||
|
||
def add_header_footer(section):
|
||
header = section.header
|
||
p = header.paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.RIGHT
|
||
p.paragraph_format.space_after = Pt(0)
|
||
add_text(p, "WANDERQ · CLIENT ACCEPTANCE", size=8.5, bold=True, color=MUTED)
|
||
|
||
footer = section.footer
|
||
p = footer.paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
p.paragraph_format.space_before = Pt(0)
|
||
add_text(p, "WanderQ 官网客户验收与资料确认清单 · 2026-08-12", size=8.5, color=MUTED)
|
||
|
||
|
||
def add_title_block(doc):
|
||
p = doc.add_paragraph()
|
||
p.paragraph_format.space_before = Pt(10)
|
||
p.paragraph_format.space_after = Pt(2)
|
||
add_text(p, "WANDERQ / 万趣旅行", size=10.5, bold=True, color=GOLD)
|
||
|
||
p = doc.add_paragraph()
|
||
p.paragraph_format.space_before = Pt(0)
|
||
p.paragraph_format.space_after = Pt(6)
|
||
add_text(p, "官网客户验收与资料确认清单", size=27, bold=True, color=DEEP_GREEN)
|
||
|
||
p = doc.add_paragraph()
|
||
p.paragraph_format.space_after = Pt(16)
|
||
add_text(p, "用于整站内容确认、真实资料补齐与正式发布前定版", size=12.5, color=MUTED)
|
||
|
||
table = doc.add_table(rows=2, cols=2)
|
||
set_table_geometry(table, [4680, 4680], indent=120)
|
||
info = [
|
||
("当前阶段", "已完成第一版开发与全站技术验收"),
|
||
("确认方式", "可在本文勾选、批注,或按编号逐项回复"),
|
||
("本轮范围", "贵州私家定制旅行官网(不含企业团建会务)"),
|
||
("建议顺序", "先确认整站方向,再分批补齐酒店与供应资料"),
|
||
]
|
||
for index, (label, value) in enumerate(info):
|
||
cell = table.cell(index // 2, index % 2)
|
||
set_cell_shading(cell, LIGHT_GREEN if index < 2 else PALE_GREEN)
|
||
p = cell.paragraphs[0]
|
||
p.paragraph_format.space_after = Pt(1)
|
||
add_text(p, label + "\n", size=8.5, bold=True, color=GREEN)
|
||
add_text(p, value, size=10.5, color=INK)
|
||
doc.add_paragraph()
|
||
|
||
|
||
def add_heading(doc, number, title, intro=None):
|
||
p = doc.add_paragraph(style="Heading 1")
|
||
p.paragraph_format.keep_with_next = True
|
||
add_text(p, f"{number} {title}", size=16, bold=True, color=DEEP_GREEN)
|
||
if intro:
|
||
p = doc.add_paragraph()
|
||
p.paragraph_format.space_after = Pt(8)
|
||
add_text(p, intro, size=10.5, color=MUTED)
|
||
|
||
|
||
def add_subheading(doc, title):
|
||
p = doc.add_paragraph(style="Heading 2")
|
||
p.paragraph_format.keep_with_next = True
|
||
add_text(p, title, size=12.5, bold=True, color=GREEN)
|
||
|
||
|
||
def add_checkbox_line(doc, text, indent=0):
|
||
p = doc.add_paragraph()
|
||
p.paragraph_format.left_indent = Inches(indent)
|
||
p.paragraph_format.space_after = Pt(4)
|
||
p.paragraph_format.keep_together = True
|
||
add_text(p, "☐ ", size=11.5, color=GREEN)
|
||
add_text(p, text, size=10.5)
|
||
|
||
|
||
def add_response_box(doc, prompt="客户意见 / 补充说明:", lines=2):
|
||
table = doc.add_table(rows=1, cols=1)
|
||
set_table_geometry(table, [9360], indent=120)
|
||
cell = table.cell(0, 0)
|
||
set_cell_shading(cell, PALE_GREEN)
|
||
set_cell_margins(cell, top=120, start=160, bottom=120, end=160)
|
||
p = cell.paragraphs[0]
|
||
p.paragraph_format.space_after = Pt(3)
|
||
add_text(p, prompt, size=9, bold=True, color=GREEN)
|
||
for _ in range(lines):
|
||
p = cell.add_paragraph()
|
||
p.paragraph_format.space_after = Pt(3)
|
||
add_text(p, "________________________________________________________________________________", size=9, color=LINE)
|
||
doc.add_paragraph().paragraph_format.space_after = Pt(2)
|
||
|
||
|
||
def add_status_table(doc):
|
||
rows = [
|
||
("整站视觉", "浅色、低饱和、欧美极简与编辑式排版", "请确认方向"),
|
||
("首屏主视觉", "保留自动轮播,并支持手动切换与暂停", "已按前期确认执行"),
|
||
("默认语言", "首次打开默认英文,可切换中文", "已按前期确认执行"),
|
||
("业务范围", "暂不加入企业团建会务", "已按前期确认执行"),
|
||
("第三方背书", "无正式文件前,不展示政府、媒体、协会等暗示性 Logo", "需资料后再上线"),
|
||
("英文评价", "无可核验原始评价和授权前,不编造评价", "需资料后再上线"),
|
||
]
|
||
table = doc.add_table(rows=1, cols=3)
|
||
set_table_geometry(table, [1900, 5060, 2400], indent=120)
|
||
for i, text in enumerate(("项目", "当前处理", "本轮确认")):
|
||
cell = table.rows[0].cells[i]
|
||
set_cell_shading(cell, GREEN)
|
||
p = cell.paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
add_text(p, text, size=9.5, bold=True, color=WHITE)
|
||
set_repeat_table_header(table.rows[0])
|
||
for row_index, row_data in enumerate(rows):
|
||
row = table.add_row()
|
||
set_cant_split(row)
|
||
cells = row.cells
|
||
for i, text in enumerate(row_data):
|
||
set_cell_shading(cells[i], WHITE if row_index % 2 == 0 else PALE_GREEN)
|
||
p = cells[i].paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.LEFT if i == 1 else WD_ALIGN_PARAGRAPH.CENTER
|
||
add_text(p, text, size=9.5, bold=(i == 0), color=INK)
|
||
set_table_geometry(table, [1900, 5060, 2400], indent=120)
|
||
|
||
|
||
def add_hotel_table(doc):
|
||
hotels = [
|
||
"兴义峰兮・洞穴民宿",
|
||
"朱砂古镇・放下云居",
|
||
"雷公山树蛙部落",
|
||
"榕江加宜梯田民宿",
|
||
"台江拾仓来隐",
|
||
"云屯・星空野奢树屋",
|
||
"枕宿・枕山河",
|
||
"兴义蘑菇野奢酒店",
|
||
]
|
||
table = doc.add_table(rows=1, cols=4)
|
||
widths = [700, 2800, 3960, 1900]
|
||
set_table_geometry(table, widths, indent=120)
|
||
headers = ("序号", "酒店候选", "请提供 / 确认", "状态")
|
||
for i, text in enumerate(headers):
|
||
cell = table.rows[0].cells[i]
|
||
set_cell_shading(cell, GREEN)
|
||
p = cell.paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||
add_text(p, text, size=9.2, bold=True, color=WHITE)
|
||
set_repeat_table_header(table.rows[0])
|
||
detail = "正式中英文名、准确地址/地图点位、经营或预订主体、可接待境外客人方式、4–6 张原图及授权、当前房型与价格渠道"
|
||
for index, hotel in enumerate(hotels, 1):
|
||
row = table.add_row()
|
||
set_cant_split(row)
|
||
cells = row.cells
|
||
values = (f"{index:02d}", hotel, detail, "☐ 待提供\n☐ 已确认")
|
||
for i, text in enumerate(values):
|
||
set_cell_shading(cells[i], WHITE if index % 2 else PALE_GREEN)
|
||
p = cells[i].paragraphs[0]
|
||
p.alignment = WD_ALIGN_PARAGRAPH.CENTER if i in (0, 3) else WD_ALIGN_PARAGRAPH.LEFT
|
||
add_text(p, text, size=8.8, bold=(i == 1), color=INK)
|
||
set_table_geometry(table, widths, indent=120)
|
||
|
||
|
||
def add_material_block(doc, title, items):
|
||
table = doc.add_table(rows=1, cols=1)
|
||
set_table_geometry(table, [9360], indent=120)
|
||
cell = table.cell(0, 0)
|
||
set_cell_shading(cell, PALE_GREEN)
|
||
set_cell_margins(cell, top=130, start=170, bottom=130, end=170)
|
||
p = cell.paragraphs[0]
|
||
p.paragraph_format.space_after = Pt(5)
|
||
add_text(p, title, size=11, bold=True, color=DEEP_GREEN)
|
||
for item in items:
|
||
p = cell.add_paragraph()
|
||
p.paragraph_format.space_after = Pt(3)
|
||
add_text(p, "☐ ", size=10.5, color=GREEN)
|
||
add_text(p, item, size=9.7)
|
||
doc.add_paragraph().paragraph_format.space_after = Pt(2)
|
||
|
||
|
||
def build():
|
||
doc = Document()
|
||
section = doc.sections[0]
|
||
section.page_width = Inches(8.5)
|
||
section.page_height = Inches(11)
|
||
section.top_margin = Inches(0.78)
|
||
section.bottom_margin = Inches(0.75)
|
||
section.left_margin = Inches(0.9)
|
||
section.right_margin = Inches(0.9)
|
||
section.header_distance = Inches(0.35)
|
||
section.footer_distance = Inches(0.35)
|
||
add_header_footer(section)
|
||
|
||
normal = doc.styles["Normal"]
|
||
normal.font.name = "Noto Sans CJK SC"
|
||
normal._element.rPr.rFonts.set(qn("w:ascii"), "Noto Sans CJK SC")
|
||
normal._element.rPr.rFonts.set(qn("w:hAnsi"), "Noto Sans CJK SC")
|
||
normal._element.rPr.rFonts.set(qn("w:eastAsia"), "Noto Sans CJK SC")
|
||
normal.font.size = Pt(10.5)
|
||
normal.font.color.rgb = RGBColor.from_string(INK)
|
||
normal.paragraph_format.space_after = Pt(6)
|
||
normal.paragraph_format.line_spacing = 1.25
|
||
|
||
for name, size, before, after in (("Heading 1", 16, 16, 8), ("Heading 2", 12.5, 12, 6)):
|
||
style = doc.styles[name]
|
||
style.font.name = "Noto Sans CJK SC"
|
||
style._element.rPr.rFonts.set(qn("w:ascii"), "Noto Sans CJK SC")
|
||
style._element.rPr.rFonts.set(qn("w:hAnsi"), "Noto Sans CJK SC")
|
||
style._element.rPr.rFonts.set(qn("w:eastAsia"), "Noto Sans CJK SC")
|
||
style.font.size = Pt(size)
|
||
style.font.bold = True
|
||
style.font.color.rgb = RGBColor.from_string(DEEP_GREEN if name == "Heading 1" else GREEN)
|
||
style.paragraph_format.space_before = Pt(before)
|
||
style.paragraph_format.space_after = Pt(after)
|
||
|
||
add_title_block(doc)
|
||
|
||
add_heading(doc, "01", "客户如何使用本清单", "请先完成整站方向确认;具体酒店、供应商和交易资料可以分批补充。任何暂时无法确认的项目,官网都会继续使用“待确认”或安全边界表达,不会替客户作无依据承诺。")
|
||
add_checkbox_line(doc, "整站方向无重大问题,可继续进入真实资料补齐和发布前定版。")
|
||
add_checkbox_line(doc, "整体方向认可,但需要按本文编号进行局部修改。")
|
||
add_checkbox_line(doc, "需要重新讨论某个栏目结构;请注明栏目和原因。")
|
||
add_response_box(doc, "请填写本轮总意见:", 3)
|
||
|
||
add_heading(doc, "02", "整站方向确认", "以下项目是此前沟通结果与当前官网的执行方式,请客户确认是否继续沿用。")
|
||
add_status_table(doc)
|
||
add_subheading(doc, "请客户确认")
|
||
add_checkbox_line(doc, "认可当前整站视觉方向,不需要大规模重做。")
|
||
add_checkbox_line(doc, "认可当前首页到详情页的栏目结构和内容顺序。")
|
||
add_checkbox_line(doc, "认可英文为默认语言,中文作为切换语言。")
|
||
add_checkbox_line(doc, "认可暂不加入企业团建会务。")
|
||
add_checkbox_line(doc, "认可未提供证明的卖点、资质和背书暂不上线。")
|
||
add_response_box(doc, "如有页面级修改,请写“页面名称 + 位置 + 修改内容”:", 3)
|
||
|
||
add_heading(doc, "03", "8 家特色酒店资料确认", "官网已经按照客户提出的 8 家住宿候选完成栏目结构,但目前不使用无关风景图冒充酒店实景。请按酒店逐家提供下列资料。")
|
||
add_hotel_table(doc)
|
||
add_subheading(doc, "每家酒店图片最低要求")
|
||
add_checkbox_line(doc, "外观或环境 1–2 张、代表房型 2–3 张、公共空间或餐饮 1–2 张,尽量提供原始高清图。")
|
||
add_checkbox_line(doc, "明确图片对应的酒店、房型、拍摄时间和是否允许官网商业使用。")
|
||
add_checkbox_line(doc, "如图片来自酒店官方或第三方平台,请提供授权依据,不能仅提供网络截图。")
|
||
add_response_box(doc, "酒店资料负责人 / 预计提供日期:", 2)
|
||
|
||
add_heading(doc, "04", "体验供应与安全资料", "客户文档中的体验方向已完整进入网站。正式对外销售前,需要把“概念内容”落实到具名运营方、具名人员和可执行条件。")
|
||
add_material_block(doc, "户外体验", [
|
||
"洞穴、瀑降、溯溪、漂流、徒步/骑行、高桥挑战等项目的具名运营方与联系人。",
|
||
"领队、向导、教练或救援人员的相关资质、经验与可公开介绍。",
|
||
"适用年龄、体能/健康限制、装备清单、天气/水位取消条件与替代方案。",
|
||
"保险责任、应急通信、撤离路线、最近医疗资源和事故处理流程。",
|
||
"真实产品时长、人数上下限、当前价格、可约日期和取消规则。",
|
||
])
|
||
add_material_block(doc, "文化与村寨体验", [
|
||
"苗绣/蜡染、侗族技艺、水族马尾绣、布依技艺等具名老师、工坊和地点。",
|
||
"村寨拜访、家庭接待、住宿、家宴、节庆参与的主人/社区/运营主体同意。",
|
||
"课程可完成内容、材料与工具、儿童参与边界、拍摄同意和作品带走/后寄方式。",
|
||
"英文支持由谁提供,以及费用如何分配给老师、主人或社区。",
|
||
])
|
||
add_material_block(doc, "特别场景与历史演绎", [
|
||
"洞穴咖啡/晚宴、山野咖啡、露营的场地许可、餐饮、消防、电力、天气与撤离资料。",
|
||
"《战青岩》《娄山关大捷》等现有产品的运营主体、当前场次、票价、英文接待与包团条件。",
|
||
"客户提出但尚未成为现有产品的二期共创内容,需单独确认开发主体、预算和周期。",
|
||
])
|
||
add_response_box(doc, "供应商与安全资料负责人 / 预计提供日期:", 2)
|
||
|
||
add_heading(doc, "05", "合作背书与真实评价", "这一部分能够显著增强官网信任感,但必须保证来源真实、关联准确并获得公开使用许可。")
|
||
add_subheading(doc, "合作、媒体与资质")
|
||
add_checkbox_line(doc, "合作机构或政府单位:正式名称、合作内容、有效期、证明文件及 Logo 使用授权。")
|
||
add_checkbox_line(doc, "媒体报道或推荐:原始报道链接/文件、准确标题和允许引用的内容。")
|
||
add_checkbox_line(doc, "协会会员、行业认证或获奖:证书、有效期、认证主体和允许展示的标识。")
|
||
add_subheading(doc, "真实英文客户评价")
|
||
add_checkbox_line(doc, "英文原评(不由开发侧翻译成“原评”)、客户姓名或允许公开的署名方式。")
|
||
add_checkbox_line(doc, "客户照片、国籍/地区、人数、实际参加线路或体验、出行时间。")
|
||
add_checkbox_line(doc, "照片和评价公开用于官网及营销材料的授权。")
|
||
add_response_box(doc, "如暂时没有完整资料,可注明“本次不上线”:", 2)
|
||
|
||
add_heading(doc, "06", "价格、预订与法律信息", "官网进入正式发布和接单阶段前,请确认以下交易信息,避免页面卖点与实际报价、履约条件不一致。")
|
||
for item in [
|
||
"各线路、体验、酒店和用车是展示参考价、起价、询价制,还是暂不公开价格。",
|
||
"报价包含与不包含:门票、住宿、餐饮、车辆、司导、装备、保险、税费和服务费。",
|
||
"预订金比例、尾款时间、支付方式、汇率或跨境支付费用。",
|
||
"取消、退款、日期变更、人数变化、天气取消和供应商取消条款。",
|
||
"公司/经营主体正式名称、注册地址、联系人、客服邮箱及适用的隐私与服务条款。",
|
||
"客户咨询信息由谁接收、如何跟进、保存多久,以及是否需要单独的隐私同意。",
|
||
]:
|
||
add_checkbox_line(doc, item)
|
||
add_response_box(doc, "价格与合同资料负责人 / 预计提供日期:", 2)
|
||
|
||
add_heading(doc, "07", "回复优先级与最终确认", "为了不影响当前设计确认,资料可以按以下顺序分批提供。")
|
||
priorities = [
|
||
("P0 / 本轮确认", "整站视觉、结构、默认英文、业务范围,以及需要立即修改的页面。"),
|
||
("P1 / 发布前必须", "酒店原图与授权、具名供应商与安全资料、价格/取消条款、经营与法律信息。"),
|
||
("P2 / 可后补增强", "合作背书、媒体材料、真实英文评价和新增内容。"),
|
||
]
|
||
table = doc.add_table(rows=1, cols=2)
|
||
set_table_geometry(table, [2200, 7160], indent=120)
|
||
for index, (level, detail) in enumerate(priorities):
|
||
row = table.rows[0] if index == 0 else table.add_row()
|
||
set_cell_shading(row.cells[0], GREEN if index == 0 else LIGHT_GREEN)
|
||
set_cell_shading(row.cells[1], PALE_GREEN)
|
||
p = row.cells[0].paragraphs[0]
|
||
add_text(p, level, size=10, bold=True, color=WHITE if index == 0 else GREEN)
|
||
p = row.cells[1].paragraphs[0]
|
||
add_text(p, detail, size=10)
|
||
set_table_geometry(table, [2200, 7160], indent=120)
|
||
doc.add_paragraph()
|
||
add_checkbox_line(doc, "确认当前官网可作为客户整站验收版本继续推进。")
|
||
add_checkbox_line(doc, "确认按本清单分批补充资料;未提供或未核验内容不作为官网承诺。")
|
||
add_checkbox_line(doc, "确认客户提供的文字、图片、Logo、评价与证明材料拥有相应公开和商业使用权限。")
|
||
add_response_box(doc, "客户代表 / 日期 / 其他说明:", 4)
|
||
|
||
OUT.parent.mkdir(parents=True, exist_ok=True)
|
||
doc.save(OUT)
|
||
print(OUT)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
build()
|