LAN RPA SAP自动化工具

简介

  • SAP GUI 前台操作一次,即可完成SAP自动化批处理制作,支持alv录制
  • 支持部分录制,即不需要录制完整事务码,可以在当前窗口执行部分重复操作
  • 无需开发、配置、传输请求,如果操作熟练,就可以做到无需测试
  • 录制完成后自动生成excel模板,保存好模板未来可以在任何sap环境继续使用
  • 适用于SAP快捷登录、日常操作的简化、一次性数据的批处理、自动化测试

环境准备

下载LAN RPA

https://download.csdn.net/download/cylcylcylcylwo/88986702icon-default.png?t=N7T8https://download.csdn.net/download/cylcylcylcylwo/88986702

执行LAN_RPA.exe文件 

配置SAP账号密码

点击 连接-编辑,会打开一个excel

填入描述(必须和sap gui里填写的描述一致,区分大小写),客户端,用户名,语言,密码

 保存excel,点击 连接-重读,读取配置的sap连接

配置SAP服务器参数

TCODE RZ11,输入  sapgui/user_scripting 设置值为TRUE,这个参数是启动SAP服务器gui script的意思。服务器重启后会失效,可以RZ10改,RZ10改完服务器重启不会失效。

配置SAP GUI通知

gui-选项

把脚本通知的勾去掉,这个配置的作用是执行脚本时不会发出gui 通知

上手

窗口管理

选择配置好的SAP连接,点击 打开 ,即可快速打开连接,快速切换用户和语言

选中打开的会话,右键

双击:跳转到窗口,如果双击窗口没跳出来,再双击一次就可以了

新会话:打开新的窗口

关闭其他:关闭其他窗口

关闭全部:关闭全部窗口 

开始录制(以下demo都存在网盘的模板文件夹,有些是录屏示例,有些已经在生产使用了,可直接执行)

选中一个会话,点击 开始录制,跳转到sap窗口,在窗口输入事务码开始操作,操作结束之后点击 结束录制 即可完成制作。

注意工具只会根据输入的值生成模板,输入框的默认值如果要作为模板可输入字段,需要手工输入一下。

demo1 批量修改物料描述

选择一个系统,点击 打开 按钮,打开一个连接

打开后选中一个会话,点击 开始录制 ,此时会自动跳转到SAP窗口。(如果选中的会话不是初始的tcode,可以点 初始化 按钮,还原到开始菜单)

输入 mm02 ,回车

输入物料号(注意mm02进来可能默认有物料号,此时需要重新输入,不能直接默认不输入物料直接回车进去),回车

 选中视图,回车

修改描述,点击保存

操作完毕后点 结束录制

 此时会弹出一个文件保存框,输入文件名

保存后会自动打开一个excel模板文件

插入一行,填写描述(此步骤可选)

在 字段 页签,添加多行数据,其中doc_num表示条目数,如果有行项目,也通过doc_num把抬头和行项目关联

 在 表1 页签,添加多行数据(这里的表1是根据选择视图的弹出框自动生成的,作用是进来选中视图。如果视图位置不固定,可以在mm02保存默认视图,这次每次直接回车,不用再选视图)

 

保存excel,选中一个会话,点击 执行 按钮

此时会跳转到SAP窗口开始执行,执行完毕会自动打开excel,查看返回消息

demo2  批量下载alv报表

打开或者选择一个会话,点击 开始录制

输入tcode,输入选择条件,执行

右键下载

输入文件名称(注意必须输入,默认带出来的删掉输一次),点击生成(或如果文件已存在就点击替换)

点 结束录制

保存模板

 填一下选择条件,和导出的文件名称

保存后点击执行

 查看返回消息

demo3 CA01创建工艺路线

录制好的模板放在网盘的 demo 文件夹了,注意录制工序的时候录一行就好了

demo4 BP维护branch code

demo5 migo 批量创建物料凭证

demo6 批量冲销物料凭证

点击 开始录制,输入se37

点击 功能模块-执行-测试序列

输入 

BAPI_GOODSMVT_CANCEL 

BAPI_TRANSACTION_COMMIT 

点击执行

 输入物料凭证和年度,点击执行

 点击返回

再点执行

点击 结束录制,在弹出的excel填写数据

在 字段 页签填写需要冲销的物料凭证

在 表1 页签按doc_num复制数据

 保存excel,点击 执行

demo7 SM30 带输入条件 批量维护

正常录制,没什么特别的,模板在demo文件夹中

demo8 CS01创建BOM

demo9 va32 重新派生

录制的时候双击第一行行项目,在明细界面操作完之后,点击返回,然后点保存

在 字段 页签填入计划协议号

在 表1 页签填入行,用doc_num和和计划协议对应,要处理几行就复制几行

对于 科目分配 页签位置不固定 的解决方法:分场景录两个模板分别跑对应的数据,数据不分开用不同模板跑两次都可以,程序遇到找不到屏幕的情况会自动跳过不会卡住的

demo10 se38创建程序

以安装ZLAN_ACC为例,直接执行demo文件夹中的 se38安装zlan_acc.xlsx 即可

自动执行到这个界面,敲一下回车就完成安装了

 如果要自己录制,也正常从se38进去录制即可

demo11 su01增加角色与参数文件

关于alv录制,和表一样只录入一行即可,有两种做法,推荐第二种

1.点击插入行按钮,在excel的sheet“表”中填入多行,会重复执行插入行和填写alv单元格的操作,如果此操作导致alv错位,建议每次插入一行,保存好了再进来插入一行

2.在空白行输入,如果有多行数据,每次执行会在下一行输入。

如果进来alv已有多行数据,需要翻页才能到空白行,可以在录制前选中alv一列筛选为空的数据,然后保存用户的缺省布局,这样每次进来从第一行开始都是alv可输入的空白行了

demo12 co01 创建生产订单

demo13 spro配置ewm Map Document Types from ERP System to EWM

此模板是每次进入spro配置一条,可以优化成:用一个窗口先打开spro,展开节点到指定位置,只录制添加新条目和返回,执行的时候也从已经展开节点的窗口执行,提升效率

demo14 FS00创建科目

详细解释

配置

点击 配置-编辑

会打开配置文件

 

[general]
theme = yeti 主题
delay = 0 执行延时,如1则会等待1秒再执行下一步。
template_no_ask = False 录制结束时会根据填写的模板路径直接保存模板,不会弹出文件选择框
bar_total = False 是否在进度条显示总条目数和当前已完成的条目数
refresh_interval = 1000 会话自动刷新间隔。如果打开的窗口太多1秒可能刷新不完,在前台会看到卡顿,此时需要调大刷新间隔
log = False 是否输出日志,如果为True,则执行完之后会把日志输出到excel上

[path]
vbs = C:\Users\10042832\AppData\Roaming\SAP\SAP GUI\Scripts\SCRIPT6.VBS vbs文件位置
template = C:/Users/DELL/Desktop/temp/ca01.xlsx 模板位置

录制原理

  1. 根据输入的字段,生成对应excel的列。所以如果要录入字段有默认值,要做一次输入操作,不能直接回车跳过屏幕了
  2. 录制多行的表时只需要录制一行,如果要进入行明细界面操作,就双击录制的那一行,在明细界面操作完了之后点击 返回 按钮,不要直接保存,以返回判断双击操作结束了。
  3. 字段和表通过doc_num关联,表录制的时候录一行,如果要操作多行,在 表 页签复制doc_num为多行,即可按顺序操作表
  4. 录制的时候不要通过F4搜索帮助选择值,直接填入值,不要有无效操作,这样执行效率最高
  5. 录制过程如果想中断,点 结束录制,然后在弹出的文件选择框点 取消 就可以了。点 开始录制 开始一次新的录制,点 结束录制 结束一次录制,如果点了 结束录制 没有保存模板文件,可以继续点 结束录制 重复生成模板,不需要重复录制
  6. 尽量录复杂的场景,把可能弹出的屏幕都录到,如果屏幕多录了执行的时候找不到屏幕会自动跳过,不会卡住
  7. 如果多次操作同一笔单据,有可能保存好了sap的锁还没释放,再进入会报错被自己锁定,可以修改操作等待时间,delay = 1,则每次操作都等1秒,放在后台慢慢执行吧

版本

v1.0 

初始版本,后续考虑要不要录windows窗口和网页,以及多窗口的并发。有需求可以交流一下

v1.1

  1. 兼容了事务码录入了/n 的场景
  2. 增加了对abap编辑器的支持,直接粘贴代码吧,手敲代码的换行还没处理
  3. 增加了终止功能,可以在执行过程中中断了
  4. 修复了连接超时断开,会话没有刷新的问题

v1.2

网盘中只更新了exe文件,用exe文件覆盖之前下载的文件夹中的exe

  1. 修复读值漏了前导零的bug
  2. 增加了对alv的支持:参考demo11 su01增加角色与参数文件
    1.编辑alv单元格
    2.插入行

V1.3

  1. 增加对spro tree操作的支持(DEMO13)
  2. 忽略窗口最大化的操作(maximize),可以后台执行,不会因为SAP窗口最大化而在前台弹出

v1.4

  1. 修复了SAP下拉框没有匹配到excel值的bug(demo fsp0)
  2. 修复了SAP单选框没有匹配到excel值的bug(demo fs00)

v1.5

增加alv场景时,把table control换行逻辑改到了,修复table不能自动写到第二行的bug

v1.7 

处理了录制结束没点返回,也没点保存的表操作场景。如只在sm30界面操作数据,不从tcode进去,也没有保存

代码

import datetime
import sys
import configparser
# from math import ceil
import webbrowser

import openpyxl
import pyperclip
from openpyxl.utils import get_column_letter
import xlsxwriter
import json
import numpy as np
import re
import time
from operator import methodcaller

# import eventlet
import subprocess
import os
import tkinter as tk
from tkinter import ttk, font, messagebox, filedialog
from tkinter import *

import pandas as pd
import ttkbootstrap
from ttkbootstrap.constants import *
from ttkbootstrap import Style
import pythoncom
# import pypiwin32
import win32com
from win32com import client
from win32com.client import Dispatch

class lan_rpa:

    def __init__(self):
        # 布局
        self.v_excption = 'lan stop'
        self.v_column = 0
        self.v_pady = 3
        self.v_padx = (10, 0)

        # 初始化sapgui
        self.__sapgui__start()

        # 变量
        self.v_filename_script = 'Script6.vbs'
        self.v_input = {}
        self.v_con_sel = {'desc': '', 'client': '', 'user': '', 'langu': ''}
        self.v_path_connection = 'connection.xlsx'
        self.v_path_config = 'config.ini'

        # 读连接
        self.__connection_read(True)

        # 读配置
        self.v_config = configparser.ConfigParser()
        self.v_config.read('config.ini')

    def __tkinter_main(self):
        root = tk.Tk()
        self.v_root = root
        root.wm_title('LAN RPA v1.2')
        # root.iconphoto(False, tk.PhotoImage(file=self.__resource_path('lan.png')))
        root.iconphoto(False, tk.PhotoImage(file='lan.png'))
        ttkbootstrap.Style().theme_use(self.v_config['general']['theme'])

        # 刷新事件
        root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)

        # 布局
        frame1 = tk.Frame(root)  # 连接选择
        self.v_frame1 = frame1
        frame2 = tk.Frame(root)  # 会话列表+资源列表
        # paned = tk.PanedWindow(frame2, orient=tk.HORIZONTAL)  # 默认是左右分布的
        # paned.pack(fill=tk.BOTH, expand=1)  # 放到主窗口,上下左右缩放
        # frame_session = tk.Frame(paned)  # 会话列表
        # paned.add(frame_session)
        # frame_explorer = tk.Frame(paned)  # 资源列表
        # paned.add(frame_explorer)
        frame3 = tk.Frame(root)
        frame4 = tk.Frame(root)
        frame1.grid(row=0, column=0)
        frame2.grid(row=1, column=0, sticky='w', padx=self.v_padx, pady=10)
        frame3.grid(row=2, column=0, sticky='w', pady=10)
        frame4.grid(row=3, column=0, sticky='w', pady=10)

        # 菜单
        self.__menubar(root)

        # 连接选择
        self.__tkinter_frame_con()
        # 会话
        # self.__tkinter_frame_session(frame_session)
        self.__tkinter_frame_session(frame2)
        # # 资源浏览器
        # self.__tkinter_frame_explorer(frame_explorer)

        # 收藏列表
        # self.__tkinter_frame_favorite(frame3, root)
        self.__tkinter_frame_record(frame3)
        # 模板文件
        self.__tkinter_frame_file(frame4)

        # root.attributes('-topmost', True) #置顶

        # 居中显示
        dialog_width = 600
        dialog_height = 400
        # root.geometry('600x450')
        self.__window_geometry(dialog_width, dialog_height, root)
        root.mainloop()

    def __window_geometry(self, dialog_width, dialog_height, root):
        screenwidth = root.winfo_screenwidth()
        screenheight = root.winfo_screenheight()
        root.geometry("%dx%d+%d+%d" % (
            dialog_width, dialog_height, (screenwidth - dialog_width) / 2, (screenheight - dialog_height) / 2))

    def __tkinter_frame_record(self, frame3):
        # ttk.Button(frame3, text='刷新', command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
        #                                                                    padx=self.v_padx)
        # tk.Button(frame3, text="刷新", command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
        #                                                                   padx=self.v_padx)
        tk.Button(frame3, text="初始化", command=self.__session_init).grid(row=0, column=self.__column_next(0),
                                                                        padx=self.v_padx)
        tk.Button(frame3, text="开始录制", command=self.__record_start).grid(row=0, column=self.__column_next(),
                                                                         padx=self.v_padx)
        tk.Button(frame3, text="结束录制", command=self.__record_end).grid(row=0, column=self.__column_next(),
                                                                       padx=self.v_padx)
        frame_progressbar = tk.Frame(frame3)
        frame_progressbar.grid(row=0, column=self.__column_next(), padx=self.v_padx)
        self.v_progress_bar = ttk.Progressbar(frame_progressbar, length='100', mode="determinate", orient=tk.HORIZONTAL)
        self.v_progress_bar.grid(row=0)
        self.v_progress_count = tk.Label(frame_progressbar, text='')
        if self.v_config['general']['bar_total'] == 'True':
            self.v_progress_count.grid(row=1)
        self.v_progress_remain = tk.Label(frame3, text='')
        self.v_progress_remain.grid(row=0, column=self.__column_next())

    def __menubar(self, root):
        menubar = tk.Menu(root)
        menu_connection = tk.Menu(menubar)
        menu_config = tk.Menu(menubar)
        menu_about = tk.Menu(menubar)

        menu_connection.add_command(label='编辑', command=self.__connection_edit)
        menu_connection.add_command(label='重读', command=self.__connection_read)

        menu_config.add_command(label='主题', command=self.__win_theme)
        menu_config.add_command(label='编辑', command=self.__config_edit)
        menu_config.add_command(label='重读', command=self.__config_read)

        menu_about.add_command(label='帮助', command=self.__about_document)
        menu_about.add_command(label='交流', command=self.__about_conmunicate)  # 777363609

        menubar.add_cascade(label='连接', menu=menu_connection)
        menubar.add_cascade(label='配置', menu=menu_config)
        menubar.add_cascade(label='关于', menu=menu_about)
        root.config(menu=menubar)

    def __tkinter_frame_session(self, frame_session):
        # session info list treeview
        col_text = ('id', 'SID', '客户端', 'TCODE', '用户', '语言')
        col_name = ('id', 'sid', 'client', 'tcode', 'user', 'langu')
        col_width = ('50', '50', '50', '180', '110', '80')
        self.v_column_len = len(col_name)  # 记下tv展示的列,因为SystemSessionId和SessionNumber在后面两列
        xbar = ttk.Scrollbar(frame_session, orient='horizontal')  # 水平滚动条
        ybar = ttk.Scrollbar(frame_session, orient='vertical')  # 垂直滚动条
        xbar.pack(side=BOTTOM, fill=X)
        ybar.pack(side=RIGHT, fill=Y)
        tv = ttk.Treeview(frame_session, column=col_name, yscrollcommand=ybar.set, xscrollcommand=xbar.set,
                          show='headings')  # 隐藏首列
        self.v_tv_session = tv
        for i in range(len(col_text)):
            tv.column(col_name[i], width=col_width[i], anchor='n')
            tv.heading(col_name[i], text=col_text[i])
        ybar['command'] = self.v_tv_session.yview
        xbar['command'] = self.v_tv_session.xview
        tv.pack()

        # 数据
        # self.__session_refresh()
        # 右键菜单
        self.v_right_click_menu = tk.Menu(frame_session, tearoff=0)
        self.v_right_click_menu.add_command(label='新会话', command=self.__session_create)
        self.v_right_click_menu.add_command(label='关闭其他', command=self.__session_close_other)
        self.v_right_click_menu.add_command(label='关闭全部', command=self.__session_close_all)
        # self.v_right_click_menu.add_command(label='关闭后面', command=self.__session_close_back)
        # 绑定事件
        tv.bind('<Double-1>', self.__session_JumpForward)  # 双击左键跳转窗口
        tv.bind('<Button-3>', self.__session_right_click)  # 右键菜单

    # def __tkinter_connection_config(self):
    #     self.v_tkt_SAPConnectionConfig = tkt_SAPConnectionConfig()
    #     self.v_tkt_SAPConnectionConfig.open()

    def main(self):

        # self.__sapgui_init()

        # session处理
        # close_all()
        # return

        # logoin = self.login_init()

        self.__tkinter_main()

        return

    def __session_close_other(self):
        session_info = self.__session_info_selected()
        for connection in self.v_application.Connections:
            for session in connection.Sessions:
                if session.Busy == False:
                    if session.Info.SystemSessionId != session_info.SystemSessionId:
                        session.StartTransaction('ex')
                        break
                    if session.Info.SessionNumber != session_info.SessionNumber:
                        session.findById("wnd[0]").close()
                        continue
        # self.__session_refresh()

    def __session_close_all(self):
        for connection in self.v_application.Connections:
            for session in connection.Sessions:
                if session.Busy == False:
                    session.StartTransaction('ex')
                    break
        # self.__session_refresh()

    def __handle_connection_create(self):
        self.__connection_create(self.v_input['desc'].get(), self.v_input['client'].get(),
                                 self.v_input['user'].get(),
                                 self.v_input['password'], self.v_input['langu'].get())

        # self.__session_refresh()

    def __session_create(self):
        session = self.__session_selected()
        session.CreateSession()

    def __session_refresh(self):
        session_info_sel = self.__session_info_selected()  # 在刷新tv数据之前先取到选中的行,为了刷新之后重新选中会话

        self.v_session_info_list = self.__session_info_list_get()
        i = 1
        data_list = []
        if self.v_session_info_list:
            for session_info in self.v_session_info_list:
                try:
                    data_obj = (str(i), session_info.SystemName, session_info.Client, session_info.Transaction,
                                session_info.User,
                                session_info.Language,
                                session_info.SystemSessionId, session_info.SessionNumber)
                    data_list.append(data_obj)
                    i = i + 1
                except Exception as e:  # 可能窗口关闭,连接断开了
                    continue

        self.v_tv_session.delete(*self.v_tv_session.get_children())
        for i in range(len(data_list)):
            self.v_tv_session.insert('', 'end', values=data_list[i])

        # 选中会话
        try:
            for child in self.v_tv_session.get_children():
                if session_info_sel.SystemSessionId == self.v_tv_session.item(child)['values'][self.v_column_len] \
                        and session_info_sel.SessionNumber == self.v_tv_session.item(child)['values'][
                    self.v_column_len + 1]:
                    self.v_tv_session.selection_set(child)
                    break
        except Exception as e:
            if self.v_tv_session.get_children():
                self.v_tv_session.selection_set(self.v_tv_session.get_children()[0])  # 默认选中第一个窗口

        # 继续刷新
        self.v_root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)

    def __on_focus_in(self, event):
        pass
        # self.__session_list_refresh()

    def __session_right_click(self, event):
        row_id = self.v_tv_session.identify_row(event.y)
        if row_id:
            self.v_tv_session.selection_set(row_id)
            self.v_right_click_menu.post(event.x_root, event.y_root)

    def __session_JumpForward(self, event):
        session = self.__session_selected()
        if session:
            session.findById("wnd[0]").Visualize(True)
            session.findById("wnd[0]").JumpForward()
            return session

    def __session_selected(self):
        session_info = self.__session_info_selected()
        try:  # 如果session关闭了,session_info.就会异常
            session = self.__session_get(session_info.SystemSessionId, session_info.SessionNumber)
            return session
        except Exception as e:
            messagebox.showinfo('选择会话', '当前会话繁忙或已关闭:' + str(e))

    def __session_info_selected(self):
        item_text = None
        for item in self.v_tv_session.selection():  # 这里values全部转成str!!
            item_text = self.v_tv_session.item(item, "values")
        if item_text:
            for session_info in self.v_session_info_list:
                try:
                    if session_info.SystemSessionId == item_text[self.v_column_len] and str(
                            session_info.SessionNumber) == \
                            item_text[self.v_column_len + 1]:
                        return session_info
                except Exception as e:
                    continue

    def __tkinter_frame_con(self):
        # Input控件
        self.v_con_sel['desc'] = tk.StringVar()
        self.v_input['desc'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['desc'], state='readonly',
                                            width='15')
        self.v_con_sel['client'] = tk.StringVar()
        self.v_input['client'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['client'],
                                              state='readonly',
                                              width='3')
        self.v_con_sel['user'] = tk.StringVar()
        self.v_input['user'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['user'], state='readonly',
                                            width='12')
        self.v_con_sel['langu'] = tk.StringVar()
        self.v_input['langu'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['langu'], state='readonly',
                                             width='5')

        # 布局
        tk.Label(self.v_frame1, text='描述').grid(row=0, column=0, padx=self.v_padx)
        self.v_input['desc'].grid(row=0, column=1)
        tk.Label(self.v_frame1, text='客户端').grid(row=0, column=2, padx=self.v_padx)
        self.v_input['client'].grid(row=0, column=3)
        tk.Label(self.v_frame1, text='用户').grid(row=0, column=4, padx=self.v_padx)
        self.v_input['user'].grid(row=0, column=5)
        tk.Label(self.v_frame1, text='语言').grid(row=0, column=6, padx=self.v_padx)
        self.v_input['langu'].grid(row=0, column=7)

        # 设置连接值
        self.__con_sel_refresh()

        # 绑定事件:下拉框列表值
        self.v_input['desc'].bind("<<ComboboxSelected>>", self.__input_selected_desc)
        self.v_input['client'].bind("<<ComboboxSelected>>", self.__input_selected_client)
        self.v_input['user'].bind("<<ComboboxSelected>>", self.__input_selected_user)

        ttk.Button(self.v_frame1, text='打开', command=self.__handle_connection_create).grid(row=0, column=8,
                                                                                           padx=self.v_padx,
                                                                                           pady=10, )

    def __con_sel_refresh(self):
        self.v_input['desc']['value'] = list(self.v_con_list.keys())
        self.v_input['desc'].current(0)  # 设置默认值
        self.__input_selected_desc(None)

    def __input_selected_desc(self, event):
        self.v_input['client'].delete(0, tk.END)
        self.v_input['client']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()].keys())
        self.v_input['client'].current(0)  # 设置默认值
        self.__input_selected_client(event)
        self.__input_selected_user(event)

    def __input_selected_client(self, event):
        self.v_input['user'].delete(0, tk.END)
        self.v_input['user']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
                                             [self.v_con_sel['client'].get()].keys())
        self.v_input['user'].current(0)  # 设置默认值
        self.__input_selected_user(event)

    def __input_selected_user(self, event):
        self.v_input['langu'].delete(0, tk.END)
        self.v_input['langu']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
                                              [self.v_con_sel['client'].get()][self.v_con_sel['user'].get()].keys())
        self.v_input['langu'].current(0)  # 设置默认值
        self.v_input['password'] = \
            self.v_con_list[self.v_con_sel['desc'].get()][self.v_con_sel['client'].get()][
                self.v_con_sel['user'].get()][
                self.v_con_sel['langu'].get()]

    def __filedialog_select_vbs(self):
        (filepath, tempfilename) = os.path.split(self.v_config['path']['vbs'])
        selected_file_path = filedialog.askopenfilename(initialdir=filepath,
                                                        filetypes=[('脚本', '*.vbs')])  # 使用askopenfilename函数选择单个文件
        if selected_file_path:
            self.v_config['path']['vbs'] = selected_file_path
            self.__config_write()
            self.v_stringvar_vbs.set(selected_file_path)

    def __filedialog_select_template(self):
        (filepath, tempfilename) = os.path.split(self.v_config['path']['template'])
        selected_file_path = filedialog.askopenfilename(initialdir=filepath,
                                                        filetypes=[('模板', '*.xlsx')])  # 使用askopenfilename函数选择单个文件
        if selected_file_path:
            self.v_config['path']['template'] = selected_file_path
            self.__config_write()
            self.v_stringvar_template.set(selected_file_path)

    def __tkinter_frame_file(self, frame3):
        v_width_input = 55

        self.v_stringvar_vbs = tk.StringVar()
        self.v_stringvar_template = tk.StringVar()
        self.__refresh_file()
        # 布局控件
        # tk.Label(frame3, text="脚本").grid(column=0, row=0, pady=v_pady)
        # tk.Entry(frame3, textvariable=self.v_stringvar_vbs, width=v_width_input).grid(column=1, row=0)
        # tk.Button(frame3, text="选择", command=self.__filedialog_select_vbs).grid(row=0, column=2)
        # tk.Button(frame3, text="解析", command=self.__file_explain).grid(row=0, column=3, padx=self.v_padx)
        tk.Label(frame3, text="模板").grid(column=0, row=1, pady=self.v_pady, padx=self.v_padx)
        tk.Entry(frame3, textvariable=self.v_stringvar_template, width=v_width_input).grid(column=1, row=1)
        tk.Button(frame3, text="选择", command=self.__filedialog_select_template).grid(row=1, column=2)
        tk.Button(frame3, text="打开", command=self.__file_open_template).grid(row=1, column=3, padx=self.v_padx)
        tk.Button(frame3, text="执行", command=self.__button_file_execute).grid(row=1, column=4, padx=self.v_padx)
        # button_exec = tk.Button(frame3, text="执行")
        # button_exec.after(1,self.__button_file_execute)
        # button_exec.grid(row=1, column=4, padx=self.v_padx)
        tk.Button(frame3, text="终止", command=self.__button_stop).grid(row=1, column=5, padx=self.v_padx)

    def __refresh_file(self):
        # self.v_stringvar_vbs.set(self.v_config['path']['vbs'])
        self.v_stringvar_template.set(self.v_config['path']['template'])

    # def __config_get(self, key):
    #     try:
    #         return self.v_config.loc[self.v_config['key'] == key, 'value'].iloc[0]
    #     except Exception as e:
    #         return ''

    def __file_close(self, file):
        xlApp = Dispatch('Excel.Application')
        try:
            xlApp.DisplayAlerts = False  # 设置不显示警告和消息框
        except Exception as e:
            messagebox.showerror('关闭excel', e)
            return
        # xlBook = xlApp.Workbooks.Open(file)
        workbooks_n = xlApp.Workbooks.Count
        # print(f'已打开工作簿的数量为:{workbooks_n}个')

        if workbooks_n < 0: return

        for i in range(1, workbooks_n + 1):  # 工作簿索引从1开始
            path_ = xlApp.Workbooks(i).Path
            name_ = xlApp.Workbooks(i).Name
            file_ = os.path.join(path_, name_)
            # path = path_ + "\\backup_" + name_
            # print(f'第{i}个excel的文件路径为:{path}')
            if file == "/".join(file_.split("\\")):  # 转义的\\替换回/
                xlApp.Workbooks(i).Close()  # 关闭当前打开的文件,不保存文件
                # xlApp.Workbooks(i).Activate()
                # # xlApp.Workbooks(i).SaveAs(path)
                # xlApp.Workbooks(path).Close()

        # xlApp.Quit()  #关闭所有打开的excel文件
        del xlApp

    def __file_open_template(self):
        try:
            os.startfile(self.v_stringvar_template.get())
        except Exception as e:
            messagebox.showerror('打开文件', e)

    def __file_execute(self):
        self.v_stop = True
        if not self.v_stringvar_template.get():
            messagebox.showerror('文件不能为空')
            return
        self.v_config['path']['template'] = self.v_stringvar_template.get()
        self.__config_write()

        # self.v_root.state('icon')  # 最小化
        oper_start = 0
        oper_end = 0
        session = self.__session_JumpForward(None)
        if not session:
            return
        self.__file_close(self.v_stringvar_template.get())  # 先关闭,再回写消息,再打开
        dict = pd.read_excel(self.v_stringvar_template.get(), sheet_name=None,dtype='str') #str防止001.1这样的被当时float

        # 循环操作
        tabix = -1
        total = len(dict['字段']['doc_num'])
        start_time = time.time()  # 记录程序开始运行时间
        for doc_num in dict['字段']['doc_num']:
            if not self.v_stop:
                break
            path_t = ''
            log = ''
            tabix = tabix + 1
            self.v_root.update()
            if str(doc_num) == 'nan':
                continue
            table_entry = False
            for i in range(len(dict['操作']['path'])):
                attr, equal, path, value = self.__file_oper_get(dict, i)
                table_entry = self.__table_entry_begin(dict['操作'], i, attr, value,
                                                       table_entry)  # 要传上一个table的Path,因为此时是win0
                table_flag = self.__file_path_check_table(path)
                alv_flag = self.__check_alv(attr)
                if alv_flag:
                    table_flag = True
                if not table_flag and not table_entry:  # 字段:也可能不是字段,比如win0
                    if oper_start != 0:  # 记录了表开始,现在回到了字段,说明表结束了
                        oper_end = i - 1
                    if attr == 'text':  # 避免focus和double click等匹配到了值(或者参考表哪里用 = 判断?)
                        for key in dict['字段']:
                            if key == path + '+' + attr:
                                value = dict['字段'].loc[dict['字段']['doc_num'] == doc_num, key].iloc[
                                    0]  # dict.loc根据行+列返回一个列的series,取series第一个值
                                if re.findall('/okcd', path):
                                    if value[:2] != '/n' and value[:2] != '/N':
                                        value = '/n' + value
                                break
                    elif attr == 'insertText':
                        for key in dict['代码']:
                            if key == value:  # 在 操作 页签的值记录了插入代码的位置
                                value_oper = value
                                if len(dict['代码'][key]) > 1:
                                    value = '\n\r'.join(dict['代码'][key]) + ',' + value_oper
                                else:
                                    value = str(dict['代码'][key][0]) + ',' + value_oper
                                break
                else:  # 表:第一行正常循环,多行后面处理
                    if oper_start == 0:
                        oper_start = i
                    # table = path.split(',')[0]
                    for key in dict:  # 循环每个sheet匹配表字段
                        if re.findall('表', key):
                            for key2 in dict[key]:  # 循环取列名
                                if key2 == path.split(',')[0] + '+' + attr:  # 匹配一次列名就定位到表了
                                    row_doc_tab = len(dict[key].loc[dict[key]['doc_num'] == doc_num, 'doc_num'])
                                    sheet_tbl = key
                                    break
                            else:  # 里面break了则触发外面break跳出第二层循环
                                continue
                            break

                # 开始执行
                if oper_start != 0 and oper_end != 0:  # 表:循环完了,开始一次性执行表全部条目
                    pagesize = 0
                    row_current = 0
                    for x in range(row_doc_tab):  # 条目数
                        # 翻页:规定表和表不关联,处理完表1再处理表2,所以在表条目维度翻页就可以
                        attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_start)
                        if self.__file_path_check_table(path_t):  # 进了表才考虑定位行
                            if 'getAbsoluteRow' in attr_t:  # 选表行
                                row_start = int(attr_t.split('(')[1].strip(')')[0])  # 选中绝对行要不要翻页的?
                                path_tc = path_t
                            else:
                                row_start = int(path_t.split(',')[1].strip(']')[0])  # 开始录制的表行(可能不是从第一行开始的)
                                path_tc = re.findall(r"(.+)/", path_t)[0]
                            if pagesize == 0:
                                pagesize = session.findById(path_tc).verticalScrollbar.pagesize
                            if not row_current:
                                row_current = row_start

                            # div_mod = divmod(row_current, pagesize)
                            # if div_mod[0] != 0 and div_mod[1] == 0:  # 翻页:用翻页按钮是因为发现CA01不填第二行无法定位到第二行
                            if row_current == pagesize:  # 翻页
                                log_r = self.__script_exec('sendVKey', '', session.findById("wnd[0]"), '82', "wnd[0]")
                                if log_r:
                                    log = log + log_r
                                row_current = row_start
                        for oper_now in range(oper_start, oper_end + 1):  # range不包含上限
                            attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_now)

                            if self.__file_path_check_table(path_t):
                                if 'getAbsoluteRow' in attr_t:  # 选表行
                                    attr_t = attr_t.split('(')[0] + '(' + str(row_current) + ')' + attr_t.split(')')[
                                        1]  # path_t就是tc的位置不用处理,但是行在attr上
                                elif 'modifyCell' in attr_t or 'insertRows':  #修改alv单元格
                                    pass
                                else:
                                    path_t = path_t.split(',')[0] + ',' + str(row_current) + ']'
                            try:
                                obj_t = session.findById(path_t)
                            except Exception as e:
                                pass
                            for key2 in dict[sheet_tbl]:  # 循环取列名
                                if key2 == path_t.split(',')[0] + '+' + attr_t \
                                        or key2 == path_t + '+' + attr_t:  # 选中行
                                    break
                            if equal_t == 'X':  # 有=号才是可输入字段
                                value_t = dict[sheet_tbl].loc[dict[sheet_tbl]['doc_num'] == doc_num, key2].iloc[
                                    x]  # 取series第x个值
                            elif path_t == 'wnd[0]' or attr_t == 'insertRows':  # 双击/ alv插入行
                                value_t = value_t
                            elif attr_t == 'modifyCell':
                                value_t = [row_current,re.findall(r"\"(.+?)\"", value_t.rsplit(',',2)[1])[0],dict[sheet_tbl].loc[dict[sheet_tbl]['doc_num'] == doc_num, key2].iloc[
                                    x]] #行/列名/值
                            else:
                                value_t = 'nan'
                            log_r = self.__script_exec(attr_t, equal_t, obj_t, value_t, path_t)
                            if log_r:
                                log = log + log_r
                        row_current = row_current + 1
                    oper_start = 0
                    oper_end = 0

                if oper_start == 0:  # 非表直接执行
                    try:
                        obj = session.findById(path)  # 到执行的时候才可以取,因为可能前面在执行表
                    except Exception as e:
                        pass
                    log_r = self.__script_exec(attr, equal, obj, value, path)
                    if log_r:
                        log = log + log_r
                # 判断表的双击是否返回或者保存了
                table_entry = self.__table_entry_end(path, attr, value, table_entry)
            # 回写消息
            msg_type = session.findById('wnd[0]/sbar').MessageType
            msg_text = session.findById('wnd[0]/sbar').Text
            dict['字段'].loc[
                tabix, 'msg_type'] = msg_type  # 正确改值方式,否则:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
            dict['字段'].loc[tabix, 'msg_text'] = msg_text
            if self.v_config['general']['log'] == 'True':
                dict['字段'].loc[tabix, 'log'] = log
            end_time = time.time()  # 记录程序结束运行时间
            self.v_progress_bar['value'] = (tabix + 1) / total * 100
            self.v_progress_count['text'] = str(tabix + 1) + '/' + str(total)
            self.v_progress_remain['text'] = '剩余时间:' + str(
                datetime.timedelta(
                    seconds=int((end_time - start_time) / (tabix + 1) * (total - (tabix + 1)))))  # int向下取整
        try:  # 只覆盖一个sheet 只有openpyxl有 a mode
            with pd.ExcelWriter(self.v_stringvar_template.get(), mode="a", engine="openpyxl",
                                if_sheet_exists="replace", ) as writer:
                pd.DataFrame(dict['字段']).to_excel(writer, sheet_name="字段", index=False)
                # 按列遍历:单元格改回文本格式
                for x in range(1, writer.sheets['字段'].max_row + 1):  # 先改有文本的单元格,再改列,直接改列不起作用
                    for y in range(0, writer.sheets['字段'].max_column):
                        writer.sheets['字段'][x][y].number_format = '@'
                for i in range(1, writer.sheets['字段'].max_column + 1):
                    writer.sheets['字段'].column_dimensions[get_column_letter(i)].number_format = '@'
        except Exception as e:
            messagebox.showerror('消息回写excel出错', e)

        self.__file_open_template()

    def __script_exec(self, attr, equal, obj, value, path):  # path可选,用于报错,可以优化成传入path和session
        value = str(value)  # 避免1.0被当做float丢失.0了
        if attr == 'ResizeWorkingPane':
            return
        elif 'getAbsoluteRow' in attr:
            attr_1 = attr.split('(')[0]
            value_1 = attr.split('(')[1].split(')')[0]
            obj = getattr(obj, attr_1)(value_1)  # 动态调用只能分两步:先取table row,再设置为True
            attr = attr.split('.')[1]

        time.sleep(int(self.v_config['general']['delay']))
        try:
            # if str(equal) != 'nan':
            if str(equal) == 'X':
                setattr(obj, attr, value)  # 设置属性值
            else:
                if str(value) == 'nan':
                    getattr(obj, attr)()  # 直接调用方法
                else:
                    if obj.type == 'GuiShell':  # 如果是abap编辑器则传入多个参数
                        if attr == 'insertText':  # 代码直接转tuple可能有特殊字符
                            getattr(obj, attr)(value.rsplit(',',2)[0], int(value.rsplit(',',2)[1]),int(value.rsplit(',',2)[2]))
                        elif attr == 'insertRows':
                            getattr(obj, attr)(value)  # 调用方法传参
                        else:
                            value = eval(value)  # 转成tuple
                            getattr(obj, attr)(*value)  # 加* tuple变参数
                    else:
                        getattr(obj, attr)(value)  # 调用方法传参
        except Exception as e:
            return str(e) + str(path) + ':' + str(attr) + ':' + str(value) + ';'

    def __file_oper_get(self, df, i):
        path = df['操作']['path'][i]
        attr = df['操作']['attr'][i]
        equal = df['操作']['equal'][i]
        value = df['操作']['value'][i]
        if attr == 'insertText' or attr == 'modifyCell':  # 如果是代码则不去掉引号
            pass
        else:
            value_n = re.findall(r"\"(.+?)\"", str(value))  # 去掉引号
            if value_n:
                value = value_n[0]
        return attr, equal, path, value

    def __file_explain(self):
        # v_len_max = 30000 #excel单元格最大字符数 32000

        if self.v_config['general']['template_no_ask'] == 'False':  # 是否问模板路径
            self.v_path = self.__filedialog_create_template()
            if self.v_path:
                self.v_stringvar_template.set(self.v_path)
            else:
                return
        operation_list = []
        try:
            with open(self.v_path_script, encoding='UTF-16') as f:  # 代码保存出来就是UTF-16不知道为啥
                for line in f.readlines():  # readlines(),函数把所有的行都读取进来;
                    line = line.strip()  # 删除行后的换行符,img_file 就是每行的内容啦
                    path = re.findall(r"session.findById\(\"(.+?)\"\).", line)  # (" ").
                    if not path:
                        continue
                    attr = re.findall(r"\).(.+?) ", line)  # 测试空格不用转义
                    if not attr:  # 没有值的情况,比如setFocus
                        attr = re.findall('(?<=\).).*$', line)
                    value = re.findall('(?<=\s).*$', line)  # \s空格 取空格右边全部
                    equal = ''
                    if value:
                        # equal = re.search('=', value[0])  # 值中找=号
                        # if equal:
                        if value[0][:1] == '=':  # 如果第一位是等于号(值中可能也有=号)
                            equal = 'X'
                            value = re.findall('(?<=\s).*$', value[0])  # \s空格 取空格右边全部
                    path = self.__array_get_first(path)
                    attr = self.__array_get_first(attr)
                    value = self.__array_get_first(value)
                    operation = (path, attr, equal, value)
                    operation_list.append(operation)
        except Exception as e:
            messagebox.showerror('解析文件', e)
            return
        dict_field = {}
        dict_tables = {}
        dict_code = {}  # 代码
        dict_operation = {'path': {}, 'attr': {}, 'equal': {}, 'value': {}}

        # 开始解析操作
        i = -1
        table_entry = False
        df_operation_list = pd.DataFrame(operation_list)
        df_operation_list.columns = ['path', 'attr', 'equal', 'value']
        for oper in operation_list:
            i = i + 1
            path = oper[0]
            attr = oper[1]
            equal = oper[2]
            value = oper[3]
            path_t = ''  # 上一个表的path
            if attr == 'caretPosition' or attr == 'verticalScrollbar.position' or attr == 'currentCellColumn':
                continue
            elif attr == 'insertText':  # 代码:先写 代码 页签,再写操作
                value_code = value.rsplit(",", 2)[0]  # 从右向左按 , 拆两次
                code_list = value_code.split(' + vbCr + "" + vbLf + ')  # 根据换行符号拆开
                for x in range(len(code_list)):
                    code_list[x] = code_list[x][1:len(code_list[x]) - 1]  # 去掉首尾引号
                dict_code[value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[
                    2]] = code_list  # 录制的脚本中文会变成?,需要录完了在excel自己覆盖一下数据
                value = value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[2]  # 如果插入代码,则把位置写到操作页签,把值写入代码页签
            dict_operation['path'][i] = path
            dict_operation['attr'][i] = attr
            dict_operation['equal'][i] = equal
            dict_operation['value'][i] = value

            # 判断表的双击是否跳转屏幕了
            # oper_next = operation_list[i + 1]
            table_entry = self.__table_entry_begin(df_operation_list, i, attr, value, table_entry)

            table_flag = self.__file_path_check_table(path)
            alv_flag = self.__check_alv(attr)
            if alv_flag: #alv跟表一样处理
                table_flag = True
            if table_flag:  # 如果是跳转的就沿用上一个表名,所以没有第一个表没有填值,只是双击的情况也需要记录表名
                if 'getAbsoluteRow' in attr:  # 选表行
                    table = path
                    tabix = re.findall(r"\((.+?)\)", attr)[0]
                else:  # 表字段
                    if not alv_flag: #table control
                        table = re.findall(r"(.+)/", path)[0]  # 贪婪匹配:从整个字符去掉最后一个匹配
                        tabix = re.findall(r",(.+?)\]", path)[0]
                    else: #alv
                        table = path
                        tabix = value_code = value.rsplit(",", 2)[0]  # 从右向左按 , 拆两次,第一个值为行

            if equal == 'X' or attr == 'setFocus' or attr == 'modifyCell':  # setFocus必须要有,有些表没有输入直接双击的,不然匹配不到表
                if alv_flag:
                    value = value.rsplit(',',2)[2]
                quotes = []
                quotes = re.findall(r"\"(.+?)\"", value)  # 值中找引号
                if quotes:
                    value = re.findall(r"\"(.+?)\"", value)[0]
                elif '"' in value:  # 如果引号中间是空,正则也是找不到的
                    value = ''

                if table_flag or table_entry:  # 当前为表或者是表双击跳转的屏幕
                    if table_flag and not alv_flag:  # 表上还是要拆分行,只保留列;alv列不写入path了,直接读operation的value取到列
                        path = path.split(',')[0]
                    if not dict_tables.__contains__(table):
                        dict_tables[table] = {}
                    if not dict_tables[table].__contains__(path + '+' + attr):
                        dict_tables[table][path + '+' + attr] = {}
                    dict_tables[table][path + '+' + attr][tabix] = value
                else:  # 字段
                    dict_field[path + '+' + attr] = {0: value}  # 一个path可能有多个属性操作,拼接起来作为字段名

                # 判断表的双击是否返回或者保存了
            table_entry = self.__table_entry_end(path, attr, value, table_entry)

        dict_field['msg_type'] = {0: ''}
        dict_field['msg_text'] = {0: ''}
        dict_field['log'] = {0: ''}

        # 写入excel
        self.__file_close(self.v_stringvar_template.get())  # 先关闭,避免写入的时候占用了
        excel_colum = []
        try:  # with就不用再save了
            with pd.ExcelWriter(self.v_stringvar_template.get(),
                                engine='xlsxwriter') as writer:  # xlsxwriter才能写格式,但是xlsxwriter只能写不能读(所以不能修改)
                df1 = pd.DataFrame(dict_field)
                df1.insert(0, 'doc_num', 1)
                df1.to_excel(writer, sheet_name='字段', index=False)  # 不要第一列的序号
                excel_colum.append(len(df1.keys()))
                i = 0
                for key in dict_tables.keys():
                    i = i + 1
                    dict_table = dict_tables[key]
                    df2 = pd.DataFrame(dict_table)
                    df2.insert(0, 'doc_num', 1)
                    df2.to_excel(writer, sheet_name='表' + str(i), index=False)
                    excel_colum.append(len(dict_table.keys()))
                # df_code = pd.DataFrame(dict_code)
                df_code = pd.DataFrame(pd.DataFrame.from_dict(dict_code, orient='index').values.T,
                                       columns=list(dict_code.keys()))  # dict_code列长度可能不一样需要特殊处理
                df_code.to_excel(writer, sheet_name='代码', na_rep='', index=False)
                excel_colum.append(len(df_code.keys()))
                df3 = pd.DataFrame(dict_operation)
                df3.to_excel(writer, sheet_name='操作', na_rep='', index=False)  # na_rep 缺失数据表示
                excel_colum.append(len(df3.keys()))

                # 设置单元格格式为文本
                workbook = writer.book
                format_text = workbook.add_format({'num_format': '@'})
                i = -1
                for sheet_name in writer.sheets:
                    i = i + 1
                    writer.sheets[sheet_name].set_column(0, excel_colum[i] - 1, None, format_text)  # 不设置宽度
        except Exception as e:
            # raise e
            messagebox.showerror('写入excel', str(e))
        else:
            self.__file_open_template()

    def __table_entry_end(self, path, attr, value, table_entry):
        if table_entry and ((attr == 'sendVKey' and value == '11') or path == "wnd[0]/tbar[0]/btn[11]"):  # 保存
            messagebox.showerror('请检查操作', '双击表跳转之后要返回,不要直接保存')
            raise self.v_excption

        if table_entry and ((attr == 'sendVKey' and value == '3') or path == "wnd[0]/tbar[0]/btn[3]"  # 返回
        ):
            table_entry = False
        return table_entry

    def __table_entry_begin(self, operation_list, i, attr, value, table_entry):  # 双击就关联表,否则就算点了button 2进到明细也认为互不关联
        if i == 0:
            return
        oper_path_pre = operation_list['path'][i - 1]
        if operation_list['path'][i] == 'wnd[0]' and (attr == 'sendVKey' and value == '2') and i + 1 < len(
                operation_list):  # 上一个path_t存在表名是表上双击的
            oper_path_next = operation_list['path'][i + 1]
            table_next = oper_path_next.split('/')[0:len(oper_path_next.split('/')) - 1]
            table_pre = oper_path_pre.split('/')[0:len(oper_path_pre.split('/')) - 1]
            if table_next != table_pre:  # 其实这样也不准确,因为跳过去可能直接翻页了,这样取到的也不是屏幕名
                table_entry = True
        return table_entry

    def __file_path_check_table(self, path):
        if path.split('/')[len(path.split('/')) - 2][:3] == 'tbl' \
                or path.split('/')[len(path.split('/')) - 1][:3] == 'tbl':  # 如果是表选中行则最后一个不是字段是表名
            return TRUE
        else:
            return False

    def __check_alv(self, attr):
        if attr == 'modifyCell' or attr == 'insertRows':
            return TRUE
        else:
            return False

    def __array_get_first(self, field):
        if field:
            field = field[0]
        else:
            field = ''
        return field

    def __connection_read(self, init=False):
        con = {}
        df = pd.read_excel(self.v_path_connection, sheet_name=None)
        # 连接
        for i in range(len(df['连接']['desc'])):
            desc = str(df['连接']['desc'][i])
            client = str(df['连接']['client'][i])
            user = str(df['连接']['user'][i])
            langu = str(df['连接']['langu'][i])
            password = str(df['连接']['password'][i])
            if not con.__contains__(desc):
                con[desc] = {}
            if not con[desc].__contains__(client):
                con[desc][client] = {}
            if not con[desc][client].__contains__(user):
                con[desc][client][user] = {}
            if not con[desc][client][user].__contains__(langu):
                con[desc][client][user][langu] = password
        self.v_con_list = con
        if not self.v_con_list:
            self.v_con_list = {"未配置": {"300": {"abap01": {"zh": "123456"}}}}
        # with open(self.v_path_connection, 'w') as fp:
        #     json.dump(self.v_con_list, fp)
        if not init:
            self.__con_sel_refresh()

    def __config_write(self):
        with open(self.v_path_config, "w+") as f:
            self.v_config.write(f)

    def __win_theme(self):
        # root = ttkbootstrap.Window()
        root = tk.Tk()
        root.wm_title('主题')
        # root.iconphoto(False, tk.PhotoImage(file='lan.png'))
        style = ttkbootstrap.Style()
        theme_names = style.theme_names()  # 以列表的形式返回多个主题名
        theme_selection = ttkbootstrap.Frame(root, padding=(10, 10, 10, 0))
        theme_selection.pack(fill=X, expand=YES)
        lbl = ttkbootstrap.Label(theme_selection, text="主题:")
        theme_cbo = ttkbootstrap.Combobox(
            master=theme_selection,
            text=style.theme.name,
            values=theme_names,
        )
        theme_cbo.pack(padx=10, side=RIGHT)
        theme_cbo.current(theme_names.index(style.theme.name))
        lbl.pack(side=RIGHT)

        def change_theme(event):
            theme_cbo_value = theme_cbo.get()
            style.theme_use(theme_cbo_value)
            # theme_selected.configure(text=theme_cbo_value)
            theme_cbo.selection_clear()
            self.v_config['general']['theme'] = theme_cbo_value
            self.__config_write()

        theme_cbo.bind('<<ComboboxSelected>>', change_theme)
        # theme_selected = ttkbootstrap.Label(
        #     master=theme_selection,
        #     text="litera",
        #     font="-size 24 -weight bold"
        # )
        # theme_selected.pack(side=LEFT)
        self.__window_geometry(200, 50, root)
        root.mainloop()

    def __filedialog_create_template(self):
        path = filedialog.asksaveasfilename(filetypes=[('excel文件', ['.xlsx'])],
                                            title='生成模板',
                                            initialfile='template.xlsx'
                                            )
        return path

    def __resource_path(self, relative_path):
        if getattr(sys, 'frozen', False):  # 是否Bundle Resource
            base_path = sys._MEIPASS
        else:
            base_path = os.path.abspath(".")
        return os.path.join(base_path, relative_path)

    def __session_info_list_get(self):
        out_list = []
        try:  # 有可能超时连接断开,一个连接都没有了
            for connection in self.v_application.Connections:
                for session in connection.Sessions:
                    outdict = {}
                    try:  # 有可能超时断开,刚好执行到session.Info,就出异常了
                        if session.Busy == False:
                            session_info = session.Info
                            outdict = session_info
                            out_list.append((outdict))
                    except Exception as e:
                        pass
        except Exception as e:
            pass
        return out_list


    def __sapgui__start(self):
        self.v_path_sapgui = "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
        if not self.__sapgui_check_logon():
            if self.v_path_sapgui and os.path.exists(self.v_path_sapgui):
                subprocess.Popen(self.v_path_sapgui)
                time.sleep(7)
                return self.__sapgui_check_logon()
            else:
                raise ValueError('Path to saplogon.exe not found')

    def __sapgui_check_logon(self):
        try:
            self.v_application = win32com.client.GetObject('SAPGUI').GetScriptingEngine
        except Exception as e:
            return False
        else:
            return True

    def __connection_create(self, desc, client, user, pwd, langu=None, close_conn=False, change_pwd=True):
        try:
            self.v_application.OpenConnection(desc, True)
        except pythoncom.com_error as error:
            hr, msg, exc, arg = error.args
            sap_error = "COM: {0} ({1})".format(msg, hr)
            msg = "Connection '{0}' not found. {1}".format(desc, sap_error)
            # raise RuntimeError(msg)
            messagebox.showerror('打开连接错误', error)
        try:
            sap_session = self.v_application.Children(self.v_application.Children.Count - 1).Children(0)
        except Exception as e:
            messagebox.showerror('获取会话', '检查SAP是否开启了sapgui script(RZ11配置)')
            return
        sap_session.findById("wnd[0]").JumpBackward()
        sap_session.findById('wnd[0]/usr/txtRSYST-BNAME').text = user
        sap_session.findById('wnd[0]/usr/pwdRSYST-BCODE').text = pwd
        sap_session.findById('wnd[0]/usr/txtRSYST-MANDT').text = client
        sap_session.findById('wnd[0]/usr/txtRSYST-LANGU').text = langu
        sap_session.findById('wnd[0]').sendVKey(0)

        # msg = sap_session.findById('wnd[0]/sbar')
        # if msg and msg[0] == "E":
        #     sap_session.findById('wnd[0]').sendVKey(15) #shift + f3
        #     raise RuntimeError("Could not log to the SAP server. {0}".format(msg[2]))

        # 重复登录
        try:
            sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2")
        except pythoncom.com_error as error:
            pass
        else:
            sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2").select()
            sap_session.findById("wnd[1]/tbar[0]/btn[0]").press()

        # copyright弹框
        try:
            sap_session.findById("wnd[1]").sendVKey(0)
        except pythoncom.com_error as error:
            pass

        # return_pwd = SAPLogonPwd.is_change_password_window_while_connect(sap_session, change_pwd=change_pwd)
        # SAPNewSession.close_new_windows_while_connect(sap_session)
        return sap_session

    def __session_get(self, SystemSessionId, SessionNumber):
        for connection in self.v_application.Connections:
            for session in connection.Sessions:
                if session.Busy == False:
                    if session.Info.SystemSessionId == SystemSessionId and session.Info.SessionNumber == SessionNumber:
                        return session

    def __session_init(self):
        session = self.__session_selected()
        session.StartTransaction('')
        # self.__session_refresh()

    def __record_start(self):
        session = self.__session_selected()
        self.v_session = session  # 获得session的句柄,这样方法执行完了,session的record可以保持不丢失
        session.Record = False
        session.SaveAsUnicode = True  # 会存UTF-16
        session.RecordFile = self.v_filename_script
        self.v_path_script = session.RecordFile  # 本地变量丢失句柄会清掉/关闭Record会丢掉先记下来
        self.v_config['path']['vbs'] = self.v_path_script
        self.__config_write()
        session.findById("wnd[0]").JumpForward()
        session.Record = True  # 执行这句之后就会创建文件,之后前台操作都是实时写文件的

    def __record_end(self):
        session = self.__session_selected()
        if not session:
            return
        if session.RecordFile:
            self.v_path_script = session.RecordFile
        else:
            self.v_path_script = self.v_config['path']['vbs']
        session.Record = False  # 会清空 session.RecordFile
        self.v_session = None  # 释放session,此时gui右下角的脚本状态会立即消失
        self.__file_explain()

    # class SAPGUI:

    # def __tv_data_set(self,tv, data):
    def __column_next(self, column=None):
        if column != None:
            self.v_column = column
        self.v_column = self.v_column + 1
        return self.v_column

    def __connection_edit(self):
        os.startfile(self.v_path_connection)

    def __config_edit(self):
        os.startfile('config.ini')

    def __config_read(self):
        self.v_config.read('config.ini')

    def __button_file_execute(self):
        # self.__file_execute()
        self.v_root.after(1, self.__file_execute)

    def __button_stop(self):
        messagebox.showinfo('终止', '收到,当前条目执行完就会停止执行')
        self.v_stop = False

    def __about_document(self):
        # webbrowser.open('https://xiaolan.blog.csdn.net/article/details/128494871')
        webbrowser.open('https://blog.csdn.net/cylcylcylcylwo/article/details/128494871')

    def __about_conmunicate(self):
        pyperclip.copy('777363609')  # 相当如写入到剪切板
        


if __name__ == "__main__":
    lan_rpa = lan_rpa()
    lan_rpa.main()

  • 5
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值