【GPT教我学python】浙政钉工资条发放

目录

  • 欢迎使用Markdown编辑器
    • 产品说明
    • 源代码
  • END

欢迎使用Markdown编辑器

产品说明

python写一个windows环境的工资条发放程序
程序界面设计如下:
1、通过路径浏览的方式,选择工资条xlsx所在路径
通过路径浏览的方式,指定名单txt所在路径
2、通过两个文本框指定搜索框的焦点坐标x,y
3、通过两个文本框指定对话输入框的焦点坐标a,b
4、通过两个文本框分别指定操点击间隔t1,单位为毫秒,搜索间隔t2,单位毫秒
5、一个校对按钮和一个发放按钮
6、label文字说明"表格第一列必须是姓名"

程序运行过程如下,运行开始时,先尝试加载工作路径中的配置文件,将之前用过的参数填入相应文本框
点击校对按钮
读取xlsx和txt文件,如果格式不对报错提示正确格式要求
对txt文件按行读取,每行用strip()处理之后不等于"姓名"的字段添加到name列表中
按照工资条表的sheet页循环,对每个sheet页做如下事情
{按行遍历,如果第一个单元格的文本通过strip()函数处理后不为"姓名",则在name列表中查询,如果没有记录在other列表中},注意如果第一个单元格为空值或者不为文本则跳过该行,sheet页便历结束后,弹窗提示陌生姓名,并保存在陌生姓名.txt中

点击发放按钮,要关闭后重新打开读取工资条xlsx,
对sheet页便历,对每个sheet页做如下事情
{用时间和函数获取当前年度和月份,记录在period变量中,格式为"xxxx年xx月"
设置titlerow标题行所在行号,namerow姓名行行号
按行便历,读取第一个单元格文本框的时候用strip()函数处理,如果第一个单元格为空值或者不为文本则跳过该行,如果为"姓名"则将这一行的行号,作为新的titlerow,

如果是name列表中的姓名,则要做这样一件事情{
将该姓名所在行行号,作为新的namerow
同时将titlerow和namerow对应的两行单元格保留其格式间距设置不变,用类似打印的方式,转换成一张图片保存在剪贴板中,然后点击x,y(搜索框的位置),输入namerow第一个的姓名值,等待t2ms后,输入回车,等待t1ms后,点击a,b(对话输入框位置),粘贴该图片,等待20ms,回车,点击a,b(对话输入框位置),输入period+sheet页名字+“请查收”,回车,等待t1ms
}
}
sheet页便历结束,将所有配置文件中的值保存在配置文件.txt中以便于下次调用,我需要知道名单里哪些名字没有发放过,弹窗提示并保存在  未发放列表.txt中

源代码

import os
import re
import sys
import tkinter as tk
from tkinter import filedialog, messagebox
import openpyxl
import pyautogui
import time
import pyperclip
from PIL import ImageGrab, Image
import configparser

from PIL import Image, ImageDraw, ImageFont
from openpyxl.utils import get_column_letter
from openpyxl.utils.cell import range_boundaries
from openpyxl.cell.cell import MergedCell
import win32clipboard
from io import BytesIO


def get_merged_value(sheet, cell):
    """获取合并单元格的值"""
    for merged_range in sheet.merged_cells.ranges:
        if cell.coordinate in merged_range:
            min_col, min_row, max_col, max_row = range_boundaries(merged_range.coord)
            if min_col!=None:
                cell_value = sheet.cell(row=min_row, column=min_col).value
                return cell_value if cell_value is not None else " "
    return cell.value if cell.value is not None else " "


def get_text_size(font, text):
    """通用字体尺寸计算方法(兼容Pillow>=10.0.0)"""
    if hasattr(font, 'getsize'):
        # 兼容旧版本 (<10.0.0)
        return font.getsize(text)
    else:
        # 新版本方法
        bbox = font.getbbox(text)
        return (bbox[2] - bbox[0], bbox[3] - bbox[1])


def draw_centered_text(draw, font, text, x, y, box_width, box_height, fill_color):
    """自动居中的文字绘制方法"""
    text_width, text_height = get_text_size(font, text)
    tx = x + (box_width - text_width) // 2
    ty = y + (box_height - text_height) // 2
    draw.text((tx, ty), text, font=font, fill=fill_color)

#解决问题:合并单元格、浮点数
def copy_table_to_clipboard(sheet, title_row, data_row):
    """将指定行生成表格图片并复制到剪贴板"""
    # ================= 配置参数 =================
    FONT_SIZE = 14  # 字体大小
    FONT_NAME = "simhei.ttf"  # 字体文件
    ROW_HEIGHT = 40  # 行高(像素)
    CELL_PADDING = 10  # 单元格内边距
    COLUMN_WIDTH_RATIO = 9  # Excel列宽到像素的转换比例(1单位≈9像素)
    MIN_COLUMN_WIDTH = 80  # 最小列宽(像素)
    LINE_COLOR = (0, 0, 0)  # 边框颜色
    HEADER_BG_COLOR = (240, 240, 240)  # 标题行背景色
    TEXT_COLOR = (0, 0, 0)  # 文字颜色

    # ================= 初始化字体 =================
    try:
        font = ImageFont.truetype(FONT_NAME, FONT_SIZE)
    except IOError:
        messagebox.showerror("错误", f"找不到字体文件{FONT_NAME}")
        return

    # ================= 列宽计算 =================
    columns = []
    for col in range(1, sheet.max_column + 1):
        # 获取标题和数据内容
        title = str(get_merged_value(sheet, sheet.cell(title_row, col)))
        data = str(get_merged_value(sheet, sheet.cell(data_row, col)))

        # 计算文本显示宽度
        title_width, _ = get_text_size(font, title)
        data_width, _ = get_text_size(font, data)

        # 计算Excel列宽转换值
        excel_width = sheet.column_dimensions[get_column_letter(col)].width
        converted_width = max(
            int(excel_width * COLUMN_WIDTH_RATIO) if excel_width else 0,
            title_width + 2 * CELL_PADDING,
            data_width + 2 * CELL_PADDING,
            MIN_COLUMN_WIDTH
        )

        columns.append({
            "title": title,
            "data": data,
            "width": converted_width
        })

    # ================= 合并列处理 =================
    merged_ranges = [range_boundaries(r.coord) for r in sheet.merged_cells.ranges]
    final_columns = []
    skip_cols = 0

    for idx in range(len(columns)):
        if skip_cols > 0:
            skip_cols -= 1
            continue

        # 检查是否合并列
        merged = False
        for min_c, min_r, max_c, max_r in merged_ranges:
            if min_r <= title_row <= max_r and min_c == idx + 1:
                span = max_c - min_c + 1
                if span > 1:
                    # 合并列总宽度
                    merged_width = sum(c["width"] for c in columns[idx:idx + span])
                    # 合并文本(取首列内容)
                    merged_title = columns[idx]["title"]
                    merged_data = columns[idx]["data"]

                    final_columns.append({
                        "title": merged_title,
                        "data": merged_data,
                        "width": merged_width
                    })
                    skip_cols = span - 1
                    merged = True
                    break
        if not merged:
            final_columns.append(columns[idx])

    # ================= 图片生成 =================
    # 计算总尺寸
    total_width = sum(c["width"] for c in final_columns) + 2 * CELL_PADDING
    total_height = 2 * ROW_HEIGHT + 2 * CELL_PADDING

    img = Image.new('RGB', (total_width, total_height), (255, 255, 255))
    draw = ImageDraw.Draw(img)

    # 绘制表格
    x = CELL_PADDING
    for col in final_columns:
        # 标题行
        draw.rectangle(
            [x, CELL_PADDING, x + col["width"], CELL_PADDING + ROW_HEIGHT],
            fill=HEADER_BG_COLOR,
            outline=LINE_COLOR
        )
        draw_centered_text(
            draw, font, col["title"],
            x, CELL_PADDING,
            col["width"], ROW_HEIGHT,
            TEXT_COLOR
        )

        # 数据行
        draw.rectangle(
            [x, CELL_PADDING + ROW_HEIGHT, x + col["width"], CELL_PADDING + 2 * ROW_HEIGHT],
            outline=LINE_COLOR
        )
        draw_centered_text(
            draw, font, col["data"],
            x, CELL_PADDING + ROW_HEIGHT,
            col["width"], ROW_HEIGHT,
            TEXT_COLOR
        )

        x += col["width"]

    # ================= 剪贴板操作 =================
    output = BytesIO()
    img.save(output, format="BMP")
    data = output.getvalue()[14:]
    output.close()

    win32clipboard.OpenClipboard()
    win32clipboard.EmptyClipboard()
    win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data)
    win32clipboard.CloseClipboard()

class SalarySenderApp:
    def __init__(self, master):
        self.master = master
        master.title("工资条发放程序")
        master.geometry("600x600")

        # 初始化配置
        self.config_file = "config.ini"
        self.config = configparser.ConfigParser()
        self.names = []  # 存储姓名列表
        self.sent_names = []  # 存储已发送名单

        # 创建界面
        self.create_widgets()
        self.load_config()

    def create_widgets(self):
        # 文件路径选择
        tk.Label(self.master, text="工资表路径:").grid(row=0, column=0)
        self.excel_path = tk.Entry(self.master, width=40)
        self.excel_path.grid(row=0, column=1)
        tk.Button(self.master, text="浏览", command=self.select_excel).grid(row=0, column=2)

        tk.Label(self.master, text="名单路径:").grid(row=1, column=0)
        self.txt_path = tk.Entry(self.master, width=40)
        self.txt_path.grid(row=1, column=1)
        tk.Button(self.master, text="浏览", command=self.select_txt).grid(row=1, column=2)

        # 坐标设置
        tk.Label(self.master, text="搜索框坐标 (x,y):").grid(row=2, column=0)
        self.x_entry = tk.Entry(self.master, width=8)
        self.x_entry.grid(row=2, column=1)
        self.y_entry = tk.Entry(self.master, width=8)
        self.y_entry.grid(row=2, column=2)

        tk.Label(self.master, text="输入框坐标 (a,b):").grid(row=3, column=0)
        self.a_entry = tk.Entry(self.master, width=8)
        self.a_entry.grid(row=3, column=1)
        self.b_entry = tk.Entry(self.master, width=8)
        self.b_entry.grid(row=3, column=2)

        # 时间间隔
        tk.Label(self.master, text="点击间隔 t1(ms):").grid(row=4, column=0)
        self.t1_entry = tk.Entry(self.master)
        self.t1_entry.grid(row=4, column=1)
        tk.Label(self.master, text="搜索间隔 t2(ms):").grid(row=4, column=2)
        self.t2_entry = tk.Entry(self.master)
        self.t2_entry.grid(row=4, column=3)

        # 功能按钮
        tk.Button(self.master, text="校对", command=self.verify).grid(row=5, column=0)
        tk.Button(self.master, text="发放", command=self.send).grid(row=5, column=1)
        tk.Label(self.master, text="表格第一列必须是姓名", fg="red").grid(row=6, column=0, columnspan=3)

        #群发消息
        tk.Label(self.master, text="群发消息").grid(row=7, column=0)
        self.message_entry = tk.Entry(self.master)
        self.message_entry.grid(row=8, column=0, rowspan=6, columnspan=6)

    def select_excel(self):
        path = filedialog.askopenfilename(filetypes=[("Excel文件", "*.xlsx")])
        self.excel_path.delete(0, tk.END)
        self.excel_path.insert(0, path)

    def select_txt(self):
        path = filedialog.askopenfilename(filetypes=[("文本文件", "*.txt")])
        self.txt_path.delete(0, tk.END)
        self.txt_path.insert(0, path)

    def load_config(self):
        if os.path.exists(self.config_file):
            self.config.read(self.config_file)
            self.excel_path.insert(0, self.config.get('DEFAULT', 'excel_path', fallback=''))
            self.txt_path.insert(0, self.config.get('DEFAULT', 'txt_path', fallback=''))
            self.x_entry.insert(0, self.config.get('DEFAULT', 'x', fallback=''))
            self.y_entry.insert(0, self.config.get('DEFAULT', 'y', fallback=''))
            self.a_entry.insert(0, self.config.get('DEFAULT', 'a', fallback=''))
            self.b_entry.insert(0, self.config.get('DEFAULT', 'b', fallback=''))
            self.t1_entry.insert(0, self.config.get('DEFAULT', 't1', fallback='500'))
            self.t2_entry.insert(0, self.config.get('DEFAULT', 't2', fallback='1000'))
            self.message_entry.insert(0, self.config.get('DEFAULT', 'm', fallback='1000'))

    def save_config(self):
        self.config['DEFAULT'] = {
            'excel_path': self.excel_path.get(),
            'txt_path': self.txt_path.get(),
            'x': self.x_entry.get(),
            'y': self.y_entry.get(),
            'a': self.a_entry.get(),
            'b': self.b_entry.get(),
            't1': self.t1_entry.get(),
            't2': self.t2_entry.get(),
            'm':self.message_entry.get()
        }
        with open(self.config_file, 'w') as f:
            self.config.write(f)

    def verify(self):
        try:
            # 检查文件存在性
            if not os.path.exists(self.txt_path.get()):
                messagebox.showerror("错误", "名单文件不存在")
                return

            # 读取并保存名单
            with open(self.txt_path.get(), 'r', encoding='utf-8') as f:
                self.names = [re.sub(r"\s+", "",line) for line in f if re.sub(r"\s+", "",line) != "姓名"]

            if not self.names:
                messagebox.showerror("错误", "名单文件内容为空或格式错误")
                return

            # 读取Excel校验
            wb = openpyxl.load_workbook(self.excel_path.get())
            unknown = []

            for sheet in wb:
                for row in sheet.iter_rows(min_row=1):
                    first_cell = row[0].value
                    if not first_cell or not isinstance(first_cell, str):
                        continue
                    cleaned_name = re.sub(r"\s+", "",first_cell)
                    if cleaned_name == "姓名":
                        continue
                    if cleaned_name not in self.names:
                        unknown.append(cleaned_name)

            if unknown:
                with open("陌生姓名.txt", 'w', encoding='utf-8') as f:
                    f.write('\n'.join(unknown))
                messagebox.showwarning("发现陌生姓名", f"发现{len(unknown)}个陌生姓名,已保存到文件")
            else:
                messagebox.showinfo("校验通过", "所有姓名均符合名单")

        except Exception as e:
            messagebox.showerror("错误", f"校验过程中出错: {str(e)}")

    def send(self):
        try:
            # 参数校验
            if not all([self.x_entry.get(), self.y_entry.get(),
                        self.a_entry.get(), self.b_entry.get()]):
                messagebox.showerror("错误", "请填写所有坐标参数")
                return

            # 读取操作参数
            x, y = int(self.x_entry.get()), int(self.y_entry.get())
            a, b = int(self.a_entry.get()), int(self.b_entry.get())
            t1 = int(self.t1_entry.get()) / 1000
            t2 = int(self.t2_entry.get()) / 1000
            m = self.message_entry.get()

            # 二次读取名单保障
            if not self.names:
                with open(self.txt_path.get(), 'r', encoding='utf-8') as f:
                    self.names = [re.sub(r"\s+", "",line) for line in f if re.sub(r"\s+", "",line) != "姓名"]
                if not self.names:
                    messagebox.showerror("错误", "无法读取有效名单")
                    return

            # 重新打开Excel
            wb = openpyxl.load_workbook(self.excel_path.get())
            period = time.strftime("%Y年%m月")
            self.sent_names = []

            for sheet in wb:
                titlerow = None
                namerow = None

                for idx, row in enumerate(sheet.iter_rows(), 1):
                    first_cell = row[0].value
                    if not first_cell or not isinstance(first_cell, str):
                        continue

                    cleaned_name = re.sub(r"\s+", "",first_cell)

                    if cleaned_name == "姓名":
                        titlerow = idx
                    elif cleaned_name in self.names:
                        namerow = idx
                        # 模拟操作流程
                        try:
                            # 搜索联系人
                            pyautogui.click(x, y)
                            #pyautogui.write(cleaned_name)中文丢失
                            pyperclip.copy(cleaned_name)
                            pyautogui.hotkey('ctrl', 'v')  # 粘贴
                            time.sleep(0.1)  # 确保粘贴完成
                            time.sleep(t2)
                            pyautogui.press('enter')
                            time.sleep(t1)
                            # 生成并复制表格图片
                            copy_table_to_clipboard(sheet, titlerow, namerow)

                            # 粘贴工资条(示例逻辑)
                            pyautogui.click(a, b)
                            time.sleep(t1)
                            pyautogui.hotkey('ctrl', 'v')  # 粘贴
                            time.sleep(0.1)  # 确保粘贴完成
                            # 此处应添加实际截图和粘贴逻辑
                            time.sleep(0.02)
                            pyautogui.press('enter')
                            time.sleep(t2 / 2)

                            # 发送信息
                            message = f"{period}{sheet.title}请查收.{m}"
                            pyperclip.copy(message)
                            pyautogui.click(a, b)
                            pyautogui.hotkey('ctrl', 'v')  # 粘贴
                            time.sleep(0.1)  # 确保粘贴完成
                            pyautogui.press('enter')
                            time.sleep(t2/2)

                            self.sent_names.append(cleaned_name)
                        except Exception as e:
                            messagebox.showwarning("操作中断", f"发送给 {cleaned_name} 时出错: {str(e)}")
                            continue

            # 保存配置
            self.save_config()

            # 生成未发送名单
            unsent = list(set(self.names) - set(self.sent_names))
            if unsent:
                with open("未发放列表.txt", 'w', encoding='utf-8') as f:
                    f.write('\n'.join(unsent))
                messagebox.showinfo("完成", f"发放完成,{len(unsent)}人未发放")
            else:
                messagebox.showinfo("完成", "所有人员工资条已成功发放")

        except Exception as e:
            messagebox.showerror("错误", f"发放过程中出错: {str(e)}")


if __name__ == "__main__":
    root = tk.Tk()
    app = SalarySenderApp(root)
    root.mainloop()

END

你可能感兴趣的:(GPT教我写代码,gpt,python,开发语言)