当前位置:   article > 正文

LAN RPA SAP自动化工具

lan rpa

简介

  • 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进去,也没有保存

代码

  1. import datetime
  2. import sys
  3. import configparser
  4. # from math import ceil
  5. import webbrowser
  6. import openpyxl
  7. import pyperclip
  8. from openpyxl.utils import get_column_letter
  9. import xlsxwriter
  10. import json
  11. import numpy as np
  12. import re
  13. import time
  14. from operator import methodcaller
  15. # import eventlet
  16. import subprocess
  17. import os
  18. import tkinter as tk
  19. from tkinter import ttk, font, messagebox, filedialog
  20. from tkinter import *
  21. import pandas as pd
  22. import ttkbootstrap
  23. from ttkbootstrap.constants import *
  24. from ttkbootstrap import Style
  25. import pythoncom
  26. # import pypiwin32
  27. import win32com
  28. from win32com import client
  29. from win32com.client import Dispatch
  30. class lan_rpa:
  31. def __init__(self):
  32. # 布局
  33. self.v_excption = 'lan stop'
  34. self.v_column = 0
  35. self.v_pady = 3
  36. self.v_padx = (10, 0)
  37. # 初始化sapgui
  38. self.__sapgui__start()
  39. # 变量
  40. self.v_filename_script = 'Script6.vbs'
  41. self.v_input = {}
  42. self.v_con_sel = {'desc': '', 'client': '', 'user': '', 'langu': ''}
  43. self.v_path_connection = 'connection.xlsx'
  44. self.v_path_config = 'config.ini'
  45. # 读连接
  46. self.__connection_read(True)
  47. # 读配置
  48. self.v_config = configparser.ConfigParser()
  49. self.v_config.read('config.ini')
  50. def __tkinter_main(self):
  51. root = tk.Tk()
  52. self.v_root = root
  53. root.wm_title('LAN RPA v1.2')
  54. # root.iconphoto(False, tk.PhotoImage(file=self.__resource_path('lan.png')))
  55. root.iconphoto(False, tk.PhotoImage(file='lan.png'))
  56. ttkbootstrap.Style().theme_use(self.v_config['general']['theme'])
  57. # 刷新事件
  58. root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)
  59. # 布局
  60. frame1 = tk.Frame(root) # 连接选择
  61. self.v_frame1 = frame1
  62. frame2 = tk.Frame(root) # 会话列表+资源列表
  63. # paned = tk.PanedWindow(frame2, orient=tk.HORIZONTAL) # 默认是左右分布的
  64. # paned.pack(fill=tk.BOTH, expand=1) # 放到主窗口,上下左右缩放
  65. # frame_session = tk.Frame(paned) # 会话列表
  66. # paned.add(frame_session)
  67. # frame_explorer = tk.Frame(paned) # 资源列表
  68. # paned.add(frame_explorer)
  69. frame3 = tk.Frame(root)
  70. frame4 = tk.Frame(root)
  71. frame1.grid(row=0, column=0)
  72. frame2.grid(row=1, column=0, sticky='w', padx=self.v_padx, pady=10)
  73. frame3.grid(row=2, column=0, sticky='w', pady=10)
  74. frame4.grid(row=3, column=0, sticky='w', pady=10)
  75. # 菜单
  76. self.__menubar(root)
  77. # 连接选择
  78. self.__tkinter_frame_con()
  79. # 会话
  80. # self.__tkinter_frame_session(frame_session)
  81. self.__tkinter_frame_session(frame2)
  82. # # 资源浏览器
  83. # self.__tkinter_frame_explorer(frame_explorer)
  84. # 收藏列表
  85. # self.__tkinter_frame_favorite(frame3, root)
  86. self.__tkinter_frame_record(frame3)
  87. # 模板文件
  88. self.__tkinter_frame_file(frame4)
  89. # root.attributes('-topmost', True) #置顶
  90. # 居中显示
  91. dialog_width = 600
  92. dialog_height = 400
  93. # root.geometry('600x450')
  94. self.__window_geometry(dialog_width, dialog_height, root)
  95. root.mainloop()
  96. def __window_geometry(self, dialog_width, dialog_height, root):
  97. screenwidth = root.winfo_screenwidth()
  98. screenheight = root.winfo_screenheight()
  99. root.geometry("%dx%d+%d+%d" % (
  100. dialog_width, dialog_height, (screenwidth - dialog_width) / 2, (screenheight - dialog_height) / 2))
  101. def __tkinter_frame_record(self, frame3):
  102. # ttk.Button(frame3, text='刷新', command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
  103. # padx=self.v_padx)
  104. # tk.Button(frame3, text="刷新", command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
  105. # padx=self.v_padx)
  106. tk.Button(frame3, text="初始化", command=self.__session_init).grid(row=0, column=self.__column_next(0),
  107. padx=self.v_padx)
  108. tk.Button(frame3, text="开始录制", command=self.__record_start).grid(row=0, column=self.__column_next(),
  109. padx=self.v_padx)
  110. tk.Button(frame3, text="结束录制", command=self.__record_end).grid(row=0, column=self.__column_next(),
  111. padx=self.v_padx)
  112. frame_progressbar = tk.Frame(frame3)
  113. frame_progressbar.grid(row=0, column=self.__column_next(), padx=self.v_padx)
  114. self.v_progress_bar = ttk.Progressbar(frame_progressbar, length='100', mode="determinate", orient=tk.HORIZONTAL)
  115. self.v_progress_bar.grid(row=0)
  116. self.v_progress_count = tk.Label(frame_progressbar, text='')
  117. if self.v_config['general']['bar_total'] == 'True':
  118. self.v_progress_count.grid(row=1)
  119. self.v_progress_remain = tk.Label(frame3, text='')
  120. self.v_progress_remain.grid(row=0, column=self.__column_next())
  121. def __menubar(self, root):
  122. menubar = tk.Menu(root)
  123. menu_connection = tk.Menu(menubar)
  124. menu_config = tk.Menu(menubar)
  125. menu_about = tk.Menu(menubar)
  126. menu_connection.add_command(label='编辑', command=self.__connection_edit)
  127. menu_connection.add_command(label='重读', command=self.__connection_read)
  128. menu_config.add_command(label='主题', command=self.__win_theme)
  129. menu_config.add_command(label='编辑', command=self.__config_edit)
  130. menu_config.add_command(label='重读', command=self.__config_read)
  131. menu_about.add_command(label='帮助', command=self.__about_document)
  132. menu_about.add_command(label='交流', command=self.__about_conmunicate) # 777363609
  133. menubar.add_cascade(label='连接', menu=menu_connection)
  134. menubar.add_cascade(label='配置', menu=menu_config)
  135. menubar.add_cascade(label='关于', menu=menu_about)
  136. root.config(menu=menubar)
  137. def __tkinter_frame_session(self, frame_session):
  138. # session info list treeview
  139. col_text = ('id', 'SID', '客户端', 'TCODE', '用户', '语言')
  140. col_name = ('id', 'sid', 'client', 'tcode', 'user', 'langu')
  141. col_width = ('50', '50', '50', '180', '110', '80')
  142. self.v_column_len = len(col_name) # 记下tv展示的列,因为SystemSessionId和SessionNumber在后面两列
  143. xbar = ttk.Scrollbar(frame_session, orient='horizontal') # 水平滚动条
  144. ybar = ttk.Scrollbar(frame_session, orient='vertical') # 垂直滚动条
  145. xbar.pack(side=BOTTOM, fill=X)
  146. ybar.pack(side=RIGHT, fill=Y)
  147. tv = ttk.Treeview(frame_session, column=col_name, yscrollcommand=ybar.set, xscrollcommand=xbar.set,
  148. show='headings') # 隐藏首列
  149. self.v_tv_session = tv
  150. for i in range(len(col_text)):
  151. tv.column(col_name[i], width=col_width[i], anchor='n')
  152. tv.heading(col_name[i], text=col_text[i])
  153. ybar['command'] = self.v_tv_session.yview
  154. xbar['command'] = self.v_tv_session.xview
  155. tv.pack()
  156. # 数据
  157. # self.__session_refresh()
  158. # 右键菜单
  159. self.v_right_click_menu = tk.Menu(frame_session, tearoff=0)
  160. self.v_right_click_menu.add_command(label='新会话', command=self.__session_create)
  161. self.v_right_click_menu.add_command(label='关闭其他', command=self.__session_close_other)
  162. self.v_right_click_menu.add_command(label='关闭全部', command=self.__session_close_all)
  163. # self.v_right_click_menu.add_command(label='关闭后面', command=self.__session_close_back)
  164. # 绑定事件
  165. tv.bind('<Double-1>', self.__session_JumpForward) # 双击左键跳转窗口
  166. tv.bind('<Button-3>', self.__session_right_click) # 右键菜单
  167. # def __tkinter_connection_config(self):
  168. # self.v_tkt_SAPConnectionConfig = tkt_SAPConnectionConfig()
  169. # self.v_tkt_SAPConnectionConfig.open()
  170. def main(self):
  171. # self.__sapgui_init()
  172. # session处理
  173. # close_all()
  174. # return
  175. # logoin = self.login_init()
  176. self.__tkinter_main()
  177. return
  178. def __session_close_other(self):
  179. session_info = self.__session_info_selected()
  180. for connection in self.v_application.Connections:
  181. for session in connection.Sessions:
  182. if session.Busy == False:
  183. if session.Info.SystemSessionId != session_info.SystemSessionId:
  184. session.StartTransaction('ex')
  185. break
  186. if session.Info.SessionNumber != session_info.SessionNumber:
  187. session.findById("wnd[0]").close()
  188. continue
  189. # self.__session_refresh()
  190. def __session_close_all(self):
  191. for connection in self.v_application.Connections:
  192. for session in connection.Sessions:
  193. if session.Busy == False:
  194. session.StartTransaction('ex')
  195. break
  196. # self.__session_refresh()
  197. def __handle_connection_create(self):
  198. self.__connection_create(self.v_input['desc'].get(), self.v_input['client'].get(),
  199. self.v_input['user'].get(),
  200. self.v_input['password'], self.v_input['langu'].get())
  201. # self.__session_refresh()
  202. def __session_create(self):
  203. session = self.__session_selected()
  204. session.CreateSession()
  205. def __session_refresh(self):
  206. session_info_sel = self.__session_info_selected() # 在刷新tv数据之前先取到选中的行,为了刷新之后重新选中会话
  207. self.v_session_info_list = self.__session_info_list_get()
  208. i = 1
  209. data_list = []
  210. if self.v_session_info_list:
  211. for session_info in self.v_session_info_list:
  212. try:
  213. data_obj = (str(i), session_info.SystemName, session_info.Client, session_info.Transaction,
  214. session_info.User,
  215. session_info.Language,
  216. session_info.SystemSessionId, session_info.SessionNumber)
  217. data_list.append(data_obj)
  218. i = i + 1
  219. except Exception as e: # 可能窗口关闭,连接断开了
  220. continue
  221. self.v_tv_session.delete(*self.v_tv_session.get_children())
  222. for i in range(len(data_list)):
  223. self.v_tv_session.insert('', 'end', values=data_list[i])
  224. # 选中会话
  225. try:
  226. for child in self.v_tv_session.get_children():
  227. if session_info_sel.SystemSessionId == self.v_tv_session.item(child)['values'][self.v_column_len] \
  228. and session_info_sel.SessionNumber == self.v_tv_session.item(child)['values'][
  229. self.v_column_len + 1]:
  230. self.v_tv_session.selection_set(child)
  231. break
  232. except Exception as e:
  233. if self.v_tv_session.get_children():
  234. self.v_tv_session.selection_set(self.v_tv_session.get_children()[0]) # 默认选中第一个窗口
  235. # 继续刷新
  236. self.v_root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)
  237. def __on_focus_in(self, event):
  238. pass
  239. # self.__session_list_refresh()
  240. def __session_right_click(self, event):
  241. row_id = self.v_tv_session.identify_row(event.y)
  242. if row_id:
  243. self.v_tv_session.selection_set(row_id)
  244. self.v_right_click_menu.post(event.x_root, event.y_root)
  245. def __session_JumpForward(self, event):
  246. session = self.__session_selected()
  247. if session:
  248. session.findById("wnd[0]").Visualize(True)
  249. session.findById("wnd[0]").JumpForward()
  250. return session
  251. def __session_selected(self):
  252. session_info = self.__session_info_selected()
  253. try: # 如果session关闭了,session_info.就会异常
  254. session = self.__session_get(session_info.SystemSessionId, session_info.SessionNumber)
  255. return session
  256. except Exception as e:
  257. messagebox.showinfo('选择会话', '当前会话繁忙或已关闭:' + str(e))
  258. def __session_info_selected(self):
  259. item_text = None
  260. for item in self.v_tv_session.selection(): # 这里values全部转成str!!
  261. item_text = self.v_tv_session.item(item, "values")
  262. if item_text:
  263. for session_info in self.v_session_info_list:
  264. try:
  265. if session_info.SystemSessionId == item_text[self.v_column_len] and str(
  266. session_info.SessionNumber) == \
  267. item_text[self.v_column_len + 1]:
  268. return session_info
  269. except Exception as e:
  270. continue
  271. def __tkinter_frame_con(self):
  272. # Input控件
  273. self.v_con_sel['desc'] = tk.StringVar()
  274. self.v_input['desc'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['desc'], state='readonly',
  275. width='15')
  276. self.v_con_sel['client'] = tk.StringVar()
  277. self.v_input['client'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['client'],
  278. state='readonly',
  279. width='3')
  280. self.v_con_sel['user'] = tk.StringVar()
  281. self.v_input['user'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['user'], state='readonly',
  282. width='12')
  283. self.v_con_sel['langu'] = tk.StringVar()
  284. self.v_input['langu'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['langu'], state='readonly',
  285. width='5')
  286. # 布局
  287. tk.Label(self.v_frame1, text='描述').grid(row=0, column=0, padx=self.v_padx)
  288. self.v_input['desc'].grid(row=0, column=1)
  289. tk.Label(self.v_frame1, text='客户端').grid(row=0, column=2, padx=self.v_padx)
  290. self.v_input['client'].grid(row=0, column=3)
  291. tk.Label(self.v_frame1, text='用户').grid(row=0, column=4, padx=self.v_padx)
  292. self.v_input['user'].grid(row=0, column=5)
  293. tk.Label(self.v_frame1, text='语言').grid(row=0, column=6, padx=self.v_padx)
  294. self.v_input['langu'].grid(row=0, column=7)
  295. # 设置连接值
  296. self.__con_sel_refresh()
  297. # 绑定事件:下拉框列表值
  298. self.v_input['desc'].bind("<<ComboboxSelected>>", self.__input_selected_desc)
  299. self.v_input['client'].bind("<<ComboboxSelected>>", self.__input_selected_client)
  300. self.v_input['user'].bind("<<ComboboxSelected>>", self.__input_selected_user)
  301. ttk.Button(self.v_frame1, text='打开', command=self.__handle_connection_create).grid(row=0, column=8,
  302. padx=self.v_padx,
  303. pady=10, )
  304. def __con_sel_refresh(self):
  305. self.v_input['desc']['value'] = list(self.v_con_list.keys())
  306. self.v_input['desc'].current(0) # 设置默认值
  307. self.__input_selected_desc(None)
  308. def __input_selected_desc(self, event):
  309. self.v_input['client'].delete(0, tk.END)
  310. self.v_input['client']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()].keys())
  311. self.v_input['client'].current(0) # 设置默认值
  312. self.__input_selected_client(event)
  313. self.__input_selected_user(event)
  314. def __input_selected_client(self, event):
  315. self.v_input['user'].delete(0, tk.END)
  316. self.v_input['user']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
  317. [self.v_con_sel['client'].get()].keys())
  318. self.v_input['user'].current(0) # 设置默认值
  319. self.__input_selected_user(event)
  320. def __input_selected_user(self, event):
  321. self.v_input['langu'].delete(0, tk.END)
  322. self.v_input['langu']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
  323. [self.v_con_sel['client'].get()][self.v_con_sel['user'].get()].keys())
  324. self.v_input['langu'].current(0) # 设置默认值
  325. self.v_input['password'] = \
  326. self.v_con_list[self.v_con_sel['desc'].get()][self.v_con_sel['client'].get()][
  327. self.v_con_sel['user'].get()][
  328. self.v_con_sel['langu'].get()]
  329. def __filedialog_select_vbs(self):
  330. (filepath, tempfilename) = os.path.split(self.v_config['path']['vbs'])
  331. selected_file_path = filedialog.askopenfilename(initialdir=filepath,
  332. filetypes=[('脚本', '*.vbs')]) # 使用askopenfilename函数选择单个文件
  333. if selected_file_path:
  334. self.v_config['path']['vbs'] = selected_file_path
  335. self.__config_write()
  336. self.v_stringvar_vbs.set(selected_file_path)
  337. def __filedialog_select_template(self):
  338. (filepath, tempfilename) = os.path.split(self.v_config['path']['template'])
  339. selected_file_path = filedialog.askopenfilename(initialdir=filepath,
  340. filetypes=[('模板', '*.xlsx')]) # 使用askopenfilename函数选择单个文件
  341. if selected_file_path:
  342. self.v_config['path']['template'] = selected_file_path
  343. self.__config_write()
  344. self.v_stringvar_template.set(selected_file_path)
  345. def __tkinter_frame_file(self, frame3):
  346. v_width_input = 55
  347. self.v_stringvar_vbs = tk.StringVar()
  348. self.v_stringvar_template = tk.StringVar()
  349. self.__refresh_file()
  350. # 布局控件
  351. # tk.Label(frame3, text="脚本").grid(column=0, row=0, pady=v_pady)
  352. # tk.Entry(frame3, textvariable=self.v_stringvar_vbs, width=v_width_input).grid(column=1, row=0)
  353. # tk.Button(frame3, text="选择", command=self.__filedialog_select_vbs).grid(row=0, column=2)
  354. # tk.Button(frame3, text="解析", command=self.__file_explain).grid(row=0, column=3, padx=self.v_padx)
  355. tk.Label(frame3, text="模板").grid(column=0, row=1, pady=self.v_pady, padx=self.v_padx)
  356. tk.Entry(frame3, textvariable=self.v_stringvar_template, width=v_width_input).grid(column=1, row=1)
  357. tk.Button(frame3, text="选择", command=self.__filedialog_select_template).grid(row=1, column=2)
  358. tk.Button(frame3, text="打开", command=self.__file_open_template).grid(row=1, column=3, padx=self.v_padx)
  359. tk.Button(frame3, text="执行", command=self.__button_file_execute).grid(row=1, column=4, padx=self.v_padx)
  360. # button_exec = tk.Button(frame3, text="执行")
  361. # button_exec.after(1,self.__button_file_execute)
  362. # button_exec.grid(row=1, column=4, padx=self.v_padx)
  363. tk.Button(frame3, text="终止", command=self.__button_stop).grid(row=1, column=5, padx=self.v_padx)
  364. def __refresh_file(self):
  365. # self.v_stringvar_vbs.set(self.v_config['path']['vbs'])
  366. self.v_stringvar_template.set(self.v_config['path']['template'])
  367. # def __config_get(self, key):
  368. # try:
  369. # return self.v_config.loc[self.v_config['key'] == key, 'value'].iloc[0]
  370. # except Exception as e:
  371. # return ''
  372. def __file_close(self, file):
  373. xlApp = Dispatch('Excel.Application')
  374. try:
  375. xlApp.DisplayAlerts = False # 设置不显示警告和消息框
  376. except Exception as e:
  377. messagebox.showerror('关闭excel', e)
  378. return
  379. # xlBook = xlApp.Workbooks.Open(file)
  380. workbooks_n = xlApp.Workbooks.Count
  381. # print(f'已打开工作簿的数量为:{workbooks_n}个')
  382. if workbooks_n < 0: return
  383. for i in range(1, workbooks_n + 1): # 工作簿索引从1开始
  384. path_ = xlApp.Workbooks(i).Path
  385. name_ = xlApp.Workbooks(i).Name
  386. file_ = os.path.join(path_, name_)
  387. # path = path_ + "\\backup_" + name_
  388. # print(f'第{i}个excel的文件路径为:{path}')
  389. if file == "/".join(file_.split("\\")): # 转义的\\替换回/
  390. xlApp.Workbooks(i).Close() # 关闭当前打开的文件,不保存文件
  391. # xlApp.Workbooks(i).Activate()
  392. # # xlApp.Workbooks(i).SaveAs(path)
  393. # xlApp.Workbooks(path).Close()
  394. # xlApp.Quit() #关闭所有打开的excel文件
  395. del xlApp
  396. def __file_open_template(self):
  397. try:
  398. os.startfile(self.v_stringvar_template.get())
  399. except Exception as e:
  400. messagebox.showerror('打开文件', e)
  401. def __file_execute(self):
  402. self.v_stop = True
  403. if not self.v_stringvar_template.get():
  404. messagebox.showerror('文件不能为空')
  405. return
  406. self.v_config['path']['template'] = self.v_stringvar_template.get()
  407. self.__config_write()
  408. # self.v_root.state('icon') # 最小化
  409. oper_start = 0
  410. oper_end = 0
  411. session = self.__session_JumpForward(None)
  412. if not session:
  413. return
  414. self.__file_close(self.v_stringvar_template.get()) # 先关闭,再回写消息,再打开
  415. dict = pd.read_excel(self.v_stringvar_template.get(), sheet_name=None,dtype='str') #str防止001.1这样的被当时float
  416. # 循环操作
  417. tabix = -1
  418. total = len(dict['字段']['doc_num'])
  419. start_time = time.time() # 记录程序开始运行时间
  420. for doc_num in dict['字段']['doc_num']:
  421. if not self.v_stop:
  422. break
  423. path_t = ''
  424. log = ''
  425. tabix = tabix + 1
  426. self.v_root.update()
  427. if str(doc_num) == 'nan':
  428. continue
  429. table_entry = False
  430. for i in range(len(dict['操作']['path'])):
  431. attr, equal, path, value = self.__file_oper_get(dict, i)
  432. table_entry = self.__table_entry_begin(dict['操作'], i, attr, value,
  433. table_entry) # 要传上一个table的Path,因为此时是win0
  434. table_flag = self.__file_path_check_table(path)
  435. alv_flag = self.__check_alv(attr)
  436. if alv_flag:
  437. table_flag = True
  438. if not table_flag and not table_entry: # 字段:也可能不是字段,比如win0
  439. if oper_start != 0: # 记录了表开始,现在回到了字段,说明表结束了
  440. oper_end = i - 1
  441. if attr == 'text': # 避免focus和double click等匹配到了值(或者参考表哪里用 = 判断?)
  442. for key in dict['字段']:
  443. if key == path + '+' + attr:
  444. value = dict['字段'].loc[dict['字段']['doc_num'] == doc_num, key].iloc[
  445. 0] # dict.loc根据行+列返回一个列的series,取series第一个值
  446. if re.findall('/okcd', path):
  447. if value[:2] != '/n' and value[:2] != '/N':
  448. value = '/n' + value
  449. break
  450. elif attr == 'insertText':
  451. for key in dict['代码']:
  452. if key == value: # 在 操作 页签的值记录了插入代码的位置
  453. value_oper = value
  454. if len(dict['代码'][key]) > 1:
  455. value = '\n\r'.join(dict['代码'][key]) + ',' + value_oper
  456. else:
  457. value = str(dict['代码'][key][0]) + ',' + value_oper
  458. break
  459. else: # 表:第一行正常循环,多行后面处理
  460. if oper_start == 0:
  461. oper_start = i
  462. # table = path.split(',')[0]
  463. for key in dict: # 循环每个sheet匹配表字段
  464. if re.findall('表', key):
  465. for key2 in dict[key]: # 循环取列名
  466. if key2 == path.split(',')[0] + '+' + attr: # 匹配一次列名就定位到表了
  467. row_doc_tab = len(dict[key].loc[dict[key]['doc_num'] == doc_num, 'doc_num'])
  468. sheet_tbl = key
  469. break
  470. else: # 里面break了则触发外面break跳出第二层循环
  471. continue
  472. break
  473. # 开始执行
  474. if oper_start != 0 and oper_end != 0: # 表:循环完了,开始一次性执行表全部条目
  475. pagesize = 0
  476. row_current = 0
  477. for x in range(row_doc_tab): # 条目数
  478. # 翻页:规定表和表不关联,处理完表1再处理表2,所以在表条目维度翻页就可以
  479. attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_start)
  480. if self.__file_path_check_table(path_t): # 进了表才考虑定位行
  481. if 'getAbsoluteRow' in attr_t: # 选表行
  482. row_start = int(attr_t.split('(')[1].strip(')')[0]) # 选中绝对行要不要翻页的?
  483. path_tc = path_t
  484. else:
  485. row_start = int(path_t.split(',')[1].strip(']')[0]) # 开始录制的表行(可能不是从第一行开始的)
  486. path_tc = re.findall(r"(.+)/", path_t)[0]
  487. if pagesize == 0:
  488. pagesize = session.findById(path_tc).verticalScrollbar.pagesize
  489. if not row_current:
  490. row_current = row_start
  491. # div_mod = divmod(row_current, pagesize)
  492. # if div_mod[0] != 0 and div_mod[1] == 0: # 翻页:用翻页按钮是因为发现CA01不填第二行无法定位到第二行
  493. if row_current == pagesize: # 翻页
  494. log_r = self.__script_exec('sendVKey', '', session.findById("wnd[0]"), '82', "wnd[0]")
  495. if log_r:
  496. log = log + log_r
  497. row_current = row_start
  498. for oper_now in range(oper_start, oper_end + 1): # range不包含上限
  499. attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_now)
  500. if self.__file_path_check_table(path_t):
  501. if 'getAbsoluteRow' in attr_t: # 选表行
  502. attr_t = attr_t.split('(')[0] + '(' + str(row_current) + ')' + attr_t.split(')')[
  503. 1] # path_t就是tc的位置不用处理,但是行在attr上
  504. elif 'modifyCell' in attr_t or 'insertRows': #修改alv单元格
  505. pass
  506. else:
  507. path_t = path_t.split(',')[0] + ',' + str(row_current) + ']'
  508. try:
  509. obj_t = session.findById(path_t)
  510. except Exception as e:
  511. pass
  512. for key2 in dict[sheet_tbl]: # 循环取列名
  513. if key2 == path_t.split(',')[0] + '+' + attr_t \
  514. or key2 == path_t + '+' + attr_t: # 选中行
  515. break
  516. if equal_t == 'X': # 有=号才是可输入字段
  517. value_t = dict[sheet_tbl].loc[dict[sheet_tbl]['doc_num'] == doc_num, key2].iloc[
  518. x] # 取series第x个值
  519. elif path_t == 'wnd[0]' or attr_t == 'insertRows': # 双击/ alv插入行
  520. value_t = value_t
  521. elif attr_t == 'modifyCell':
  522. 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[
  523. x]] #行/列名/值
  524. else:
  525. value_t = 'nan'
  526. log_r = self.__script_exec(attr_t, equal_t, obj_t, value_t, path_t)
  527. if log_r:
  528. log = log + log_r
  529. row_current = row_current + 1
  530. oper_start = 0
  531. oper_end = 0
  532. if oper_start == 0: # 非表直接执行
  533. try:
  534. obj = session.findById(path) # 到执行的时候才可以取,因为可能前面在执行表
  535. except Exception as e:
  536. pass
  537. log_r = self.__script_exec(attr, equal, obj, value, path)
  538. if log_r:
  539. log = log + log_r
  540. # 判断表的双击是否返回或者保存了
  541. table_entry = self.__table_entry_end(path, attr, value, table_entry)
  542. # 回写消息
  543. msg_type = session.findById('wnd[0]/sbar').MessageType
  544. msg_text = session.findById('wnd[0]/sbar').Text
  545. dict['字段'].loc[
  546. tabix, 'msg_type'] = msg_type # 正确改值方式,否则:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
  547. dict['字段'].loc[tabix, 'msg_text'] = msg_text
  548. if self.v_config['general']['log'] == 'True':
  549. dict['字段'].loc[tabix, 'log'] = log
  550. end_time = time.time() # 记录程序结束运行时间
  551. self.v_progress_bar['value'] = (tabix + 1) / total * 100
  552. self.v_progress_count['text'] = str(tabix + 1) + '/' + str(total)
  553. self.v_progress_remain['text'] = '剩余时间:' + str(
  554. datetime.timedelta(
  555. seconds=int((end_time - start_time) / (tabix + 1) * (total - (tabix + 1))))) # int向下取整
  556. try: # 只覆盖一个sheet 只有openpyxl有 a mode
  557. with pd.ExcelWriter(self.v_stringvar_template.get(), mode="a", engine="openpyxl",
  558. if_sheet_exists="replace", ) as writer:
  559. pd.DataFrame(dict['字段']).to_excel(writer, sheet_name="字段", index=False)
  560. # 按列遍历:单元格改回文本格式
  561. for x in range(1, writer.sheets['字段'].max_row + 1): # 先改有文本的单元格,再改列,直接改列不起作用
  562. for y in range(0, writer.sheets['字段'].max_column):
  563. writer.sheets['字段'][x][y].number_format = '@'
  564. for i in range(1, writer.sheets['字段'].max_column + 1):
  565. writer.sheets['字段'].column_dimensions[get_column_letter(i)].number_format = '@'
  566. except Exception as e:
  567. messagebox.showerror('消息回写excel出错', e)
  568. self.__file_open_template()
  569. def __script_exec(self, attr, equal, obj, value, path): # path可选,用于报错,可以优化成传入path和session
  570. value = str(value) # 避免1.0被当做float丢失.0了
  571. if attr == 'ResizeWorkingPane':
  572. return
  573. elif 'getAbsoluteRow' in attr:
  574. attr_1 = attr.split('(')[0]
  575. value_1 = attr.split('(')[1].split(')')[0]
  576. obj = getattr(obj, attr_1)(value_1) # 动态调用只能分两步:先取table row,再设置为True
  577. attr = attr.split('.')[1]
  578. time.sleep(int(self.v_config['general']['delay']))
  579. try:
  580. # if str(equal) != 'nan':
  581. if str(equal) == 'X':
  582. setattr(obj, attr, value) # 设置属性值
  583. else:
  584. if str(value) == 'nan':
  585. getattr(obj, attr)() # 直接调用方法
  586. else:
  587. if obj.type == 'GuiShell': # 如果是abap编辑器则传入多个参数
  588. if attr == 'insertText': # 代码直接转tuple可能有特殊字符
  589. getattr(obj, attr)(value.rsplit(',',2)[0], int(value.rsplit(',',2)[1]),int(value.rsplit(',',2)[2]))
  590. elif attr == 'insertRows':
  591. getattr(obj, attr)(value) # 调用方法传参
  592. else:
  593. value = eval(value) # 转成tuple
  594. getattr(obj, attr)(*value) # 加* tuple变参数
  595. else:
  596. getattr(obj, attr)(value) # 调用方法传参
  597. except Exception as e:
  598. return str(e) + str(path) + ':' + str(attr) + ':' + str(value) + ';'
  599. def __file_oper_get(self, df, i):
  600. path = df['操作']['path'][i]
  601. attr = df['操作']['attr'][i]
  602. equal = df['操作']['equal'][i]
  603. value = df['操作']['value'][i]
  604. if attr == 'insertText' or attr == 'modifyCell': # 如果是代码则不去掉引号
  605. pass
  606. else:
  607. value_n = re.findall(r"\"(.+?)\"", str(value)) # 去掉引号
  608. if value_n:
  609. value = value_n[0]
  610. return attr, equal, path, value
  611. def __file_explain(self):
  612. # v_len_max = 30000 #excel单元格最大字符数 32000
  613. if self.v_config['general']['template_no_ask'] == 'False': # 是否问模板路径
  614. self.v_path = self.__filedialog_create_template()
  615. if self.v_path:
  616. self.v_stringvar_template.set(self.v_path)
  617. else:
  618. return
  619. operation_list = []
  620. try:
  621. with open(self.v_path_script, encoding='UTF-16') as f: # 代码保存出来就是UTF-16不知道为啥
  622. for line in f.readlines(): # readlines(),函数把所有的行都读取进来;
  623. line = line.strip() # 删除行后的换行符,img_file 就是每行的内容啦
  624. path = re.findall(r"session.findById\(\"(.+?)\"\).", line) # (" ").
  625. if not path:
  626. continue
  627. attr = re.findall(r"\).(.+?) ", line) # 测试空格不用转义
  628. if not attr: # 没有值的情况,比如setFocus
  629. attr = re.findall('(?<=\).).*$', line)
  630. value = re.findall('(?<=\s).*$', line) # \s空格 取空格右边全部
  631. equal = ''
  632. if value:
  633. # equal = re.search('=', value[0]) # 值中找=号
  634. # if equal:
  635. if value[0][:1] == '=': # 如果第一位是等于号(值中可能也有=号)
  636. equal = 'X'
  637. value = re.findall('(?<=\s).*$', value[0]) # \s空格 取空格右边全部
  638. path = self.__array_get_first(path)
  639. attr = self.__array_get_first(attr)
  640. value = self.__array_get_first(value)
  641. operation = (path, attr, equal, value)
  642. operation_list.append(operation)
  643. except Exception as e:
  644. messagebox.showerror('解析文件', e)
  645. return
  646. dict_field = {}
  647. dict_tables = {}
  648. dict_code = {} # 代码
  649. dict_operation = {'path': {}, 'attr': {}, 'equal': {}, 'value': {}}
  650. # 开始解析操作
  651. i = -1
  652. table_entry = False
  653. df_operation_list = pd.DataFrame(operation_list)
  654. df_operation_list.columns = ['path', 'attr', 'equal', 'value']
  655. for oper in operation_list:
  656. i = i + 1
  657. path = oper[0]
  658. attr = oper[1]
  659. equal = oper[2]
  660. value = oper[3]
  661. path_t = '' # 上一个表的path
  662. if attr == 'caretPosition' or attr == 'verticalScrollbar.position' or attr == 'currentCellColumn':
  663. continue
  664. elif attr == 'insertText': # 代码:先写 代码 页签,再写操作
  665. value_code = value.rsplit(",", 2)[0] # 从右向左按 , 拆两次
  666. code_list = value_code.split(' + vbCr + "" + vbLf + ') # 根据换行符号拆开
  667. for x in range(len(code_list)):
  668. code_list[x] = code_list[x][1:len(code_list[x]) - 1] # 去掉首尾引号
  669. dict_code[value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[
  670. 2]] = code_list # 录制的脚本中文会变成?,需要录完了在excel自己覆盖一下数据
  671. value = value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[2] # 如果插入代码,则把位置写到操作页签,把值写入代码页签
  672. dict_operation['path'][i] = path
  673. dict_operation['attr'][i] = attr
  674. dict_operation['equal'][i] = equal
  675. dict_operation['value'][i] = value
  676. # 判断表的双击是否跳转屏幕了
  677. # oper_next = operation_list[i + 1]
  678. table_entry = self.__table_entry_begin(df_operation_list, i, attr, value, table_entry)
  679. table_flag = self.__file_path_check_table(path)
  680. alv_flag = self.__check_alv(attr)
  681. if alv_flag: #alv跟表一样处理
  682. table_flag = True
  683. if table_flag: # 如果是跳转的就沿用上一个表名,所以没有第一个表没有填值,只是双击的情况也需要记录表名
  684. if 'getAbsoluteRow' in attr: # 选表行
  685. table = path
  686. tabix = re.findall(r"\((.+?)\)", attr)[0]
  687. else: # 表字段
  688. if not alv_flag: #table control
  689. table = re.findall(r"(.+)/", path)[0] # 贪婪匹配:从整个字符去掉最后一个匹配
  690. tabix = re.findall(r",(.+?)\]", path)[0]
  691. else: #alv
  692. table = path
  693. tabix = value_code = value.rsplit(",", 2)[0] # 从右向左按 , 拆两次,第一个值为行
  694. if equal == 'X' or attr == 'setFocus' or attr == 'modifyCell': # setFocus必须要有,有些表没有输入直接双击的,不然匹配不到表
  695. if alv_flag:
  696. value = value.rsplit(',',2)[2]
  697. quotes = []
  698. quotes = re.findall(r"\"(.+?)\"", value) # 值中找引号
  699. if quotes:
  700. value = re.findall(r"\"(.+?)\"", value)[0]
  701. elif '"' in value: # 如果引号中间是空,正则也是找不到的
  702. value = ''
  703. if table_flag or table_entry: # 当前为表或者是表双击跳转的屏幕
  704. if table_flag and not alv_flag: # 表上还是要拆分行,只保留列;alv列不写入path了,直接读operation的value取到列
  705. path = path.split(',')[0]
  706. if not dict_tables.__contains__(table):
  707. dict_tables[table] = {}
  708. if not dict_tables[table].__contains__(path + '+' + attr):
  709. dict_tables[table][path + '+' + attr] = {}
  710. dict_tables[table][path + '+' + attr][tabix] = value
  711. else: # 字段
  712. dict_field[path + '+' + attr] = {0: value} # 一个path可能有多个属性操作,拼接起来作为字段名
  713. # 判断表的双击是否返回或者保存了
  714. table_entry = self.__table_entry_end(path, attr, value, table_entry)
  715. dict_field['msg_type'] = {0: ''}
  716. dict_field['msg_text'] = {0: ''}
  717. dict_field['log'] = {0: ''}
  718. # 写入excel
  719. self.__file_close(self.v_stringvar_template.get()) # 先关闭,避免写入的时候占用了
  720. excel_colum = []
  721. try: # with就不用再save了
  722. with pd.ExcelWriter(self.v_stringvar_template.get(),
  723. engine='xlsxwriter') as writer: # xlsxwriter才能写格式,但是xlsxwriter只能写不能读(所以不能修改)
  724. df1 = pd.DataFrame(dict_field)
  725. df1.insert(0, 'doc_num', 1)
  726. df1.to_excel(writer, sheet_name='字段', index=False) # 不要第一列的序号
  727. excel_colum.append(len(df1.keys()))
  728. i = 0
  729. for key in dict_tables.keys():
  730. i = i + 1
  731. dict_table = dict_tables[key]
  732. df2 = pd.DataFrame(dict_table)
  733. df2.insert(0, 'doc_num', 1)
  734. df2.to_excel(writer, sheet_name='表' + str(i), index=False)
  735. excel_colum.append(len(dict_table.keys()))
  736. # df_code = pd.DataFrame(dict_code)
  737. df_code = pd.DataFrame(pd.DataFrame.from_dict(dict_code, orient='index').values.T,
  738. columns=list(dict_code.keys())) # dict_code列长度可能不一样需要特殊处理
  739. df_code.to_excel(writer, sheet_name='代码', na_rep='', index=False)
  740. excel_colum.append(len(df_code.keys()))
  741. df3 = pd.DataFrame(dict_operation)
  742. df3.to_excel(writer, sheet_name='操作', na_rep='', index=False) # na_rep 缺失数据表示
  743. excel_colum.append(len(df3.keys()))
  744. # 设置单元格格式为文本
  745. workbook = writer.book
  746. format_text = workbook.add_format({'num_format': '@'})
  747. i = -1
  748. for sheet_name in writer.sheets:
  749. i = i + 1
  750. writer.sheets[sheet_name].set_column(0, excel_colum[i] - 1, None, format_text) # 不设置宽度
  751. except Exception as e:
  752. # raise e
  753. messagebox.showerror('写入excel', str(e))
  754. else:
  755. self.__file_open_template()
  756. def __table_entry_end(self, path, attr, value, table_entry):
  757. if table_entry and ((attr == 'sendVKey' and value == '11') or path == "wnd[0]/tbar[0]/btn[11]"): # 保存
  758. messagebox.showerror('请检查操作', '双击表跳转之后要返回,不要直接保存')
  759. raise self.v_excption
  760. if table_entry and ((attr == 'sendVKey' and value == '3') or path == "wnd[0]/tbar[0]/btn[3]" # 返回
  761. ):
  762. table_entry = False
  763. return table_entry
  764. def __table_entry_begin(self, operation_list, i, attr, value, table_entry): # 双击就关联表,否则就算点了button 2进到明细也认为互不关联
  765. if i == 0:
  766. return
  767. oper_path_pre = operation_list['path'][i - 1]
  768. if operation_list['path'][i] == 'wnd[0]' and (attr == 'sendVKey' and value == '2') and i + 1 < len(
  769. operation_list): # 上一个path_t存在表名是表上双击的
  770. oper_path_next = operation_list['path'][i + 1]
  771. table_next = oper_path_next.split('/')[0:len(oper_path_next.split('/')) - 1]
  772. table_pre = oper_path_pre.split('/')[0:len(oper_path_pre.split('/')) - 1]
  773. if table_next != table_pre: # 其实这样也不准确,因为跳过去可能直接翻页了,这样取到的也不是屏幕名
  774. table_entry = True
  775. return table_entry
  776. def __file_path_check_table(self, path):
  777. if path.split('/')[len(path.split('/')) - 2][:3] == 'tbl' \
  778. or path.split('/')[len(path.split('/')) - 1][:3] == 'tbl': # 如果是表选中行则最后一个不是字段是表名
  779. return TRUE
  780. else:
  781. return False
  782. def __check_alv(self, attr):
  783. if attr == 'modifyCell' or attr == 'insertRows':
  784. return TRUE
  785. else:
  786. return False
  787. def __array_get_first(self, field):
  788. if field:
  789. field = field[0]
  790. else:
  791. field = ''
  792. return field
  793. def __connection_read(self, init=False):
  794. con = {}
  795. df = pd.read_excel(self.v_path_connection, sheet_name=None)
  796. # 连接
  797. for i in range(len(df['连接']['desc'])):
  798. desc = str(df['连接']['desc'][i])
  799. client = str(df['连接']['client'][i])
  800. user = str(df['连接']['user'][i])
  801. langu = str(df['连接']['langu'][i])
  802. password = str(df['连接']['password'][i])
  803. if not con.__contains__(desc):
  804. con[desc] = {}
  805. if not con[desc].__contains__(client):
  806. con[desc][client] = {}
  807. if not con[desc][client].__contains__(user):
  808. con[desc][client][user] = {}
  809. if not con[desc][client][user].__contains__(langu):
  810. con[desc][client][user][langu] = password
  811. self.v_con_list = con
  812. if not self.v_con_list:
  813. self.v_con_list = {"未配置": {"300": {"abap01": {"zh": "123456"}}}}
  814. # with open(self.v_path_connection, 'w') as fp:
  815. # json.dump(self.v_con_list, fp)
  816. if not init:
  817. self.__con_sel_refresh()
  818. def __config_write(self):
  819. with open(self.v_path_config, "w+") as f:
  820. self.v_config.write(f)
  821. def __win_theme(self):
  822. # root = ttkbootstrap.Window()
  823. root = tk.Tk()
  824. root.wm_title('主题')
  825. # root.iconphoto(False, tk.PhotoImage(file='lan.png'))
  826. style = ttkbootstrap.Style()
  827. theme_names = style.theme_names() # 以列表的形式返回多个主题名
  828. theme_selection = ttkbootstrap.Frame(root, padding=(10, 10, 10, 0))
  829. theme_selection.pack(fill=X, expand=YES)
  830. lbl = ttkbootstrap.Label(theme_selection, text="主题:")
  831. theme_cbo = ttkbootstrap.Combobox(
  832. master=theme_selection,
  833. text=style.theme.name,
  834. values=theme_names,
  835. )
  836. theme_cbo.pack(padx=10, side=RIGHT)
  837. theme_cbo.current(theme_names.index(style.theme.name))
  838. lbl.pack(side=RIGHT)
  839. def change_theme(event):
  840. theme_cbo_value = theme_cbo.get()
  841. style.theme_use(theme_cbo_value)
  842. # theme_selected.configure(text=theme_cbo_value)
  843. theme_cbo.selection_clear()
  844. self.v_config['general']['theme'] = theme_cbo_value
  845. self.__config_write()
  846. theme_cbo.bind('<<ComboboxSelected>>', change_theme)
  847. # theme_selected = ttkbootstrap.Label(
  848. # master=theme_selection,
  849. # text="litera",
  850. # font="-size 24 -weight bold"
  851. # )
  852. # theme_selected.pack(side=LEFT)
  853. self.__window_geometry(200, 50, root)
  854. root.mainloop()
  855. def __filedialog_create_template(self):
  856. path = filedialog.asksaveasfilename(filetypes=[('excel文件', ['.xlsx'])],
  857. title='生成模板',
  858. initialfile='template.xlsx'
  859. )
  860. return path
  861. def __resource_path(self, relative_path):
  862. if getattr(sys, 'frozen', False): # 是否Bundle Resource
  863. base_path = sys._MEIPASS
  864. else:
  865. base_path = os.path.abspath(".")
  866. return os.path.join(base_path, relative_path)
  867. def __session_info_list_get(self):
  868. out_list = []
  869. try: # 有可能超时连接断开,一个连接都没有了
  870. for connection in self.v_application.Connections:
  871. for session in connection.Sessions:
  872. outdict = {}
  873. try: # 有可能超时断开,刚好执行到session.Info,就出异常了
  874. if session.Busy == False:
  875. session_info = session.Info
  876. outdict = session_info
  877. out_list.append((outdict))
  878. except Exception as e:
  879. pass
  880. except Exception as e:
  881. pass
  882. return out_list
  883. def __sapgui__start(self):
  884. self.v_path_sapgui = "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
  885. if not self.__sapgui_check_logon():
  886. if self.v_path_sapgui and os.path.exists(self.v_path_sapgui):
  887. subprocess.Popen(self.v_path_sapgui)
  888. time.sleep(7)
  889. return self.__sapgui_check_logon()
  890. else:
  891. raise ValueError('Path to saplogon.exe not found')
  892. def __sapgui_check_logon(self):
  893. try:
  894. self.v_application = win32com.client.GetObject('SAPGUI').GetScriptingEngine
  895. except Exception as e:
  896. return False
  897. else:
  898. return True
  899. def __connection_create(self, desc, client, user, pwd, langu=None, close_conn=False, change_pwd=True):
  900. try:
  901. self.v_application.OpenConnection(desc, True)
  902. except pythoncom.com_error as error:
  903. hr, msg, exc, arg = error.args
  904. sap_error = "COM: {0} ({1})".format(msg, hr)
  905. msg = "Connection '{0}' not found. {1}".format(desc, sap_error)
  906. # raise RuntimeError(msg)
  907. messagebox.showerror('打开连接错误', error)
  908. try:
  909. sap_session = self.v_application.Children(self.v_application.Children.Count - 1).Children(0)
  910. except Exception as e:
  911. messagebox.showerror('获取会话', '检查SAP是否开启了sapgui script(RZ11配置)')
  912. return
  913. sap_session.findById("wnd[0]").JumpBackward()
  914. sap_session.findById('wnd[0]/usr/txtRSYST-BNAME').text = user
  915. sap_session.findById('wnd[0]/usr/pwdRSYST-BCODE').text = pwd
  916. sap_session.findById('wnd[0]/usr/txtRSYST-MANDT').text = client
  917. sap_session.findById('wnd[0]/usr/txtRSYST-LANGU').text = langu
  918. sap_session.findById('wnd[0]').sendVKey(0)
  919. # msg = sap_session.findById('wnd[0]/sbar')
  920. # if msg and msg[0] == "E":
  921. # sap_session.findById('wnd[0]').sendVKey(15) #shift + f3
  922. # raise RuntimeError("Could not log to the SAP server. {0}".format(msg[2]))
  923. # 重复登录
  924. try:
  925. sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2")
  926. except pythoncom.com_error as error:
  927. pass
  928. else:
  929. sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2").select()
  930. sap_session.findById("wnd[1]/tbar[0]/btn[0]").press()
  931. # copyright弹框
  932. try:
  933. sap_session.findById("wnd[1]").sendVKey(0)
  934. except pythoncom.com_error as error:
  935. pass
  936. # return_pwd = SAPLogonPwd.is_change_password_window_while_connect(sap_session, change_pwd=change_pwd)
  937. # SAPNewSession.close_new_windows_while_connect(sap_session)
  938. return sap_session
  939. def __session_get(self, SystemSessionId, SessionNumber):
  940. for connection in self.v_application.Connections:
  941. for session in connection.Sessions:
  942. if session.Busy == False:
  943. if session.Info.SystemSessionId == SystemSessionId and session.Info.SessionNumber == SessionNumber:
  944. return session
  945. def __session_init(self):
  946. session = self.__session_selected()
  947. session.StartTransaction('')
  948. # self.__session_refresh()
  949. def __record_start(self):
  950. session = self.__session_selected()
  951. self.v_session = session # 获得session的句柄,这样方法执行完了,session的record可以保持不丢失
  952. session.Record = False
  953. session.SaveAsUnicode = True # 会存UTF-16
  954. session.RecordFile = self.v_filename_script
  955. self.v_path_script = session.RecordFile # 本地变量丢失句柄会清掉/关闭Record会丢掉先记下来
  956. self.v_config['path']['vbs'] = self.v_path_script
  957. self.__config_write()
  958. session.findById("wnd[0]").JumpForward()
  959. session.Record = True # 执行这句之后就会创建文件,之后前台操作都是实时写文件的
  960. def __record_end(self):
  961. session = self.__session_selected()
  962. if not session:
  963. return
  964. if session.RecordFile:
  965. self.v_path_script = session.RecordFile
  966. else:
  967. self.v_path_script = self.v_config['path']['vbs']
  968. session.Record = False # 会清空 session.RecordFile
  969. self.v_session = None # 释放session,此时gui右下角的脚本状态会立即消失
  970. self.__file_explain()
  971. # class SAPGUI:
  972. # def __tv_data_set(self,tv, data):
  973. def __column_next(self, column=None):
  974. if column != None:
  975. self.v_column = column
  976. self.v_column = self.v_column + 1
  977. return self.v_column
  978. def __connection_edit(self):
  979. os.startfile(self.v_path_connection)
  980. def __config_edit(self):
  981. os.startfile('config.ini')
  982. def __config_read(self):
  983. self.v_config.read('config.ini')
  984. def __button_file_execute(self):
  985. # self.__file_execute()
  986. self.v_root.after(1, self.__file_execute)
  987. def __button_stop(self):
  988. messagebox.showinfo('终止', '收到,当前条目执行完就会停止执行')
  989. self.v_stop = False
  990. def __about_document(self):
  991. # webbrowser.open('https://xiaolan.blog.csdn.net/article/details/128494871')
  992. webbrowser.open('https://blog.csdn.net/cylcylcylcylwo/article/details/128494871')
  993. def __about_conmunicate(self):
  994. pyperclip.copy('777363609') # 相当如写入到剪切板
  995. if __name__ == "__main__":
  996. lan_rpa = lan_rpa()
  997. lan_rpa.main()

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/651278
推荐阅读
相关标签
  

闽ICP备14008679号