添加签名图片处理功能
This commit is contained in:
@@ -239,8 +239,8 @@ class DocxWriter:
|
||||
f". 课程目标达成情况的合理性评价")
|
||||
self.set_run_font(run, 14, 'Times New Roman', '黑体', True)
|
||||
|
||||
rows = 9
|
||||
cols = 4
|
||||
rows = 11
|
||||
cols = 6
|
||||
table = doc.add_table(rows=rows, cols=cols)
|
||||
# 设置外侧框线粗1.5磅,内侧框线粗0.5磅
|
||||
self.set_table_borders(table)
|
||||
@@ -250,12 +250,24 @@ class DocxWriter:
|
||||
cell_end = table.cell(0, cols - 1)
|
||||
cell_start.merge(cell_end)
|
||||
# 合并第二行至最后
|
||||
for i in range(1, 9):
|
||||
if i == 2:
|
||||
continue
|
||||
cell_start = table.cell(i, 1)
|
||||
cell_end = table.cell(i, cols - 1)
|
||||
cell_start.merge(cell_end)
|
||||
for i in range(1, rows):
|
||||
match i:
|
||||
case 2:
|
||||
table.cell(i, 2).merge(table.cell(i, 3))
|
||||
table.cell(i, 4).merge(table.cell(i, 5))
|
||||
table.cell(i, 1).width = Cm(7.42)
|
||||
table.cell(i, 2).width = Cm(7.42)
|
||||
table.cell(i, 4).width = Cm(7.41)
|
||||
case 8 | 10:
|
||||
table.cell(i - 1, 0).merge(table.cell(i, 0))
|
||||
table.cell(i, 1).width = Cm(11.23)
|
||||
table.cell(i, 2).width = Cm(1.48)
|
||||
table.cell(i, 3).width = Cm(3.4)
|
||||
table.cell(i, 4).width = Cm(1.39)
|
||||
case _:
|
||||
cell_start = table.cell(i, 1)
|
||||
cell_end = table.cell(i, cols - 1)
|
||||
cell_start.merge(cell_end)
|
||||
# 填充数据
|
||||
self.put_data_to_table(table, self.excel_reader.get_word_template_part_3)
|
||||
|
||||
@@ -276,9 +288,37 @@ class DocxWriter:
|
||||
|
||||
for t_index, table in enumerate(doc.tables):
|
||||
self.set_table_borders(table)
|
||||
# part_3_table_index 表格第9和11行(索引8和10)特殊边框处理
|
||||
if t_index in part_3_table_index:
|
||||
for r_idx in [8, 10]:
|
||||
row = table.rows[r_idx]
|
||||
prev_row = table.rows[r_idx - 1]
|
||||
# 上一行(第8行和第10行,索引7和9)第2-6列移除下边框
|
||||
for c_idx in range(1, 6):
|
||||
self.set_cell_border(prev_row.cells[c_idx], bottom=0)
|
||||
# 第2列(索引1):没有上边框和右边框
|
||||
self.set_cell_border(row.cells[1], top=0, right=0)
|
||||
# 第3-5列(索引2-4):没有上边框和左右边框
|
||||
for c_idx in [2, 3, 4]:
|
||||
self.set_cell_border(row.cells[c_idx], top=0, left=0, right=0)
|
||||
# 第6列(索引5):没有上边框和左边框
|
||||
self.set_cell_border(row.cells[5], top=0, left=0)
|
||||
# 插入签名图片
|
||||
if self.excel_reader.major_director_signature_image is not None:
|
||||
self.insert_pil_image(table.cell(8, 3),
|
||||
self.excel_reader.major_director_signature_image,
|
||||
height=Cm(1.2))
|
||||
if self.excel_reader.course_leader_signature_image is not None:
|
||||
self.insert_pil_image(table.cell(10, 3),
|
||||
self.excel_reader.course_leader_signature_image,
|
||||
height=Cm(1.2))
|
||||
for r_index, row in enumerate(table.rows):
|
||||
row.height_rule = WD_ROW_HEIGHT_RULE.AT_LEAST
|
||||
row.height = Cm(0.7)
|
||||
# part_3_table_index 表格第9和11行(索引8和10)行高为1.2cm
|
||||
if t_index in part_3_table_index and r_index in [8, 10]:
|
||||
row.height = Cm(1.2)
|
||||
else:
|
||||
row.height = Cm(0.7)
|
||||
for c_index, cell in enumerate(row.cells):
|
||||
cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
|
||||
self.set_cell_margins(cell, start=57, end=57)
|
||||
@@ -330,12 +370,14 @@ class DocxWriter:
|
||||
special_cell = [
|
||||
(1, 1),
|
||||
(2, 1),
|
||||
(3, 1),
|
||||
(3, 1),
|
||||
(4, 1),
|
||||
(5, 1),
|
||||
(6, 1),
|
||||
(7, 1),
|
||||
(8, 1),
|
||||
(9, 1),
|
||||
(10, 1),
|
||||
]
|
||||
if r_index == 0:
|
||||
for run in paragraph.runs:
|
||||
@@ -379,6 +421,7 @@ class DocxWriter:
|
||||
"""
|
||||
设置单元格边框
|
||||
kwargs: top, bottom, left, right, inside_h, inside_v
|
||||
值为0时移除边框,值大于0时设置边框粗细
|
||||
"""
|
||||
tc = cell._tc
|
||||
tcPr = tc.get_or_add_tcPr()
|
||||
@@ -391,10 +434,14 @@ class DocxWriter:
|
||||
if value is not None:
|
||||
tag = 'w:{}'.format(key)
|
||||
border = OxmlElement(tag)
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), str(int(value * 8)))
|
||||
border.set(qn('w:space'), '0')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
if value == 0:
|
||||
# 移除边框
|
||||
border.set(qn('w:val'), 'nil')
|
||||
else:
|
||||
border.set(qn('w:val'), 'single')
|
||||
border.set(qn('w:sz'), str(int(value * 8)))
|
||||
border.set(qn('w:space'), '0')
|
||||
border.set(qn('w:color'), 'auto')
|
||||
tcBorders.append(border)
|
||||
|
||||
# 将边框添加到单元格属性中
|
||||
@@ -490,6 +537,15 @@ class DocxWriter:
|
||||
run = paragraph.add_run()
|
||||
run.add_picture(image_stream, width=width, height=height)
|
||||
|
||||
def insert_pil_image(self, cell, pil_image, width=None, height=Cm(4.5)):
|
||||
"""插入PIL Image对象到单元格"""
|
||||
image_stream = io.BytesIO()
|
||||
pil_image.save(image_stream, format='PNG')
|
||||
image_stream.seek(0)
|
||||
paragraph = cell.paragraphs[0]
|
||||
run = paragraph.add_run()
|
||||
run.add_picture(image_stream, width=width, height=height)
|
||||
|
||||
def is_chinese(self, char):
|
||||
"""判断字符是否为中文"""
|
||||
if '\u4e00' <= char <= '\u9fff':
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
import datetime
|
||||
import traceback
|
||||
import io
|
||||
from typing import Optional, Callable
|
||||
|
||||
import openpyxl
|
||||
@@ -22,6 +23,7 @@ from openpyxl.utils import get_column_letter, column_index_from_string
|
||||
from openpyxl.workbook.workbook import Workbook
|
||||
from openpyxl.worksheet.worksheet import Worksheet
|
||||
from packaging import version
|
||||
from PIL import Image
|
||||
|
||||
from module import LOGLEVEL, COMPATIBLE_VERSION
|
||||
from module.schema import Performance
|
||||
@@ -49,6 +51,27 @@ class ExcelReader:
|
||||
ignore_version_check: bool
|
||||
pic_list: list
|
||||
suggestion_template_list: list[Optional[str]]
|
||||
major_director_signature_image: Optional[Image.Image]
|
||||
course_leader_signature_image: Optional[Image.Image]
|
||||
|
||||
class _SheetImageLoader:
|
||||
"""Lightweight image loader scoped for ExcelReader use."""
|
||||
|
||||
def __init__(self, sheet: Worksheet):
|
||||
self._images: dict[str, Callable[[], bytes]] = {}
|
||||
for image in getattr(sheet, "_images", []):
|
||||
row = image.anchor._from.row + 1
|
||||
col = get_column_letter(image.anchor._from.col + 1)
|
||||
self._images[f"{col}{row}"] = image._data
|
||||
|
||||
def image_in(self, cell: str) -> bool:
|
||||
return cell in self._images
|
||||
|
||||
def get(self, cell: str) -> Image.Image:
|
||||
if cell not in self._images:
|
||||
raise ValueError(f"Cell {cell} doesn't contain an image")
|
||||
image = io.BytesIO(self._images[cell]())
|
||||
return Image.open(image)
|
||||
|
||||
def __init__(self, file_path: str, version_check: bool = False,
|
||||
signal: Callable[[str, str], None] = lambda x, y: print(x)):
|
||||
@@ -75,6 +98,8 @@ class ExcelReader:
|
||||
self.pic_list = []
|
||||
self.signal = signal
|
||||
self.suggestion_template_list = []
|
||||
self.major_director_signature_image = None
|
||||
self.course_leader_signature_image = None
|
||||
|
||||
def parse_excel(self):
|
||||
try:
|
||||
@@ -104,6 +129,13 @@ class ExcelReader:
|
||||
# 读取课程负责人
|
||||
self.course_lead_teacher_name = sheet["D8"].value
|
||||
|
||||
need_signature_images = CUR_VERSION >= version.parse("9.4") and sheet["H10"].value == "是"
|
||||
if need_signature_images:
|
||||
self._load_signature_images()
|
||||
else:
|
||||
self.major_director_signature_image = None
|
||||
self.course_leader_signature_image = None
|
||||
|
||||
# 读取班级和人数
|
||||
max_class_size = 4
|
||||
match CUR_VERSION:
|
||||
@@ -259,6 +291,24 @@ class ExcelReader:
|
||||
def run(self):
|
||||
self.parse_excel()
|
||||
|
||||
def _load_signature_images(self):
|
||||
signature_cells = {
|
||||
"major_director_signature_image": "K34",
|
||||
"course_leader_signature_image": "K35",
|
||||
}
|
||||
wb_with_images: Workbook = openpyxl.load_workbook(self.file_path, data_only=True)
|
||||
try:
|
||||
sheet_with_images: Worksheet = wb_with_images["初始录入"]
|
||||
loader = self._SheetImageLoader(sheet_with_images)
|
||||
|
||||
for attr, cell in signature_cells.items():
|
||||
if loader.image_in(cell):
|
||||
setattr(self, attr, loader.get(cell))
|
||||
else:
|
||||
setattr(self, attr, None)
|
||||
finally:
|
||||
wb_with_images.close()
|
||||
|
||||
def clear_all_data(self):
|
||||
self.kpi_list = []
|
||||
self.kpi_number = 0
|
||||
@@ -278,6 +328,8 @@ class ExcelReader:
|
||||
self.hml_list = []
|
||||
self.question_data = {}
|
||||
self.pic_list = []
|
||||
self.major_director_signature_image = None
|
||||
self.course_leader_signature_image = None
|
||||
|
||||
def set_version_check(self, version_check: bool):
|
||||
self.ignore_version_check = version_check
|
||||
@@ -486,7 +538,7 @@ class ExcelReader:
|
||||
yield "改进措施"
|
||||
yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n"
|
||||
f"{self.suggestion_template_list[0] if self.suggestion_template_list[0] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}")
|
||||
for i in range(88888):
|
||||
while True:
|
||||
yield "如果您看到了本段文字,请联系开发者"
|
||||
|
||||
def get_word_template_part_2(self):
|
||||
@@ -581,7 +633,7 @@ class ExcelReader:
|
||||
yield "改进措施"
|
||||
yield ("注:改进措施,包括课时分配、教材选用、教学方式、教学方法、教学内容、评分标准、过程评价及帮扶\n"
|
||||
f"{self.suggestion_template_list[1] if self.suggestion_template_list[1] is not None else '\n\n\n在这填入您的改进措施\n\n\n'}")
|
||||
for i in range(88888):
|
||||
while True:
|
||||
yield "如果您看到了本段文字,请联系开发者"
|
||||
|
||||
def get_word_template_part_3(self):
|
||||
@@ -589,7 +641,7 @@ class ExcelReader:
|
||||
yield "评价样本的合理性"
|
||||
yield "R全体样本 £抽样样本"
|
||||
yield "评价依据的合理性"
|
||||
yield "考核方法 R合适 £不合适 "
|
||||
yield "考核方法 R合适 £不合适"
|
||||
yield "考核内容是否支撑课程目标 R是 £否"
|
||||
yield "评分标准 R明确 £不明确"
|
||||
yield "计算过程的合理性"
|
||||
@@ -603,14 +655,22 @@ class ExcelReader:
|
||||
yield "专业负责人/系主任(签字)"
|
||||
yield ("整改意见:\n"
|
||||
"\n\n\n"
|
||||
f"{self.suggestion_template_list[3] if self.suggestion_template_list[3] is not None else '\n\n\n'}"
|
||||
f"\n\n\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{datetime.datetime.now().strftime("%Y-%m-%d")}\n")
|
||||
f"{" "*8}{self.suggestion_template_list[3] if self.suggestion_template_list[3] is not None else '\n\n\n'}\n\n\n")
|
||||
yield ""
|
||||
yield "签字:"
|
||||
yield ""
|
||||
yield "日期:"
|
||||
yield datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
yield "课程负责人(签字)"
|
||||
yield ("拟整改计划与措施:\n"
|
||||
"\n\n\n"
|
||||
f"{self.suggestion_template_list[4] if self.suggestion_template_list[4] is not None else '\n\n\n'}"
|
||||
f"\n\n\n\t\t\t\t\t\t\t\t\t签字:\t\t\t日期:{datetime.datetime.now().strftime("%Y-%m-%d")}\n")
|
||||
for i in range(88888):
|
||||
f"{" "*8}{self.suggestion_template_list[4] if self.suggestion_template_list[4] is not None else '\n\n\n'}\n\n\n")
|
||||
yield ""
|
||||
yield "签字:"
|
||||
yield ""
|
||||
yield "日期:"
|
||||
yield datetime.datetime.now().strftime("%Y-%m-%d")
|
||||
while True:
|
||||
yield "如果您看到了本段文字,请联系开发者"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user