pywinauto
下面是一个关于pywinauto库的简要详解,包括一些常见的语法和示例。pywinauto是一个用于自动化Windows桌面应用程序的Python库,它允许你模拟用户与应用程序的交互,例如打开应用程序、点击按钮、输入文本等等。
功能/语法 | 描述 | 示例 |
---|---|---|
安装 | 使用pip安装pywinauto库: | pip install pywinauto |
导入 | 导入pywinauto库: | import pywinauto |
启动应用程序 | 启动Windows应用程序,通过应用程序的可执行文件路径或应用程序名称: | app = pywinauto.Application().start("notepad.exe") |
连接到应用程序 | 连接到已经运行的应用程序,通过应用程序的标题或进程ID: | app = pywinauto.Application(backend="uia").connect(title="Untitled - Notepad") |
查找窗口元素 | 查找窗口元素,通过窗口的标题、类名、控件名等属性: | dlg = app.DialogName |
模拟键盘输入 | 模拟键盘输入,如输入文本、按下特定键: | dlg.type_keys("Hello, World!")或dlg.type_keys(" {ENTER}") |
模拟鼠标操作 | 模拟鼠标操作,如点击、拖动等: | button = dlg.ButtonName button.click_input() |
获取和设置控件的属性 | 获取和设置控件的属性,如文本、可见性等: | text = button.window_text() button.set_focus() get_value()(输入框内容) |
等待窗口和控件出现 | 等待特定的窗口或控件出现,以便进行后续操作: | dlg.wait("visible") button.wait("enabled", timeout=10) |
关闭应用程序 | 关闭应用程序窗口: | app.kill() |
这只是pywinauto库的一小部分功能和语法示例。你可以根据具体的需求和应用程序来使用更多的功能和方法。需要注意的是,不同的Windows应用程序可能需要使用不同的后端(例如"win32"或"uia")来进行自动化,因此在使用pywinauto时需要根据应用程序的特性来选择合适的后端。
print_control_identifiers
是pywinauto库中一个非常有用的方法,它可以用来打印特定窗口或控件的识别标识符。这些标识符包括窗口的类名、标题、控件名、控件类型等等信息,对于识别和定位控件以及编写自动化脚本非常有帮助。
以下是print_control_identifiers
的用法:
import pywinauto
# 启动或连接到目标应用程序
app = pywinauto.Application().start("notepad.exe")
# 或者
app = pywinauto.Application(backend="uia").connect(title="Untitled - Notepad")
# 获取特定窗口或控件的标识符
dlg = app.DialogName # 用实际的窗口名替换 "DialogName"
dlg.print_control_identifiers()
在上面的示例中,我们首先启动或连接到目标应用程序,然后使用窗口的名称(DialogName
)来获取窗口对象。接下来,我们调用 print_control_identifiers
方法来打印窗口及其所有子控件的标识符信息。
运行这个代码,你将会看到窗口及其控件的标识符信息被打印到控制台上,这些信息包括了窗口的类名、标题、控件名、控件类型等等。这些信息可以帮助你更容易地识别和定位窗口和控件,以便在自动化脚本中执行操作。
请确保将实际的窗口名替换为你要自动化的应用程序窗口的名称。这样,你就可以了解到窗口和控件的标识符信息,从而编写自动化脚本。
2.connect
和 start
是Pywinauto库中用于与应用程序进行交互的两种不同方法:
-
start
方法:start
方法用于启动新的应用程序进程。它会启动指定的应用程序,然后返回一个表示该应用程序的对象。- 如果应用程序未启动,
start
方法将启动它,并返回一个表示应用程序的对象。 - 如果应用程序已经在运行,
start
方法将返回一个表示应用程序的对象,而不会启动新的进程。
示例:
from pywinauto import Application app = Application().start("notepad.exe")
-
connect
方法:connect
方法用于连接到已经运行的应用程序进程。它不会启动新的应用程序进程,而是尝试与已经运行的应用程序进程建立连接。- 如果指定的应用程序已经在运行,
connect
方法将返回一个表示应用程序的对象。 - 如果指定的应用程序未运行,
connect
方法将引发错误。
示例:
from pywinauto import Application app = Application().connect(title="Untitled - Notepad")
总之,start
用于启动应用程序进程,而 connect
用于连接到已经运行的应用程序进程。您可以根据需要选择使用哪种方法来与应用程序进行交互。如果您知道应用程序是否已经在运行,可以使用 connect
更方便,否则可以使用 start
来启动应用程序。
另存为
from pywinauto.application import Application
def interact_with_save_dialog(file_path, key_word="另存为"):
# 启动应用程序或获取已运行的实例
app = Application().connect(title_re=key_word)
# 获取主窗口
main_window = app.window(title_re=key_word)
# 获取编辑框并清空内容
edit_control = main_window.Edit
edit_control.set_edit_text("")
# 输入新的文件路径
edit_control.type_keys(file_path)
# 模拟按下回车键
main_window.type_keys("{ENTER}")
# 示例用法
interact_with_save_dialog(r'C:\Users\yys\Desktop\新建文件夹\save.exe')
登录
from pywinauto import Application
from time import sleep
import os
#encoding:gbk
import tkinter.messagebox
import tkinter # 导入TKinter模块
# 堡垒机账号
blj_name = '***'
# 堡垒机密码
pass_wd = '***'
def input_tk():
ytm = tkinter.Tk() # 创建Tk对象
ytm.title("文本") # 设置窗口标题
# ytm.geometry("200x200") # 设置窗口尺寸
screen_width = ytm.winfo_screenwidth() # 获得屏幕宽度
screen_height = ytm.winfo_screenheight() # 获得屏幕高度
ytm.update_idletasks() # 刷新GUI
ytm.withdraw() # 暂时不显示窗口来移动位置
ytm.geometry('%dx%d+%d+%d' % (
ytm.winfo_width(), ytm.winfo_height(), (screen_width - ytm.winfo_width()) / 2,
(screen_height - ytm.winfo_height()) / 2)) # center window on desktop
ytm.deiconify()
l1 = tkinter.Label(ytm, text="请输入6位动态口令") # 标签
l1.pack() # 指定包管理器放置组件
user_text = tkinter.Entry() # 创建文本框
# print(user_text)
user_text.pack()
# ytm.destroy()
def getuser():
global get_text
get_text = user_text.get() # 获取文本框内容
#
if not get_text.strip():
tkinter.messagebox.showwarning('警告', '输入内容为空')
return
elif len(get_text.strip()) != 6:
tkinter.messagebox.showwarning('警告', '输入的不是6位')
return
print(get_text)
if get_text:
ytm.destroy()
def eventhandler(event):
if event.keysym == 'Return':
print('按下了确定')
getuser()
elif event.keysym == 'Right':
print('按下了方向键右键!')
buttton = tkinter.Button(ytm, text="确定", command=getuser) # command绑定获取文本框内容方法
buttton.bind_all('<Return>', eventhandler)
buttton.pack()
ytm.mainloop() # 进入主循环
print("get_text=", get_text)
return get_text
os.system("taskkill /f /t /im iexplore.exe")
# 启动 Internet Explorer
ie_app = ''
for i in range(3):
try:
ie_app = Application(backend="uia").start(r'C:\Program Files\Internet Explorer\iexplore.exe https://10.78.15.66/iam/login.jsp')
break
except Exception as e:
sleep(1)
# 连接到 Internet Explorer
# ie_app = Application(backend="uia").connect(title_re=".*Internet Explorer")
main_window = ie_app.top_window()
main_window.maximize()
# print(main_window.print_control_identifiers())
main_window.child_window(title="详细信息", control_type="DataItem").click_input()
main_window.child_window(title="转到此网页(不推荐)", control_type="Text").click_input()
sleep(1)
# print(main_window.print_control_identifiers())
ret = input_tk()
for i in range(5):
try:
main_window.child_window(auto_id="user_text", control_type="Edit").type_keys(blj_name)
value = main_window.child_window(auto_id="user_text", control_type="Edit").get_value()
if blj_name == value:
print(str(i) + ", 输入用户名成功")
break
else:
sleep(1)
except Exception as e:
pass
else:
raise Exception("输入用户名失败")
main_window.type_keys('{TAB}')
main_window.type_keys(pass_wd)
main_window.type_keys('{TAB}')
sleep(0.1)
main_window.type_keys(ret)
button = main_window.child_window(title="登 录", auto_id="login_in", control_type="Button")
button.wait('visible', timeout=10)
button.click()
打印设计
class Main:
def __init__(self, url=None, browser=None, web_page=None, set_date = ''):
self.main_window = ''
def connect_auto(self):
app = Application(backend="uia").connect(title_re=".*- Google Chrome")
# app = Application(backend="uia").start("chrome.exe www.baidu.com --force-renderer-accessibility")
self.main_window = app.top_window()
def to_print(self, key_word='ifinance-bankplat', device='Microsoft Print to PDF (已重定向 3)'):
flag = 0
have_select = 0
log.info("打印流程")
for i in range(30):
try:
# 界面按钮打印
if not self.main_window:
self.connect_auto()
main_childs = self.main_window.children()
# [<uiawrapper.UIAWrapper - '中信银行企业网银', Document, 3713038629963538981>, <uiawrapper.UIAWrapper - '中信银行企业网银', Document, 3713039836424661181>, <uiawrapper.UIAWrapper - '', TitleBar, 9137333838359754939>, <uiawrapper.UIAWrapper - 'Google Chrome', Pane, 3527539>, <uiawrapper.UIAWrapper - '打印', Pane, 3527539>]
main_child = ''
for main_child in main_childs:
if key_word in main_child.window_text() and main_child.get_properties()['friendly_class_name'] == "Document":
# log.info("满足")
break
else:
print(main_childs)
log.info("没有对应的%s,请检查打印页面Document属性关键词" % key_word)
return False
# device = 'Microsoft Print to PDF (已重定向 3)'
try:
load = self.main_window.child_window(title="正在加载预览", control_type="Text")
load.button.wait('exists enabled visible', timeout=3)
if load:
# print("加载中")
sleep(3)
continue
except Exception as e:
# print(e)
if 'There are 2 elements that match' in str(e):
sleep(3)
continue
pass
main_childs = main_child.children()[0].children()
for index, child in enumerate(main_childs):
# print(child.window_text())
# if child.window_text() == '打印' and child.get_properties()['friendly_class_name'] == "Button":
if '张纸' in child.window_text():
child = main_childs[index +2].children()[0].children()[-1].children()[0]
for i in range(3):
current_selection = child.selected_text()
log.info(current_selection)
if current_selection != device:
log.info("需要选择%s" % device)
child.click_input()
child.select(device)
sleep(0.5)
else:
log.info("已选择,无需选择%s" % device)
have_select = 1
break
break
if have_select:
for child in main_childs:
if child.window_text() == '打印' and child.get_properties()['friendly_class_name'] == "Button":
child.click()
sleep(1)
log.info("打印成功到→%s" % device)
flag = 1
return True
# self.main_window.child_window(title="打印", control_type="Button").click()
# wait_element(selector=r''':scope > Pane[name*="Google Chrome"] Button[name="打印"]''', timeout=200).invoke()
if flag == 1:
break
sleep(2)
except Exception as e:
log.info(str(i) + ':'+ str(e))
sleep(2)
pass
else:
return False
# raise Exception("打印失败")
Main().to_print(key_word='结算部流水账', device='Microsoft Print to PDF (已重定向 3)')
--force-renderer-accessibility 关闭谷歌渲染
from pywinauto import Application
# 连接到已经打开的Notepad应用程序
app = Application(backend="uia").connect(title_re=".*- Google Chrome")
# app = Application(backend="uia").start("chrome.exe www.baidu.com --force-renderer-accessibility")
main_window = app.top_window()
# print(main_window.print_control_identifiers())
# 获取"更多设置"按钮
more_settings_button = main_window.child_window(title="更多设置", control_type="Button")
# 获取"更多设置"按钮的父元素
parent = more_settings_button.parent()
# 获取父元素的所有子元素
children = parent.children()
# 在子元素列表中找到"更多设置"按钮,然后获取它的下一个兄弟元素
for i, child in enumerate(children):
if child == more_settings_button and i + 1 < len(children):
print_button = children[i + 1]
print_button.click()
break
根据索引
from pywinauto import Application
# 连接到已经打开的Notepad应用程序
app = Application(backend="uia").connect(title_re=".*- Google Chrome")
# app = Application(backend="uia").start("chrome.exe www.baidu.com --force-renderer-accessibility")
main_window = app.top_window()
main_childs = main_window.children()
for main_child in main_childs:
if 'ifinance-bankplat' in main_child.window_text() and main_child.get_properties()['friendly_class_name'] == "Document":
# print(main_window.children()[0])
for child in main_child.children()[0].children():
if child.window_text() == '打印' and child.get_properties()['friendly_class_name'] == "Button":
print(child)
child.click()
from pywinauto import Application
# 连接到已经打开的Notepad应用程序
app = Application(backend="uia").connect(title_re=".*- Google Chrome")
print(app)
# # 获取Notepad的主窗口
# 获取应用程序的主窗口
main_window = app.top_window()
print(main_window)
# print(main_window.print_control_identifiers())
#
xs = main_window.child_window(title_re=".*显示", control_type="Custom")
print(xs.window_text())
error = xs.child_window( control_type="Text")
print(error.window_text())
main_window.child_window(title="确定", control_type="Button").click()
判断下拉框,选择下拉框,
from pywinauto import Application
from time import sleep
from pywinauto import Desktop
# 连接到已经打开的Notepad应用程序
app = Application(backend="uia").connect(title_re=".*- Google Chrome")
# app = Application(backend="uia").start("chrome.exe www.baidu.com --force-renderer-accessibility")
main_window = app.top_window()
children = main_window.children()
print(children[0].children()[-2])
print(children[0].children())
childrens = children[0].children()
# select_text = '另存为 PDF'
select_text = 'Microsoft Print to PDF'
for index, main_child in enumerate(childrens):
if main_child.window_text() == '目标打印机':
child = childrens[index +1]
for i in range(3):
current_selection = child.selected_text()
print(current_selection)
if current_selection != select_text:
print("需要选择%s" % select_text)
child.click_input()
child.select(select_text)
print(child)
sleep(0.5)
else:
print("已选择,无需选择%s" % select_text)
break
break
wait_until
是pywinauto
库中用于等待某个条件满足的方法。它可以用于等待窗口、控件等出现或达到特定状态的情况。以下是wait_until
方法的基本用法:
from pywinauto.application import Application
from pywinauto.timings import wait_until
# 启动应用程序或连接到已经打开的应用程序
app = Application(backend="uia").start("notepad.exe")
# 等待Notepad应用程序的主窗口出现,最多等待10秒
main_window = app.window(title="无标题 - 记事本").wait_until(timeout=10, retry_interval=0.5, active=True)
# 在主窗口中查找控件并操作
edit_box = main_window.window(control_type="Edit")
edit_box.set_text("Hello, World!")
# 等待一段时间后关闭应用程序
main_window.wait("visible", timeout=10)
main_window.close()
在上面的示例中,我们首先启动或连接到Notepad应用程序,然后使用wait_until
方法等待Notepad的主窗口出现。wait_until
方法接受以下参数:
timeout
:等待的最大时间(秒)。retry_interval
:重试间隔,即多久检查一次条件是否满足(秒)。active
:如果设置为True
,则等待窗口变为活动窗口。
一旦主窗口出现并变为活动窗口,我们就可以在主窗口中查找控件(这里是一个文本框),并对其进行操作。然后,我们再次使用wait
方法等待窗口变得可见,然后关闭应用程序。
元素超时等待
print_button.wait('exists enabled visible', timeout=10)
# 使用control_type方法获取元素的控件类型
control_type = print_button.get_properties()['control_type']
print("元素的控件类型:", control_type)
AttributeError: 'UIAWrapper' object has no attribute 'child_window'
from pywinauto.timings import wait_until
wrapper = app.top_window().children()[0]
wait_until(timeout=5, retry_interval=0.1, wrapper.is_enabled)
click
和 click_input
是 Pywinauto 中用于模拟点击操作的两种不同方法,它们之间有一些重要的区别:
-
click
:click
方法用于模拟点击操作,但它侧重于在UI元素上触发点击事件。- 这种方法通常更接近模拟用户实际点击的方式,可以触发与鼠标点击相关的事件和操作。
- 但有些UI元素可能不支持
click
方法,因为它需要触发点击事件,而不是模拟点击操作。
-
click_input
:click_input
方法用于模拟实际的鼠标点击操作,就像用户手动点击一样。- 这个方法模拟鼠标指针移动到UI元素上,然后模拟鼠标点击,触发点击事件。
- 通常,
click_input
更通用,因为它可以在不支持click
的UI元素上使用。
在大多数情况下,你可能会首选使用 click_input
方法,因为它更通用且适用于大多数UI元素。只有当你需要特别模拟点击事件时,才考虑使用 click
方法。要根据你的需求和应用程序的特性来选择合适的方法。
from pywinauto.findwindows import ElementNotFoundError
def get_element_center_coordinates(element):
try:
# 获取元素的位置和大小信息
element_rect = element.Rectangle()
# 计算元素的中心点坐标
center_x = (element_rect.left + element_rect.right) // 2
center_y = (element_rect.top + element_rect.bottom) // 2
return center_x, center_y
except ElementNotFoundError as e:
print(f"元素未找到: {e}")
return None
except Exception as e:
print(f"发生错误: {e}")
return None
# 示例用法
from pywinauto.application import Application
app_path = "YourApplication.exe" # 替换为你的应用程序路径
window_title = "Your Application Title" # 替换为你的应用程序标题
try:
# 启动应用程序或连接到已经打开的应用程序
app = Application().start(app_path)
# 或者连接到已经打开的应用程序
# app = Application(backend="uia").connect(title=window_title)
# 获取顶层窗口对象
window = app.top_window()
# 获取窗口元素,例如一个按钮
element = window.ButtonControlName # 替换为实际元素名称
# 获取元素的中心点坐标
center_coords = get_element_center_coordinates(element)
if center_coords:
print("元素中心点坐标:({}, {})".format(center_coords[0], center_coords[1]))
else:
print("无法获取元素中心点坐标。")
except ElementNotFoundError as e:
print(f"窗口或元素未找到: {e}")
except Exception as e:
print(f"发生错误: {e}")
main_window.maximize()
from pywinauto.keyboard import SendKeys
以下是一些常见的 SendKeys 中的符号表示法,用于模拟不同的键盘快捷键:
^
:Ctrl%
:Alt+
:Shift{ENTER}
:Enter 键{TAB}
:Tab 键{BACKSPACE}
:Backspace 键{DELETE}
:Delete 键{LEFT}
:左箭头键{RIGHT}
:右箭头键{UP}
:上箭头键{DOWN}
:下箭头键{HOME}
:Home 键{END}
:End 键{PGUP}
:Page Up 键{PGDN}
:Page Down 键{F1}
,{F2}
, ...{F12}
:F1 到 F12 键
这些符号可以组合使用,例如 ^a
表示 Ctrl+A,%{TAB}
表示 Alt+Tab。你还可以使用 {}
括号包裹一些特殊键的名字,比如 {SPACE}
表示空格键。
请注意,这只是一份基本的参考列表,具体的应用场景可能需要进一步的了解和测试。 SendKeys 的语法相对简单,但在某些情况下可能会受到限制。在复杂的自动化任务中,可能需要考虑使用更强大的自动化工具或库。
这个错误通常发生在Pywinauto试图匹配多个元素时。当你的标准匹配到多个元素时,Pywinauto无法确定应该选择哪一个,因此会抛出这个错误(there are 2 elements that math the criteria{XXX})。
你可以通过添加found_index
参数来解决这个问题。found_index
参数允许你指定在匹配的元素中选择哪一个。例如,如果你想要选择第一个匹配的元素,你可以这样做:
app.window(found_index=0, title='窗口0').type_keys('~')
在这个例子中,found_index=0
表示选择第一个匹配的元素¹²⁴。
在Pywinauto中,你可以使用rectangle
方法来获取元素的坐标。这个方法会返回一个包含元素位置信息的RECT
对象,你可以通过这个对象来获取元素的左上角和右下角的坐标³。
以下是一个例子:
from pywinauto import Application
# 启动或连接到一个应用
app = Application(backend="uia").start('path_to_your_application.exe')
# 获取一个窗口
window = app.window(title="your window title")
# 获取一个元素
element = window.child_window(title="your element title")
# 获取元素的坐标
rect = element.rectangle()
# 输出元素的坐标
print("Left: ", rect.left)
print("Top: ", rect.top)
print("Right: ", rect.right)
print("Bottom: ", rect.bottom)
在这个例子中,rect.left
和rect.top
代表元素左上角的坐标,rect.right
和rect.bottom
代表元素右下角的坐标。
在Pywinauto中,select
方法主要用于两种场景:列表框(ListBox)和菜单项(MenuItem)。
- 列表框(ListBox):
select
方法用于选择列表框中的一个或多个项目。你可以通过索引,文本或两者的组合来指定要选择的项目。例如:
# 通过索引选择
listbox.select(0) # 选择第一个项目
# 通过文本选择
listbox.select('项目1') # 选择名为'项目1'的项目
# 通过索引和文本的组合选择
listbox.select(('项目1', 1)) # 选择名为'项目1'且索引为1的项目
- 菜单项(MenuItem):
select
方法用于选择菜单中的一个项目。你需要提供一个字符串,其中包含菜单路径,各级菜单项之间用->
分隔。例如:
# 选择文件菜单下的打开项目
app.YourWindow.menu_select('文件->打开')
此外,还有一个menu_select
方法,它用于选择菜单中的一个项目,与select
方法在菜单项上的用法相。
希望这个信息对你有所帮助!
def get_siblings_with_matching(control, matching_title=None, matching_type=None):
"""
找到符合给定标题和/或类型的所有同级控件。
:param control: 目标控件。
:param matching_title: (可选) 需要匹配的控件标题。
:param matching_type: (可选) 需要匹配的控件类型。
:return: 列表,包含所有匹配的同级控件。
"""
siblings = get_siblings(control)
matched_controls = []
for sibling in siblings:
title_matches = True if not matching_title else sibling.window_text() == matching_title
type_matches = True if matching_type is None else sibling.element_info.control_type == matching_type
if title_matches and type_matches:
matched_controls.append(sibling)
return matched_controls
def get_siblings(control):
"""
获取给定控件的所有同级控件。
:param control: 目标控件。
:return: 列表,包含目标控件的所有同级控件。
"""
parent = control.parent()
children = parent.children()
siblings = [child for child in children if child != control]
return siblings
这段代码中,title_matches
和type_matches
变量用于决定是否匹配标题和/或类型。如果对应的匹配参数(matching_title
, matching_type
)没有提供,视为自动匹配成功(title_matches
或type_matches
=True)。只有当两个变量都为True
时,才会认为找到了一个匹配的同级控件,并将其添加到结果列表中。
这样,你就可以根据实际情况,选择性地提供matching_title
和/或matching_type
参数,如只提供matching_type
时:
# 假设有一个控件`target_control`已经被定位
matched_siblings = get_siblings_with_matching(target_control, matching_type="特定类型")
for sibling in matched_siblings:
print(f"找到匹配控件: {sibling.window_text()} 类型: {sibling.control_type()}")
pywinauto
的ComboBox
类有以下一些属性和方法:
collapse()
:收起下拉列表¹1。expand()
:展开下拉列表¹1。get_expand_state()
:获取下拉列表的展开状态¹1。is_editable()
:判断下拉列表是否可编辑¹1。item_count()
:返回下拉列表中的项目数量¹1。select(item)
:选择下拉列表中的项目,item
可以是要选择的项目的0基础索引,也可以是要选择的字符串¹1。selected_index()
:返回选中项目的索引¹1。selected_text()
:返回选中的文本,如果没有选中的项目,则返回None¹1。texts()
:返回下拉列表中的项目的文本¹1。
以上是pywinauto
的ComboBox
类的一些主要属性和方法,具体使用时还需要根据实际情况进行调用。¹1²2。
源: 与必应的对话, 2024/4/25
(1) pywinauto.controls.uia_controls — pywinauto 0.6.8 documentation. https://pywinauto.readthedocs.io/en/latest/code/pywinauto.controls.uia_controls.html.
(2) Methods available to each different control type — pywinauto 0.6.8 .... https://pywinauto.readthedocs.io/en/latest/controls_overview.html.
(3) Custom combo box recognition in pywinauto - Stack Overflow. https://stackoverflow.com/questions/47583544/custom-combo-box-recognition-in-pywinauto.
(4) pywinauto comobox 获取当前选中的信息 - CSDN文库. https://wenku.csdn.net/answer/79897e6e62cd423d91f0a3a915fd732f.
(5) undefined. https://msdn.microsoft.com/en-us/library/windows/desktop/ee671290.
本文作者: 永生
本文链接: https://yys.zone/detail/?id=307
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
评论列表 (0 条评论)