246 lines
8.7 KiB
Python
246 lines
8.7 KiB
Python
|
#!/usr/bin/python
|
|||
|
# -*- coding: utf-8 -*-
|
|||
|
# @version : 1.0
|
|||
|
# @Create Time : 2022/5/6 17:25
|
|||
|
# @File : excel_manage.py
|
|||
|
# @IDE : PyCharm
|
|||
|
# @desc : EXCEL 文件操作
|
|||
|
|
|||
|
import datetime
|
|||
|
import os
|
|||
|
import re
|
|||
|
from pathlib import Path
|
|||
|
|
|||
|
from openpyxl.utils import get_column_letter
|
|||
|
from openpyxl import load_workbook, Workbook
|
|||
|
from application.settings import STATIC_ROOT, STATIC_URL
|
|||
|
from openpyxl.styles import Alignment, Font, PatternFill, Border, Side
|
|||
|
from utils.file.file_base import FileBase
|
|||
|
from .excel_schema import AlignmentModel, FontModel, PatternFillModel
|
|||
|
|
|||
|
|
|||
|
class ExcelManage:
|
|||
|
"""
|
|||
|
excel 文件序列化
|
|||
|
"""
|
|||
|
|
|||
|
# 列名,A-Z
|
|||
|
EXCEL_COLUMNS = [chr(a) for a in range(ord('A'), ord('Z') + 1)]
|
|||
|
|
|||
|
def __init__(self):
|
|||
|
self.sheet = None
|
|||
|
self.wb = None
|
|||
|
|
|||
|
def open_workbook(self, file: str, read_only: bool = False, data_only: bool = False) -> None:
|
|||
|
"""
|
|||
|
初始化 excel 文件
|
|||
|
:param file: 文件名称或者对象
|
|||
|
:param read_only: 是否只读,优化读取速度
|
|||
|
:param data_only: 是否加载文件对象
|
|||
|
:return:
|
|||
|
"""
|
|||
|
# 加载excel文件,获取表单
|
|||
|
self.wb = load_workbook(file, read_only=read_only, data_only=data_only)
|
|||
|
|
|||
|
def open_sheet(
|
|||
|
self,
|
|||
|
sheet_name: str = None,
|
|||
|
file: str = None,
|
|||
|
read_only: bool = False,
|
|||
|
data_only: bool = False
|
|||
|
) -> None:
|
|||
|
"""
|
|||
|
初始化 excel 文件
|
|||
|
:param sheet_name: 表单名称,为空则默认第一个
|
|||
|
:param file:
|
|||
|
:param read_only:
|
|||
|
:param data_only:
|
|||
|
:return:
|
|||
|
"""
|
|||
|
# 加载excel文件,获取表单
|
|||
|
if not self.wb:
|
|||
|
self.open_workbook(file, read_only, data_only)
|
|||
|
if sheet_name:
|
|||
|
if sheet_name in self.get_sheets():
|
|||
|
self.sheet = self.wb[sheet_name]
|
|||
|
else:
|
|||
|
self.sheet = self.wb.create_sheet(sheet_name)
|
|||
|
else:
|
|||
|
self.sheet = self.wb.active
|
|||
|
|
|||
|
def get_sheets(self) -> list:
|
|||
|
"""
|
|||
|
读取所有工作区名称
|
|||
|
:return: 一维数组
|
|||
|
"""
|
|||
|
return self.wb.sheetnames
|
|||
|
|
|||
|
def create_excel(self, sheet_name: str = None) -> None:
|
|||
|
"""
|
|||
|
创建 excel 文件
|
|||
|
:param sheet_name: 表单名称,为空则默认第一个
|
|||
|
:return:
|
|||
|
"""
|
|||
|
# 加载excel文件,获取表单
|
|||
|
self.wb = Workbook()
|
|||
|
self.sheet = self.wb.active
|
|||
|
if sheet_name:
|
|||
|
self.sheet.title = sheet_name
|
|||
|
|
|||
|
def readlines(self, min_row: int = 1, min_col: int = 1, max_row: int = None, max_col: int = None) -> list:
|
|||
|
"""
|
|||
|
读取指定表单所有数据
|
|||
|
:param min_row: 最小行
|
|||
|
:param min_col: 最小列
|
|||
|
:param max_row: 最大行
|
|||
|
:param max_col: 最大列
|
|||
|
:return: 二维数组
|
|||
|
"""
|
|||
|
rows = self.sheet.iter_rows(min_row=min_row, min_col=min_col, max_row=max_row, max_col=max_col)
|
|||
|
result = []
|
|||
|
for row in rows:
|
|||
|
_row = []
|
|||
|
for cell in row:
|
|||
|
_row.append(cell.value)
|
|||
|
if any(_row):
|
|||
|
result.append(_row)
|
|||
|
return result
|
|||
|
|
|||
|
def get_header(self, row: int = 1, col: int = None, asterisk: bool = False) -> list:
|
|||
|
"""
|
|||
|
读取指定表单的表头(第一行数据)
|
|||
|
:param row: 指定行
|
|||
|
:param col: 最大列
|
|||
|
:param asterisk: 是否去除 * 号
|
|||
|
:return: 一维数组
|
|||
|
"""
|
|||
|
rows = self.sheet.iter_rows(min_row=row, max_col=col)
|
|||
|
result = []
|
|||
|
for row in rows:
|
|||
|
for cell in row:
|
|||
|
value = cell.value.replace("*", "").strip() if asterisk else cell.value.strip()
|
|||
|
result.append(value)
|
|||
|
break
|
|||
|
return result
|
|||
|
|
|||
|
def write_list(self, rows: list, header: list = None) -> None:
|
|||
|
"""
|
|||
|
写入 excel文件
|
|||
|
:param rows: 行数据集
|
|||
|
:param header: 表头
|
|||
|
:return:
|
|||
|
"""
|
|||
|
if header:
|
|||
|
self.sheet.append(header)
|
|||
|
pattern_fill_style = PatternFillModel(start_color='D9D9D9', end_color='D9D9D9', fill_type='solid')
|
|||
|
font_style = FontModel(bold=True)
|
|||
|
self.__set_row_style(1, len(header), pattern_fill_style=pattern_fill_style, font_style=font_style)
|
|||
|
for index, data in enumerate(rows):
|
|||
|
format_columns = {
|
|||
|
"date_columns": []
|
|||
|
}
|
|||
|
for i in range(0, len(data)):
|
|||
|
if isinstance(data[i], datetime.datetime):
|
|||
|
data[i] = data[i].strftime("%Y-%m-%d %H:%M:%S")
|
|||
|
format_columns["date_columns"].append(i + 1)
|
|||
|
elif isinstance(data[i], bool):
|
|||
|
data[i] = 1 if data[i] else 0
|
|||
|
self.sheet.append(data)
|
|||
|
self.__set_row_style(index + 2 if header else index + 1, len(data))
|
|||
|
self.__set_row_format(index + 2 if header else index + 1, format_columns)
|
|||
|
self.__auto_width()
|
|||
|
self.__set_row_border()
|
|||
|
|
|||
|
def save_excel(self, path: str = "excel_manage"):
|
|||
|
"""
|
|||
|
保存 excel 文件到本地 static 目录
|
|||
|
:param path: static 指定目录类别
|
|||
|
:return:
|
|||
|
"""
|
|||
|
file_path = FileBase.generate_static_file_path(path=path, suffix="xlsx")
|
|||
|
Path(file_path).parent.mkdir(parents=True, exist_ok=True)
|
|||
|
self.wb.save(file_path)
|
|||
|
return {
|
|||
|
"local_path": file_path,
|
|||
|
"remote_path": file_path.replace(STATIC_ROOT, STATIC_URL)
|
|||
|
}
|
|||
|
|
|||
|
def __set_row_style(
|
|||
|
self,
|
|||
|
row: int,
|
|||
|
max_column: int,
|
|||
|
alignment_style: AlignmentModel = AlignmentModel(),
|
|||
|
font_style: FontModel = FontModel(),
|
|||
|
pattern_fill_style: PatternFillModel = PatternFillModel()
|
|||
|
):
|
|||
|
"""
|
|||
|
设置行样式
|
|||
|
:param row: 行
|
|||
|
:param max_column: 最大列
|
|||
|
:param alignment_style: 单元格内容的对齐设置
|
|||
|
:param font_style: 单元格内容的字体样式设置
|
|||
|
:param pattern_fill_style: 单元格的填充模式设置
|
|||
|
:return:
|
|||
|
"""
|
|||
|
for index in range(0, max_column):
|
|||
|
alignment = Alignment(**alignment_style.model_dump())
|
|||
|
font = Font(**font_style.model_dump())
|
|||
|
pattern_fill = PatternFill(**pattern_fill_style.model_dump())
|
|||
|
self.sheet.cell(row=row, column=index + 1).alignment = alignment
|
|||
|
self.sheet.cell(row=row, column=index + 1).font = font
|
|||
|
self.sheet.cell(row=row, column=index + 1).fill = pattern_fill
|
|||
|
|
|||
|
def __set_row_format(self, row: int, columns: dict):
|
|||
|
"""
|
|||
|
格式化行数据类型
|
|||
|
:param row: 行
|
|||
|
:param columns: 列数据
|
|||
|
:return:
|
|||
|
"""
|
|||
|
for index in columns.get("date_columns", []):
|
|||
|
self.sheet.cell(row=row, column=index).number_format = "yyyy-mm-dd h:mm:ss"
|
|||
|
|
|||
|
def __set_row_border(self):
|
|||
|
"""
|
|||
|
设置行边框
|
|||
|
:return:
|
|||
|
"""
|
|||
|
# 创建 Border 对象并设置边框样式
|
|||
|
border = Border(
|
|||
|
left=Side(border_style="thin", color="000000"),
|
|||
|
right=Side(border_style="thin", color="000000"),
|
|||
|
top=Side(border_style="thin", color="000000"),
|
|||
|
bottom=Side(border_style="thin", color="000000")
|
|||
|
)
|
|||
|
# 设置整个表格的边框
|
|||
|
for row in self.sheet.iter_rows():
|
|||
|
for cell in row:
|
|||
|
cell.border = border
|
|||
|
|
|||
|
def __auto_width(self):
|
|||
|
"""
|
|||
|
设置自适应列宽
|
|||
|
:return:
|
|||
|
"""
|
|||
|
# 设置一个字典用于保存列宽数据
|
|||
|
dims = {}
|
|||
|
# 遍历表格数据,获取自适应列宽数据
|
|||
|
for row in self.sheet.rows:
|
|||
|
for cell in row:
|
|||
|
if cell.value:
|
|||
|
# 遍历整个表格,把该列所有的单元格文本进行长度对比,找出最长的单元格
|
|||
|
# 在对比单元格文本时需要将中文字符识别为1.7个长度,英文字符识别为1个,这里只需要将文本长度直接加上中文字符数量即可
|
|||
|
# re.findall('([\u4e00-\u9fa5])', cell.value)能够识别大部分中文字符
|
|||
|
cell_len = 0.7 * len(re.findall('([\u4e00-\u9fa5])', str(cell.value))) + len(str(cell.value))
|
|||
|
dims[cell.column] = max((dims.get(cell.column, 0), cell_len))
|
|||
|
for col, value in dims.items():
|
|||
|
# 设置列宽,get_column_letter用于获取数字列号对应的字母列号,最后值+2是用来调整最终效果的
|
|||
|
self.sheet.column_dimensions[get_column_letter(col)].width = value + 10
|
|||
|
|
|||
|
def close(self):
|
|||
|
"""
|
|||
|
关闭文件
|
|||
|
:return:
|
|||
|
"""
|
|||
|
self.wb.close()
|